diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2020-10-06 15:45:05 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-10-08 18:54:29 +0200 |
commit | 04bc9d83f22a7266fb0caf53c2c3da0d9b06fef4 (patch) | |
tree | 883b9e3eba194efa05dbab3592fca8f1273b844b | |
parent | e8007671d4ec6d791cb337b297f2beb7e5300929 (diff) |
Allow optional imports in qmldir files
This is useful for modules that select their imports at runtime using
qmlRegisterModuleImport(). We can list all possible variants as optional
imports so that tools can see what types might be available.
Task-number: QTBUG-87130
Change-Id: I8a37bdde79aef3619fd1f05e5ea6781d521afa88
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/qml/Qt6QmlBuildInternals.cmake | 2 | ||||
-rw-r--r-- | src/qml/Qt6QmlMacros.cmake | 42 | ||||
-rw-r--r-- | src/qml/qml/qqml.cpp | 8 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype.cpp | 2 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 6 | ||||
-rw-r--r-- | src/qml/qmldirparser/qqmldirparser.cpp | 105 | ||||
-rw-r--r-- | src/qml/qmldirparser/qqmldirparser_p.h | 17 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimporter.cpp | 10 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/Things/qmldir | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/optionalImport.qml | 5 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 1 | ||||
-rw-r--r-- | tools/qmlimportscanner/main.cpp | 2 |
12 files changed, 123 insertions, 78 deletions
diff --git a/src/qml/Qt6QmlBuildInternals.cmake b/src/qml/Qt6QmlBuildInternals.cmake index d74b1d55fe..0e702feea2 100644 --- a/src/qml/Qt6QmlBuildInternals.cmake +++ b/src/qml/Qt6QmlBuildInternals.cmake @@ -39,6 +39,7 @@ function(qt_internal_add_qml_module target) set(qml_module_multi_args IMPORTS + OPTIONAL_IMPORTS TYPEINFO DEPENDENCIES ) @@ -134,6 +135,7 @@ function(qt_internal_add_qml_module target) VERSION ${arg_VERSION} QML_FILES ${arg_QML_FILES} IMPORTS "${arg_IMPORTS}" + OPTIONAL_IMPORTS "${arg_OPTIONAL_IMPORTS}" TYPEINFO "${arg_TYPEINFO}" DO_NOT_INSTALL_METADATA DO_NOT_CREATE_TARGET diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index 4bd457311a..0afd365948 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -63,6 +63,11 @@ # as version to forward the version the current module is being imported with, # e.g. QtQuick/auto. (OPTIONAL) # +# OPTIONAL_IMPORTS: List of other Qml Modules that this module may import at +# run-time. Those are not automatically imported by the QML engine when +# importing the current module, but rather serve as hints to tools like +# qmllint. Versions can be specified in the same as for IMPORT. (OPTIONAL) +# # RESOURCE_EXPORT: In static builds, when Qml files are processed via the Qt # Quick Compiler generate a separate static library that will be linked in # as an Interface. Supply an output variable to perform any custom actions @@ -109,6 +114,7 @@ function(qt6_add_qml_module target) SOURCES QML_FILES IMPORTS + OPTIONAL_IMPORTS DEPENDENCIES ) @@ -283,23 +289,29 @@ function(qt6_add_qml_module target) # plugins.qmltypes is actually generated. string(APPEND qmldir_file_contents "typeinfo plugins.qmltypes\n") endif() - foreach(import IN LISTS arg_IMPORTS) - string(FIND ${import} "/" slash_position REVERSE) - if (slash_position EQUAL -1) - string(APPEND qmldir_file_contents "import ${import}\n") - else() - string(SUBSTRING ${import} 0 ${slash_position} import_module) - math(EXPR slash_position "${slash_position} + 1") - string(SUBSTRING ${import} ${slash_position} -1 import_version) - if (import_version MATCHES "[0-9]+\\.[0-9]+" OR import_version MATCHES "[0-9]+") - string(APPEND qmldir_file_contents "import ${import_module} ${import_version}\n") - elseif (import_version MATCHES "auto") - string(APPEND qmldir_file_contents "import ${import_module} auto\n") + + macro(_add_imports imports import_string) + foreach(import IN LISTS ${imports}) + string(FIND ${import} "/" slash_position REVERSE) + if (slash_position EQUAL -1) + string(APPEND qmldir_file_contents "${import_string} ${import}\n") else() - message(FATAL_ERROR "Invalid module import version number. Expected 'VersionMajor', 'VersionMajor.VersionMinor' or 'auto'.") + string(SUBSTRING ${import} 0 ${slash_position} import_module) + math(EXPR slash_position "${slash_position} + 1") + string(SUBSTRING ${import} ${slash_position} -1 import_version) + if (import_version MATCHES "[0-9]+\\.[0-9]+" OR import_version MATCHES "[0-9]+") + string(APPEND qmldir_file_contents "${import_string} ${import_module} ${import_version}\n") + elseif (import_version MATCHES "auto") + string(APPEND qmldir_file_contents "${import_string} ${import_module} auto\n") + else() + message(FATAL_ERROR "Invalid module ${import_string} version number. Expected 'VersionMajor', 'VersionMajor.VersionMinor' or 'auto'.") + endif() endif() - endif() - endforeach() + endforeach() + endmacro() + + _add_imports(arg_IMPORTS "import") + _add_imports(arg_OPTIONAL_IMPORTS "optional import") foreach(dependency IN LISTS arg_DEPENDENCIES) string(FIND ${dependency} "/" slash_position REVERSE) diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index d26a09b081..e61cf417e3 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -75,12 +75,12 @@ void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor) static QQmlDirParser::Import resolveImport(const QString &uri, int importMajor, int importMinor) { if (importMajor == QQmlModuleImportAuto) - return QQmlDirParser::Import(uri, QTypeRevision(), true); + return QQmlDirParser::Import(uri, QTypeRevision(), QQmlDirParser::Import::Auto); else if (importMajor == QQmlModuleImportLatest) - return QQmlDirParser::Import(uri, QTypeRevision(), false); + return QQmlDirParser::Import(uri, QTypeRevision(), QQmlDirParser::Import::Default); else if (importMinor == QQmlModuleImportLatest) - return QQmlDirParser::Import(uri, QTypeRevision::fromMajorVersion(importMajor), false); - return QQmlDirParser::Import(uri, QTypeRevision::fromVersion(importMajor, importMinor), false); + return QQmlDirParser::Import(uri, QTypeRevision::fromMajorVersion(importMajor), QQmlDirParser::Import::Default); + return QQmlDirParser::Import(uri, QTypeRevision::fromVersion(importMajor, importMinor), QQmlDirParser::Import::Default); } static QTypeRevision resolveModuleVersion(int moduleMajor) diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 16b05898d1..71fc753c3b 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -724,7 +724,7 @@ void QQmlMetaType::registerModuleImport(const QString &uri, QTypeRevision module static bool operator==(const QQmlDirParser::Import &a, const QQmlDirParser::Import &b) { - return a.module == b.module && a.version == b.version && a.isAutoImport == b.isAutoImport; + return a.module == b.module && a.version == b.version && a.flags == b.flags; } void QQmlMetaType::unregisterModuleImport(const QString &uri, QTypeRevision moduleVersion, diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 6b4ce26c97..1b14467266 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -742,11 +742,13 @@ bool QQmlTypeLoader::Blob::loadImportDependencies(PendingImportPtr currentImport = QQmlMetaType::moduleImports(currentImport->uri, currentImport->version) + qmldir.imports(); for (const auto &implicitImport : implicitImports) { + if (implicitImport.flags & QQmlDirParser::Import::Optional) + continue; auto dependencyImport = std::make_shared<PendingImport>(); dependencyImport->uri = implicitImport.module; dependencyImport->qualifier = currentImport->qualifier; - dependencyImport->version = implicitImport.isAutoImport ? currentImport->version - : implicitImport.version; + dependencyImport->version = (implicitImport.flags & QQmlDirParser::Import::Auto) + ? currentImport->version : implicitImport.version; if (!addImport(dependencyImport, QQmlImports::ImportLowPrecedence, errors)) { QQmlError error; error.setDescription( diff --git a/src/qml/qmldirparser/qqmldirparser.cpp b/src/qml/qmldirparser/qqmldirparser.cpp index 0588e5bca7..403c04b1e1 100644 --- a/src/qml/qmldirparser/qqmldirparser.cpp +++ b/src/qml/qmldirparser/qqmldirparser.cpp @@ -110,6 +110,50 @@ bool QQmlDirParser::parse(const QString &source) quint16 lineNumber = 0; bool firstLine = true; + auto readImport = [&](const QString *sections, int sectionCount, Import::Flags flags) { + Import import; + if (sectionCount == 2) { + import = Import(sections[1], QTypeRevision(), flags); + } else if (sectionCount == 3) { + if (sections[2] == QLatin1String("auto")) { + import = Import(sections[1], QTypeRevision(), flags | Import::Auto); + } else { + const auto version = parseVersion(sections[2]); + if (version.isValid()) { + import = Import(sections[1], version, flags); + } else { + reportError(lineNumber, 0, + QStringLiteral("invalid version %1, expected <major>.<minor>") + .arg(sections[2])); + return false; + } + } + } else { + reportError(lineNumber, 0, + QStringLiteral("%1 requires 1 or 2 arguments, but %2 were provided") + .arg(sections[0]).arg(sectionCount - 1)); + return false; + } + if (sections[0] == QStringLiteral("import")) + _imports.append(import); + else + _dependencies.append(import); + return true; + }; + + auto readPlugin = [&](const QString *sections, int sectionCount, bool isOptional) { + if (sectionCount < 2 || sectionCount > 3) { + reportError(lineNumber, 0, QStringLiteral("plugin directive requires one or two " + "arguments, but %1 were provided") + .arg(sectionCount - 1)); + return false; + } + + const Plugin entry(sections[1], sections[2], isOptional); + _plugins.append(entry); + return true; + }; + const QChar *ch = source.constData(); while (!ch->isNull()) { ++lineNumber; @@ -176,34 +220,26 @@ bool QQmlDirParser::parse(const QString &source) _typeNamespace = sections[1]; } 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)); - + if (!readPlugin(sections, sectionCount, false)) continue; - } - - 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")); + if (sectionCount < 2) { + reportError(lineNumber, 0, QStringLiteral("optional directive requires further " + "arguments, but none were provided.")); continue; } - if (sectionCount < 3 || sectionCount > 4) { - reportError(lineNumber, 0, - QStringLiteral("plugin directive requires one or two arguments, but %1 were provided") - .arg(sectionCount - 2)); + if (sections[1] == QStringLiteral("plugin")) { + if (!readPlugin(sections + 1, sectionCount - 1, true)) + continue; + } else if (sections[1] == QLatin1String("import")) { + if (!readImport(sections + 1, sectionCount - 1, Import::Optional)) + continue; + } else { + reportError(lineNumber, 0, QStringLiteral("only import and plugin can be optional, " + "not %1.").arg(sections[1])); continue; } - - const Plugin entry(sections[2], sections[3], true); - _plugins.append(entry); - } else if (sections[0] == QLatin1String("classname")) { if (sectionCount < 2) { reportError(lineNumber, 0, @@ -261,33 +297,8 @@ bool QQmlDirParser::parse(const QString &source) _designerSupported = true; } else if (sections[0] == QLatin1String("import") || sections[0] == QLatin1String("depends")) { - Import import; - if (sectionCount == 2) { - import = Import(sections[1], QTypeRevision(), false); - } else if (sectionCount == 3) { - if (sections[2] == QLatin1String("auto")) { - import = Import(sections[1], QTypeRevision(), true); - } else { - const auto version = parseVersion(sections[2]); - if (version.isValid()) { - import = Import(sections[1], version, false); - } else { - reportError(lineNumber, 0, - QStringLiteral("invalid version %1, expected <major>.<minor>") - .arg(sections[2])); - continue; - } - } - } else { - reportError(lineNumber, 0, - QStringLiteral("%1 requires 1 or 2 arguments, but %2 were provided") - .arg(sections[0]).arg(sectionCount - 1)); + if (!readImport(sections, sectionCount, Import::Default)) continue; - } - if (sections[0] == QStringLiteral("import")) - _imports.append(import); - else - _dependencies.append(import); } else if (sectionCount == 2) { // No version specified (should only be used for relative qmldir files) const Component entry(sections[0], sections[1], QTypeRevision()); diff --git a/src/qml/qmldirparser/qqmldirparser_p.h b/src/qml/qmldirparser/qqmldirparser_p.h index 48f808ff12..098b15a51b 100644 --- a/src/qml/qmldirparser/qqmldirparser_p.h +++ b/src/qml/qmldirparser/qqmldirparser_p.h @@ -134,13 +134,22 @@ public: struct Import { + enum Flag { + Default = 0x0, + Auto = 0x1, // forward the version of the importing module + Optional = 0x2 // is not automatically imported but only a tooling hint + }; + Q_DECLARE_FLAGS(Flags, Flag) + Import() = default; - Import(QString module, QTypeRevision version, bool isAutoImport) - : module(module), version(version), isAutoImport(isAutoImport) {} + Import(QString module, QTypeRevision version, Flags flags) + : module(module), version(version), flags(flags) + { + } QString module; - QTypeRevision version; // default: lastest version - bool isAutoImport = false; // if set: forward the version of the importing module + QTypeRevision version; // invalid version is latest version, unless Flag::Auto + Flags flags; }; QMultiHash<QString,Component> components() const; diff --git a/src/qmlcompiler/qqmljsimporter.cpp b/src/qmlcompiler/qqmljsimporter.cpp index 73d7637d79..e33538b653 100644 --- a/src/qmlcompiler/qqmljsimporter.cpp +++ b/src/qmlcompiler/qqmljsimporter.cpp @@ -89,14 +89,15 @@ void QQmlJSImporter::readQmltypes( for (const QString &dependency : qAsConst(dependencyStrings)) { const auto blank = dependency.indexOf(u' '); if (blank < 0) { - dependencies->append(QQmlDirParser::Import(dependency, {}, false)); + dependencies->append(QQmlDirParser::Import(dependency, {}, + QQmlDirParser::Import::Default)); continue; } const QString module = dependency.left(blank); const QString versionString = dependency.mid(blank + 1).trimmed(); if (versionString == QStringLiteral("auto")) { - dependencies->append(QQmlDirParser::Import(module, {}, true)); + dependencies->append(QQmlDirParser::Import(module, {}, QQmlDirParser::Import::Auto)); continue; } @@ -107,7 +108,8 @@ void QQmlJSImporter::readQmltypes( : QTypeRevision::fromVersion(versionString.left(dot).toUShort(), versionString.mid(dot + 1).toUShort()); - dependencies->append(QQmlDirParser::Import(module, version, false)); + dependencies->append(QQmlDirParser::Import(module, version, + QQmlDirParser::Import::Default)); } } @@ -174,7 +176,7 @@ void QQmlJSImporter::importDependencies( for (auto const &import : qAsConst(import.imports)) { importHelper(import.module, types, prefix, - import.isAutoImport ? version : import.version); + (import.flags & QQmlDirParser::Import::Auto) ? version : import.version); } } diff --git a/tests/auto/qml/qmllint/data/Things/qmldir b/tests/auto/qml/qmllint/data/Things/qmldir index 554f75d313..3c48ae0648 100644 --- a/tests/auto/qml/qmllint/data/Things/qmldir +++ b/tests/auto/qml/qmllint/data/Things/qmldir @@ -3,3 +3,4 @@ Something 1.0 SomethingElse.qml typeinfo plugins.qmltypes depends QtQuick 2.0 import QtQml +optional import QtQuick.LocalStorage auto diff --git a/tests/auto/qml/qmllint/data/optionalImport.qml b/tests/auto/qml/qmllint/data/optionalImport.qml new file mode 100644 index 0000000000..2be7cd444a --- /dev/null +++ b/tests/auto/qml/qmllint/data/optionalImport.qml @@ -0,0 +1,5 @@ +import Things 6.0 + +QtObject { + property var db: LocalStorage.openDatabaseSync("foo", "2", "bar", 4) +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 09ffd2c9ec..4c18253b0c 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -282,6 +282,7 @@ void TestQmllint::cleanQmlCode_data() QTest::newRow("enumFromQtQml") << QStringLiteral("enumFromQtQml.qml"); QTest::newRow("anchors1") << QStringLiteral("anchors1.qml"); QTest::newRow("anchors2") << QStringLiteral("anchors2.qml"); + QTest::newRow("optionalImport") << QStringLiteral("optionalImport.qml"); } void TestQmllint::cleanQmlCode() diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index 5729c74855..70dec3ed96 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -191,7 +191,7 @@ QVariantMap pluginsForModulePath(const QString &modulePath, const QString &versi const auto imports = parser.imports(); for (const auto &import : imports) { - if (import.isAutoImport) { + if (import.flags & QQmlDirParser::Import::Auto) { importsAndDependencies.append( import.module + QLatin1Char(' ') + (version.isEmpty() ? QString::fromLatin1("auto") : version)); |