Support QML type registration split: build system + tools integration

This change addresses the following issue:
- Module A wants to have an optional QML API without depending on
  declarative (e.g., because it has a C++ only API usable for Widgets
  based applications that normally would not link against declarative).
- Thus we add a module B with all the QML support (type registration,
  maybe additional types/functions/image providers).
- Currently, this would require to wrap every type from module A into
  QML_FOREIGN manually, adding large amounts of boilerplate.

To solve this, we extend qmltyperegistrar and the CMake API:
- qmltyperegistrar gains a new --extract option to generate a file with
  all QML_FOREIGN declarations. More precisely, it generates a header
  and source file; the source file includes the header and the moc
  generated file.
- We expose this in cmake via a new qt6_generate_foreign_qml_types
  function. That function takes two targets, the source library's target
  and the QML module's target. It then runs qmltyperegistrar on the
  source library, and adds the generated files to the QML module's
  target, and adds the proper dependencies between the targets.

The remaining step to achieve the goal of split registration is to
provide the QML registration macros in a separate header.

Task-number: QTBUG-92258
Change-Id: I51c4ef660ca7476b556b1991a6c76bbcad2c69af
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
This commit is contained in:
Fabian Kosmale 2021-05-25 16:49:42 +02:00
parent a3deb2ff59
commit 96c1337aef
11 changed files with 424 additions and 13 deletions

View File

@ -1881,6 +1881,48 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
endfunction()
endif()
function(qt6_generate_foreign_qml_types lib_target qml_target)
qt6_extract_metatypes(${lib_target})
get_target_property(target_metatypes_json_file ${lib_target} INTERFACE_QT_META_TYPES_BUILD_FILE)
if (NOT target_metatypes_json_file)
message(FATAL_ERROR "Need target metatypes.json file")
endif()
set(registration_files_base ${lib_target}_${qml_target})
set(additional_sources ${registration_files_base}.cpp ${registration_files_base}.h)
add_custom_command(
OUTPUT
${additional_sources}
DEPENDS
${target}
${target_metatypes_json_file}
${QT_CMAKE_EXPORT_NAMESPACE}::qmltyperegistrar
COMMAND
${QT_TOOL_COMMAND_WRAPPER_PATH}
$<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::qmltyperegistrar>
"--extract"
-o ${registration_files_base}
${target_metatypes_json_file}
COMMENT "Generate QML registration code for target ${target}"
)
target_sources(${qml_target} PRIVATE ${additional_sources})
qt6_wrap_cpp(${additional_sources} TARGET ${qml_target})
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
if(QT_DEFAULT_MAJOR_VERSION EQUAL 6)
function(qt_generate_foreign_qml_types)
qt6_generate_foreign_qml_types(${ARGV})
endfunction()
else()
message(FATAL_ERROR "qt_generate_foreign_qml_types() is only available in Qt 6.")
endif()
endif()
# target: Expected to be the backing target for a qml module. Certain target
# properties normally set by qt6_add_qml_module() will be retrieved from this
# target. (REQUIRED)

View File

@ -133,6 +133,60 @@ void MetaTypesJsonProcessor::postProcessForeignTypes()
sortTypes(m_types);
}
QString MetaTypesJsonProcessor::extractRegisteredTypes() const
{
QString registrationHelper;
for (const auto &obj: m_types) {
const QString className = obj[u"className"].toString();
const QString foreignClassName = className+ u"Foreign";
const auto classInfos = obj[u"classInfos"].toArray();
QString qmlElement;
QString qmlUncreatable;
QString qmlAttached;
bool isSingleton = false;
bool isExplicitlyUncreatable = false;
for (QJsonValue entry: classInfos) {
const auto name = entry[u"name"].toString();
const auto value = entry[u"value"].toString();
if (name == u"QML.Element") {
if (value == u"auto") {
qmlElement = u"QML_NAMED_ELEMENT("_qs + className + u")"_qs;
} else if (value == u"anonymous") {
qmlElement = u"QML_ANONYMOUS"_qs;
} else {
qmlElement = u"QML_NAMED_ELEMENT(" + value + u")";
}
} else if (name == u"QML.Creatable" && value == u"false") {
isExplicitlyUncreatable = true;
} else if (name == u"QML.UncreatableReason") {
qmlUncreatable = u"QML_UNCREATABLE(\"" + value + u"\")";
} else if (name == u"QML.Attached") {
qmlAttached = u"QML_ATTACHED("_qs + value + u")";
} else if (name == u"QML.Singleton") {
isSingleton = true;
}
}
if (qmlElement.isEmpty())
continue; // no relevant entries found
const QString spaces = u" "_qs;
registrationHelper += u"\nstruct "_qs + foreignClassName + u"{\n Q_GADGET\n"_qs;
registrationHelper += spaces + u"QML_FOREIGN(" + className + u")\n"_qs;
registrationHelper += spaces + qmlElement + u"\n"_qs;
if (isSingleton)
registrationHelper += spaces + u"QML_SINGLETON\n"_qs;
if (isExplicitlyUncreatable) {
if (qmlUncreatable.isEmpty())
registrationHelper += spaces + uR"(QML_UNCREATABLE(""))" + u"n";
else
registrationHelper += spaces + qmlUncreatable + u"\n";
}
if (!qmlAttached.isEmpty())
registrationHelper += spaces + qmlAttached + u"\n";
registrationHelper += u"};\n";
}
return registrationHelper;
}
MetaTypesJsonProcessor::RegistrationMode MetaTypesJsonProcessor::qmlTypeRegistrationMode(
const QJsonObject &classDef)
{

View File

@ -51,6 +51,8 @@ public:
QStringList referencedTypes() const { return m_referencedTypes; }
QStringList includes() const { return m_includes; }
QString extractRegisteredTypes() const;
private:
enum RegistrationMode {
NoRegistration,

View File

@ -38,6 +38,7 @@
#include <QFile>
#include <QScopedPointer>
#include <QSaveFile>
#include <QFileInfo>
#include <cstdlib>
@ -75,6 +76,40 @@ static bool argumentsFromCommandLineAndFile(QStringList &allArguments, const QSt
return true;
}
static int runExtract(const QString & baseName, const MetaTypesJsonProcessor &processor) {
if (processor.types().isEmpty()) {
fprintf(stderr, "Error: No types to register found in library\n");
return EXIT_FAILURE;
}
QFile headerFile(baseName + u".h");
bool ok = headerFile.open(QFile::WriteOnly);
if (!ok) {
fprintf(stderr, "Error: Cannot open %s for writing\n", qPrintable(headerFile.fileName()));
return EXIT_FAILURE;
}
auto prefix = QString::fromLatin1(
"#ifndef %1_H\n"
"#define %1_H\n"
"#include <QtQml/qqml.h>\n"
"#include <QtQml/qqmlmoduleregistration.h>\n").arg(baseName.toUpper());
const QStringList includes = processor.includes();
for (const QString &include: includes)
prefix += u"\n#include <%1>"_qs.arg(include);
headerFile.write((prefix + processor.extractRegisteredTypes()).toUtf8() + "\n#endif");
QFile sourceFile(baseName + u".cpp");
ok = sourceFile.open(QFile::WriteOnly);
if (!ok) {
fprintf(stderr, "Error: Cannot open %s for writing\n", qPrintable(sourceFile.fileName()));
return EXIT_FAILURE;
}
// the string split is necessaury because cmake's automoc scanner would otherwise pick up the include
QString code = u"#include \"%1.h\"\n#include "_qs.arg(baseName);
code += uR"("moc_%1.cpp")"_qs.arg(baseName);
sourceFile.write(code.toUtf8());
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
// Produce reliably the same output for the same input by disabling QHash's random seeding.
@ -144,6 +179,10 @@ int main(int argc, char **argv)
"want to follow Qt's versioning scheme."));
parser.addOption(followForeignVersioningOption);
QCommandLineOption extract(u"extract"_qs);
extract.setDescription(u"Extract QML types from a module and use QML_FOREIGN to register them"_qs);
parser.addOption(extract);
parser.addPositionalArgument(QStringLiteral("[MOC generated json file]"),
QStringLiteral("MOC generated json output."));
@ -153,10 +192,35 @@ int main(int argc, char **argv)
parser.process(arguments);
const QString module = parser.value(importNameOption);
MetaTypesJsonProcessor processor(parser.isSet(privateIncludesOption));
if (!processor.processTypes(parser.positionalArguments()))
return EXIT_FAILURE;
processor.postProcessTypes();
if (parser.isSet(foreignTypesOption))
processor.processForeignTypes(parser.value(foreignTypesOption).split(QLatin1Char(',')));
processor.postProcessForeignTypes();
if (parser.isSet(extract)) {
if (!parser.isSet(outputOption)) {
fprintf(stderr, "Error: The output file name must be provided\n");
return EXIT_FAILURE;
}
QString baseName = parser.value(outputOption);
return runExtract(baseName, processor);
}
FILE *output = stdout;
QScopedPointer<FILE, ScopedPointerFileCloser> outputFile;
if (parser.isSet(outputOption)) {
// extract does its own file handling
QString outputName = parser.value(outputOption);
#if defined(_MSC_VER)
if (_wfopen_s(&output, reinterpret_cast<const wchar_t *>(outputName.utf16()), L"w") != 0) {
@ -180,19 +244,6 @@ int main(int argc, char **argv)
"#include <QtQml/qqml.h>\n"
"#include <QtQml/qqmlmoduleregistration.h>\n");
const QString module = parser.value(importNameOption);
MetaTypesJsonProcessor processor(parser.isSet(privateIncludesOption));
if (!processor.processTypes(parser.positionalArguments()))
return EXIT_FAILURE;
processor.postProcessTypes();
if (parser.isSet(foreignTypesOption))
processor.processForeignTypes(parser.value(foreignTypesOption).split(QLatin1Char(',')));
processor.postProcessForeignTypes();
const QStringList includes = processor.includes();
for (const QString &include : includes)
fprintf(output, "\n#include <%s>", qPrintable(include));

View File

@ -44,6 +44,7 @@ if(TARGET Qt::Quick)
endif()
add_subdirectory(qqmljsscope)
endif()
add_subdirectory(qmlsplitlib)
if(TARGET Qt::Widgets)
add_subdirectory(qjsengine)
add_subdirectory(qjsvalue)

View File

@ -0,0 +1,31 @@
include(GenerateExportHeader)
qt_add_library(tst_qmlsplitlib_library
lib.h
lib.cpp
)
qt_autogen_tools_initial_setup(tst_qmlsplitlib_library)
target_include_directories(tst_qmlsplitlib_library PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # find autogenerated header
generate_export_header(tst_qmlsplitlib_library)
target_link_libraries(tst_qmlsplitlib_library Qt::Core)
qt_internal_add_test(tst_qmlsplitlib
SOURCES
tst_qmlsplitlib.cpp
LIBRARIES
Qt::Core
Qt::Qml
)
qt_autogen_tools_initial_setup(tst_qmlsplitlib)
qt6_generate_foreign_qml_types(tst_qmlsplitlib_library tst_qmlsplitlib)
qt6_add_qml_module(tst_qmlsplitlib
VERSION 1.0
URI "SplitLib"
QML_FILES
main.qml
)
target_link_libraries(tst_qmlsplitlib PRIVATE tst_qmlsplitlib_library)

View File

@ -0,0 +1,5 @@
#include "lib.h"
bool SplitLib::transmogrify() { return true; }
#include "moc_lib.cpp"

View File

@ -0,0 +1,84 @@
/****************************************************************************
**
** Copyright (C) 2021 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$
**
****************************************************************************/
#ifndef SPLITLIB_LIB_H
#define SPLITLIB_LIB_H
#include "tst_qmlsplitlib_library_export.h"
#include <QtCore/qglobal.h>
#include <QObject>
// FIXME: should at some point be in qtbase or separate repo
// taken from qqmlregistration.h
#define QML_ELEMENT \
Q_CLASSINFO("QML.Element", "auto")
#define QML_PRIVATE_NAMESPACE \
QT_PREPEND_NAMESPACE(QQmlPrivate)
#define QML_REGISTER_TYPES_AND_REVISIONS \
QT_PREPEND_NAMESPACE(qmlRegisterTypesAndRevisions)
QT_BEGIN_NAMESPACE
namespace QQmlPrivate {
template<typename, typename> struct QmlSingleton;
}
template<typename T, typename... Args>
void qmlRegisterTypesAndRevisions(const char *uri, int versionMajor, QList<int> *);
QT_END_NAMESPACE
#define QML_SINGLETON \
Q_CLASSINFO("QML.Singleton", "true") \
enum class QmlIsSingleton {yes = true}; \
template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlSingleton; \
template<typename T, typename... Args> \
friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *);
#define QML_NAMED_ELEMENT(NAME) \
Q_CLASSINFO("QML.Element", #NAME)
class TST_QMLSPLITLIB_LIBRARY_EXPORT SplitLib : public QObject
{
public:
Q_OBJECT
QML_ELEMENT
Q_INVOKABLE bool transmogrify();
};
class TST_QMLSPLITLIB_LIBRARY_EXPORT Foo : public QObject
{
public:
Q_OBJECT
QML_NAMED_ELEMENT(Bar)
QML_SINGLETON
};
#endif

View File

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** 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.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import SplitLib
SplitLib {
property string s: Bar.objectName
}

View File

@ -0,0 +1,32 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QtQml/qqmlextensionplugin.h>
Q_IMPORT_QML_PLUGIN(TimeExamplePlugin)
Q_IMPORT_QML_PLUGIN(BasicExtension)

View File

@ -0,0 +1,54 @@
/****************************************************************************
**
** Copyright (C) 2021 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 <QQmlEngine>
#include <QQmlComponent>
#include <QObject>
#include <qtest.h>
#include "lib.h"
class tst_splitlib : public QObject
{
Q_OBJECT
private slots:
void verifyComponent();
};
void tst_splitlib::verifyComponent()
{
QQmlEngine engine;
QQmlComponent c(&engine, QStringLiteral("qrc:/SplitLib/main.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer o(c.create());
QVERIFY(!o.isNull());
auto lib = qobject_cast<SplitLib *>(o.get());
QVERIFY(lib);
}
QTEST_MAIN(tst_splitlib)
#include "tst_qmlsplitlib.moc"