qmldir: Allow for specifying default imports

Adds the option to specify default optional imports in qmldir for
tooling to load. Previously we would just load all entries.

This allows code that that relies on modules utilizing optional imports
(i.e. everything utilizing QtQuick.Controls) to still be supported in a
limited capacity.

This change also adds the necessary CMake API to add these entries to
qmldir files. It also disables loading optional imports by default
and only leaves them enabled for qmllint.

Change-Id: Iff6aaac9cb0ec72b7a2853b60840a4d28c84aa25
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Maximilian Goldstein 2022-02-07 13:53:49 +01:00
parent 0f0987c160
commit 2ea887cca0
12 changed files with 67 additions and 12 deletions

View File

@ -36,6 +36,7 @@ macro(qt_internal_get_internal_add_qml_module_keywords
IMPORTS
IMPORT_PATH
OPTIONAL_IMPORTS
DEFAULT_IMPORTS
DEPENDENCIES
PAST_MAJOR_VERSIONS
)

View File

@ -66,6 +66,7 @@ function(qt6_add_qml_module target)
IMPORTS
IMPORT_PATH
OPTIONAL_IMPORTS
DEFAULT_IMPORTS
DEPENDENCIES
PAST_MAJOR_VERSIONS
)
@ -382,7 +383,7 @@ function(qt6_add_qml_module target)
set(arg_TYPEINFO ${target}.qmltypes)
endif()
foreach(import_set IN ITEMS IMPORTS OPTIONAL_IMPORTS)
foreach(import_set IN ITEMS IMPORTS OPTIONAL_IMPORTS DEFAULT_IMPORTS)
foreach(import IN LISTS arg_${import_set})
string(FIND ${import} "/" slash_position REVERSE)
if (slash_position EQUAL -1)
@ -986,6 +987,8 @@ function(_qt_internal_target_generate_qmldir target)
_qt_internal_qmldir_item_list(import QT_QML_MODULE_IMPORTS)
_qt_internal_qmldir_item_list("optional import" QT_QML_MODULE_OPTIONAL_IMPORTS)
_qt_internal_qmldir_item_list("default import" QT_QML_MODULE_DEFAULT_IMPORTS)
_qt_internal_qmldir_item_list(depends QT_QML_MODULE_DEPENDENCIES)
get_target_property(prefix ${target} QT_QML_MODULE_RESOURCE_PREFIX)

View File

@ -52,6 +52,7 @@ qt_add_qml_module(
[TYPEINFO typeinfo]
[IMPORTS ...]
[OPTIONAL_IMPORTS ...]
[DEFAULT_IMPORTS ...]
[DEPENDENCIES ...]
[IMPORT_PATH ...]
[SOURCES ...]
@ -469,6 +470,15 @@ like \c qmllint. Versions can be specified in the same way as for \c IMPORTS.
Each module listed here will be added as an \c{optional import} entry in the
generated \l{Module Definition qmldir Files}{qmldir} file.
\c DEFAULT_IMPORTS specifies which of the optional imports are the default entries
that should be loaded by tooling. One entry should be specified for every group of
\c OPTIONAL_IMPORTS in the module. As optional imports are only resolved at runtime,
tooling like qmllint cannot in general know which of the optional imports should
be resolved. To remedy this, you can specify one of the optional imports as the
default import; tooling will then pick it. If you have one optional import that
gets used at runtime without any further configuration, that is an ideal candidate
for the default import.
\c DEPENDENCIES provides a list of other QML modules that this module depends
on, but doesn't necessarily import. It would typically be used for dependencies
that only exist at the C++ level, such as a module registering a class to QML

View File

@ -241,6 +241,23 @@ bool QQmlDirParser::parse(const QString &source)
"not %1.").arg(sections[1]));
continue;
}
} else if (sections[0] == QLatin1String("default")) {
if (sectionCount < 2) {
reportError(lineNumber, 0,
QStringLiteral("default directive requires further "
"arguments, but none were provided."));
continue;
}
if (sections[1] == QLatin1String("import")) {
if (!readImport(sections + 1, sectionCount - 1,
Import::Flags({ Import::Optional, Import::OptionalDefault })))
continue;
} else {
reportError(lineNumber, 0,
QStringLiteral("only optional imports can have a a defaultl, "
"not %1.")
.arg(sections[1]));
}
} else if (sections[0] == QLatin1String("classname")) {
if (sectionCount < 2) {
reportError(lineNumber, 0,

View File

@ -134,9 +134,11 @@ 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
Default = 0x0,
Auto = 0x1, // forward the version of the importing module
Optional = 0x2, // is not automatically imported but only a tooling hint
OptionalDefault =
0x4, // tooling hint only, denotes this entry should be imported by tooling
};
Q_DECLARE_FLAGS(Flags, Flag)

View File

@ -271,11 +271,29 @@ void QQmlJSImporter::importDependencies(const QQmlJSImporter::Import &import,
for (auto const &dependency : qAsConst(import.dependencies))
importHelper(dependency.module, types, QString(), dependency.version, true);
bool hasOptionalImports = false;
for (auto const &import : qAsConst(import.imports)) {
if (import.flags & QQmlDirParser::Import::Optional) {
hasOptionalImports = true;
if (!m_useOptionalImports) {
continue;
}
if (!(import.flags & QQmlDirParser::Import::OptionalDefault))
continue;
}
importHelper(import.module, types, isDependency ? QString() : prefix,
(import.flags & QQmlDirParser::Import::Auto) ? version : import.version,
isDependency);
}
if (hasOptionalImports && !m_useOptionalImports) {
m_warnings.append(
{ u"%1 uses optional imports which are not supported. Some types might not be found."_qs
.arg(import.name),
QtCriticalMsg, QQmlJS::SourceLocation() });
}
}
static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry,

View File

@ -50,10 +50,12 @@ class QQmlJSImporter
public:
using ImportedTypes = QHash<QString, QQmlJSImportedScope>;
QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper)
: m_importPaths(importPaths)
, m_builtins({})
, m_mapper(mapper)
QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper,
bool useOptionalImports = false)
: m_importPaths(importPaths),
m_builtins({}),
m_mapper(mapper),
m_useOptionalImports(useOptionalImports)
{}
QQmlJSResourceFileMapper *resourceFileMapper() { return m_mapper; }
@ -153,6 +155,7 @@ private:
QList<QQmlJS::DiagnosticMessage> m_warnings;
AvailableTypes m_builtins;
QQmlJSResourceFileMapper *m_mapper = nullptr;
bool m_useOptionalImports;
};
QT_END_NAMESPACE

View File

@ -69,7 +69,7 @@ private:
};
QQmlJSLinter::QQmlJSLinter(const QStringList &importPaths, bool useAbsolutePath)
: m_useAbsolutePath(useAbsolutePath), m_importer(importPaths, nullptr)
: m_useAbsolutePath(useAbsolutePath), m_importer(importPaths, nullptr, true)
{
}

View File

@ -12,13 +12,14 @@ qt_internal_add_qml_module(QuickControls2
IMPORTS
QtQuick.Controls.impl/auto
OPTIONAL_IMPORTS
QtQuick.Controls.Basic/auto
QtQuick.Controls.Fusion/auto
QtQuick.Controls.Material/auto
QtQuick.Controls.Imagine/auto
QtQuick.Controls.Universal/auto
QtQuick.Controls.Windows/auto
QtQuick.Controls.macOS/auto
DEFAULT_IMPORTS
QtQuick.Controls.Basic/auto
NO_PLUGIN_OPTIONAL
NO_GENERATE_PLUGIN_SOURCE
SOURCES

View File

@ -3,4 +3,4 @@ Something 1.0 SomethingElse.qml
typeinfo plugins.qmltypes
depends QtQuick 2.0
import QtQml 2.0
optional import QtQuick.LocalStorage auto
default import QtQuick.LocalStorage auto

View File

@ -939,7 +939,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");
QTest::newRow("defaultImport") << QStringLiteral("defaultImport.qml");
QTest::newRow("goodAliasObject") << QStringLiteral("goodAliasObject.qml");
QTest::newRow("jsmoduleimport") << QStringLiteral("jsmoduleimport.qml");
QTest::newRow("overridescript") << QStringLiteral("overridescript.qml");