/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include struct ScopedPointerFileCloser { static inline void cleanup(FILE *handle) { if (handle) fclose(handle); } }; enum RegistrationMode { NoRegistration, ObjectRegistration, GadgetRegistration, NamespaceRegistration }; static RegistrationMode qmlTypeRegistrationMode(const QJsonObject &classDef) { const QJsonArray classInfos = classDef[QLatin1String("classInfos")].toArray(); for (const QJsonValue &info: classInfos) { const QString name = info[QLatin1String("name")].toString(); if (name == QLatin1String("QML.Element")) { if (classDef[QLatin1String("object")].toBool()) return ObjectRegistration; if (classDef[QLatin1String("gadget")].toBool()) return GadgetRegistration; if (classDef[QLatin1String("namespace")].toBool()) return NamespaceRegistration; qWarning() << "Not registering classInfo which is neither an object, " "nor a gadget, nor a namespace:" << name; break; } } return NoRegistration; } static bool argumentsFromCommandLineAndFile(QStringList &allArguments, const QStringList &arguments) { allArguments.reserve(arguments.size()); for (const QString &argument : arguments) { // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it if (argument.startsWith(QLatin1Char('@'))) { QString optionsFile = argument; optionsFile.remove(0, 1); if (optionsFile.isEmpty()) { fprintf(stderr, "The @ option requires an input file"); return false; } QFile f(optionsFile); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { fprintf(stderr, "Cannot open options file specified with @"); return false; } while (!f.atEnd()) { QString line = QString::fromLocal8Bit(f.readLine().trimmed()); if (!line.isEmpty()) allArguments << line; } } else { allArguments << argument; } } return true; } static QVector foreignRelatedTypes(const QVector &types, const QVector &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 processedRelatedNames; QQueue typeQueue; typeQueue.append(types.toList()); QVector 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 \"" "rather than \"#include \".")); parser.addOption(privateIncludesOption); QCommandLineOption importNameOption(QStringLiteral("import-name")); importNameOption.setDescription(QStringLiteral("Name of the module to use for type and module " "registrations.")); importNameOption.setValueName(QStringLiteral("module name")); parser.addOption(importNameOption); QCommandLineOption majorVersionOption(QStringLiteral("major-version")); majorVersionOption.setDescription(QStringLiteral("Major version to use for type and module " "registrations.")); majorVersionOption.setValueName(QStringLiteral("major version")); parser.addOption(majorVersionOption); QCommandLineOption minorVersionOption(QStringLiteral("minor-version")); minorVersionOption.setDescription(QStringLiteral("Minor version to use for module " "registration.")); minorVersionOption.setValueName(QStringLiteral("minor version")); parser.addOption(minorVersionOption); QCommandLineOption pluginTypesOption(QStringLiteral("generate-qmltypes")); pluginTypesOption.setDescription(QStringLiteral("Generate qmltypes into specified file.")); pluginTypesOption.setValueName(QStringLiteral("qmltypes file")); parser.addOption(pluginTypesOption); QCommandLineOption foreignTypesOption(QStringLiteral("foreign-types")); foreignTypesOption.setDescription(QStringLiteral( "Comma separated list of other modules' metatypes files " "to consult for foreign types when generating " "qmltypes file.")); foreignTypesOption.setValueName(QStringLiteral("foreign types")); parser.addOption(foreignTypesOption); parser.addPositionalArgument(QStringLiteral("[MOC generated json file]"), QStringLiteral("MOC generated json output.")); QStringList arguments; if (!argumentsFromCommandLineAndFile(arguments, app.arguments())) return EXIT_FAILURE; parser.process(arguments); FILE *output = stdout; QScopedPointer outputFile; if (parser.isSet(outputOption)) { QString outputName = parser.value(outputOption); #if defined(_MSC_VER) if (_wfopen_s(&output, reinterpret_cast(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 \n" "#include \n"); QStringList includes; QVector types; QVector 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; } } const bool privateIncludes = parser.isSet(privateIncludesOption); auto resolvedInclude = [&](const QString &include) { return (privateIncludes && include.endsWith(QLatin1String("_p.h"))) ? QLatin1String("private/") + include : include; }; auto processMetaObject = [&](const QJsonObject &metaObject) { const QString include = resolvedInclude(metaObject[QLatin1String("inputFile")].toString()); const QJsonArray classes = metaObject[QLatin1String("classes")].toArray(); for (const auto &cls : classes) { QJsonObject classDef = cls.toObject(); classDef.insert(QLatin1String("inputFile"), include); switch (qmlTypeRegistrationMode(classDef)) { case NamespaceRegistration: case GadgetRegistration: case ObjectRegistration: { if (!include.endsWith(QLatin1String(".h")) && !include.endsWith(QLatin1String(".hpp")) && !include.endsWith(QLatin1String(".hxx")) && include.contains(QLatin1Char('.'))) { fprintf(stderr, "Class %s is declared in %s, which appears not to be a header.\n" "The compilation of its registration to QML may fail.\n", qPrintable(classDef.value(QLatin1String("qualifiedClassName")) .toString()), qPrintable(include)); } includes.append(include); { bool shouldRegister = true; for (const QJsonValue &v : classDef.value(QLatin1String("classInfos")).toArray()) { if (v[QLatin1String("name")].toString() == QLatin1String("QML.ManualRegistration")) shouldRegister = QStringView(u"true").compare(v[QLatin1String("value")].toString(), Qt::CaseInsensitive) != 0; } classDef.insert(QLatin1String("registerable"), shouldRegister); } types.append(classDef); break; } case NoRegistration: foreignTypes.append(classDef); break; } } }; 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 &types) { std::sort(types.begin(), types.end(), [&](const QJsonObject &a, const QJsonObject &b) { return a.value(qualifiedClassNameKey).toString() < b.value(qualifiedClassNameKey).toString(); }); }; sortTypes(types); std::sort(includes.begin(), includes.end()); const auto newEnd = std::unique(includes.begin(), includes.end()); includes.erase(newEnd, includes.end()); for (const QString &include : qAsConst(includes)) 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{", qPrintable(functionName)); const auto majorVersion = parser.value(majorVersionOption); const auto minorVersion = parser.value(minorVersionOption); if (minorVersion.toInt() != 0) { fprintf(output, "\n qmlRegisterModule(\"%s\", %s, 0);", qPrintable(module), qPrintable(majorVersion)); } for (const QJsonObject &classDef : qAsConst(types)) { if (!classDef.value(QLatin1String("registerable")).toBool()) continue; const QString className = classDef[QLatin1String("qualifiedClassName")].toString(); if (classDef.value(QLatin1String("namespace")).toBool()) { QString targetName = className; for (const QJsonValue &v : classDef.value(QLatin1String("classInfos")).toArray()) { if (v[QLatin1String("name")].toString() == QLatin1String("QML.Foreign")) targetName = v[QLatin1String("value")].toString(); } fprintf(output, "\n qmlRegisterNamespaceAndRevisions(&%s::staticMetaObject, \"%s\", %s, nullptr, &%s::staticMetaObject);", qPrintable(targetName), qPrintable(module), qPrintable(majorVersion), qPrintable(className)); } else { fprintf(output, "\n qmlRegisterTypesAndRevisions<%s>(\"%s\", %s);", qPrintable(className), qPrintable(module), qPrintable(majorVersion)); } } fprintf(output, "\n qmlRegisterModule(\"%s\", %s, %s);", qPrintable(module), qPrintable(majorVersion), qPrintable(minorVersion)); fprintf(output, "\n}\n"); fprintf(output, "\nstatic const QQmlModuleRegistration registration(\"%s\", %s);\n", qPrintable(module), 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; } auto const asObject = metaObject.toObject(); const QString include = asObject[QLatin1String("inputFile")].toString(); const QJsonArray classes = asObject[QLatin1String("classes")].toArray(); for (const auto &cls : classes) { QJsonObject classDef = cls.toObject(); classDef.insert(QLatin1String("inputFile"), include); foreignTypes.append(classDef); } } } } sortTypes(foreignTypes); types += foreignRelatedTypes(types, foreignTypes); sortTypes(types); QmlTypesCreator creator; creator.setOwnTypes(std::move(types)); creator.setForeignTypes(std::move(foreignTypes)); creator.setModule(module); creator.setVersion(QTypeRevision::fromVersion(parser.value(majorVersionOption).toInt(), 0)); creator.generate(parser.value(pluginTypesOption)); return EXIT_SUCCESS; }