1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include <QFile>
5#include <QJsonArray>
6#include <QJsonValue>
7
8#include "qqmltyperegistrar_p.h"
9#include "qqmltypescreator_p.h"
10
11QT_BEGIN_NAMESPACE
12using namespace Qt::Literals;
13
14struct ExclusiveVersionRange
15{
16 QString claimerName;
17 QTypeRevision addedIn;
18 QTypeRevision removedIn;
19};
20
21/*!
22 * \brief True if x was removed before y was introduced.
23 * \param o
24 * \return
25 */
26bool operator<(const ExclusiveVersionRange &x, const ExclusiveVersionRange &y)
27{
28 if (x.removedIn.isValid())
29 return y.addedIn.isValid() ? x.removedIn <= y.addedIn : true;
30 else
31 return false;
32}
33
34/*!
35 * \brief True when x and y share a common version. (Warning: not transitive!)
36 * \param o
37 * \return
38 */
39bool operator==(const ExclusiveVersionRange &x, const ExclusiveVersionRange &y)
40{
41 return !(x < y) && !(y < x);
42}
43
44bool QmlTypeRegistrar::argumentsFromCommandLineAndFile(QStringList &allArguments,
45 const QStringList &arguments)
46{
47 allArguments.reserve(asize: arguments.size());
48 for (const QString &argument : arguments) {
49 // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
50 if (argument.startsWith(c: QLatin1Char('@'))) {
51 QString optionsFile = argument;
52 optionsFile.remove(i: 0, len: 1);
53 if (optionsFile.isEmpty()) {
54 fprintf(stderr, format: "The @ option requires an input file");
55 return false;
56 }
57 QFile f(optionsFile);
58 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
59 fprintf(stderr, format: "Cannot open options file specified with @");
60 return false;
61 }
62 while (!f.atEnd()) {
63 QString line = QString::fromLocal8Bit(ba: f.readLine().trimmed());
64 if (!line.isEmpty())
65 allArguments << line;
66 }
67 } else {
68 allArguments << argument;
69 }
70 }
71 return true;
72}
73
74int QmlTypeRegistrar::runExtract(const QString &baseName, const MetaTypesJsonProcessor &processor)
75{
76 if (processor.types().isEmpty()) {
77 fprintf(stderr, format: "Error: No types to register found in library\n");
78 return EXIT_FAILURE;
79 }
80 QFile headerFile(baseName + u".h");
81 bool ok = headerFile.open(flags: QFile::WriteOnly);
82 if (!ok) {
83 fprintf(stderr, format: "Error: Cannot open %s for writing\n", qPrintable(headerFile.fileName()));
84 return EXIT_FAILURE;
85 }
86 auto prefix = QString::fromLatin1(
87 ba: "#ifndef %1_H\n"
88 "#define %1_H\n"
89 "#include <QtQml/qqml.h>\n"
90 "#include <QtQml/qqmlmoduleregistration.h>\n").arg(a: baseName.toUpper());
91 const QStringList includes = processor.includes();
92 for (const QString &include: includes)
93 prefix += u"\n#include <%1>"_s.arg(a: include);
94 headerFile.write(data: (prefix + processor.extractRegisteredTypes()).toUtf8() + "\n#endif");
95
96 QFile sourceFile(baseName + u".cpp");
97 ok = sourceFile.open(flags: QFile::WriteOnly);
98 if (!ok) {
99 fprintf(stderr, format: "Error: Cannot open %s for writing\n", qPrintable(sourceFile.fileName()));
100 return EXIT_FAILURE;
101 }
102 // the string split is necessaury because cmake's automoc scanner would otherwise pick up the include
103 QString code = u"#include \"%1.h\"\n#include "_s.arg(a: baseName);
104 code += uR"("moc_%1.cpp")"_s.arg(a: baseName);
105 sourceFile.write(data: code.toUtf8());
106 return EXIT_SUCCESS;
107}
108
109QJsonValue QmlTypeRegistrar::findType(const QString &name) const
110{
111 for (const QJsonObject &type : m_types) {
112 if (type[QLatin1String("qualifiedClassName")] != name)
113 continue;
114 return type;
115 }
116 return QJsonValue();
117};
118
119QJsonValue QmlTypeRegistrar::findTypeForeign(const QString &name) const
120{
121 for (const QJsonObject &type : m_foreignTypes) {
122 if (type[QLatin1String("qualifiedClassName")] != name)
123 continue;
124 return type;
125 }
126 return QJsonValue();
127};
128
129QString conflictingVersionToString(const ExclusiveVersionRange &r)
130{
131 using namespace Qt::StringLiterals;
132
133 QString s = r.claimerName;
134 if (r.addedIn.isValid()) {
135 s += u" (added in %1.%2)"_s.arg(a: r.addedIn.majorVersion()).arg(a: r.addedIn.minorVersion());
136 }
137 if (r.removedIn.isValid()) {
138 s += u" (removed in %1.%2)"_s.arg(a: r.removedIn.majorVersion())
139 .arg(a: r.removedIn.minorVersion());
140 }
141 return s;
142};
143
144void QmlTypeRegistrar::write(QTextStream &output)
145{
146 output << uR"(/****************************************************************************
147** Generated QML type registration code
148**
149** WARNING! All changes made in this file will be lost!
150*****************************************************************************/
151
152)"_s;
153
154 output << u"#include <QtQml/qqml.h>\n"_s;
155 output << u"#include <QtQml/qqmlmoduleregistration.h>\n"_s;
156
157 for (const QString &include : m_includes)
158 output << u"\n#include <%1>"_s.arg(a: include);
159
160 output << u"\n\n"_s;
161
162 // Keep this in sync with _qt_internal_get_escaped_uri in CMake
163 QString moduleAsSymbol = m_module;
164 moduleAsSymbol.replace(re: QRegularExpression(QStringLiteral("[^A-Za-z0-9]")), QStringLiteral("_"));
165
166 QString underscoredModuleAsSymbol = m_module;
167 underscoredModuleAsSymbol.replace(before: QLatin1Char('.'), after: QLatin1Char('_'));
168
169 if (underscoredModuleAsSymbol != moduleAsSymbol
170 || underscoredModuleAsSymbol.isEmpty()
171 || underscoredModuleAsSymbol.front().isDigit()) {
172 qWarning() << m_module << "is an invalid QML module URI. You cannot import this.";
173 }
174
175 const QString functionName = QStringLiteral("qml_register_types_") + moduleAsSymbol;
176 output << uR"(
177#if !defined(QT_STATIC)
178#define Q_QMLTYPE_EXPORT Q_DECL_EXPORT
179#else
180#define Q_QMLTYPE_EXPORT
181#endif
182)"_s;
183
184 if (!m_targetNamespace.isEmpty())
185 output << u"namespace "_s << m_targetNamespace << u" {\n"_s;
186
187 output << u"Q_QMLTYPE_EXPORT void "_s << functionName << u"()\n{"_s;
188 const quint8 majorVersion = m_moduleVersion.majorVersion();
189 const quint8 minorVersion = m_moduleVersion.minorVersion();
190
191 for (const auto &version : m_pastMajorVersions) {
192 output << uR"(
193 qmlRegisterModule("%1", %2, 0);
194 qmlRegisterModule("%1", %2, 254);)"_s.arg(a: m_module)
195 .arg(a: version);
196 }
197
198 if (minorVersion != 0) {
199 output << uR"(
200 qmlRegisterModule("%1", %2, 0);)"_s.arg(a: m_module)
201 .arg(a: majorVersion);
202 }
203
204 QVector<QString> typesRegisteredAnonymously;
205 QHash<QString, QList<ExclusiveVersionRange>> qmlElementInfos;
206
207 for (const QJsonObject &classDef : m_types) {
208 const QString className = classDef[QLatin1String("qualifiedClassName")].toString();
209
210 QString targetName = className;
211 QString extendedName;
212 bool seenQmlElement = false;
213 QString qmlElementName;
214 QTypeRevision addedIn;
215 QTypeRevision removedIn;
216
217 const QJsonArray classInfos = classDef.value(key: QLatin1String("classInfos")).toArray();
218 for (const QJsonValueConstRef v : classInfos) {
219 const QString name = v[QStringLiteral("name")].toString();
220 if (name == QStringLiteral("QML.Element")) {
221 seenQmlElement = true;
222 qmlElementName = v[QStringLiteral("value")].toString();
223 } else if (name == QStringLiteral("QML.Foreign"))
224 targetName = v[QLatin1String("value")].toString();
225 else if (name == QStringLiteral("QML.Extended"))
226 extendedName = v[QStringLiteral("value")].toString();
227 else if (name == QStringLiteral("QML.AddedInVersion")) {
228 int version = v[QStringLiteral("value")].toString().toInt();
229 addedIn = QTypeRevision::fromEncodedVersion(value: version);
230 } else if (name == QStringLiteral("QML.RemovedInVersion")) {
231 int version = v[QStringLiteral("value")].toString().toInt();
232 removedIn = QTypeRevision::fromEncodedVersion(value: version);
233 }
234 }
235
236 if (seenQmlElement && qmlElementName != u"anonymous") {
237 if (qmlElementName == u"auto")
238 qmlElementName = className;
239 qmlElementInfos[qmlElementName].append(t: { .claimerName: className, .addedIn: addedIn, .removedIn: removedIn });
240 }
241
242 // We want all related metatypes to be registered by name, so that we can look them up
243 // without including the C++ headers. That's the reason for the QMetaType(foo).id() calls.
244
245 if (classDef.value(key: QLatin1String("namespace")).toBool()) {
246 // We need to figure out if the _target_ is a namespace. If not, it already has a
247 // QMetaType and we don't need to generate one.
248
249 QString targetTypeName = targetName;
250 const auto targetIsNamespace = [&]() {
251 if (className == targetName)
252 return true;
253
254 const QStringList namespaces = MetaTypesJsonProcessor::namespaces(classDef);
255 const QJsonObject *target = QmlTypesClassDescription::findType(
256 types: m_types, foreign: m_foreignTypes, name: targetName, namespaces);
257
258 if (!target)
259 return false;
260
261 if (target->value(QStringLiteral("namespace")).toBool())
262 return true;
263
264 if (target->value(QStringLiteral("object")).toBool())
265 targetTypeName += QStringLiteral(" *");
266
267 return false;
268 };
269
270 if (targetIsNamespace()) {
271 output << uR"(
272 {
273 Q_CONSTINIT static auto metaType = QQmlPrivate::metaTypeForNamespace(
274 [](const QtPrivate::QMetaTypeInterface *) {return &%1::staticMetaObject;},
275 "%2");
276 QMetaType(&metaType).id();
277 })"_s.arg(args&: targetName, args&: targetTypeName);
278 } else {
279 output << u"\n QMetaType::fromType<%1>().id();"_s.arg(a: targetTypeName);
280 }
281
282 auto metaObjectPointer = [](const QString &name) -> QString {
283 return u'&' + name + QStringLiteral("::staticMetaObject");
284 };
285
286 if (seenQmlElement) {
287 output << uR"(
288 qmlRegisterNamespaceAndRevisions(%1, "%2", %3, nullptr, %4, %5);)"_s
289 .arg(args: metaObjectPointer(targetName), args&: m_module)
290 .arg(a: majorVersion)
291 .arg(args: metaObjectPointer(className),
292 args: extendedName.isEmpty() ? QStringLiteral("nullptr")
293 : metaObjectPointer(extendedName));
294 }
295 } else {
296 if (seenQmlElement) {
297 auto checkRevisions = [&](const QJsonArray &array, const QString &type) {
298 for (auto it = array.constBegin(); it != array.constEnd(); ++it) {
299 auto object = it->toObject();
300 if (!object.contains(key: QLatin1String("revision")))
301 continue;
302
303 QTypeRevision revision = QTypeRevision::fromEncodedVersion(value: object[QLatin1String("revision")].toInt());
304 if (m_moduleVersion < revision) {
305 qWarning().noquote()
306 << "Warning:" << className << "is trying to register" << type
307 << object[QStringLiteral("name")].toString()
308 << "with future version" << revision
309 << "when module version is only" << m_moduleVersion;
310 }
311 }
312 };
313
314 const QJsonArray methods = classDef[QLatin1String("methods")].toArray();
315 const QJsonArray properties = classDef[QLatin1String("properties")].toArray();
316
317 if (m_moduleVersion.isValid()) {
318 checkRevisions(properties, QLatin1String("property"));
319 checkRevisions(methods, QLatin1String("method"));
320 }
321
322 output << uR"(
323 qmlRegisterTypesAndRevisions<%1>("%2", %3);)"_s.arg(args: className, args&: m_module)
324 .arg(a: majorVersion);
325
326 const QJsonValue superClasses = classDef[QLatin1String("superClasses")];
327
328 if (superClasses.isArray()) {
329 for (const QJsonValueRef object : superClasses.toArray()) {
330 if (object[QStringLiteral("access")] != QStringLiteral("public"))
331 continue;
332
333 QString superClassName = object[QStringLiteral("name")].toString();
334
335 QVector<QString> classesToCheck;
336
337 auto checkForRevisions = [&](const QString &typeName) -> void {
338 auto type = findType(name: typeName);
339
340 if (!type.isObject()) {
341 type = findTypeForeign(name: typeName);
342 if (!type.isObject())
343 return;
344
345 for (const QString &section :
346 { QStringLiteral("properties"), QStringLiteral("signals"),
347 QStringLiteral("methods") }) {
348 bool foundRevisionEntry = false;
349 for (const QJsonValueRef entry : type[section].toArray()) {
350 if (entry.toObject().contains(QStringLiteral("revision"))) {
351 foundRevisionEntry = true;
352 break;
353 }
354 }
355 if (foundRevisionEntry) {
356 if (typesRegisteredAnonymously.contains(str: typeName))
357 break;
358
359 typesRegisteredAnonymously.append(t: typeName);
360
361 if (m_followForeignVersioning) {
362 output << uR"(
363 qmlRegisterAnonymousTypesAndRevisions<%1>("%2", %3);)"_s.arg(args: typeName, args&: m_module)
364 .arg(a: majorVersion);
365 break;
366 }
367
368 for (const auto &version : m_pastMajorVersions
369 + decltype(m_pastMajorVersions){
370 majorVersion }) {
371 output << uR"(
372 qmlRegisterAnonymousType<%1, 254>("%2", %3);)"_s.arg(args: typeName, args&: m_module)
373 .arg(a: version);
374 }
375 break;
376 }
377 }
378 }
379
380 const QJsonValue superClasses = type[QLatin1String("superClasses")];
381
382 if (superClasses.isArray()) {
383 for (const QJsonValueRef object : superClasses.toArray()) {
384 if (object[QStringLiteral("access")]
385 != QStringLiteral("public"))
386 continue;
387 classesToCheck << object[QStringLiteral("name")].toString();
388 }
389 }
390 };
391
392 checkForRevisions(superClassName);
393
394 while (!classesToCheck.isEmpty())
395 checkForRevisions(classesToCheck.takeFirst());
396 }
397 }
398 } else {
399 output << uR"(
400 QMetaType::fromType<%1%2>().id();)"_s.arg(
401 args: className, args: classDef.value(key: QLatin1String("object")).toBool() ? u" *" : u"");
402 }
403 }
404 }
405
406 for (const auto [qmlName, exportsForSameQmlName] : qmlElementInfos.asKeyValueRange()) {
407 // needs a least two cpp classes exporting the same qml element to potentially have a
408 // conflict
409 if (exportsForSameQmlName.size() < 2)
410 continue;
411
412 // sort exports by versions to find conflicting exports
413 std::sort(first: exportsForSameQmlName.begin(), last: exportsForSameQmlName.end());
414 auto conflictingExportStartIt = exportsForSameQmlName.cbegin();
415 while (1) {
416 // conflicting versions evaluate to true under operator==
417 conflictingExportStartIt =
418 std::adjacent_find(first: conflictingExportStartIt, last: exportsForSameQmlName.cend());
419 if (conflictingExportStartIt == exportsForSameQmlName.cend())
420 break;
421
422 auto conflictingExportEndIt = std::find_if_not(
423 first: conflictingExportStartIt, last: exportsForSameQmlName.cend(),
424 pred: [=](const auto &x) -> bool { return x == *conflictingExportStartIt; });
425 QString registeringCppClasses = conflictingExportStartIt->claimerName;
426 std::for_each(first: std::next(x: conflictingExportStartIt), last: conflictingExportEndIt,
427 f: [&](const auto &q) {
428 registeringCppClasses += u", %1"_s.arg(conflictingVersionToString(q));
429 });
430 qWarning().noquote() << "Warning:" << qmlName
431 << "was registered multiple times by following Cpp classes: "
432 << registeringCppClasses;
433 conflictingExportStartIt = conflictingExportEndIt;
434 }
435 }
436 output << uR"(
437 qmlRegisterModule("%1", %2, %3);
438}
439
440static const QQmlModuleRegistration registration("%1", %4);
441)"_s.arg(a: m_module)
442 .arg(a: majorVersion)
443 .arg(a: minorVersion)
444 .arg(a: functionName);
445
446 if (!m_targetNamespace.isEmpty())
447 output << u"} // namespace %1\n"_s.arg(a: m_targetNamespace);
448}
449
450bool QmlTypeRegistrar::generatePluginTypes(const QString &pluginTypesFile)
451{
452 QmlTypesCreator creator;
453 creator.setOwnTypes(m_types);
454 creator.setForeignTypes(m_foreignTypes);
455 creator.setReferencedTypes(m_referencedTypes);
456 creator.setModule(m_module);
457 creator.setVersion(QTypeRevision::fromVersion(majorVersion: m_moduleVersion.majorVersion(), minorVersion: 0));
458
459 return creator.generate(outFileName: pluginTypesFile);
460}
461
462void QmlTypeRegistrar::setModuleNameAndNamespace(const QString &module,
463 const QString &targetNamespace)
464{
465 m_module = module;
466 m_targetNamespace = targetNamespace;
467}
468void QmlTypeRegistrar::setModuleVersions(QTypeRevision moduleVersion,
469 const QList<quint8> &pastMajorVersions,
470 bool followForeignVersioning)
471{
472 m_moduleVersion = moduleVersion;
473 m_pastMajorVersions = pastMajorVersions;
474 m_followForeignVersioning = followForeignVersioning;
475}
476void QmlTypeRegistrar::setIncludes(const QList<QString> &includes)
477{
478 m_includes = includes;
479}
480void QmlTypeRegistrar::setTypes(const QVector<QJsonObject> &types,
481 const QVector<QJsonObject> &foreignTypes)
482{
483 m_types = types;
484 m_foreignTypes = foreignTypes;
485}
486void QmlTypeRegistrar::setReferencedTypes(const QStringList &referencedTypes)
487{
488 m_referencedTypes = referencedTypes;
489}
490
491QT_END_NAMESPACE
492

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