diff options
Diffstat (limited to 'src/qml')
-rw-r--r-- | src/qml/Qt6QmlBuildInternals.cmake | 7 | ||||
-rw-r--r-- | src/qml/Qt6QmlMacros.cmake | 13 | ||||
-rw-r--r-- | src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc | 7 | ||||
-rw-r--r-- | src/qml/qml/qqmlengine.cpp | 3 | ||||
-rw-r--r-- | src/qml/qml/qqmlimport.cpp | 124 | ||||
-rw-r--r-- | src/qml/qml/qqmlimport_p.h | 4 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype.cpp | 21 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype_p.h | 12 | ||||
-rw-r--r-- | src/qml/qmldirparser/qqmldirparser.cpp | 21 | ||||
-rw-r--r-- | src/qml/qmldirparser/qqmldirparser_p.h | 5 |
10 files changed, 144 insertions, 73 deletions
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 |