1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "qmltypescreator.h"
30
31#include <QCoreApplication>
32#include <QCommandLineParser>
33#include <QtDebug>
34#include <QJsonDocument>
35#include <QJsonArray>
36#include <QJsonValue>
37#include <QJsonObject>
38#include <QFile>
39#include <QScopedPointer>
40#include <QSaveFile>
41#include <QQueue>
42
43#include <cstdlib>
44
45struct ScopedPointerFileCloser
46{
47 static inline void cleanup(FILE *handle) { if (handle) fclose(stream: handle); }
48};
49
50enum RegistrationMode {
51 NoRegistration,
52 ObjectRegistration,
53 GadgetRegistration,
54 NamespaceRegistration
55};
56
57static RegistrationMode qmlTypeRegistrationMode(const QJsonObject &classDef)
58{
59 const QJsonArray classInfos = classDef[QLatin1String("classInfos")].toArray();
60 for (const QJsonValue &info: classInfos) {
61 const QString name = info[QLatin1String("name")].toString();
62 if (name == QLatin1String("QML.Element")) {
63 if (classDef[QLatin1String("object")].toBool())
64 return ObjectRegistration;
65 if (classDef[QLatin1String("gadget")].toBool())
66 return GadgetRegistration;
67 if (classDef[QLatin1String("namespace")].toBool())
68 return NamespaceRegistration;
69 qWarning() << "Not registering classInfo which is neither an object, "
70 "nor a gadget, nor a namespace:"
71 << name;
72 break;
73 }
74 }
75 return NoRegistration;
76}
77
78static QVector<QJsonObject> foreignRelatedTypes(const QVector<QJsonObject> &types,
79 const QVector<QJsonObject> &foreignTypes)
80{
81 const QLatin1String classInfosKey("classInfos");
82 const QLatin1String nameKey("name");
83 const QLatin1String qualifiedClassNameKey("qualifiedClassName");
84 const QLatin1String qmlNamePrefix("QML.");
85 const QLatin1String qmlForeignName("QML.Foreign");
86 const QLatin1String qmlAttachedName("QML.Attached");
87 const QLatin1String valueKey("value");
88 const QLatin1String superClassesKey("superClasses");
89 const QLatin1String accessKey("access");
90 const QLatin1String publicAccess("public");
91
92 QSet<QString> processedRelatedNames;
93 QQueue<QJsonObject> typeQueue;
94 typeQueue.append(t: types.toList());
95 QVector<QJsonObject> relatedTypes;
96
97 // First mark all classes registered from this module as already processed.
98 for (const QJsonObject &type : types) {
99 processedRelatedNames.insert(value: type.value(key: qualifiedClassNameKey).toString());
100 const auto classInfos = type.value(key: classInfosKey).toArray();
101 for (const QJsonValue &classInfo : classInfos) {
102 const QJsonObject obj = classInfo.toObject();
103 if (obj.value(key: nameKey).toString() == qmlForeignName) {
104 processedRelatedNames.insert(value: obj.value(key: valueKey).toString());
105 break;
106 }
107 }
108 }
109
110 // Then mark all classes registered from other modules as already processed.
111 // We don't want to generate them again for this module.
112 for (const QJsonObject &foreignType : foreignTypes) {
113 const auto classInfos = foreignType.value(key: classInfosKey).toArray();
114 bool seenQmlPrefix = false;
115 for (const QJsonValue &classInfo : classInfos) {
116 const QJsonObject obj = classInfo.toObject();
117 const QString name = obj.value(key: nameKey).toString();
118 if (!seenQmlPrefix && name.startsWith(s: qmlNamePrefix)) {
119 processedRelatedNames.insert(value: foreignType.value(key: qualifiedClassNameKey).toString());
120 seenQmlPrefix = true;
121 }
122 if (name == qmlForeignName) {
123 processedRelatedNames.insert(value: obj.value(key: valueKey).toString());
124 break;
125 }
126 }
127 }
128
129 auto addType = [&](const QString &typeName) {
130 if (processedRelatedNames.contains(value: typeName))
131 return;
132 processedRelatedNames.insert(value: typeName);
133 if (const QJsonObject *other = QmlTypesClassDescription::findType(types: foreignTypes, name: typeName)) {
134 relatedTypes.append(t: *other);
135 typeQueue.enqueue(t: *other);
136 }
137 };
138
139 // Then recursively iterate the super types and attached types, marking the
140 // ones we are interested in as related.
141 while (!typeQueue.isEmpty()) {
142 const QJsonObject classDef = typeQueue.dequeue();
143
144 const auto classInfos = classDef.value(key: classInfosKey).toArray();
145 for (const QJsonValue &classInfo : classInfos) {
146 const QJsonObject obj = classInfo.toObject();
147 if (obj.value(key: nameKey).toString() == qmlAttachedName) {
148 addType(obj.value(key: valueKey).toString());
149 } else if (obj.value(key: nameKey).toString() == qmlForeignName) {
150 const QString foreignClassName = obj.value(key: valueKey).toString();
151 if (const QJsonObject *other = QmlTypesClassDescription::findType(
152 types: foreignTypes, name: foreignClassName)) {
153 const auto otherSupers = other->value(key: superClassesKey).toArray();
154 if (!otherSupers.isEmpty()) {
155 const QJsonObject otherSuperObject = otherSupers.first().toObject();
156 if (otherSuperObject.value(key: accessKey).toString() == publicAccess)
157 addType(otherSuperObject.value(key: nameKey).toString());
158 }
159
160 const auto otherClassInfos = other->value(key: classInfosKey).toArray();
161 for (const QJsonValue &otherClassInfo : otherClassInfos) {
162 const QJsonObject obj = otherClassInfo.toObject();
163 if (obj.value(key: nameKey).toString() == qmlAttachedName) {
164 addType(obj.value(key: valueKey).toString());
165 break;
166 }
167 // No, you cannot chain QML_FOREIGN declarations. Sorry.
168 }
169 break;
170 }
171 }
172 }
173
174 const auto supers = classDef.value(key: superClassesKey).toArray();
175 if (!supers.isEmpty()) {
176 const QJsonObject superObject = supers.first().toObject();
177 if (superObject.value(key: accessKey).toString() == publicAccess)
178 addType(superObject.value(key: nameKey).toString());
179 }
180 }
181
182 return relatedTypes;
183}
184
185int main(int argc, char **argv)
186{
187 // Produce reliably the same output for the same input by disabling QHash's random seeding.
188 qSetGlobalQHashSeed(newSeed: 0);
189
190 QCoreApplication app(argc, argv);
191 QCoreApplication::setApplicationName(QStringLiteral("qmltyperegistrar"));
192 QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
193
194 QCommandLineParser parser;
195 parser.addHelpOption();
196 parser.addVersionOption();
197
198 QCommandLineOption outputOption(QStringLiteral("o"));
199 outputOption.setDescription(QStringLiteral("Write output to specified file."));
200 outputOption.setValueName(QStringLiteral("file"));
201 outputOption.setFlags(QCommandLineOption::ShortOptionStyle);
202 parser.addOption(commandLineOption: outputOption);
203
204 QCommandLineOption privateIncludesOption(
205 QStringLiteral("private-includes"),
206 QStringLiteral("Include headers ending in \"_p.h\" using \"#include <private/foo_p.h>\""
207 "rather than \"#include <foo_p.h>\"."));
208 parser.addOption(commandLineOption: privateIncludesOption);
209
210 QCommandLineOption importNameOption(QStringLiteral("import-name"));
211 importNameOption.setDescription(QStringLiteral("Name of the module to use for type and module "
212 "registrations."));
213 importNameOption.setValueName(QStringLiteral("module name"));
214 parser.addOption(commandLineOption: importNameOption);
215
216 QCommandLineOption majorVersionOption(QStringLiteral("major-version"));
217 majorVersionOption.setDescription(QStringLiteral("Major version to use for type and module "
218 "registrations."));
219 majorVersionOption.setValueName(QStringLiteral("major version"));
220 parser.addOption(commandLineOption: majorVersionOption);
221
222 QCommandLineOption minorVersionOption(QStringLiteral("minor-version"));
223 minorVersionOption.setDescription(QStringLiteral("Minor version to use for module "
224 "registration."));
225 minorVersionOption.setValueName(QStringLiteral("minor version"));
226 parser.addOption(commandLineOption: minorVersionOption);
227
228 QCommandLineOption pluginTypesOption(QStringLiteral("generate-qmltypes"));
229 pluginTypesOption.setDescription(QStringLiteral("Generate qmltypes into specified file."));
230 pluginTypesOption.setValueName(QStringLiteral("qmltypes file"));
231 parser.addOption(commandLineOption: pluginTypesOption);
232
233 QCommandLineOption foreignTypesOption(QStringLiteral("foreign-types"));
234 foreignTypesOption.setDescription(QStringLiteral(
235 "Comma separated list of other modules' metatypes files "
236 "to consult for foreign types when generating "
237 "qmltypes file."));
238 foreignTypesOption.setValueName(QStringLiteral("foreign types"));
239 parser.addOption(commandLineOption: foreignTypesOption);
240
241 QCommandLineOption dependenciesOption(QStringLiteral("dependencies"));
242 dependenciesOption.setDescription(QStringLiteral("JSON file with dependencies to be stated in "
243 "qmltypes file."));
244 dependenciesOption.setValueName(QStringLiteral("dependencies.json"));
245 parser.addOption(commandLineOption: dependenciesOption);
246
247 parser.addPositionalArgument(QStringLiteral("[MOC generated json file]"),
248 QStringLiteral("MOC generated json output."));
249
250 parser.process(app);
251
252 FILE *output = stdout;
253 QScopedPointer<FILE, ScopedPointerFileCloser> outputFile;
254
255 if (parser.isSet(option: outputOption)) {
256 QString outputName = parser.value(option: outputOption);
257#if defined(_MSC_VER)
258 if (_wfopen_s(&output, reinterpret_cast<const wchar_t *>(outputName.utf16()), L"w") != 0) {
259#else
260 output = fopen(filename: QFile::encodeName(fileName: outputName).constData(), modes: "w"); // create output file
261 if (!output) {
262#endif
263 fprintf(stderr, format: "Error: Cannot open %s for writing\n", qPrintable(outputName));
264 return EXIT_FAILURE;
265 }
266 outputFile.reset(other: output);
267 }
268
269 fprintf(stream: output,
270 format: "/****************************************************************************\n"
271 "** Generated QML type registration code\n**\n");
272 fprintf(stream: output,
273 format: "** WARNING! All changes made in this file will be lost!\n"
274 "*****************************************************************************/\n\n");
275 fprintf(stream: output,
276 format: "#include <QtQml/qqml.h>\n"
277 "#include <QtQml/qqmlmoduleregistration.h>\n");
278
279 QStringList includes;
280 QVector<QJsonObject> types;
281 QVector<QJsonObject> foreignTypes;
282
283 const QString module = parser.value(option: importNameOption);
284 const QStringList files = parser.positionalArguments();
285 for (const QString &source: files) {
286 QJsonDocument metaObjects;
287 {
288 QFile f(source);
289 if (!f.open(flags: QIODevice::ReadOnly)) {
290 fprintf(stderr, format: "Error opening %s for reading\n", qPrintable(source));
291 return EXIT_FAILURE;
292 }
293 QJsonParseError error = {.offset: 0, .error: QJsonParseError::NoError};
294 metaObjects = QJsonDocument::fromJson(json: f.readAll(), error: &error);
295 if (error.error != QJsonParseError::NoError) {
296 fprintf(stderr, format: "Error parsing %s\n", qPrintable(source));
297 return EXIT_FAILURE;
298 }
299 }
300
301 const bool privateIncludes = parser.isSet(option: privateIncludesOption);
302 auto resolvedInclude = [&](const QString &include) {
303 return (privateIncludes && include.endsWith(s: QLatin1String("_p.h")))
304 ? QLatin1String("private/") + include
305 : include;
306 };
307
308 auto processMetaObject = [&](const QJsonObject &metaObject) {
309 const QString include = resolvedInclude(metaObject[QLatin1String("inputFile")].toString());
310 const QJsonArray classes = metaObject[QLatin1String("classes")].toArray();
311 for (const auto &cls : classes) {
312 QJsonObject classDef = cls.toObject();
313 classDef.insert(key: QLatin1String("inputFile"), value: include);
314
315 switch (qmlTypeRegistrationMode(classDef)) {
316 case NamespaceRegistration:
317 case GadgetRegistration:
318 case ObjectRegistration: {
319 if (!include.endsWith(s: QLatin1String(".h"))
320 && !include.endsWith(s: QLatin1String(".hpp"))
321 && !include.endsWith(s: QLatin1String(".hxx"))
322 && include.contains(c: QLatin1Char('.'))) {
323 fprintf(stderr,
324 format: "Class %s is declared in %s, which appears not to be a header.\n"
325 "The compilation of its registration to QML may fail.\n",
326 qPrintable(classDef.value(QLatin1String("qualifiedClassName"))
327 .toString()),
328 qPrintable(include));
329 }
330 includes.append(t: include);
331 classDef.insert(key: QLatin1String("registerable"), value: true);
332
333 types.append(t: classDef);
334 break;
335 }
336 case NoRegistration:
337 foreignTypes.append(t: classDef);
338 break;
339 }
340 }
341 };
342
343 if (metaObjects.isArray()) {
344 const QJsonArray metaObjectsArray = metaObjects.array();
345 for (const auto &metaObject : metaObjectsArray) {
346 if (!metaObject.isObject()) {
347 fprintf(stderr, format: "Error parsing %s: JSON is not an object\n",
348 qPrintable(source));
349 return EXIT_FAILURE;
350 }
351
352 processMetaObject(metaObject.toObject());
353 }
354 } else if (metaObjects.isObject()) {
355 processMetaObject(metaObjects.object());
356 } else {
357 fprintf(stderr, format: "Error parsing %s: JSON is not an object or an array\n",
358 qPrintable(source));
359 return EXIT_FAILURE;
360 }
361 }
362
363 const QLatin1String qualifiedClassNameKey("qualifiedClassName");
364 auto sortTypes = [&](QVector<QJsonObject> &types) {
365 std::sort(first: types.begin(), last: types.end(), comp: [&](const QJsonObject &a, const QJsonObject &b) {
366 return a.value(key: qualifiedClassNameKey).toString() <
367 b.value(key: qualifiedClassNameKey).toString();
368 });
369 };
370
371 sortTypes(types);
372
373 std::sort(first: includes.begin(), last: includes.end());
374 const auto newEnd = std::unique(first: includes.begin(), last: includes.end());
375 includes.erase(afirst: newEnd, alast: includes.end());
376
377 for (const QString &include : qAsConst(t&: includes))
378 fprintf(stream: output, format: "\n#include <%s>", qPrintable(include));
379
380 fprintf(stream: output, format: "\n\n");
381
382 QString moduleAsSymbol = module;
383 moduleAsSymbol.replace(before: QLatin1Char('.'), after: QLatin1Char('_'));
384
385 const QString functionName = QStringLiteral("qml_register_types_") + moduleAsSymbol;
386
387 fprintf(stream: output, format: "void %s()\n{", qPrintable(functionName));
388 const auto majorVersion = parser.value(option: majorVersionOption);
389
390 for (const QJsonObject &classDef : qAsConst(t&: types)) {
391 if (!classDef.value(key: QLatin1String("registerable")).toBool())
392 continue;
393
394 const QString className = classDef[QLatin1String("qualifiedClassName")].toString();
395
396 if (classDef.value(key: QLatin1String("namespace")).toBool()) {
397 fprintf(stream: output, format: "\n qmlRegisterNamespaceAndRevisions(&%s::staticMetaObject, \"%s\", %s);",
398 qPrintable(className), qPrintable(module), qPrintable(majorVersion));
399 } else {
400 fprintf(stream: output, format: "\n qmlRegisterTypesAndRevisions<%s>(\"%s\", %s);",
401 qPrintable(className), qPrintable(module), qPrintable(majorVersion));
402 }
403 }
404
405 fprintf(stream: output, format: "\n qmlRegisterModule(\"%s\", %s, %s);",
406 qPrintable(module), qPrintable(majorVersion),
407 qPrintable(parser.value(minorVersionOption)));
408 fprintf(stream: output, format: "\n}\n");
409 fprintf(stream: output, format: "\nstatic const QQmlModuleRegistration registration(\"%s\", %s, %s);\n",
410 qPrintable(module), qPrintable(majorVersion), qPrintable(functionName));
411
412 if (!parser.isSet(option: pluginTypesOption))
413 return EXIT_SUCCESS;
414
415 if (parser.isSet(option: foreignTypesOption)) {
416 const QStringList foreignTypesFiles = parser.value(option: foreignTypesOption)
417 .split(sep: QLatin1Char(','));
418 for (const QString &types : foreignTypesFiles) {
419 QFile typesFile(types);
420 if (!typesFile.open(flags: QIODevice::ReadOnly)) {
421 fprintf(stderr, format: "Cannot open foreign types file %s\n", qPrintable(types));
422 continue;
423 }
424
425 QJsonParseError error = {.offset: 0, .error: QJsonParseError::NoError};
426 QJsonDocument foreignMetaObjects = QJsonDocument::fromJson(json: typesFile.readAll(), error: &error);
427 if (error.error != QJsonParseError::NoError) {
428 fprintf(stderr, format: "Error parsing %s\n", qPrintable(types));
429 continue;
430 }
431
432 const QJsonArray foreignObjectsArray = foreignMetaObjects.array();
433 for (const auto &metaObject : foreignObjectsArray) {
434 if (!metaObject.isObject()) {
435 fprintf(stderr, format: "Error parsing %s: JSON is not an object\n",
436 qPrintable(types));
437 continue;
438 }
439
440 const QString include = metaObject[QLatin1String("inputFile")].toString();
441 const QJsonArray classes = metaObject[QLatin1String("classes")].toArray();
442 for (const auto &cls : classes) {
443 QJsonObject classDef = cls.toObject();
444 classDef.insert(key: QLatin1String("inputFile"), value: include);
445 foreignTypes.append(t: classDef);
446 }
447 }
448 }
449 }
450
451 sortTypes(foreignTypes);
452 types += foreignRelatedTypes(types, foreignTypes);
453 sortTypes(types);
454
455 QmlTypesCreator creator;
456 creator.setOwnTypes(std::move(types));
457 creator.setForeignTypes(std::move(foreignTypes));
458 creator.setModule(module);
459 creator.setMajorVersion(parser.value(option: majorVersionOption).toInt());
460
461 creator.generate(outFileName: parser.value(option: pluginTypesOption), dependenciesFileName: parser.value(option: dependenciesOption));
462 return EXIT_SUCCESS;
463}
464

source code of qtdeclarative/src/qmltyperegistrar/qmltyperegistrar.cpp