Discern between "auto" and versioned imports in qmldirs

You can now import the latest version, a specific version, or, "auto"
which is the same version as the parent module.

[ChangeLog][QtQml] You can now procedurally add module imports to
modules, using qmlRegisterModuleImport(). However, actual import
statements in qmldir files should be preferred wherever possible.

Fixes: QTBUG-84899
Change-Id: I3b32dd8b07a19d31b6538b9a6bb436840862f345
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2020-06-17 15:42:05 +02:00
parent 6de0287d7c
commit 36df81b3bc
27 changed files with 280 additions and 76 deletions

View File

@ -1,12 +1,12 @@
# Generated from qtqml.pro.
set(module_dynamic_qml_imports
QtQml.Models
QtQml.Models/auto
)
if (QT_FEATURE_qml_worker_script)
list(APPEND module_dynamic_qml_imports
QtQml.WorkerScript
QtQml.WorkerScript/auto
)
endif()

View File

@ -15,9 +15,9 @@ DYNAMIC_QMLDIR = \
"classname QtQmlPlugin" \
"typeinfo plugins.qmltypes" \
"designersupported" \
"import QtQml.Models"
"import QtQml.Models auto"
qtConfig(qml-worker-script): DYNAMIC_QMLDIR += \
"import QtQml.WorkerScript"
"import QtQml.WorkerScript auto"
load(qml_plugin)

View File

@ -11,7 +11,7 @@ qt_add_qml_module(qtquick2plugin
CLASSNAME QtQuick2Plugin
SKIP_TYPE_REGISTRATION
IMPORTS
QtQml
QtQml/auto
SOURCES
plugin.cpp
PUBLIC_LIBRARIES

View File

@ -3,4 +3,4 @@ plugin qtquick2plugin
classname QtQuick2Plugin
typeinfo plugins.qmltypes
designersupported
import QtQml
import QtQml auto

View File

@ -1,2 +1,2 @@
module QtQuick.Window
import QtQuick
import QtQuick auto

View File

@ -57,7 +57,11 @@
# that can be read by QML tools such as Qt Creator to access information about
# the types defined by the module's plugins. (OPTIONAL)
#
# IMPORTS: List of other Qml Modules that this module imports. (OPTIONAL)
# IMPORTS: List of other Qml Modules that this module imports. A version can be
# specified by appending it after a slash(/), e.g QtQuick/2.0. The minor
# version may be omitted, e.g. QtQuick/2. Alternatively "auto" may be given
# as version to forward the version the current module is being imported with,
# e.g. QtQuick/auto. (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
@ -254,7 +258,21 @@ function(qt6_add_qml_module target)
string(APPEND qmldir_file_contents "typeinfo plugins.qmltypes\n")
endif()
foreach(import IN LISTS arg_IMPORTS)
string(APPEND qmldir_file_contents "import ${import}\n")
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")
else()
message(FATAL_ERROR "Invalid module import version number. Expected 'VersionMajor', 'VersionMajor.VersionMinor' or 'auto'.")
endif()
endif()
endforeach()
foreach(dependency IN LISTS arg_DEPENDENCIES)

View File

@ -246,6 +246,23 @@ depends MyOtherModule 1.0
load QML (perhaps conditionally) which then depends on other
modules. In such cases, the \c depends declaration is necessary
to include the other modules in application packages.
\row
\li
\code
import <ModuleIdentifier> [<Version>]
\endcode
\li Declares that this module imports another.
Example:
\code
import MyOtherModule 1.0
\endcode
The types from the other module are made available in the same type
namespace as this module is imported into. Omitting the version
imports the latest version available of the other module, specifying
\c auto as version imports the same version as the version of this
module specified in the QML \c import statement.
\row
\li

View File

@ -72,35 +72,107 @@ void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor)
QQmlMetaType::registerModule(uri, QTypeRevision::fromVersion(versionMajor, versionMinor));
}
static QQmlDirParser::Import resolveImport(const QString &uri, int importMajor, int importMinor)
{
if (importMajor == QQmlModuleImportAuto)
return QQmlDirParser::Import(uri, QTypeRevision(), true);
else if (importMajor == QQmlModuleImportLatest)
return QQmlDirParser::Import(uri, QTypeRevision(), false);
else if (importMinor == QQmlModuleImportLatest)
return QQmlDirParser::Import(uri, QTypeRevision::fromMajorVersion(importMajor), false);
return QQmlDirParser::Import(uri, QTypeRevision::fromVersion(importMajor, importMinor), false);
}
static QTypeRevision resolveModuleVersion(int moduleMajor)
{
return moduleMajor == QQmlModuleImportModuleAny
? QTypeRevision()
: QTypeRevision::fromMajorVersion(moduleMajor);
}
/*!
* \enum QQmlModuleImportSpecialVersions
*
* Defines some special values that can be passed to the version arguments of
* qmlRegisterModuleImport() and qmlUnregisterModuleImport().
*
* \value QQmlModuleImportModuleAny When passed as majorVersion of the base
* module, signifies that the import is to be
* applied to any version of the module.
* \value QQmlModuleImportLatest When passed as major or minor version of
* the imported module, signifies that the
* latest overall, or latest minor version
* of a specified major version shall be
* imported.
* \value QQmlModuleImportAuto When passed as major version of the imported
* module, signifies that the version of the
* base module shall be forwarded.
*/
/*!
* Registers an implicit import for module \a uri of major version \a majorVersion
*
* This has the same effect as an \c import statement in a qmldir file: Whenever
* \a uri version \a majorVersion is imported, \a import is automatically
* imported, too, with the same version.
* \a uri of version \a moduleMajor is imported, \a import of version
* \a importMajor.\a importMinor is automatically imported, too. If
* \a importMajor is \l QmlModuleImportLatest the latest version
* available of that module is imported, and \a importMinor does not matter. If
* \a importMinor is \l QmlModuleImportLatest the latest minor version of a
* \a importMajor is chosen. If \a importMajor is \l QmlModuleImportAuto the
* version of \a import is version of \a uri being imported, and \a importMinor
* does not matter. If \a moduleMajor is \a QmlModuleImportModuleAny the module
* import is applied for any major version of \a uri. For example, you may
* specify that whenever any version of MyModule is imported, the latest version
* of MyOtherModule should be imported. Then, the following call would be
* appropriate:
*
* \code
* qmlRegisterModuleImport("MyModule", QmlModuleImportModuleAny,
* "MyOtherModule", QmlModuleImportLatest);
* \endcode
*
* Or, you may specify that whenever major version 5 of "MyModule" is imported,
* then version 3.14 of "MyOtherModule" should be imported:
*
* \code
* qmlRegisterModuleImport("MyModule", 5, "MyOtherModule", 3, 14);
* \endcode
*
* Finally, if you always want the same version of "MyOtherModule" to be
* imported whenever "MyModule" is imported, specify the following:
*
* \code
* qmlRegisterModuleImport("MyModule", QmlModuleImportModuleAny,
* "MyOtherModule", QmlModuleImportAuto);
* \endcode
*
* \sa qmlUnregisterModuleImport()
*/
void qmlRegisterModuleImport(const char *uri, int majorVersion, const char *import)
void qmlRegisterModuleImport(const char *uri, int moduleMajor,
const char *import, int importMajor, int importMinor)
{
QQmlMetaType::registerModuleImport(
QString::fromUtf8(uri), QTypeRevision::fromMajorVersion(majorVersion),
QString::fromUtf8(import));
QString::fromUtf8(uri), resolveModuleVersion(moduleMajor),
resolveImport(QString::fromUtf8(import), importMajor, importMinor));
}
/*!
* Removes a module import previously registered with qmlRegisterModuleImport()
*
* Calling this function makes sure that \a import is not automatically imported
* anymore when \a uri of version \a majorVersion is.
* Calling this function makes sure that \a import of version
* \a{importMajor}.\a{importMinor} is not automatically imported anymore when
* \a uri of version \a moduleMajor is. The version resolution works the same
* way as with \l qmlRegisterModuleImport().
*
* \sa qmlRegisterModuleImport()
*/
void qmlUnregisterModuleImport(const char *uri, int majorVersion, const char *import)
void qmlUnregisterModuleImport(const char *uri, int moduleMajor,
const char *import, int importMajor, int importMinor)
{
QQmlMetaType::unregisterModuleImport(
QString::fromUtf8(uri), QTypeRevision::fromMajorVersion(majorVersion),
QString::fromUtf8(import));
QString::fromUtf8(uri), resolveModuleVersion(moduleMajor),
resolveImport(QString::fromUtf8(import), importMajor, importMinor));
}
//From qqml.h

View File

@ -675,8 +675,20 @@ QT_WARNING_POP
Q_QML_EXPORT bool qmlProtectModule(const char* uri, int majVersion);
Q_QML_EXPORT void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor);
Q_QML_EXPORT void qmlRegisterModuleImport(const char *uri, int majorVersion, const char *import);
Q_QML_EXPORT void qmlUnregisterModuleImport(const char *uri, int majorVersion, const char *import);
enum QQmlModuleImportSpecialVersions: int {
QQmlModuleImportModuleAny = -1,
QQmlModuleImportLatest = -1,
QQmlModuleImportAuto = -2
};
Q_QML_EXPORT void qmlRegisterModuleImport(const char *uri, int moduleMajor,
const char *import,
int importMajor = QQmlModuleImportLatest,
int importMinor = QQmlModuleImportLatest);
Q_QML_EXPORT void qmlUnregisterModuleImport(const char *uri, int moduleMajor,
const char *import,
int importMajor = QQmlModuleImportLatest,
int importMinor = QQmlModuleImportLatest);
template<typename T>
QObject *qmlAttachedPropertiesObject(const QObject *obj, bool create = true)

View File

@ -628,22 +628,53 @@ bool QQmlMetaType::protectModule(const QString &uri, QTypeRevision version, bool
return range.first != range.second;
}
void QQmlMetaType::registerModuleImport(const QString &uri, QTypeRevision version, const QString &import)
void QQmlMetaType::registerModuleImport(const QString &uri, QTypeRevision moduleVersion,
const QQmlDirParser::Import &import)
{
QQmlMetaTypeDataPtr data;
QQmlTypeModule *module = getTypeModule(uri, version, data);
Q_ASSERT(module);
module->addImport(import);
data->moduleImports.insert(QQmlMetaTypeData::VersionedUri(uri, moduleVersion), import);
}
void QQmlMetaType::unregisterModuleImport(const QString &uri, QTypeRevision version, const QString &import)
static bool operator==(const QQmlDirParser::Import &a, const QQmlDirParser::Import &b)
{
return a.module == b.module && a.version == b.version && a.isAutoImport == b.isAutoImport;
}
void QQmlMetaType::unregisterModuleImport(const QString &uri, QTypeRevision moduleVersion,
const QQmlDirParser::Import &import)
{
QQmlMetaTypeDataPtr data;
data->moduleImports.remove(QQmlMetaTypeData::VersionedUri(uri, moduleVersion), import);
}
QList<QQmlDirParser::Import> QQmlMetaType::moduleImports(
const QString &uri, QTypeRevision version)
{
QQmlMetaTypeDataPtr data;
QQmlTypeModule *module = getTypeModule(uri, version, data);
Q_ASSERT(module);
module->removeImport(import);
const auto unrevisioned = data->moduleImports.equal_range(
QQmlMetaTypeData::VersionedUri(uri, QTypeRevision()));
QList<QQmlDirParser::Import> result(unrevisioned.first, unrevisioned.second);
if (version.hasMajorVersion())
return result + data->moduleImports.values(QQmlMetaTypeData::VersionedUri(uri, version));
// Use latest module available with that URI.
const auto begin = data->moduleImports.begin();
auto it = unrevisioned.first;
if (it == begin)
return result;
const QQmlMetaTypeData::VersionedUri latestVersion = (--it).key();
if (latestVersion.uri != uri)
return result;
do {
result += *it;
} while (it != begin && (--it).key() == latestVersion);
return result;
}
void QQmlMetaType::registerModule(const char *uri, QTypeRevision version)

View File

@ -54,6 +54,7 @@
#include <private/qtqmlglobal_p.h>
#include <private/qqmltype_p.h>
#include <private/qqmlproxymetaobject_p.h>
#include <private/qqmldirparser_p.h>
QT_BEGIN_NAMESPACE
@ -98,8 +99,12 @@ public:
static void registerModule(const char *uri, QTypeRevision version);
static bool protectModule(const QString &uri, QTypeRevision version,
bool protectAllVersions = false);
static void registerModuleImport(const QString &uri, QTypeRevision version, const QString &import);
static void unregisterModuleImport(const QString &uri, QTypeRevision version, const QString &import);
static void registerModuleImport(const QString &uri, QTypeRevision version,
const QQmlDirParser::Import &import);
static void unregisterModuleImport(const QString &uri, QTypeRevision version,
const QQmlDirParser::Import &import);
static QList<QQmlDirParser::Import> moduleImports(const QString &uri, QTypeRevision version);
static int typeId(const char *uri, QTypeRevision version, const char *qmlName);

View File

@ -116,6 +116,9 @@ struct QQmlMetaTypeData
QQmlTypeModule *findTypeModule(const QString &module, QTypeRevision version);
QQmlTypeModule *addTypeModule(std::unique_ptr<QQmlTypeModule> module);
using ModuleImports = QMultiMap<VersionedUri, QQmlDirParser::Import>;
ModuleImports moduleImports;
QHash<QString, void (*)()> moduleTypeRegistrationFunctions;
bool registerModuleTypes(const QString &uri);

View File

@ -714,15 +714,15 @@ void QQmlTypeLoader::Blob::dependencyComplete(QQmlDataBlob *blob)
bool QQmlTypeLoader::Blob::loadImportDependencies(PendingImportPtr currentImport, const QString &qmldirUri, QList<QQmlError> *errors)
{
const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirUri);
const QQmlTypeModule *module = QQmlMetaType::typeModule(currentImport->uri,
currentImport->version);
const QStringList implicitImports = module ? (module->imports() + qmldir.imports())
: qmldir.imports();
for (const QString &implicitImport : implicitImports) {
const QList<QQmlDirParser::Import> implicitImports
= QQmlMetaType::moduleImports(currentImport->uri, currentImport->version)
+ qmldir.imports();
for (const auto &implicitImport : implicitImports) {
auto dependencyImport = std::make_shared<PendingImport>();
dependencyImport->uri = implicitImport;
dependencyImport->uri = implicitImport.module;
dependencyImport->qualifier = currentImport->qualifier;
dependencyImport->version = currentImport->version;
dependencyImport->version = implicitImport.isAutoImport ? currentImport->version
: implicitImport.version;
if (!addImport(dependencyImport, QQmlImports::ImportLowPrecedence, errors)) {
QQmlError error;
error.setDescription(

View File

@ -105,7 +105,7 @@ QQmlDirPlugins QQmlTypeLoaderQmldirContent::plugins() const
return m_parser.plugins();
}
QStringList QQmlTypeLoaderQmldirContent::imports() const
QQmlDirImports QQmlTypeLoaderQmldirContent::imports() const
{
return m_parser.imports();
}

View File

@ -78,7 +78,7 @@ public:
QQmlDirComponents components() const;
QQmlDirScripts scripts() const;
QQmlDirPlugins plugins() const;
QStringList imports() const;
QQmlDirImports imports() const;
QString pluginLocation() const;

View File

@ -123,22 +123,4 @@ void QQmlTypeModule::walkCompositeSingletons(const std::function<void(const QQml
}
}
QStringList QQmlTypeModule::imports() const
{
QMutexLocker lock(&m_mutex);
return m_imports;
}
void QQmlTypeModule::addImport(const QString &import)
{
QMutexLocker lock(&m_mutex);
m_imports.append(import);
}
void QQmlTypeModule::removeImport(const QString &import)
{
QMutexLocker lock(&m_mutex);
m_imports.removeAll(import);
}
QT_END_NAMESPACE

View File

@ -104,16 +104,10 @@ public:
void walkCompositeSingletons(const std::function<void(const QQmlType &)> &callback) const;
void addImport(const QString &import);
void removeImport(const QString &import);
QStringList imports() const;
private:
const QString m_module;
const quint8 m_majorVersion = 0;
QStringList m_imports;
// Can only ever decrease
QAtomicInt m_minMinorVersion = std::numeric_limits<quint8>::max();

View File

@ -276,12 +276,28 @@ bool QQmlDirParser::parse(const QString &source)
reportError(lineNumber, 0, QStringLiteral("invalid version %1, expected <major>.<minor>").arg(sections[2]));
}
} else if (sections[0] == QLatin1String("import")) {
if (sectionCount != 2) {
if (sectionCount == 2) {
_imports << Import(sections[1], QTypeRevision(), false);
} else if (sectionCount == 3) {
if (sections[2] == QLatin1String("auto")) {
_imports << Import(sections[1], QTypeRevision(), true);
} else {
const auto version = parseVersion(sections[2]);
if (version.isValid()) {
_imports << 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("import requires 2 arguments, but %1 were provided").arg(sectionCount - 1));
QStringLiteral("import requires 1 or 2 arguments, but %1 were provided")
.arg(sectionCount - 1));
continue;
}
_imports << sections[1];
} else if (sectionCount == 2) {
// No version specified (should only be used for relative qmldir files)
const Component entry(sections[0], sections[1], QTypeRevision());
@ -374,7 +390,7 @@ QHash<QString, QQmlDirParser::Component> QQmlDirParser::dependencies() const
return _dependencies;
}
QStringList QQmlDirParser::imports() const
QList<QQmlDirParser::Import> QQmlDirParser::imports() const
{
return _imports;
}

View File

@ -132,9 +132,20 @@ public:
QTypeRevision version = QTypeRevision::zero();
};
struct Import
{
Import() = default;
Import(QString module, QTypeRevision version, bool isAutoImport)
: module(module), version(version), isAutoImport(isAutoImport) {}
QString module;
QTypeRevision version; // default: lastest version
bool isAutoImport = false; // if set: forward the version of the importing module
};
QMultiHash<QString,Component> components() const;
QHash<QString,Component> dependencies() const;
QStringList imports() const;
QList<Import> imports() const;
QList<Script> scripts() const;
QList<Plugin> plugins() const;
bool designerSupported() const;
@ -161,7 +172,7 @@ private:
QString _typeNamespace;
QMultiHash<QString,Component> _components;
QHash<QString,Component> _dependencies;
QStringList _imports;
QList<Import> _imports;
QList<Script> _scripts;
QList<Plugin> _plugins;
bool _designerSupported = false;
@ -172,6 +183,7 @@ private:
using QQmlDirComponents = QMultiHash<QString,QQmlDirParser::Component>;
using QQmlDirScripts = QList<QQmlDirParser::Script>;
using QQmlDirPlugins = QList<QQmlDirParser::Plugin>;
using QQmlDirImports = QList<QQmlDirParser::Import>;
QDebug &operator<< (QDebug &, const QQmlDirParser::Component &);
QDebug &operator<< (QDebug &, const QQmlDirParser::Script &);

View File

@ -0,0 +1,8 @@
import modulewithimplicitautoimport 2.11 as MyNS
MyNS.Test {
MyNS.Item { // Implicitly imported from QtQuick
// containmentMask added in 2.11. Version is forwarded.
objectName: containmentMask.objectName
}
MyNS.ListModel {}
}

View File

@ -0,0 +1,8 @@
import modulewithimplicitversionedimport 2.0 as MyNS
MyNS.Test {
MyNS.Item { // Implicitly imported from QtQuick
// containmentMask added in 2.11. Despite version 2.0 above, the module imports 2.15.
objectName: containmentMask.objectName
}
MyNS.ListModel {}
}

View File

@ -0,0 +1,3 @@
import QtQuick 2.0
Item {
}

View File

@ -0,0 +1,3 @@
import QtQuick auto
Test 2.0 Test.qml
Dummy 2.22 Test.qml

View File

@ -0,0 +1,3 @@
import QtQuick 2.0
Item {
}

View File

@ -0,0 +1,2 @@
import QtQuick 2.15
Test 2.0 Test.qml

View File

@ -579,10 +579,25 @@ void tst_QQMLTypeLoader::implicitImport()
{
QQmlEngine engine;
engine.addImportPath(testFile("imports"));
QQmlComponent component(&engine, testFileUrl("implicitimporttest.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
{
QQmlComponent component(&engine, testFileUrl("implicitimporttest.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
{
QQmlComponent component(&engine, testFileUrl("implicitautoimporttest.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
{
QQmlComponent component(&engine, testFileUrl("implicitversionedimporttest.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
}
void tst_QQMLTypeLoader::compositeSingletonCycle()

View File

@ -141,8 +141,8 @@ FindWarningVisitor::Import FindWarningVisitor::readQmldir(const QString &path)
Import result;
auto reader = createQmldirParserForFile(path + SlashQmldir);
const auto imports = reader.imports();
for (const QString &import : imports)
result.dependencies.append(import);
for (const auto &import : imports)
result.dependencies.append(import.module); // TODO: version
QHash<QString, ScopeTree::Ptr> qmlComponents;
const auto components = reader.components();