2 ******************************************************************************
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
8 * @see The GNU Public License (GPL) Version 3
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "pluginspec.h"
30 #include "pluginspec.h"
31 #include "pluginspec_p.h"
33 #include "iplugin_p.h"
34 #include "pluginmanager.h"
36 #include <QtCore/QFile>
37 #include <QtCore/QFileInfo>
38 #include <QtCore/QXmlStreamReader>
39 #include <QtCore/QRegExp>
40 #include <QtCore/QCoreApplication>
44 // Using the patched version breaks on Fedora 10, KDE4.2.2/Qt4.5.
45 # define USE_UNPATCHED_QPLUGINLOADER 1
47 # define USE_UNPATCHED_QPLUGINLOADER 1
50 #if USE_UNPATCHED_QPLUGINLOADER
52 # include <QtCore/QPluginLoader>
53 typedef QT_PREPEND_NAMESPACE (QPluginLoader
) PluginLoader
;
57 # include "patchedpluginloader.cpp"
58 typedef PatchedPluginLoader PluginLoader
;
63 \class ExtensionSystem::PluginDependency
64 \brief Struct that contains the name and required compatible version number of a plugin's dependency.
66 This reflects the data of a dependency tag in the plugin's xml description file.
67 The name and version are used to resolve the dependency, i.e. a plugin with the given name and
68 plugin \c {compatibility version <= dependency version <= plugin version} is searched for.
70 See also ExtensionSystem::IPlugin for more information about plugin dependencies and
75 \variable ExtensionSystem::PluginDependency::name
76 String identifier of the plugin.
80 \variable ExtensionSystem::PluginDependency::version
81 Version string that a plugin must match to fill this dependency.
85 \class ExtensionSystem::PluginSpec
86 \brief Contains the information of the plugins xml description file and
87 information about the plugin's current state.
89 The plugin spec is also filled with more information as the plugin
90 goes through its loading process (see PluginSpec::State).
91 If an error occurs, the plugin spec is the place to look for the
96 \enum ExtensionSystem::PluginSpec::State
98 The plugin goes through several steps while being loaded.
99 The state gives a hint on what went wrong in case of an error.
102 Starting point: Even the xml description file was not read.
104 The xml description file has been successfully read, and its
105 information is available via the PluginSpec.
107 The dependencies given in the description file have been
108 successfully found, and are available via the dependencySpecs() method.
110 The plugin's library is loaded and the plugin instance created
111 (available through plugin()).
113 The plugin instance's IPlugin::initialize() method has been called
114 and returned a success value.
116 The plugin's dependencies are successfully initialized and
117 extensionsInitialized has been called. The loading process is
120 The plugin has been shut down, i.e. the plugin's IPlugin::shutdown() method has been called.
122 The plugin instance has been deleted.
124 using namespace ExtensionSystem
;
125 using namespace ExtensionSystem::Internal
;
128 \fn bool PluginDependency::operator==(const PluginDependency &other)
131 bool PluginDependency::operator==(const PluginDependency
&other
)
133 return name
== other
.name
&& version
== other
.version
;
137 \fn PluginSpec::PluginSpec()
140 PluginSpec::PluginSpec()
141 : d(new PluginSpecPrivate(this))
145 \fn PluginSpec::~PluginSpec()
148 PluginSpec::~PluginSpec()
155 \fn QString PluginSpec::name() const
156 The plugin name. This is valid after the PluginSpec::Read state is reached.
158 QString
PluginSpec::name() const
164 \fn QString PluginSpec::version() const
165 The plugin version. This is valid after the PluginSpec::Read state is reached.
167 QString
PluginSpec::version() const
173 \fn QString PluginSpec::compatVersion() const
174 The plugin compatibility version. This is valid after the PluginSpec::Read state is reached.
176 QString
PluginSpec::compatVersion() const
178 return d
->compatVersion
;
182 \fn QString PluginSpec::vendor() const
183 The plugin vendor. This is valid after the PluginSpec::Read state is reached.
185 QString
PluginSpec::vendor() const
191 \fn QString PluginSpec::copyright() const
192 The plugin copyright. This is valid after the PluginSpec::Read state is reached.
194 QString
PluginSpec::copyright() const
200 \fn QString PluginSpec::license() const
201 The plugin license. This is valid after the PluginSpec::Read state is reached.
203 QString
PluginSpec::license() const
209 \fn QString PluginSpec::description() const
210 The plugin description. This is valid after the PluginSpec::Read state is reached.
212 QString
PluginSpec::description() const
214 return d
->description
;
218 \fn QString PluginSpec::url() const
219 The plugin url where you can find more information about the plugin. This is valid after the PluginSpec::Read state is reached.
221 QString
PluginSpec::url() const
227 \fn QList<PluginDependency> PluginSpec::dependencies() const
228 The plugin dependencies. This is valid after the PluginSpec::Read state is reached.
230 QList
<PluginDependency
> PluginSpec::dependencies() const
232 return d
->dependencies
;
236 \fn PluginSpec::PluginArgumentDescriptions PluginSpec::argumentDescriptions() const
237 Returns a list of descriptions of command line arguments the plugin processes.
240 PluginSpec::PluginArgumentDescriptions
PluginSpec::argumentDescriptions() const
242 return d
->argumentDescriptions
;
246 \fn QString PluginSpec::location() const
247 The absolute path to the directory containing the plugin xml description file
248 this PluginSpec corresponds to.
250 QString
PluginSpec::location() const
256 \fn QString PluginSpec::filePath() const
257 The absolute path to the plugin xml description file (including the file name)
258 this PluginSpec corresponds to.
260 QString
PluginSpec::filePath() const
266 \fn QStringList PluginSpec::arguments() const
267 Command line arguments specific to that plugin. Set at startup
270 QStringList
PluginSpec::arguments() const
276 \fn void PluginSpec::setArguments(const QStringList &arguments)
277 Set the command line arguments specific to that plugin to \a arguments.
280 void PluginSpec::setArguments(const QStringList
&arguments
)
282 d
->arguments
= arguments
;
286 \fn PluginSpec::addArgument(const QString &argument)
287 Adds \a argument to the command line arguments specific to that plugin.
290 void PluginSpec::addArgument(const QString
&argument
)
292 d
->arguments
.push_back(argument
);
297 \fn PluginSpec::State PluginSpec::state() const
298 The state in which the plugin currently is.
299 See the description of the PluginSpec::State enum for details.
301 PluginSpec::State
PluginSpec::state() const
307 \fn bool PluginSpec::hasError() const
308 Returns whether an error occurred while reading/starting the plugin.
310 bool PluginSpec::hasError() const
316 \fn QString PluginSpec::errorString() const
317 Detailed, possibly multi-line, error description in case of an error.
319 QString
PluginSpec::errorString() const
321 return d
->errorString
;
325 \fn bool PluginSpec::provides(const QString &pluginName, const QString &version) const
326 Returns if this plugin can be used to fill in a dependency of the given
327 \a pluginName and \a version.
329 \sa PluginSpec::dependencies()
331 bool PluginSpec::provides(const QString
&pluginName
, const QString
&version
) const
333 return d
->provides(pluginName
, version
);
337 \fn IPlugin *PluginSpec::plugin() const
338 The corresponding IPlugin instance, if the plugin library has already been successfully loaded,
339 i.e. the PluginSpec::Loaded state is reached.
341 IPlugin
*PluginSpec::plugin() const
347 \fn QList<PluginSpec *> PluginSpec::dependencySpecs() const
348 Returns the list of dependencies, already resolved to existing plugin specs.
349 Valid if PluginSpec::Resolved state is reached.
351 \sa PluginSpec::dependencies()
353 QList
<PluginSpec
*> PluginSpec::dependencySpecs() const
355 return d
->dependencySpecs
;
358 // ==========PluginSpecPrivate==================
361 const char *const PLUGIN
= "plugin";
362 const char *const PLUGIN_NAME
= "name";
363 const char *const PLUGIN_VERSION
= "version";
364 const char *const PLUGIN_COMPATVERSION
= "compatVersion";
365 const char *const VENDOR
= "vendor";
366 const char *const COPYRIGHT
= "copyright";
367 const char *const LICENSE
= "license";
368 const char *const DESCRIPTION
= "description";
369 const char *const URL
= "url";
370 const char *const DEPENDENCYLIST
= "dependencyList";
371 const char *const DEPENDENCY
= "dependency";
372 const char *const DEPENDENCY_NAME
= "name";
373 const char *const DEPENDENCY_VERSION
= "version";
374 const char *const ARGUMENTLIST
= "argumentList";
375 const char *const ARGUMENT
= "argument";
376 const char *const ARGUMENT_NAME
= "name";
377 const char *const ARGUMENT_PARAMETER
= "parameter";
380 \fn PluginSpecPrivate::PluginSpecPrivate(PluginSpec *spec)
383 PluginSpecPrivate::PluginSpecPrivate(PluginSpec
*spec
)
385 state(PluginSpec::Invalid
),
391 \fn bool PluginSpecPrivate::read(const QString &fileName)
394 bool PluginSpecPrivate::read(const QString
&fileName
)
406 state
= PluginSpec::Invalid
;
409 dependencies
.clear();
410 QFile
file(fileName
);
411 if (!file
.exists()) {
412 return reportError(tr("File does not exist: %1").arg(file
.fileName()));
414 if (!file
.open(QIODevice::ReadOnly
)) {
415 return reportError(tr("Could not open file for read: %1").arg(file
.fileName()));
417 QFileInfo
fileInfo(file
);
418 location
= fileInfo
.absolutePath();
419 filePath
= fileInfo
.absoluteFilePath();
420 QXmlStreamReader
reader(&file
);
421 while (!reader
.atEnd()) {
423 switch (reader
.tokenType()) {
424 case QXmlStreamReader::StartElement
:
425 readPluginSpec(reader
);
431 if (reader
.hasError()) {
432 return reportError(tr("Error parsing file %1: %2, at line %3, column %4")
433 .arg(file
.fileName())
434 .arg(reader
.errorString())
435 .arg(reader
.lineNumber())
436 .arg(reader
.columnNumber()));
438 state
= PluginSpec::Read
;
443 \fn bool PluginSpecPrivate::reportError(const QString &err)
446 bool PluginSpecPrivate::reportError(const QString
&err
)
453 static inline QString
msgAttributeMissing(const char *elt
, const char *attribute
)
455 return QCoreApplication::translate("PluginSpec", "'%1' misses attribute '%2'").arg(QLatin1String(elt
), QLatin1String(attribute
));
458 static inline QString
msgInvalidFormat(const char *content
)
460 return QCoreApplication::translate("PluginSpec", "'%1' has invalid format").arg(content
);
463 static inline QString
msgInvalidElement(const QString
&name
)
465 return QCoreApplication::translate("PluginSpec", "Invalid element '%1'").arg(name
);
468 static inline QString
msgUnexpectedClosing(const QString
&name
)
470 return QCoreApplication::translate("PluginSpec", "Unexpected closing element '%1'").arg(name
);
473 static inline QString
msgUnexpectedToken()
475 return QCoreApplication::translate("PluginSpec", "Unexpected token");
479 \fn void PluginSpecPrivate::readPluginSpec(QXmlStreamReader &reader)
482 void PluginSpecPrivate::readPluginSpec(QXmlStreamReader
&reader
)
484 QString element
= reader
.name().toString();
486 if (element
!= QString(PLUGIN
)) {
487 reader
.raiseError(QCoreApplication::translate("PluginSpec", "Expected element '%1' as top level element").arg(PLUGIN
));
490 name
= reader
.attributes().value(PLUGIN_NAME
).toString();
491 if (name
.isEmpty()) {
492 reader
.raiseError(msgAttributeMissing(PLUGIN
, PLUGIN_NAME
));
495 version
= reader
.attributes().value(PLUGIN_VERSION
).toString();
496 if (version
.isEmpty()) {
497 reader
.raiseError(msgAttributeMissing(PLUGIN
, PLUGIN_VERSION
));
500 if (!isValidVersion(version
)) {
501 reader
.raiseError(msgInvalidFormat(PLUGIN_VERSION
));
504 compatVersion
= reader
.attributes().value(PLUGIN_COMPATVERSION
).toString();
505 if (!compatVersion
.isEmpty() && !isValidVersion(compatVersion
)) {
506 reader
.raiseError(msgInvalidFormat(PLUGIN_COMPATVERSION
));
508 } else if (compatVersion
.isEmpty()) {
509 compatVersion
= version
;
511 while (!reader
.atEnd()) {
513 switch (reader
.tokenType()) {
514 case QXmlStreamReader::StartElement
:
515 element
= reader
.name().toString();
516 if (element
== VENDOR
) {
517 vendor
= reader
.readElementText().trimmed();
518 } else if (element
== COPYRIGHT
) {
519 copyright
= reader
.readElementText().trimmed();
520 } else if (element
== LICENSE
) {
521 license
= reader
.readElementText().trimmed();
522 } else if (element
== DESCRIPTION
) {
523 description
= reader
.readElementText().trimmed();
524 } else if (element
== URL
) {
525 url
= reader
.readElementText().trimmed();
526 } else if (element
== DEPENDENCYLIST
) {
527 readDependencies(reader
);
528 } else if (element
== ARGUMENTLIST
) {
529 readArgumentDescriptions(reader
);
531 reader
.raiseError(msgInvalidElement(name
));
534 case QXmlStreamReader::EndDocument
:
535 case QXmlStreamReader::Comment
:
536 case QXmlStreamReader::EndElement
:
537 case QXmlStreamReader::Characters
:
540 reader
.raiseError(msgUnexpectedToken());
547 \fn void PluginSpecPrivate::readArgumentDescriptions(QXmlStreamReader &reader)
551 void PluginSpecPrivate::readArgumentDescriptions(QXmlStreamReader
&reader
)
555 while (!reader
.atEnd()) {
557 switch (reader
.tokenType()) {
558 case QXmlStreamReader::StartElement
:
559 element
= reader
.name().toString();
560 if (element
== ARGUMENT
) {
561 readArgumentDescription(reader
);
563 reader
.raiseError(msgInvalidElement(name
));
566 case QXmlStreamReader::Comment
:
567 case QXmlStreamReader::Characters
:
569 case QXmlStreamReader::EndElement
:
570 element
= reader
.name().toString();
571 if (element
== ARGUMENTLIST
) {
574 reader
.raiseError(msgUnexpectedClosing(element
));
577 reader
.raiseError(msgUnexpectedToken());
584 \fn void PluginSpecPrivate::readArgumentDescription(QXmlStreamReader &reader)
587 void PluginSpecPrivate::readArgumentDescription(QXmlStreamReader
&reader
)
589 PluginArgumentDescription arg
;
591 arg
.name
= reader
.attributes().value(ARGUMENT_NAME
).toString();
592 if (arg
.name
.isEmpty()) {
593 reader
.raiseError(msgAttributeMissing(ARGUMENT
, ARGUMENT_NAME
));
596 arg
.parameter
= reader
.attributes().value(ARGUMENT_PARAMETER
).toString();
597 arg
.description
= reader
.readElementText();
598 if (reader
.tokenType() != QXmlStreamReader::EndElement
) {
599 reader
.raiseError(msgUnexpectedToken());
601 argumentDescriptions
.push_back(arg
);
605 \fn void PluginSpecPrivate::readDependencies(QXmlStreamReader &reader)
608 void PluginSpecPrivate::readDependencies(QXmlStreamReader
&reader
)
612 while (!reader
.atEnd()) {
614 switch (reader
.tokenType()) {
615 case QXmlStreamReader::StartElement
:
616 element
= reader
.name().toString();
617 if (element
== DEPENDENCY
) {
618 readDependencyEntry(reader
);
620 reader
.raiseError(msgInvalidElement(name
));
623 case QXmlStreamReader::Comment
:
624 case QXmlStreamReader::Characters
:
626 case QXmlStreamReader::EndElement
:
627 element
= reader
.name().toString();
628 if (element
== DEPENDENCYLIST
) {
631 reader
.raiseError(msgUnexpectedClosing(element
));
634 reader
.raiseError(msgUnexpectedToken());
641 \fn void PluginSpecPrivate::readDependencyEntry(QXmlStreamReader &reader)
644 void PluginSpecPrivate::readDependencyEntry(QXmlStreamReader
&reader
)
646 PluginDependency dep
;
648 dep
.name
= reader
.attributes().value(DEPENDENCY_NAME
).toString();
649 if (dep
.name
.isEmpty()) {
650 reader
.raiseError(msgAttributeMissing(DEPENDENCY
, DEPENDENCY_NAME
));
653 dep
.version
= reader
.attributes().value(DEPENDENCY_VERSION
).toString();
654 if (!dep
.version
.isEmpty() && !isValidVersion(dep
.version
)) {
655 reader
.raiseError(msgInvalidFormat(DEPENDENCY_VERSION
));
658 dependencies
.append(dep
);
660 if (reader
.tokenType() != QXmlStreamReader::EndElement
) {
661 reader
.raiseError(msgUnexpectedToken());
666 \fn bool PluginSpecPrivate::provides(const QString &pluginName, const QString &pluginVersion) const
669 bool PluginSpecPrivate::provides(const QString
&pluginName
, const QString
&pluginVersion
) const
671 if (QString::compare(pluginName
, name
, Qt::CaseInsensitive
) != 0) {
674 return (versionCompare(version
, pluginVersion
) >= 0) && (versionCompare(compatVersion
, pluginVersion
) <= 0);
678 \fn QRegExp &PluginSpecPrivate::versionRegExp()
681 QRegExp
&PluginSpecPrivate::versionRegExp()
683 static QRegExp
reg("([0-9]+)(?:[.]([0-9]+))?(?:[.]([0-9]+))?(?:_([0-9]+))?");
688 \fn bool PluginSpecPrivate::isValidVersion(const QString &version)
691 bool PluginSpecPrivate::isValidVersion(const QString
&version
)
693 return versionRegExp().exactMatch(version
);
697 \fn int PluginSpecPrivate::versionCompare(const QString &version1, const QString &version2)
700 int PluginSpecPrivate::versionCompare(const QString
&version1
, const QString
&version2
)
702 QRegExp reg1
= versionRegExp();
703 QRegExp reg2
= versionRegExp();
705 if (!reg1
.exactMatch(version1
)) {
708 if (!reg2
.exactMatch(version2
)) {
713 for (int i
= 0; i
< 4; ++i
) {
714 number1
= reg1
.cap(i
+ 1).toInt();
715 number2
= reg2
.cap(i
+ 1).toInt();
716 if (number1
< number2
) {
719 if (number1
> number2
) {
727 \fn bool PluginSpecPrivate::resolveDependencies(const QList<PluginSpec *> &specs)
730 bool PluginSpecPrivate::resolveDependencies(const QList
<PluginSpec
*> &specs
)
735 if (state
== PluginSpec::Resolved
) {
736 state
= PluginSpec::Read
; // Go back, so we just re-resolve the dependencies.
738 if (state
!= PluginSpec::Read
) {
739 errorString
= QCoreApplication::translate("PluginSpec", "Resolving dependencies failed because state != Read");
743 QList
<PluginSpec
*> resolvedDependencies
;
744 foreach(const PluginDependency
&dependency
, dependencies
) {
745 PluginSpec
*found
= 0;
747 foreach(PluginSpec
* spec
, specs
) {
748 if (spec
->provides(dependency
.name
, dependency
.version
)) {
755 if (!errorString
.isEmpty()) {
756 errorString
.append("\n");
758 errorString
.append(QCoreApplication::translate("PluginSpec", "Could not resolve dependency '%1(%2)'")
759 .arg(dependency
.name
).arg(dependency
.version
));
762 resolvedDependencies
.append(found
);
767 dependencySpecs
= resolvedDependencies
;
768 state
= PluginSpec::Resolved
;
773 \fn bool PluginSpecPrivate::loadLibrary()
776 bool PluginSpecPrivate::loadLibrary()
781 if (state
!= PluginSpec::Resolved
) {
782 if (state
== PluginSpec::Loaded
) {
785 errorString
= QCoreApplication::translate("PluginSpec", "Loading the library failed because state != Resolved");
792 QString libName
= QString("%1/%2.dll").arg(location
).arg(name
);
793 #elif defined(Q_OS_MAC)
794 QString libName
= QString("%1/lib%2.dylib").arg(location
).arg(name
);
796 QString libName
= QString("%1/lib%2.so").arg(location
).arg(name
);
802 QString libName
= QString("%1/%2d.dll").arg(location
).arg(name
);
803 #elif defined(Q_OS_MAC)
804 QString libName
= QString("%1/lib%2_debug.dylib").arg(location
).arg(name
);
806 QString libName
= QString("%1/lib%2.so").arg(location
).arg(name
);
811 PluginLoader
loader(libName
);
812 if (!loader
.load()) {
814 errorString
= libName
+ QString::fromLatin1(": ") + loader
.errorString();
817 IPlugin
*pluginObject
= qobject_cast
<IPlugin
*>(loader
.instance());
820 errorString
= QCoreApplication::translate("PluginSpec", "Plugin is not valid (does not derive from IPlugin)");
824 state
= PluginSpec::Loaded
;
825 plugin
= pluginObject
;
826 plugin
->d
->pluginSpec
= q
;
831 \fn bool PluginSpecPrivate::initializePlugin()
834 bool PluginSpecPrivate::initializePlugin()
839 if (state
!= PluginSpec::Loaded
) {
840 if (state
== PluginSpec::Initialized
) {
843 errorString
= QCoreApplication::translate("PluginSpec", "Initializing the plugin failed because state != Loaded");
848 errorString
= QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to initialize");
853 if (!plugin
->initialize(arguments
, &err
)) {
854 errorString
= QCoreApplication::translate("PluginSpec", "Plugin initialization failed: %1").arg(err
);
858 state
= PluginSpec::Initialized
;
863 \fn bool PluginSpecPrivate::initializeExtensions()
866 bool PluginSpecPrivate::initializeExtensions()
871 if (state
!= PluginSpec::Initialized
) {
872 if (state
== PluginSpec::Running
) {
875 errorString
= QCoreApplication::translate("PluginSpec", "Cannot perform extensionsInitialized because state != Initialized");
880 errorString
= QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to perform extensionsInitialized");
884 plugin
->extensionsInitialized();
885 state
= PluginSpec::Running
;
890 \fn bool PluginSpecPrivate::stop()
893 void PluginSpecPrivate::stop()
899 state
= PluginSpec::Stopped
;
903 \fn bool PluginSpecPrivate::kill()
906 void PluginSpecPrivate::kill()
913 state
= PluginSpec::Deleted
;