Introduce qmlRegisterModule()

This is particularly useful for keeping the versions of related modules in
sync. For example, when QtQuick.Controls introduces new types or revisions
and bumps up the minor version, qmlRegisterModule() can be used to make the
same version available for QtQuick.Controls.Styles in case it doesn't have
new types or revisions to register.

[ChangeLog][QtQml] Introduced qmlRegisterModule() that can be used to
make a certain module version available, even if no types or revisions
are registered for that version.

Change-Id: I5ec457465cd778bb0adda55771d195f69cd4b31a
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
J-P Nurmi 2016-10-20 15:42:27 +02:00
parent 0cbda65924
commit c56cc0c6c2
10 changed files with 174 additions and 43 deletions

View File

@ -544,3 +544,15 @@
in order to be found.
*/
/*!
\since 5.9
\fn void qmlRegisterModule(const char* uri, int versionMajor, int versionMinor);
\relates QQmlEngine
This function registers a module in a particular \a uri with a version specified
in \a versionMajor and \a versionMinor.
This can be used to make a certain module version available, even if no types
are registered for that version. This is particularly useful for keeping the
versions of related modules in sync.
*/

View File

@ -526,6 +526,7 @@ QT_WARNING_POP
//The C++ version of protected namespaces in qmldir
Q_QML_EXPORT bool qmlProtectModule(const char* uri, int majVersion);
Q_QML_EXPORT void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor);
template<typename T>
QObject *qmlAttachedPropertiesObject(const QObject *obj, bool create = true)

View File

@ -1267,6 +1267,19 @@ bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *da
return true;
}
// NOTE: caller must hold a QMutexLocker on "data"
QQmlTypeModule *getTypeModule(const QHashedString &uri, int majorVersion, QQmlMetaTypeData *data)
{
QQmlMetaTypeData::VersionedUri versionedUri(uri, majorVersion);
QQmlTypeModule *module = data->uriToModule.value(versionedUri);
if (!module) {
module = new QQmlTypeModule;
module->d->uri = versionedUri;
data->uriToModule.insert(versionedUri, module);
}
return module;
}
// NOTE: caller must hold a QMutexLocker on "data"
void addTypeToData(QQmlType* type, QQmlMetaTypeData *data)
{
@ -1293,13 +1306,8 @@ void addTypeToData(QQmlType* type, QQmlMetaTypeData *data)
if (!type->module().isEmpty()) {
const QHashedString &mod = type->module();
QQmlMetaTypeData::VersionedUri versionedUri(mod, type->majorVersion());
QQmlTypeModule *module = data->uriToModule.value(versionedUri);
if (!module) {
module = new QQmlTypeModule;
module->d->uri = versionedUri;
data->uriToModule.insert(versionedUri, module);
}
QQmlTypeModule *module = getTypeModule(mod, type->majorVersion(), data);
Q_ASSERT(module);
module->d->add(type);
}
}
@ -1442,6 +1450,20 @@ bool qmlProtectModule(const char *uri, int majVersion)
return false;
}
//From qqml.h
void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor)
{
QMutexLocker lock(metaTypeDataLock());
QQmlMetaTypeData *data = metaTypeData();
QQmlTypeModule *module = getTypeModule(QString::fromUtf8(uri), versionMajor, data);
Q_ASSERT(module);
QQmlTypeModulePrivate *p = QQmlTypeModulePrivate::get(module);
p->minMinorVersion = qMin(p->minMinorVersion, versionMinor);
p->maxMinorVersion = qMax(p->maxMinorVersion, versionMinor);
}
bool QQmlMetaType::namespaceContainsRegistrations(const QString &uri, int majorVersion)
{
const QQmlMetaTypeData *data = metaTypeData();

View File

@ -266,6 +266,7 @@ public:
private:
//Used by register functions and creates the QQmlTypeModule for them
friend QQmlTypeModule *getTypeModule(const QHashedString &uri, int majorVersion, QQmlMetaTypeData *data);
friend void addTypeToData(QQmlType* type, QQmlMetaTypeData *data);
friend struct QQmlMetaTypeData;
friend Q_QML_EXPORT void qmlClearTypeRegistrations();

View File

@ -0,0 +1,3 @@
import org.qtproject.AutoTestQmlPluginType 2.2
MyPluginType { valueOnlyIn2: 123 }

View File

@ -0,0 +1,12 @@
TEMPLATE = lib
CONFIG += plugin
SOURCES = plugin.cpp
QT = core qml
DESTDIR = ../imports/org/qtproject/AutoTestQmlPluginType.2.2
QT += core-private gui-private qml-private
IMPORT_FILES = \
qmldir
include (../../../shared/imports.pri)

View File

@ -0,0 +1,72 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QStringList>
#include <QtQml/qqmlextensionplugin.h>
#include <QtQml/qqml.h>
#include <QDebug>
class MyPluginType : public QObject
{
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue)
Q_PROPERTY(int valueOnlyIn2 READ value WRITE setValue)
public:
MyPluginType(QObject *parent=0) : QObject(parent)
{
qWarning("import2.2 worked");
}
int value() const { return v; }
void setValue(int i) { v = i; }
private:
int v;
};
class MyPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
MyPlugin()
{
qWarning("plugin2.2 created");
}
void registerTypes(const char *uri)
{
Q_ASSERT(QLatin1String(uri) == "org.qtproject.AutoTestQmlPluginType");
qmlRegisterType<MyPluginType>(uri, 2, 0, "MyPluginType");
qmlRegisterModule(uri, 2, 2);
}
};
#include "plugin.moc"

View File

@ -0,0 +1 @@
plugin plugin

View File

@ -20,7 +20,8 @@ SUBDIRS =\
protectedModule\
plugin/childplugin\
plugin.2/childplugin\
plugin.2.1/childplugin
plugin.2.1/childplugin\
plugin.2.2
tst_qqmlmoduleplugin_pro.depends += plugin
SUBDIRS += tst_qqmlmoduleplugin.pro

View File

@ -52,8 +52,7 @@ public:
private slots:
virtual void initTestCase();
void importsPlugin();
void importsPlugin2();
void importsPlugin21();
void importsPlugin_data();
void importsMixedQmlCppPlugin();
void incorrectPluginCase();
void importPluginWithQmlFile();
@ -70,6 +69,7 @@ private slots:
void importStrictModule();
void importStrictModule_data();
void importProtectedModule();
void importVersionedModule();
void importsChildPlugin();
void importsChildPlugin2();
void importsChildPlugin21();
@ -130,12 +130,15 @@ void tst_qqmlmoduleplugin::initTestCase()
void tst_qqmlmoduleplugin::importsPlugin()
{
QFETCH(QString, suffix);
QFETCH(QString, qmlFile);
QQmlEngine engine;
engine.addImportPath(m_importsDirectory);
QTest::ignoreMessage(QtWarningMsg, "plugin created");
QTest::ignoreMessage(QtWarningMsg, "import worked");
QTest::ignoreMessage(QtWarningMsg, qPrintable(QString("plugin%1 created").arg(suffix)));
QTest::ignoreMessage(QtWarningMsg, qPrintable(QString("import%1 worked").arg(suffix)));
QTest::ignoreMessage(QtWarningMsg, "Module 'org.qtproject.AutoTestQmlPluginType' does not contain a module identifier directive - it cannot be protected from external registrations.");
QQmlComponent component(&engine, testFileUrl(QStringLiteral("works.qml")));
QQmlComponent component(&engine, testFileUrl(qmlFile));
foreach (QQmlError err, component.errors())
qWarning() << err;
VERIFY_ERRORS(0);
@ -145,38 +148,15 @@ void tst_qqmlmoduleplugin::importsPlugin()
delete object;
}
void tst_qqmlmoduleplugin::importsPlugin2()
void tst_qqmlmoduleplugin::importsPlugin_data()
{
QQmlEngine engine;
engine.addImportPath(m_importsDirectory);
QTest::ignoreMessage(QtWarningMsg, "plugin2 created");
QTest::ignoreMessage(QtWarningMsg, "import2 worked");
QTest::ignoreMessage(QtWarningMsg, "Module 'org.qtproject.AutoTestQmlPluginType' does not contain a module identifier directive - it cannot be protected from external registrations.");
QQmlComponent component(&engine, testFileUrl(QStringLiteral("works2.qml")));
foreach (QQmlError err, component.errors())
qWarning() << err;
VERIFY_ERRORS(0);
QObject *object = component.create();
QVERIFY(object != 0);
QCOMPARE(object->property("value").toInt(),123);
delete object;
}
QTest::addColumn<QString>("suffix");
QTest::addColumn<QString>("qmlFile");
void tst_qqmlmoduleplugin::importsPlugin21()
{
QQmlEngine engine;
engine.addImportPath(m_importsDirectory);
QTest::ignoreMessage(QtWarningMsg, "plugin2.1 created");
QTest::ignoreMessage(QtWarningMsg, "import2.1 worked");
QTest::ignoreMessage(QtWarningMsg, "Module 'org.qtproject.AutoTestQmlPluginType' does not contain a module identifier directive - it cannot be protected from external registrations.");
QQmlComponent component(&engine, testFileUrl(QStringLiteral("works21.qml")));
foreach (QQmlError err, component.errors())
qWarning() << err;
VERIFY_ERRORS(0);
QObject *object = component.create();
QVERIFY(object != 0);
QCOMPARE(object->property("value").toInt(),123);
delete object;
QTest::newRow("1.0") << "" << "works.qml";
QTest::newRow("2.0") << "2" << "works2.qml";
QTest::newRow("2.1") << "2.1" << "works21.qml";
QTest::newRow("2.2") << "2.2" << "works22.qml";
}
void tst_qqmlmoduleplugin::incorrectPluginCase()
@ -578,6 +558,32 @@ void tst_qqmlmoduleplugin::importProtectedModule()
QVERIFY(object != 0);
}
void tst_qqmlmoduleplugin::importVersionedModule()
{
qmlRegisterType<QObject>("org.qtproject.VersionedModule", 1, 0, "TestType");
qmlRegisterModule("org.qtproject.VersionedModule", 1, 1);
QQmlEngine engine;
engine.addImportPath(m_importsDirectory);
QUrl url(testFileUrl("empty.qml"));
QQmlComponent component(&engine);
component.setData("import org.qtproject.VersionedModule 1.0\n TestType {}\n", url);
QScopedPointer<QObject> object10(component.create());
QVERIFY(!object10.isNull());
component.setData("import org.qtproject.VersionedModule 1.1\n TestType {}\n", url);
QScopedPointer<QObject> object11(component.create());
QVERIFY(!object11.isNull());
component.setData("import org.qtproject.VersionedModule 1.2\n TestType {}\n", url);
QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
QScopedPointer<QObject> object12(component.create());
QVERIFY(object12.isNull());
QCOMPARE(component.errorString(), QString("%1:1 module \"org.qtproject.VersionedModule\" version 1.2 is not installed\n").arg(url.toString()));
}
void tst_qqmlmoduleplugin::importsChildPlugin()
{
QQmlEngine engine;