aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2020-06-02 16:50:20 +0200
committerUlf Hermann <ulf.hermann@qt.io>2020-06-09 08:01:02 +0200
commit5dc14c88f9510795835fb4f0a0d46d67c40f7020 (patch)
tree937173d548a6ad0d4c46cf75914adb6f6b140254 /src
parent6a48a81319b886c8a3f85e1eb024186b05d0f3af (diff)
Allow QML plugins to be optional
If a plugin does nothing but load the library that provides the types, we can skip the plugin loading by linking the library directly. State that in the qmldir file, and evaluate it when loading the module. Task-number: QTBUG-84639 Change-Id: I2097237866a50f66c55e4653ad119fe10e18a893 Reviewed-by: Paul Wicking <paul.wicking@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/imports/models/CMakeLists.txt2
-rw-r--r--src/imports/models/qmldir2
-rw-r--r--src/imports/workerscript/CMakeLists.txt2
-rw-r--r--src/imports/workerscript/qmldir2
-rw-r--r--src/qml/Qt6QmlBuildInternals.cmake7
-rw-r--r--src/qml/Qt6QmlMacros.cmake13
-rw-r--r--src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc7
-rw-r--r--src/qml/qml/qqmlengine.cpp3
-rw-r--r--src/qml/qml/qqmlimport.cpp124
-rw-r--r--src/qml/qml/qqmlimport_p.h4
-rw-r--r--src/qml/qml/qqmlmetatype.cpp21
-rw-r--r--src/qml/qml/qqmlmetatype_p.h12
-rw-r--r--src/qml/qmldirparser/qqmldirparser.cpp21
-rw-r--r--src/qml/qmldirparser/qqmldirparser_p.h5
14 files changed, 148 insertions, 77 deletions
diff --git a/src/imports/models/CMakeLists.txt b/src/imports/models/CMakeLists.txt
index a6d4065202..7d08563f59 100644
--- a/src/imports/models/CMakeLists.txt
+++ b/src/imports/models/CMakeLists.txt
@@ -9,7 +9,7 @@ qt_add_qml_module(modelsplugin
VERSION "${CMAKE_PROJECT_VERSION}"
DESIGNER_SUPPORTED
CLASSNAME QtQmlModelsPlugin
- SKIP_TYPE_REGISTRATION
+ PLUGIN_OPTIONAL # special case
SOURCES
plugin.cpp
PUBLIC_LIBRARIES
diff --git a/src/imports/models/qmldir b/src/imports/models/qmldir
index 2dd20b923e..341694a34d 100644
--- a/src/imports/models/qmldir
+++ b/src/imports/models/qmldir
@@ -1,4 +1,4 @@
module QtQml.Models
-plugin modelsplugin
+optional plugin modelsplugin
classname QtQmlModelsPlugin
designersupported
diff --git a/src/imports/workerscript/CMakeLists.txt b/src/imports/workerscript/CMakeLists.txt
index 57b2739a10..b1e98b2af6 100644
--- a/src/imports/workerscript/CMakeLists.txt
+++ b/src/imports/workerscript/CMakeLists.txt
@@ -9,7 +9,7 @@ qt_add_qml_module(workerscriptplugin
VERSION "${CMAKE_PROJECT_VERSION}"
DESIGNER_SUPPORTED
CLASSNAME QtQmlWorkerScriptPlugin
- SKIP_TYPE_REGISTRATION
+ PLUGIN_OPTIONAL # special case
SOURCES
plugin.cpp
PUBLIC_LIBRARIES
diff --git a/src/imports/workerscript/qmldir b/src/imports/workerscript/qmldir
index 02ff9ea188..f1ff798d75 100644
--- a/src/imports/workerscript/qmldir
+++ b/src/imports/workerscript/qmldir
@@ -1,4 +1,4 @@
module QtQml.WorkerScript
-plugin workerscriptplugin
+optional plugin workerscriptplugin
classname QtQmlWorkerScriptPlugin
designersupported
diff --git a/src/qml/Qt6QmlBuildInternals.cmake b/src/qml/Qt6QmlBuildInternals.cmake
index 7ef66ce766..aeec04e810 100644
--- a/src/qml/Qt6QmlBuildInternals.cmake
+++ b/src/qml/Qt6QmlBuildInternals.cmake
@@ -17,6 +17,7 @@ include_guard(GLOBAL)
# VERSION: Version of the qml module
# SKIP_TYPE_REGISTRATION: All qml files are expected to be registered by the
# c++ plugin code.
+# PLUGIN_OPTIONAL: Any plugins are optional
#
function(qt_add_qml_module target)
@@ -25,6 +26,7 @@ function(qt_add_qml_module target)
DESIGNER_SUPPORTED
DO_NOT_INSTALL
SKIP_TYPE_REGISTRATION
+ PLUGIN_OPTIONAL
)
set(qml_module_single_args
@@ -103,6 +105,10 @@ function(qt_add_qml_module target)
set(skip_registration_arg SKIP_TYPE_REGISTRATION)
endif()
+ if (arg_PLUGIN_OPTIONAL)
+ set(plugin_optional_arg PLUGIN_OPTIONAL)
+ endif()
+
if (arg_GENERATE_QMLTYPES)
set(generate_qmltypes_arg GENERATE_QMLTYPES)
endif()
@@ -113,6 +119,7 @@ function(qt_add_qml_module target)
${designer_supported_arg}
${no_create_option}
${skip_registration_arg}
+ ${plugin_optional_arg}
${classname_arg}
${generate_qmltypes_arg}
RESOURCE_PREFIX "/qt-project.org/imports"
diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake
index 9671712bd3..43a590d310 100644
--- a/src/qml/Qt6QmlMacros.cmake
+++ b/src/qml/Qt6QmlMacros.cmake
@@ -68,6 +68,10 @@
# to not list any qml types. These are expected to be registered by the
# c++ plugin code instead.
#
+# PLUGIN_OPTIONAL: The plugin is marked as optional in the qmldir file. If the
+# type registration functions are already available by other means, typically
+# by linking a library proxied by the plugin, it won't be loaded.
+#
function(qt6_add_qml_module target)
@@ -76,6 +80,7 @@ function(qt6_add_qml_module target)
DESIGNER_SUPPORTED
DO_NOT_INSTALL_METADATA
SKIP_TYPE_REGISTRATION
+ PLUGIN_OPTIONAL
INSTALL_QML_FILES
)
@@ -224,7 +229,13 @@ function(qt6_add_qml_module target)
set(qmldir_file "${CMAKE_CURRENT_BINARY_DIR}/qmldir")
set_target_properties(${target} PROPERTIES QT_QML_MODULE_QMLDIR_FILE ${qmldir_file})
set(qmldir_file_contents "module ${arg_URI}\n")
- string(APPEND qmldir_file_contents "plugin ${target}\n")
+
+ if (arg_PLUGIN_OPTIONAL)
+ string(APPEND qmldir_file_contents "optional plugin ${target}\n")
+ else()
+ string(APPEND qmldir_file_contents "plugin ${target}\n")
+ endif()
+
if (arg_CLASSNAME)
string(APPEND qmldir_file_contents "classname ${arg_CLASSNAME}\n")
endif()
diff --git a/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc b/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc
index 396a83b821..e9dc5a043a 100644
--- a/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc
+++ b/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc
@@ -163,11 +163,16 @@ MyScript 1.0 MyScript.js
\row
\li
\code
-plugin <Name> [<Path>]
+[optional] plugin <Name> [<Path>]
\endcode
\li Declares a plugin to be made available by the module.
\list
+ \li \c optional denotes that the plugin itself does not contain
+ any relevant code and only serves to load a library it links
+ to. If given, and if any types for the module are already
+ available, indicating that the library has been loaded by some
+ other means, QML will not load the plugin.
\li \c <Name> is the plugin library name. This is usually not the
same as the file name of the plugin binary, which is platform
dependent; e.g. the library \c MyAppTypes would produce
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp
index 1f203fae88..381d934b8b 100644
--- a/src/qml/qml/qqmlengine.cpp
+++ b/src/qml/qml/qqmlengine.cpp
@@ -2202,7 +2202,8 @@ void QQmlEngine::setPluginPathList(const QStringList &paths)
bool QQmlEngine::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors)
{
Q_D(QQmlEngine);
- return d->importDatabase.importDynamicPlugin(filePath, uri, QString(), QTypeRevision(), errors);
+ return d->importDatabase.importDynamicPlugin(filePath, uri, QString(), QTypeRevision(), false,
+ errors);
}
#endif
diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp
index c17e3db8a1..1ae9505dc8 100644
--- a/src/qml/qml/qqmlimport.cpp
+++ b/src/qml/qml/qqmlimport.cpp
@@ -138,7 +138,7 @@ bool isPathAbsolute(const QString &path)
struct RegisteredPlugin {
QString uri;
- QPluginLoader* loader;
+ QPluginLoader *loader = nullptr;
};
struct StringRegisteredPluginMap : public QMap<QString, RegisteredPlugin> {
@@ -1229,30 +1229,36 @@ bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath,
int dynamicPluginsFound = 0;
int staticPluginsFound = 0;
-#if QT_CONFIG(library)
+ auto handleErrors = [&]() {
+ if (errors) {
+ // XXX TODO: should we leave the import plugin error alone?
+ // Here, we pop it off the top and coalesce it into this error's message.
+ // The reason is that the lower level may add url and line/column numbering information.
+ QQmlError error;
+ error.setDescription(
+ QQmlImportDatabase::tr(
+ "plugin cannot be loaded for module \"%1\": %2")
+ .arg(uri, errors->takeFirst().description()));
+ error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
+ errors->prepend(error);
+ }
+ };
+
const auto qmldirPlugins = qmldir.plugins();
for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
- QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name);
- if (!resolvedFilePath.isEmpty()) {
- dynamicPluginsFound++;
- if (!database->importDynamicPlugin(resolvedFilePath, uri, typeNamespace, version, errors)) {
- if (errors) {
- // XXX TODO: should we leave the import plugin error alone?
- // Here, we pop it off the top and coalesce it into this error's message.
- // The reason is that the lower level may add url and line/column numbering information.
- QQmlError error;
- error.setDescription(
- QQmlImportDatabase::tr(
- "plugin cannot be loaded for module \"%1\": %2")
- .arg(uri, errors->takeFirst().description()));
- error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
- errors->prepend(error);
- }
- return false;
- }
+ const QString resolvedFilePath = database->resolvePlugin(
+ typeLoader, qmldirPath, plugin.path, plugin.name);
+
+ if (resolvedFilePath.isEmpty())
+ continue;
+
+ ++dynamicPluginsFound;
+ if (!database->importDynamicPlugin(
+ resolvedFilePath, uri, typeNamespace, version, plugin.optional, errors)) {
+ handleErrors();
+ return false;
}
}
-#endif // QT_CONFIG(library)
if (dynamicPluginsFound < qmldirPluginCount) {
// Check if the missing plugins can be resolved statically. We do this by looking at
@@ -2178,16 +2184,9 @@ void QQmlImportDatabase::setImportPathList(const QStringList &paths)
/*!
\internal
*/
-static bool registerPluginTypes(QObject *instance, const QString &basePath, const QString &uri,
- const QString &typeNamespace, QTypeRevision version,
- QList<QQmlError> *errors)
+static bool lockModule(const QString &uri, const QString &typeNamespace, QTypeRevision version,
+ QList<QQmlError> *errors)
{
- if (qmlImportTrace())
- qDebug().nospace() << "QQmlImportDatabase::registerPluginTypes: " << uri << " from " << basePath;
-
- if (!QQmlMetaType::registerPluginTypes(instance, basePath, uri, typeNamespace, version, errors))
- return false;
-
if (version.hasMajorVersion() && !typeNamespace.isEmpty()
&& !QQmlMetaType::protectModule(uri, version)) {
QQmlError error;
@@ -2229,8 +2228,12 @@ bool QQmlImportDatabase::importStaticPlugin(
plugin.loader = nullptr;
plugins->insert(uniquePluginID, plugin);
- if (!registerPluginTypes(instance, basePath, uri, typeNamespace, version, errors))
+ if (QQmlMetaType::registerPluginTypes(
+ instance, basePath, uri, typeNamespace, version, errors)
+ == QQmlMetaType::RegistrationResult::Failure
+ || !lockModule(uri, typeNamespace, version, errors)) {
return false;
+ }
}
// Release the lock on plugins early as we're done with the global part. Releasing the lock
@@ -2245,13 +2248,12 @@ bool QQmlImportDatabase::importStaticPlugin(
return true;
}
-#if QT_CONFIG(library)
/*!
\internal
*/
bool QQmlImportDatabase::importDynamicPlugin(
const QString &filePath, const QString &uri, const QString &typeNamespace,
- QTypeRevision version, QList<QQmlError> *errors)
+ QTypeRevision version, bool isOptional, QList<QQmlError> *errors)
{
QFileInfo fileInfo(filePath);
const QString absoluteFilePath = fileInfo.absoluteFilePath();
@@ -2279,35 +2281,56 @@ bool QQmlImportDatabase::importDynamicPlugin(
return false;
}
- QPluginLoader* loader = nullptr;
- if (!typesRegistered) {
- loader = new QPluginLoader(absoluteFilePath);
+ RegisteredPlugin plugin;
+ plugin.uri = uri;
+
+ const QString absolutePath = fileInfo.absolutePath();
+ if (!typesRegistered && isOptional) {
+ switch (QQmlMetaType::registerPluginTypes(nullptr, absolutePath, uri, typeNamespace,
+ version, errors)) {
+ case QQmlMetaType::RegistrationResult::NoRegistrationFunction:
+ // try again with plugin
+ break;
+ case QQmlMetaType::RegistrationResult::Success:
+ if (!lockModule(uri, typeNamespace, version, errors))
+ return false;
+ // instance and loader intentionally left at nullptr
+ plugins->insert(absoluteFilePath, plugin);
+ typesRegistered = true;
+ break;
+ case QQmlMetaType::RegistrationResult::Failure:
+ return false;
+ }
+ }
- if (!loader->load()) {
+#if QT_CONFIG(library)
+ if (!typesRegistered) {
+ plugin.loader = new QPluginLoader(absoluteFilePath);
+ if (!plugin.loader->load()) {
if (errors) {
QQmlError error;
- error.setDescription(loader->errorString());
+ error.setDescription(plugin.loader->errorString());
errors->prepend(error);
}
- delete loader;
+ delete plugin.loader;
return false;
}
- } else {
- loader = plugins->value(absoluteFilePath).loader;
- }
- instance = loader->instance();
-
- if (!typesRegistered) {
- RegisteredPlugin plugin;
- plugin.uri = uri;
- plugin.loader = loader;
+ instance = plugin.loader->instance();
plugins->insert(absoluteFilePath, plugin);
// Continue with shared code path for dynamic and static plugins:
- if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, version, errors))
+ if (QQmlMetaType::registerPluginTypes(
+ instance, fileInfo.absolutePath(), uri, typeNamespace, version, errors)
+ == QQmlMetaType::RegistrationResult::Failure
+ || !lockModule(uri, typeNamespace, version, errors)) {
return false;
+ }
+ } else {
+ if (QPluginLoader *loader = plugins->value(absoluteFilePath).loader)
+ instance = loader->instance();
}
+#endif // QT_CONFIG(library)
}
// Release the lock on plugins early as we're done with the global part. Releasing the lock
@@ -2335,10 +2358,12 @@ bool QQmlImportDatabase::removeDynamicPlugin(const QString &filePath)
if (!loader)
return false;
+#if QT_CONFIG(library)
if (!loader->unload()) {
qWarning("Unloading %s failed: %s", qPrintable(it->uri),
qPrintable(loader->errorString()));
}
+#endif
delete loader;
plugins->erase(it);
@@ -2356,7 +2381,6 @@ QStringList QQmlImportDatabase::dynamicPlugins() const
}
return results;
}
-#endif // QT_CONFIG(library)
void QQmlImportDatabase::clearDirCache()
{
diff --git a/src/qml/qml/qqmlimport_p.h b/src/qml/qml/qqmlimport_p.h
index 57baa2c87f..5f16880199 100644
--- a/src/qml/qml/qqmlimport_p.h
+++ b/src/qml/qml/qqmlimport_p.h
@@ -226,13 +226,11 @@ public:
QQmlImportDatabase(QQmlEngine *);
~QQmlImportDatabase();
-#if QT_CONFIG(library)
bool importDynamicPlugin(const QString &filePath, const QString &uri,
const QString &importNamespace, QTypeRevision version,
- QList<QQmlError> *errors);
+ bool isOptional, QList<QQmlError> *errors);
bool removeDynamicPlugin(const QString &filePath);
QStringList dynamicPlugins() const;
-#endif
QStringList importPathList(PathType type = LocalOrRemote) const;
void setImportPathList(const QStringList &paths);
diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp
index 61e8e323c6..fd307e4575 100644
--- a/src/qml/qml/qqmlmetatype.cpp
+++ b/src/qml/qml/qqmlmetatype.cpp
@@ -681,9 +681,9 @@ public:
};
-bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePath,
- const QString &uri, const QString &typeNamespace,
- QTypeRevision version, QList<QQmlError> *errors)
+QQmlMetaType::RegistrationResult QQmlMetaType::registerPluginTypes(
+ QObject *instance, const QString &basePath, const QString &uri,
+ const QString &typeNamespace, QTypeRevision version, QList<QQmlError> *errors)
{
if (!typeNamespace.isEmpty() && typeNamespace != uri) {
// This is an 'identified' module
@@ -695,7 +695,7 @@ bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePat
.arg(typeNamespace).arg(uri));
errors->prepend(error);
}
- return false;
+ return RegistrationResult::Failure;
}
QStringList failures;
@@ -713,7 +713,7 @@ bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePat
.arg(typeNamespace));
errors->prepend(error);
}
- return false;
+ return RegistrationResult::Failure;
}
} else {
// This is not an identified module - provide a warning
@@ -722,7 +722,7 @@ bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePat
"it cannot be protected from external registrations.").arg(uri));
}
- if (!qobject_cast<QQmlEngineExtensionInterface *>(instance)) {
+ if (instance && !qobject_cast<QQmlEngineExtensionInterface *>(instance)) {
QQmlTypesExtensionInterface *iface = qobject_cast<QQmlTypesExtensionInterface *>(instance);
if (!iface) {
if (errors) {
@@ -732,7 +732,7 @@ bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePat
"QQmlEngineExtensionInterface").arg(typeNamespace));
errors->prepend(error);
}
- return false;
+ return RegistrationResult::Failure;
}
if (auto *plugin = qobject_cast<QQmlExtensionPlugin *>(instance)) {
@@ -746,7 +746,8 @@ bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePat
iface->registerTypes(moduleId);
}
- data->registerModuleTypes(uri);
+ if (failures.isEmpty() && !data->registerModuleTypes(uri))
+ return RegistrationResult::NoRegistrationFunction;
if (!failures.isEmpty()) {
if (errors) {
@@ -756,11 +757,11 @@ bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePat
errors->prepend(error);
}
}
- return false;
+ return RegistrationResult::Failure;
}
}
- return true;
+ return RegistrationResult::Success;
}
/*
diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h
index 08d79d2e5b..9ce33ab545 100644
--- a/src/qml/qml/qqmlmetatype_p.h
+++ b/src/qml/qml/qqmlmetatype_p.h
@@ -73,14 +73,20 @@ struct CompositeMetaTypeIds
class Q_QML_PRIVATE_EXPORT QQmlMetaType
{
public:
+ enum class RegistrationResult {
+ Success,
+ Failure,
+ NoRegistrationFunction
+ };
+
static QQmlType registerType(const QQmlPrivate::RegisterType &type);
static QQmlType registerInterface(const QQmlPrivate::RegisterInterface &type);
static QQmlType registerSingletonType(const QQmlPrivate::RegisterSingletonType &type);
static QQmlType registerCompositeSingletonType(const QQmlPrivate::RegisterCompositeSingletonType &type);
static QQmlType registerCompositeType(const QQmlPrivate::RegisterCompositeType &type);
- static bool registerPluginTypes(QObject *instance, const QString &basePath,
- const QString &uri, const QString &typeNamespace,
- QTypeRevision version, QList<QQmlError> *errors);
+ static RegistrationResult registerPluginTypes(QObject *instance, const QString &basePath,
+ const QString &uri, const QString &typeNamespace,
+ QTypeRevision version, QList<QQmlError> *errors);
static QQmlType typeForUrl(const QString &urlString, const QHashedStringRef& typeName,
bool isCompositeSingleton, QList<QQmlError> *errors,
QTypeRevision version = QTypeRevision());
diff --git a/src/qml/qmldirparser/qqmldirparser.cpp b/src/qml/qmldirparser/qqmldirparser.cpp
index cb16d2e57a..a8a450d2ca 100644
--- a/src/qml/qmldirparser/qqmldirparser.cpp
+++ b/src/qml/qmldirparser/qqmldirparser.cpp
@@ -178,15 +178,32 @@ bool QQmlDirParser::parse(const QString &source)
} else if (sections[0] == QLatin1String("plugin")) {
if (sectionCount < 2 || sectionCount > 3) {
reportError(lineNumber, 0,
- QStringLiteral("plugin directive requires one or two arguments, but %1 were provided").arg(sectionCount - 1));
+ QStringLiteral("plugin directive requires one or two arguments, but %1 were provided")
+ .arg(sectionCount - 1));
continue;
}
- const Plugin entry(sections[1], sections[2]);
+ const Plugin entry(sections[1], sections[2], false);
_plugins.append(entry);
+ } else if (sections[0] == QLatin1String("optional")) {
+ if (sectionCount < 2 || sections[1] != QLatin1String("plugin")) {
+ reportError(lineNumber, 0, QStringLiteral("only plugins can be optional"));
+ continue;
+ }
+
+ if (sectionCount < 3 || sectionCount > 4) {
+ reportError(lineNumber, 0,
+ QStringLiteral("plugin directive requires one or two arguments, but %1 were provided")
+ .arg(sectionCount - 2));
+ continue;
+ }
+
+ const Plugin entry(sections[2], sections[3], true);
+ _plugins.append(entry);
+
} else if (sections[0] == QLatin1String("classname")) {
if (sectionCount < 2) {
reportError(lineNumber, 0,
diff --git a/src/qml/qmldirparser/qqmldirparser_p.h b/src/qml/qmldirparser/qqmldirparser_p.h
index ccdac8f484..a5a548daca 100644
--- a/src/qml/qmldirparser/qqmldirparser_p.h
+++ b/src/qml/qmldirparser/qqmldirparser_p.h
@@ -88,14 +88,15 @@ public:
{
Plugin() = default;
- Plugin(const QString &name, const QString &path)
- : name(name), path(path)
+ Plugin(const QString &name, const QString &path, bool optional)
+ : name(name), path(path), optional(optional)
{
checkNonRelative("Plugin", name, path);
}
QString name;
QString path;
+ bool optional = false;
};
struct Component