diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2019-10-01 14:43:37 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2019-10-14 10:42:29 +0200 |
commit | ee62b2824fe91e95a3f8218b93ba55a2ef6660d0 (patch) | |
tree | ff403362ee4b73c9d90c640d29bf22a0aba94c23 /tools | |
parent | 8cb08ca42157bb43b523103a6d1be94534be0597 (diff) | |
download | qtdeclarative-ee62b2824fe91e95a3f8218b93ba55a2ef6660d0.tar.gz |
Add support for semi-automatic QML type registrations
We can use the new moc JSON output to collect all meta-objects at build
time and, for those that include QML element registration meta-data,
generate code that automatically registers these types with QML. This
eliminates the need to call qmlRegisterType manually.
For now this generates free-standing functions (per module) that need to
be called manually. This is intended as an intermediate step.
Task-number: QTBUG-68796
Change-Id: Ib414eef9757344feee488ebc7388f957b975347f
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmlplugindump/qmlplugindump.pro | 10 | ||||
-rw-r--r-- | tools/qmltyperegistrar/qmltyperegistrar.cpp | 404 | ||||
-rw-r--r-- | tools/qmltyperegistrar/qmltyperegistrar.pro | 26 | ||||
-rw-r--r-- | tools/qmltyperegistrar/qmltypes.prf | 66 | ||||
-rw-r--r-- | tools/qmltyperegistrar/qmltypesclassdescription.cpp | 161 | ||||
-rw-r--r-- | tools/qmltyperegistrar/qmltypesclassdescription.h | 58 | ||||
-rw-r--r-- | tools/qmltyperegistrar/qmltypescreator.cpp | 357 | ||||
-rw-r--r-- | tools/qmltyperegistrar/qmltypescreator.h | 68 | ||||
-rw-r--r-- | tools/shared/qmlstreamwriter.cpp (renamed from tools/qmlplugindump/qmlstreamwriter.cpp) | 0 | ||||
-rw-r--r-- | tools/shared/qmlstreamwriter.h (renamed from tools/qmlplugindump/qmlstreamwriter.h) | 0 | ||||
-rw-r--r-- | tools/tools.pro | 2 |
11 files changed, 1148 insertions, 4 deletions
diff --git a/tools/qmlplugindump/qmlplugindump.pro b/tools/qmlplugindump/qmlplugindump.pro index 62b08e9334..e374ae45f4 100644 --- a/tools/qmlplugindump/qmlplugindump.pro +++ b/tools/qmlplugindump/qmlplugindump.pro @@ -5,14 +5,16 @@ CONFIG += no_import_scan QTPLUGIN.platforms = qminimal +INCLUDEPATH += ../shared + SOURCES += \ main.cpp \ - qmlstreamwriter.cpp \ - qmltypereader.cpp + qmltypereader.cpp \ + ../shared/qmlstreamwriter.cpp HEADERS += \ - qmlstreamwriter.h \ - qmltypereader.h + qmltypereader.h \ + ../shared/qmlstreamwriter.h macx { # Prevent qmlplugindump from popping up in the dock when launched. diff --git a/tools/qmltyperegistrar/qmltyperegistrar.cpp b/tools/qmltyperegistrar/qmltyperegistrar.cpp new file mode 100644 index 0000000000..2cf309ff0d --- /dev/null +++ b/tools/qmltyperegistrar/qmltyperegistrar.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module 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 "qmltypescreator.h" + +#include <QCoreApplication> +#include <QCommandLineParser> +#include <QtDebug> +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonValue> +#include <QJsonObject> +#include <QFile> +#include <QScopedPointer> +#include <QSaveFile> +#include <QQueue> + +#include <cstdlib> + +struct ScopedPointerFileCloser +{ + static inline void cleanup(FILE *handle) { if (handle) fclose(handle); } +}; + +static bool acceptClassForQmlTypeRegistration(const QJsonObject &classDef) +{ + const QJsonArray classInfos = classDef[QLatin1String("classInfos")].toArray(); + for (const QJsonValue &info: classInfos) { + if (info[QLatin1String("name")].toString().startsWith(QLatin1String("QML."))) + return true; + } + return false; +} + +static QVector<QJsonObject> foreignRelatedTypes(const QVector<QJsonObject> &types, + const QVector<QJsonObject> &foreignTypes) +{ + const QLatin1String classInfosKey("classInfos"); + const QLatin1String nameKey("name"); + const QLatin1String qualifiedClassNameKey("qualifiedClassName"); + const QLatin1String qmlNamePrefix("QML."); + const QLatin1String qmlForeignName("QML.Foreign"); + const QLatin1String qmlAttachedName("QML.Attached"); + const QLatin1String valueKey("value"); + const QLatin1String superClassesKey("superClasses"); + const QLatin1String accessKey("access"); + const QLatin1String publicAccess("public"); + + QSet<QString> processedRelatedNames; + QQueue<QJsonObject> typeQueue; + typeQueue.append(types.toList()); + QVector<QJsonObject> relatedTypes; + + // First mark all classes registered from this module as already processed. + for (const QJsonObject &type : types) { + processedRelatedNames.insert(type.value(qualifiedClassNameKey).toString()); + const auto classInfos = type.value(classInfosKey).toArray(); + for (const QJsonValue &classInfo : classInfos) { + const QJsonObject obj = classInfo.toObject(); + if (obj.value(nameKey).toString() == qmlForeignName) { + processedRelatedNames.insert(obj.value(valueKey).toString()); + break; + } + } + } + + // Then mark all classes registered from other modules as already processed. + // We don't want to generate them again for this module. + for (const QJsonObject &foreignType : foreignTypes) { + const auto classInfos = foreignType.value(classInfosKey).toArray(); + bool seenQmlPrefix = false; + for (const QJsonValue &classInfo : classInfos) { + const QJsonObject obj = classInfo.toObject(); + const QString name = obj.value(nameKey).toString(); + if (!seenQmlPrefix && name.startsWith(qmlNamePrefix)) { + processedRelatedNames.insert(foreignType.value(qualifiedClassNameKey).toString()); + seenQmlPrefix = true; + } + if (name == qmlForeignName) { + processedRelatedNames.insert(obj.value(valueKey).toString()); + break; + } + } + } + + auto addType = [&](const QString &typeName) { + if (processedRelatedNames.contains(typeName)) + return; + processedRelatedNames.insert(typeName); + if (const QJsonObject *other = QmlTypesClassDescription::findType(foreignTypes, typeName)) { + relatedTypes.append(*other); + typeQueue.enqueue(*other); + } + }; + + // Then recursively iterate the super types and attached types, marking the + // ones we are interested in as related. + while (!typeQueue.isEmpty()) { + const QJsonObject classDef = typeQueue.dequeue(); + + const auto classInfos = classDef.value(classInfosKey).toArray(); + for (const QJsonValue &classInfo : classInfos) { + const QJsonObject obj = classInfo.toObject(); + if (obj.value(nameKey).toString() == qmlAttachedName) { + addType(obj.value(valueKey).toString()); + } else if (obj.value(nameKey).toString() == qmlForeignName) { + const QString foreignClassName = obj.value(valueKey).toString(); + if (const QJsonObject *other = QmlTypesClassDescription::findType( + foreignTypes, foreignClassName)) { + const auto otherSupers = other->value(superClassesKey).toArray(); + if (!otherSupers.isEmpty()) { + const QJsonObject otherSuperObject = otherSupers.first().toObject(); + if (otherSuperObject.value(accessKey).toString() == publicAccess) + addType(otherSuperObject.value(nameKey).toString()); + } + + const auto otherClassInfos = other->value(classInfosKey).toArray(); + for (const QJsonValue &otherClassInfo : otherClassInfos) { + const QJsonObject obj = otherClassInfo.toObject(); + if (obj.value(nameKey).toString() == qmlAttachedName) { + addType(obj.value(valueKey).toString()); + break; + } + // No, you cannot chain QML_FOREIGN declarations. Sorry. + } + break; + } + } + } + + const auto supers = classDef.value(superClassesKey).toArray(); + if (!supers.isEmpty()) { + const QJsonObject superObject = supers.first().toObject(); + if (superObject.value(accessKey).toString() == publicAccess) + addType(superObject.value(nameKey).toString()); + } + } + + return relatedTypes; +} + +int main(int argc, char **argv) +{ + // Produce reliably the same output for the same input by disabling QHash's random seeding. + qSetGlobalQHashSeed(0); + + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral("qmltyperegistrar")); + QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption outputOption(QStringLiteral("o")); + outputOption.setDescription(QStringLiteral("Write output to specified file.")); + outputOption.setValueName(QStringLiteral("file")); + outputOption.setFlags(QCommandLineOption::ShortOptionStyle); + parser.addOption(outputOption); + + QCommandLineOption privateIncludesOption( + QStringLiteral("private-includes"), + QStringLiteral("Include headers ending in \"_p.h\" using \"#include <private/foo_p.h>\"" + "rather than \"#include <foo_p.h>\".")); + parser.addOption(privateIncludesOption); + + QCommandLineOption importNameOption(QStringLiteral("import-name")); + importNameOption.setDescription(QStringLiteral("Name of the module to use with QML type registrations.")); + importNameOption.setValueName(QStringLiteral("QML module name")); + parser.addOption(importNameOption); + + QCommandLineOption majorVersionOption(QStringLiteral("major-version")); + majorVersionOption.setDescription(QStringLiteral("Major version to use for type registrations.")); + majorVersionOption.setValueName(QStringLiteral("major version")); + parser.addOption(majorVersionOption); + + QCommandLineOption pluginTypesOption(QStringLiteral("generate-plugintypes")); + pluginTypesOption.setDescription(QStringLiteral("Generate plugins.qmltypes into specified directory.")); + pluginTypesOption.setValueName(QStringLiteral("qmltypes target Directory")); + parser.addOption(pluginTypesOption); + + QCommandLineOption foreignTypesOption(QStringLiteral("foreign-types")); + foreignTypesOption.setDescription(QStringLiteral("Consider foreign types when generating plugins.qmltypes.")); + foreignTypesOption.setValueName(QStringLiteral("Comma separated list of other modules to consult for types.")); + parser.addOption(foreignTypesOption); + + QCommandLineOption dependenciesOption(QStringLiteral("dependencies")); + dependenciesOption.setDescription(QStringLiteral("Dependencies to be stated in plugins.qmltypes")); + dependenciesOption.setValueName(QStringLiteral("name of JSON file with dependencies")); + parser.addOption(dependenciesOption); + + parser.addPositionalArgument(QStringLiteral("[MOC generated json file]"), + QStringLiteral("MOC generated json output")); + + parser.process(app); + + FILE *output = stdout; + QScopedPointer<FILE, ScopedPointerFileCloser> outputFile; + + if (parser.isSet(outputOption)) { + QString outputName = parser.value(outputOption); +#if defined(_MSC_VER) + if (_wfopen_s(&output, reinterpret_cast<const wchar_t *>(outputName.utf16()), L"w") != 0) { +#else + output = fopen(QFile::encodeName(outputName).constData(), "w"); // create output file + if (!output) { +#endif + fprintf(stderr, "Error: Cannot open %s for writing\n", qPrintable(outputName)); + return EXIT_FAILURE; + } + outputFile.reset(output); + } + + fprintf(output, + "/****************************************************************************\n" + "** Generated QML type registration code\n**\n"); + fprintf(output, + "** WARNING! All changes made in this file will be lost!\n" + "*****************************************************************************/\n\n"); + fprintf(output, + "#include <QtQml/qqmlengine.h>\n"); + + QStringList includes; + QVector<QJsonObject> types; + QVector<QJsonObject> foreignTypes; + + const QString module = parser.value(importNameOption); + const QStringList files = parser.positionalArguments(); + for (const QString &source: files) { + QJsonDocument metaObjects; + { + QFile f(source); + if (!f.open(QIODevice::ReadOnly)) { + fprintf(stderr, "Error opening %s for reading\n", qPrintable(source)); + return EXIT_FAILURE; + } + QJsonParseError error = {0, QJsonParseError::NoError}; + metaObjects = QJsonDocument::fromJson(f.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + fprintf(stderr, "Error parsing %s\n", qPrintable(source)); + return EXIT_FAILURE; + } + } + + auto processMetaObject = [&](const QJsonObject &metaObject) { + const QJsonArray classes = metaObject[QLatin1String("classes")].toArray(); + for (const auto &cls : classes) { + QJsonObject classDef = cls.toObject(); + if (acceptClassForQmlTypeRegistration(classDef)) { + const QString include = metaObject[QLatin1String("inputFile")].toString(); + const bool declaredInHeader = include.endsWith(QLatin1String(".h")); + if (declaredInHeader) { + includes.append(include); + classDef.insert(QLatin1String("registerable"), true); + } else { + fprintf(stderr, "Cannot generate QML type registration for class %s " + "because it is not declared in a header.", + qPrintable(classDef.value(QLatin1String("qualifiedClassName")) + .toString())); + } + types.append(classDef); + } else { + foreignTypes.append(classDef); + } + } + }; + + if (metaObjects.isArray()) { + const QJsonArray metaObjectsArray = metaObjects.array(); + for (const auto &metaObject : metaObjectsArray) { + if (!metaObject.isObject()) { + fprintf(stderr, "Error parsing %s: JSON is not an object\n", + qPrintable(source)); + return EXIT_FAILURE; + } + + processMetaObject(metaObject.toObject()); + } + } else if (metaObjects.isObject()) { + processMetaObject(metaObjects.object()); + } else { + fprintf(stderr, "Error parsing %s: JSON is not an object or an array\n", + qPrintable(source)); + return EXIT_FAILURE; + } + } + + const QLatin1String qualifiedClassNameKey("qualifiedClassName"); + auto sortTypes = [&](QVector<QJsonObject> &types) { + std::sort(types.begin(), types.end(), [&](const QJsonObject &a, const QJsonObject &b) { + return a.value(qualifiedClassNameKey).toString() < + b.value(qualifiedClassNameKey).toString(); + }); + }; + + sortTypes(types); + + fprintf(output, "\n#include <QtQml/qqmlmoduleregistration.h>"); + const bool privateIncludes = parser.isSet(privateIncludesOption); + for (const QString &include : qAsConst(includes)) { + if (privateIncludes && include.endsWith(QLatin1String("_p.h"))) + fprintf(output, "\n#include <private/%s>", qPrintable(include)); + else + fprintf(output, "\n#include <%s>", qPrintable(include)); + } + + fprintf(output, "\n\n"); + + QString moduleAsSymbol = module; + moduleAsSymbol.replace(QLatin1Char('.'), QLatin1Char('_')); + + const QString functionName = QStringLiteral("qml_register_types_") + moduleAsSymbol; + + fprintf(output, "void %s()\n{\n", qPrintable(functionName)); + const auto majorVersion = parser.value(majorVersionOption); + + for (const QJsonObject &classDef : qAsConst(types)) { + if (!classDef.value(QLatin1String("registerable")).toBool()) + continue; + + const QString className = classDef[QLatin1String("qualifiedClassName")].toString(); + fprintf(output, "\n qmlRegisterTypesAndRevisions<%s>(\"%s\", %s);", qPrintable(className), + qPrintable(module), qPrintable(majorVersion)); + } + + fprintf(output, "\n qmlRegisterModule(\"%s\", %s, QT_VERSION_MINOR);", + qPrintable(module), qPrintable(majorVersion)); + fprintf(output, "\n}\n"); + fprintf(output, "static const QQmlModuleRegistration registration(\"%s\", %s, %s);\n", + qPrintable(module), qPrintable(majorVersion), qPrintable(functionName)); + + if (!parser.isSet(pluginTypesOption)) + return EXIT_SUCCESS; + + if (parser.isSet(foreignTypesOption)) { + const QStringList foreignTypesFiles = parser.value(foreignTypesOption) + .split(QLatin1Char(',')); + for (const QString &types : foreignTypesFiles) { + QFile typesFile(types); + if (!typesFile.open(QIODevice::ReadOnly)) { + fprintf(stderr, "Cannot open foreign types file %s\n", qPrintable(types)); + continue; + } + + QJsonParseError error = {0, QJsonParseError::NoError}; + QJsonDocument foreignMetaObjects = QJsonDocument::fromJson(typesFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + fprintf(stderr, "Error parsing %s\n", qPrintable(types)); + continue; + } + + const QJsonArray foreignObjectsArray = foreignMetaObjects.array(); + for (const auto &metaObject : foreignObjectsArray) { + if (!metaObject.isObject()) { + fprintf(stderr, "Error parsing %s: JSON is not an object\n", + qPrintable(types)); + continue; + } + + const QJsonArray classes = metaObject[QLatin1String("classes")].toArray(); + for (const auto &cls : classes) + foreignTypes.append(cls.toObject()); + } + } + } + + sortTypes(foreignTypes); + types += foreignRelatedTypes(types, foreignTypes); + sortTypes(types); + + QmlTypesCreator creator; + creator.setOwnTypes(std::move(types)); + creator.setForeignTypes(std::move(foreignTypes)); + creator.setModule(module); + creator.setMajorVersion(parser.value(majorVersionOption).toInt()); + + creator.generate(parser.value(pluginTypesOption), parser.value(dependenciesOption)); + return EXIT_SUCCESS; +} diff --git a/tools/qmltyperegistrar/qmltyperegistrar.pro b/tools/qmltyperegistrar/qmltyperegistrar.pro new file mode 100644 index 0000000000..802526d964 --- /dev/null +++ b/tools/qmltyperegistrar/qmltyperegistrar.pro @@ -0,0 +1,26 @@ +option(host_build) + +QT = core-private +DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII + +QMAKE_TARGET_DESCRIPTION = QML Types Registrar + +INCLUDEPATH += ../shared + +SOURCES += \ + qmltyperegistrar.cpp \ + ../shared/qmlstreamwriter.cpp \ + qmltypesclassdescription.cpp \ + qmltypescreator.cpp + +HEADERS += \ + ../shared/qmlstreamwriter.h \ + qmltypesclassdescription.h \ + qmltypescreator.h + +build_integration.files = qmltypes.prf +build_integration.path = $$[QT_HOST_DATA]/mkspecs/features +prefix_build: INSTALLS += build_integration +else: COPIES += build_integration + +load(qt_tool) diff --git a/tools/qmltyperegistrar/qmltypes.prf b/tools/qmltyperegistrar/qmltypes.prf new file mode 100644 index 0000000000..495cc1b0cd --- /dev/null +++ b/tools/qmltyperegistrar/qmltypes.prf @@ -0,0 +1,66 @@ +CONFIG += metatypes + +qtPrepareTool(QML_TYPEREGISTRAR, qmltyperegistrar) + +# from moc.prf +isEmpty(QML_IMPORT_MAJOR_VERSION):!isEmpty(IMPORT_VERSION): \ + QML_IMPORT_MAJOR_VERSION = $$section(IMPORT_VERSION, ., 0, 0) +isEmpty(QML_IMPORT_NAME):!isEmpty(TARGETPATH) { + QML_IMPORT_NAME = $$replace(TARGETPATH, "/", ".") + QML_IMPORT_NAME = $$replace(QML_IMPORT_NAME, .$${QML_IMPORT_MAJOR_VERSION}$, '') +} + +isEmpty(QMLTYPES_FILENAME) { + plugin: QMLTYPES_FILENAME = $$OUT_PWD/plugins.qmltypes + else: QMLTYPES_FILENAME = $$OUT_PWD/$${TEMPLATE}.qmltypes +} + +qt_module_deps = $$replace(QT, -private$, '') +qt_module_deps += $$replace(QT_PRIVATE, -private$, '') +qt_module_deps = $$replace(qt_module_deps, _private$, '') +all_qt_module_deps = $$resolve_depends(qt_module_deps, "QT.", ".depends" ".run_depends") +foreign_types = +for(dep, all_qt_module_deps): \ + foreign_types += $$[QT_INSTALL_LIBS]/metatypes/$$lower($$eval(QT.$${dep}.module))_metatypes.json + +QML_TYPEREGISTRAR_FLAGS = \ + --generate-plugintypes=$$QMLTYPES_FILENAME \ + --import-name=$$QML_IMPORT_NAME \ + --major-version=$$QML_IMPORT_MAJOR_VERSION \ + --foreign-types=$$join(foreign_types, ',') + +DEPENDENCIESFILE = $$_PRO_FILE_PWD_/dependencies.json +exists($$DEPENDENCIESFILE): QML_TYPEREGISTRAR_FLAGS += --dependencies=$$DEPENDENCIESFILE + +!isEmpty(MODULE_PRIVATE_INCLUDES): QML_TYPEREGISTRAR_FLAGS += --private-includes + +METATYPES_JSON = $$lower($$basename(TARGET))_metatypes.json + +TYPEREGISTRATIONS = $$lower($$basename(TARGET))_qmltyperegistrations$${first(QMAKE_EXT_CPP)} + +qmltyperegistrar_compiler.CONFIG += combine +qmltyperegistrar_compiler.commands = \ + $$QML_TYPEREGISTRAR $$QML_TYPEREGISTRAR_FLAGS -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} +qmltyperegistrar_compiler.input = METATYPES_JSON +qmltyperegistrar_compiler.output = $$TYPEREGISTRATIONS +qmltyperegistrar_compiler.variable_out = SOURCES +qmltyperegistrar_compiler.name = Automatic QML type registration +qmltyperegistrar_compiler.dependency_type = TYPE_C + +qmltyperegistrar_qmltypes.input = METATYPES_JSON +qmltyperegistrar_qmltypes.depends = $$TYPEREGISTRATIONS +qmltyperegistrar_qmltypes.output = $$QMLTYPES_FILENAME +qmltyperegistrar_qmltypes.CONFIG = no_link +qmltyperegistrar_qmltypes.commands = $$escape_expand(\\n) # force creation of rule + +install_qmltypes { + isEmpty(QMLTYPES_INSTALL_DIR): \ + QMLTYPES_INSTALL_DIR = $$[QT_INSTALL_QML]/$$TARGETPATH + do_install.files = $$QMLTYPES_FILENAME + do_install.path = $$QMLTYPES_INSTALL_DIR + do_install.CONFIG += no_link + prefix_build: INSTALLS += do_install + else: COPIES += do_install +} + +QMAKE_EXTRA_COMPILERS += qmltyperegistrar_compiler qmltyperegistrar_qmltypes diff --git a/tools/qmltyperegistrar/qmltypesclassdescription.cpp b/tools/qmltyperegistrar/qmltypesclassdescription.cpp new file mode 100644 index 0000000000..8189bcd52e --- /dev/null +++ b/tools/qmltyperegistrar/qmltypesclassdescription.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module 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 "qmltypesclassdescription.h" + +#include <QtCore/qjsonarray.h> + +static void collectExtraVersions(const QJsonObject *component, const QString &key, + QList<int> &extraVersions) +{ + const QJsonArray &items = component->value(key).toArray(); + for (const QJsonValue &item : items) { + const QJsonObject obj = item.toObject(); + const auto revision = obj.find(QLatin1String("revision")); + if (revision != obj.end()) { + const int extraVersion = revision.value().toInt(); + if (!extraVersions.contains(extraVersion)) + extraVersions.append(extraVersion); + } + } +} + +const QJsonObject *QmlTypesClassDescription::findType(const QVector<QJsonObject> &types, + const QString &name) +{ + static const QLatin1String qualifiedClassNameKey("qualifiedClassName"); + auto it = std::lower_bound(types.begin(), types.end(), name, + [&](const QJsonObject &type, const QString &typeName) { + return type.value(qualifiedClassNameKey).toString() < typeName; + }); + + return (it != types.end() && it->value(qualifiedClassNameKey) == name) ? &(*it) : nullptr; +} + +void QmlTypesClassDescription::collect(const QJsonObject *classDef, + const QVector<QJsonObject> &types, + const QVector<QJsonObject> &foreign, + bool topLevel) +{ + const auto classInfos = classDef->value(QLatin1String("classInfos")).toArray(); + for (const QJsonValue &classInfo : classInfos) { + const QJsonObject obj = classInfo.toObject(); + const QString name = obj[QLatin1String("name")].toString(); + const QString value = obj[QLatin1String("value")].toString(); + + if (name == QLatin1String("DefaultProperty")) { + if (defaultProp.isEmpty()) + defaultProp = value; + } else if (name == QLatin1String("QML.AddedInMinorVersion")) { + if (topLevel) { + addedInRevision = value.toInt(); + revisions.append(value.toInt()); + } else if (!elementName.isEmpty()) { + revisions.append(value.toInt()); + } + } + + if (!topLevel) + continue; + + // These only apply to the original class + if (name == QLatin1String("QML.Element")) { + if (value == QLatin1String("auto")) + elementName = classDef->value(QLatin1String("className")).toString(); + else if (value != QLatin1String("anonymous")) + elementName = value; + } else if (name == QLatin1String("QML.RemovedInMinorVersion")) { + removedInRevision = value.toInt(); + } else if (name == QLatin1String("QML.Creatable")) { + isCreatable = (value != QLatin1String("false")); + } else if (name == QLatin1String("QML.Attached")) { + attachedType = value; + if (const QJsonObject *other = findType(types, attachedType)) + collect(other, types, foreign, false); + else if (const QJsonObject *other = findType(foreign, attachedType)) + collect(other, types, foreign, false); + } else if (name == QLatin1String("QML.Singleton")) { + if (value == QLatin1String("true")) + isSingleton = true; + } else if (name == QLatin1String("QML.Foreign")) { + if (const QJsonObject *other = findType(foreign, value)) { + classDef = other; + if (defaultProp.isEmpty()) { + // Foreign type can have a default property + const auto classInfos = classDef->value(QLatin1String("classInfos")).toArray(); + for (const QJsonValue &classInfo : classInfos) { + QJsonObject obj = classInfo.toObject(); + if (obj[QLatin1String("name")].toString() == QLatin1String("DefaultProperty")) { + defaultProp = obj[QLatin1String("value")].toString(); + break; + } + } + } + } + } else if (name == QLatin1String("QML.Root")) { + isRootClass = true; + isBuiltin = true; + } else if (name == QLatin1String("QML.Builtin")) { + isBuiltin = true; + } + } + + if (!elementName.isEmpty()) { + collectExtraVersions(classDef, QString::fromLatin1("properties"), revisions); + collectExtraVersions(classDef, QString::fromLatin1("slots"), revisions); + collectExtraVersions(classDef, QString::fromLatin1("methods"), revisions); + collectExtraVersions(classDef, QString::fromLatin1("signals"), revisions); + } + + const auto supers = classDef->value(QLatin1String("superClasses")).toArray(); + if (!supers.isEmpty()) { + const QJsonObject superObject = supers.first().toObject(); + if (superObject[QLatin1String("access")].toString() == QLatin1String("public")) { + const QString superName = superObject[QLatin1String("name")].toString(); + if (topLevel && superClass.isEmpty()) + superClass = superName; + + if (const QJsonObject *other = findType(types, superName)) + collect(other, types, foreign, false); + else if (const QJsonObject *other = findType(foreign, superName)) + collect(other, types, foreign, false); + } + } + + if (addedInRevision == -1) { + revisions.append(0); + addedInRevision = 0; + } + + std::sort(revisions.begin(), revisions.end(), + [](int a, int b) { return QByteArray::number(a) < QByteArray::number(b); }); + const auto end = std::unique(revisions.begin(), revisions.end()); + revisions.erase(end, revisions.end()); + + resolvedClass = classDef; +} diff --git a/tools/qmltyperegistrar/qmltypesclassdescription.h b/tools/qmltyperegistrar/qmltypesclassdescription.h new file mode 100644 index 0000000000..8f3a6ea124 --- /dev/null +++ b/tools/qmltyperegistrar/qmltypesclassdescription.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module 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 QMLTYPESCLASSDESCRIPTION_H +#define QMLTYPESCLASSDESCRIPTION_H + +#include <QtCore/qstring.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qvector.h> +#include <QtCore/qset.h> + +struct QmlTypesClassDescription +{ + const QJsonObject *resolvedClass = nullptr; + QString elementName; + QString defaultProp; + QString superClass; + QString attachedType; + QList<int> revisions; + int addedInRevision = -1; + int removedInRevision = -1; + bool isCreatable = true; + bool isSingleton = false; + bool isRootClass = false; + bool isBuiltin = false; + + void collect(const QJsonObject *classDef, const QVector<QJsonObject> &types, + const QVector<QJsonObject> &foreign, bool topLevel); + + static const QJsonObject *findType(const QVector<QJsonObject> &types, const QString &name); +}; + +#endif // QMLTYPESCLASSDESCRIPTION_H diff --git a/tools/qmltyperegistrar/qmltypescreator.cpp b/tools/qmltyperegistrar/qmltypescreator.cpp new file mode 100644 index 0000000000..7bac6a87d8 --- /dev/null +++ b/tools/qmltyperegistrar/qmltypescreator.cpp @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module 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 "qmltypescreator.h" +#include "qmlstreamwriter.h" +#include "qmltypesclassdescription.h" + +#include <QtCore/qset.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qsavefile.h> +#include <QtCore/qfile.h> +#include <QtCore/qjsondocument.h> + +static QString enquote(const QString &string) +{ + QString s = string; + return QString::fromLatin1("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\")) + .replace(QLatin1Char('"'),QLatin1String("\\\""))); +} + +void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &collector) +{ + m_qml.writeScriptBinding( + QLatin1String("name"), + enquote(collector.resolvedClass->value( + QLatin1String("qualifiedClassName")).toString())); + + if (!collector.defaultProp.isEmpty()) + m_qml.writeScriptBinding(QLatin1String("defaultProperty"), enquote(collector.defaultProp)); + + if (!collector.superClass.isEmpty()) + m_qml.writeScriptBinding(QLatin1String("prototype"), enquote(collector.superClass)); + + if (collector.elementName.isEmpty()) + return; + + QStringList exports; + QStringList metaObjects; + + for (auto it = collector.revisions.begin(), end = collector.revisions.end(); it != end; ++it) { + const int revision = *it; + if (revision < collector.addedInRevision) + continue; + if (collector.removedInRevision > collector.addedInRevision + && revision >= collector.removedInRevision) { + break; + } + + if (collector.isBuiltin) { + exports.append(enquote(QString::fromLatin1("QML/%1 1.0").arg(collector.elementName))); + metaObjects.append(QLatin1String("0")); + } + + exports.append(enquote(QString::fromLatin1("%1/%2 %3.%4") + .arg(m_module).arg(collector.elementName) + .arg(m_majorVersion).arg(revision))); + metaObjects.append(QString::number(revision)); + } + + m_qml.writeArrayBinding(QLatin1String("exports"), exports); + + if (!collector.isCreatable || collector.isSingleton) + m_qml.writeScriptBinding(QLatin1String("isCreatable"), QLatin1String("false")); + + if (collector.isSingleton) + m_qml.writeScriptBinding(QLatin1String("isSingleton"), QLatin1String("true")); + + m_qml.writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjects); + + if (!collector.attachedType.isEmpty()) + m_qml.writeScriptBinding(QLatin1String("attachedType"), enquote(collector.attachedType)); +} + +void QmlTypesCreator::writeType(const QJsonObject &property, const QString &key, bool isReadonly, + bool parsePointer) +{ + auto it = property.find(key); + if (it == property.end()) + return; + + QString type = (*it).toString(); + if (type.isEmpty() || type == QLatin1String("void")) + return; + + const QLatin1String typeKey("type"); + + bool isList = false; + bool isPointer = false; + + if (type == QLatin1String("QString")) { + type = QLatin1String("string"); + } else if (type == QLatin1String("qreal")) { + type = QLatin1String("double"); + } else if (type == QLatin1String("qint32")) { + type = QLatin1String("int"); + } else if (type == QLatin1String("quint32")) { + type = QLatin1String("uint"); + } else if (type == QLatin1String("qint64")) { + type = QLatin1String("qlonglong"); + } else if (type == QLatin1String("quint64")) { + type = QLatin1String("qulonglong"); + } else { + + const QLatin1String listProperty("QQmlListProperty<"); + if (type.startsWith(listProperty)) { + isList = true; + const int listPropertySize = listProperty.size(); + type = type.mid(listPropertySize, type.size() - listPropertySize - 1); + } + + if (parsePointer && type.endsWith(QLatin1Char('*'))) { + isPointer = true; + type = type.left(type.size() - 1); + } + } + + m_qml.writeScriptBinding(typeKey, enquote(type)); + const QLatin1String trueString("true"); + if (isList) + m_qml.writeScriptBinding(QLatin1String("isList"), trueString); + if (isReadonly) + m_qml.writeScriptBinding(QLatin1String("isReadonly"), trueString); + if (isPointer) + m_qml.writeScriptBinding(QLatin1String("isPointer"), trueString); +} + +void QmlTypesCreator::writeProperties(const QJsonArray &properties, QSet<QString> ¬ifySignals) +{ + for (const QJsonValue &property : properties) { + const QJsonObject obj = property.toObject(); + const QString name = obj[QLatin1String("name")].toString(); + m_qml.writeStartObject(QLatin1String("Property")); + m_qml.writeScriptBinding(QLatin1String("name"), enquote(name)); + const auto it = obj.find(QLatin1String("revision")); + if (it != obj.end()) + m_qml.writeScriptBinding(QLatin1String("revision"), QString::number(it.value().toInt())); + writeType(obj, QLatin1String("type"), !obj.contains(QLatin1String("write")), true); + m_qml.writeEndObject(); + + const QString notify = obj[QLatin1String("notify")].toString(); + if (notify == name + QLatin1String("Changed")) + notifySignals.insert(notify); + } +} + +void QmlTypesCreator::writeMethods(const QJsonArray &methods, const QString &type, + const QSet<QString> ¬ifySignals) +{ + for (const QJsonValue &method : methods) { + const QJsonObject obj = method.toObject(); + if (obj[QLatin1String("access")].toString() != QLatin1String("public")) + continue; + const QString name = obj[QLatin1String("name")].toString(); + const QJsonArray arguments = method[QLatin1String("arguments")].toArray(); + const auto revision = obj.find(QLatin1String("revision")); + if (notifySignals.contains(name) && arguments.isEmpty() && revision == obj.end()) + continue; + m_qml.writeStartObject(type); + m_qml.writeScriptBinding(QLatin1String("name"), enquote(name)); + if (revision != obj.end()) + m_qml.writeScriptBinding(QLatin1String("revision"), QString::number(revision.value().toInt())); + writeType(obj, QLatin1String("returnType"), false, false); + for (const QJsonValue &argument : arguments) { + const QJsonObject obj = argument.toObject(); + m_qml.writeStartObject(QLatin1String("Parameter")); + const QString name = obj[QLatin1String("name")].toString(); + if (!name.isEmpty()) + m_qml.writeScriptBinding(QLatin1String("name"), enquote(name)); + writeType(obj, QLatin1String("type"), false, true); + m_qml.writeEndObject(); + } + m_qml.writeEndObject(); + } +} + +void QmlTypesCreator::writeEnums(const QJsonArray &enums) +{ + for (const auto &item : enums) { + const QJsonObject obj = item.toObject(); + const QJsonArray values = obj.value(QLatin1String("values")).toArray(); + QStringList valueList; + + for (const QJsonValue &value : values) + valueList.append(enquote(value.toString())); + + m_qml.writeStartObject(QLatin1String("Enum")); + m_qml.writeScriptBinding(QLatin1String("name"), + enquote(obj.value(QLatin1String("name")).toString())); + m_qml.writeArrayBinding(QLatin1String("values"), valueList); + m_qml.writeEndObject(); + } +} + +void QmlTypesCreator::writeComponents() +{ + const QLatin1String nameKey("name"); + const QLatin1String signalsKey("signals"); + const QLatin1String enumsKey("enums"); + const QLatin1String propertiesKey("properties"); + const QLatin1String slotsKey("slots"); + const QLatin1String methodsKey("methods"); + const QLatin1String accessKey("access"); + const QLatin1String typeKey("type"); + const QLatin1String argumentsKey("arguments"); + + const QLatin1String destroyedName("destroyed"); + const QLatin1String deleteLaterName("deleteLater"); + const QLatin1String toStringName("toString"); + const QLatin1String destroyName("destroy"); + const QLatin1String delayName("delay"); + + const QLatin1String signalElement("Signal"); + const QLatin1String componentElement("Component"); + const QLatin1String methodElement("Method"); + + const QLatin1String publicAccess("public"); + const QLatin1String intType("int"); + + for (const QJsonObject &component : m_ownTypes) { + m_qml.writeStartObject(componentElement); + + QmlTypesClassDescription collector; + collector.collect(&component, m_ownTypes, m_foreignTypes, true); + + writeClassProperties(collector); + + const QJsonObject *classDef = collector.resolvedClass; + writeEnums(classDef->value(enumsKey).toArray()); + + QSet<QString> notifySignals; + writeProperties(classDef->value(propertiesKey).toArray(), notifySignals); + + if (collector.isRootClass) { + + // Hide destroyed() signals + QJsonArray componentSignals = classDef->value(signalsKey).toArray(); + for (auto it = componentSignals.begin(); it != componentSignals.end();) { + if (it->toObject().value(nameKey).toString() == destroyedName) + it = componentSignals.erase(it); + else + ++it; + } + writeMethods(componentSignals, signalElement, notifySignals); + + // Hide deleteLater() methods + QJsonArray componentMethods = classDef->value(methodsKey).toArray() + + classDef->value(slotsKey).toArray(); + for (auto it = componentMethods.begin(); it != componentMethods.end();) { + if (it->toObject().value(nameKey).toString() == deleteLaterName) + it = componentMethods.erase(it); + else + ++it; + } + + // Add toString() + QJsonObject toStringMethod; + toStringMethod.insert(nameKey, toStringName); + toStringMethod.insert(accessKey, publicAccess); + componentMethods.append(toStringMethod); + + // Add destroy() + QJsonObject destroyMethod; + destroyMethod.insert(nameKey, destroyName); + destroyMethod.insert(accessKey, publicAccess); + componentMethods.append(destroyMethod); + + // Add destroy(int) + QJsonObject destroyMethodWithArgument; + destroyMethodWithArgument.insert(nameKey, destroyName); + destroyMethodWithArgument.insert(accessKey, publicAccess); + QJsonObject delayArgument; + delayArgument.insert(nameKey, delayName); + delayArgument.insert(typeKey, intType); + QJsonArray destroyArguments; + destroyArguments.append(delayArgument); + destroyMethodWithArgument.insert(argumentsKey, destroyArguments); + componentMethods.append(destroyMethodWithArgument); + + writeMethods(componentMethods, methodElement); + } else { + writeMethods(classDef->value(signalsKey).toArray(), signalElement, notifySignals); + writeMethods(classDef->value(slotsKey).toArray(), methodElement); + writeMethods(classDef->value(methodsKey).toArray(), methodElement); + } + m_qml.writeEndObject(); + } +} + +void QmlTypesCreator::generate(const QString &outFileName, const QString &dependenciesFileName) +{ + m_qml.writeStartDocument(); + m_qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2); + m_qml.write(QString::fromLatin1( + "\n// This file describes the plugin-supplied types contained in the library." + "\n// It is used for QML tooling purposes only." + "\n//" + "\n// This file was auto-generated by qmltyperegistrar.\n\n")); + m_qml.writeStartObject(QLatin1String("Module")); + + QStringList dependencies; + if (!dependenciesFileName.isEmpty()) { + QFile file(dependenciesFileName); + if (!file.open(QIODevice::ReadOnly)) { + fprintf(stderr, "Failed to open %s\n", qPrintable(dependenciesFileName)); + } else { + QJsonParseError error { -1, QJsonParseError::NoError }; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + fprintf(stderr, "Failed to parse %s\n", qPrintable(dependenciesFileName)); + } else { + const QJsonArray array = doc.array(); + for (const QJsonValue &value : array) + dependencies.append(enquote(value.toString())); + } + } + } else { + // Default dependency is QtQuick 2.0 + dependencies.append(enquote(QLatin1String("QtQuick 2.0"))); + } + + m_qml.writeArrayBinding(QLatin1String("dependencies"), dependencies); + + writeComponents(); + + m_qml.writeEndObject(); + + QSaveFile file(outFileName); + file.open(QIODevice::WriteOnly); + file.write(m_output); + file.commit(); +} + diff --git a/tools/qmltyperegistrar/qmltypescreator.h b/tools/qmltyperegistrar/qmltypescreator.h new file mode 100644 index 0000000000..9207a64b7e --- /dev/null +++ b/tools/qmltyperegistrar/qmltypescreator.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module 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 QMLTYPESCREATOR_H +#define QMLTYPESCREATOR_H + +#include "qmlstreamwriter.h" +#include "qmltypesclassdescription.h" + +#include <QtCore/qstring.h> +#include <QtCore/qset.h> + +class QmlTypesCreator +{ +public: + QmlTypesCreator() : m_qml(&m_output) {} + + void generate(const QString &outFileName, const QString &dependenciesFileName); + + void setOwnTypes(QVector<QJsonObject> ownTypes) { m_ownTypes = std::move(ownTypes); } + void setForeignTypes(QVector<QJsonObject> foreignTypes) { m_foreignTypes = std::move(foreignTypes); } + void setModule(QString module) { m_module = std::move(module); } + void setMajorVersion(int majorVersion) { m_majorVersion = majorVersion; } + +private: + void writeClassProperties(const QmlTypesClassDescription &collector); + void writeType(const QJsonObject &property, const QString &key, bool isReadonly, + bool parsePointer); + void writeProperties(const QJsonArray &properties, QSet<QString> ¬ifySignals); + void writeMethods(const QJsonArray &methods, const QString &type, + const QSet<QString> ¬ifySignals = QSet<QString>()); + void writeEnums(const QJsonArray &enums); + void writeComponents(); + + QByteArray m_output; + QmlStreamWriter m_qml; + QVector<QJsonObject> m_ownTypes; + QVector<QJsonObject> m_foreignTypes; + QString m_module; + int m_majorVersion = 0; +}; + +#endif // QMLTYPESCREATOR_H diff --git a/tools/qmlplugindump/qmlstreamwriter.cpp b/tools/shared/qmlstreamwriter.cpp index b0fbc4e443..b0fbc4e443 100644 --- a/tools/qmlplugindump/qmlstreamwriter.cpp +++ b/tools/shared/qmlstreamwriter.cpp diff --git a/tools/qmlplugindump/qmlstreamwriter.h b/tools/shared/qmlstreamwriter.h index cb642159ea..cb642159ea 100644 --- a/tools/qmlplugindump/qmlstreamwriter.h +++ b/tools/shared/qmlstreamwriter.h diff --git a/tools/tools.pro b/tools/tools.pro index 25ed760903..69b79e8816 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -10,6 +10,8 @@ qtConfig(qml-devtools) { qtConfig(commandlineparser):qtConfig(xmlstreamwriter): SUBDIRS += qmlcachegen } +qtConfig(commandlineparser): SUBDIRS += qmltyperegistrar + qtConfig(thread):!android|android_app:!wasm:!rtems { SUBDIRS += \ qml |