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 <QCborArray>
6#include <QCborValue>
7
8#include "qqmltyperegistrar_p.h"
9#include "qqmltypescreator_p.h"
10#include "qanystringviewutils_p.h"
11#include "qqmltyperegistrarconstants_p.h"
12#include "qqmltyperegistrarutils_p.h"
13
14#include <algorithm>
15
16QT_BEGIN_NAMESPACE
17using namespace Qt::Literals;
18using namespace Constants;
19using namespace Constants::MetatypesDotJson;
20using namespace Constants::MetatypesDotJson::Qml;
21using namespace QAnyStringViewUtils;
22
23struct ExclusiveVersionRange
24{
25 QAnyStringView fileName;
26 QString claimerName;
27 QTypeRevision addedIn;
28 QTypeRevision removedIn;
29};
30
31/*!
32 * \brief True if x was removed before y was introduced.
33 * \param o
34 * \return
35 */
36bool operator<(const ExclusiveVersionRange &x, const ExclusiveVersionRange &y)
37{
38 if (x.removedIn.isValid())
39 return y.addedIn.isValid() ? x.removedIn <= y.addedIn : true;
40 else
41 return false;
42}
43
44/*!
45 * \brief True when x and y share a common version. (Warning: not transitive!)
46 * \param o
47 * \return
48 */
49bool operator==(const ExclusiveVersionRange &x, const ExclusiveVersionRange &y)
50{
51 return !(x < y) && !(y < x);
52}
53
54bool QmlTypeRegistrar::argumentsFromCommandLineAndFile(QStringList &allArguments,
55 const QStringList &arguments)
56{
57 allArguments.reserve(asize: arguments.size());
58 for (const QString &argument : arguments) {
59 // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
60 if (argument.startsWith(c: QLatin1Char('@'))) {
61 QString optionsFile = argument;
62 optionsFile.remove(i: 0, len: 1);
63 if (optionsFile.isEmpty()) {
64 warning(fileName: optionsFile) << "The @ option requires an input file";
65 return false;
66 }
67 QFile f(optionsFile);
68 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
69 warning(fileName: optionsFile) << "Cannot open options file specified with @";
70 return false;
71 }
72 while (!f.atEnd()) {
73 QString line = QString::fromLocal8Bit(ba: f.readLine().trimmed());
74 if (!line.isEmpty())
75 allArguments << line;
76 }
77 } else {
78 allArguments << argument;
79 }
80 }
81 return true;
82}
83
84int QmlTypeRegistrar::runExtract(
85 const QString &baseName, const QString &nameSpace, const MetaTypesJsonProcessor &processor)
86{
87 if (processor.types().isEmpty()) {
88 error(fileName: baseName) << "No types to register found in library";
89 return EXIT_FAILURE;
90 }
91 QFile headerFile(baseName + u".h");
92 bool ok = headerFile.open(flags: QFile::WriteOnly);
93 if (!ok) {
94 error(fileName: headerFile.fileName()) << "Cannot open header file for writing";
95 return EXIT_FAILURE;
96 }
97
98 QString includeGuard = baseName;
99 static const QRegularExpression nonAlNum(QLatin1String("[^a-zA-Z0-9_]"));
100 includeGuard.replace(re: nonAlNum, after: QLatin1String("_"));
101
102 auto prefix = QString::fromLatin1(
103 ba: "#ifndef %1_H\n"
104 "#define %1_H\n"
105 "#include <QtQml/qqml.h>\n"
106 "#include <QtQml/qqmlmoduleregistration.h>\n").arg(a: includeGuard);
107 auto postfix = QString::fromLatin1(ba: "\n#endif // %1_H\n").arg(a: includeGuard);
108
109 const QList<QString> includes = processor.includes();
110 for (const QString &include: includes)
111 prefix += u"\n#include <%1>"_s.arg(a: include);
112 if (!nameSpace.isEmpty()) {
113 prefix += u"\nnamespace %1 {"_s.arg(a: nameSpace);
114 postfix.prepend(s: u"\n} // namespace %1"_s.arg(a: nameSpace));
115 }
116
117 headerFile.write(data: (prefix + processor.extractRegisteredTypes() + postfix).toUtf8());
118
119 QFile sourceFile(baseName + u".cpp");
120 ok = sourceFile.open(flags: QFile::WriteOnly);
121 if (!ok) {
122 error(fileName: sourceFile.fileName()) << "Cannot open implementation file for writing";
123 return EXIT_FAILURE;
124 }
125 // the string split is necessaury because cmake's automoc scanner would otherwise pick up the include
126 QString code = u"#include \"%1.h\"\n#include "_s.arg(a: baseName);
127 code += uR"("moc_%1.cpp")"_s.arg(a: baseName);
128 sourceFile.write(data: code.toUtf8());
129 sourceFile.write(data: "\n");
130 return EXIT_SUCCESS;
131}
132
133MetaType QmlTypeRegistrar::findType(QAnyStringView name) const
134{
135 for (const MetaType &type : m_types) {
136 if (type.qualifiedClassName() != name)
137 continue;
138 return type;
139 }
140 return MetaType();
141};
142
143MetaType QmlTypeRegistrar::findTypeForeign(QAnyStringView name) const
144{
145 for (const MetaType &type : m_foreignTypes) {
146 if (type.qualifiedClassName() != name)
147 continue;
148 return type;
149 }
150 return MetaType();
151};
152
153QString conflictingVersionToString(const ExclusiveVersionRange &r)
154{
155 using namespace Qt::StringLiterals;
156
157 QString s = r.claimerName;
158 if (r.addedIn.isValid()) {
159 s += u" (added in %1.%2)"_s.arg(a: r.addedIn.majorVersion()).arg(a: r.addedIn.minorVersion());
160 }
161 if (r.removedIn.isValid()) {
162 s += u" (removed in %1.%2)"_s.arg(a: r.removedIn.majorVersion())
163 .arg(a: r.removedIn.minorVersion());
164 }
165 return s;
166};
167
168// Return a name for the registration variable containing the module to
169// avoid clashes in Unity builds.
170static QString registrationVarName(const QString &module)
171{
172 auto specialCharPred = [](QChar c) { return !c.isLetterOrNumber(); };
173 QString result = module;
174 result[0] = result.at(i: 0).toLower();
175 result.erase(first: std::remove_if(first: result.begin(), last: result.end(), pred: specialCharPred), last: result.end());
176 return result + "Registration"_L1;
177}
178
179void QmlTypeRegistrar::write(QTextStream &output, QAnyStringView outFileName) const
180{
181 output << uR"(/****************************************************************************
182** Generated QML type registration code
183**
184** WARNING! All changes made in this file will be lost!
185*****************************************************************************/
186
187)"_s;
188
189 output << u"#include <QtQml/qqml.h>\n"_s;
190 output << u"#include <QtQml/qqmlmoduleregistration.h>\n"_s;
191
192 for (const QString &include : m_includes) {
193 output << u"\n#if __has_include(<%1>)"_s.arg(a: include);
194 output << u"\n# include <%1>"_s.arg(a: include);
195 output << u"\n#endif"_s;
196 }
197
198 output << u"\n\n"_s;
199
200 // Keep this in sync with _qt_internal_get_escaped_uri in CMake
201 QString moduleAsSymbol = m_module;
202 static const QRegularExpression nonAlnumRegexp(QLatin1String("[^A-Za-z0-9]"));
203 moduleAsSymbol.replace(re: nonAlnumRegexp, QStringLiteral("_"));
204
205 QString underscoredModuleAsSymbol = m_module;
206 underscoredModuleAsSymbol.replace(before: QLatin1Char('.'), after: QLatin1Char('_'));
207
208 if (underscoredModuleAsSymbol != moduleAsSymbol
209 || underscoredModuleAsSymbol.isEmpty()
210 || underscoredModuleAsSymbol.front().isDigit()) {
211 warning(fileName: outFileName) << m_module << "is an invalid QML module URI. You cannot import this.";
212 }
213
214 const QString functionName = QStringLiteral("qml_register_types_") + moduleAsSymbol;
215 output << uR"(
216#if !defined(QT_STATIC)
217#define Q_QMLTYPE_EXPORT Q_DECL_EXPORT
218#else
219#define Q_QMLTYPE_EXPORT
220#endif
221)"_s;
222
223 if (!m_targetNamespace.isEmpty())
224 output << u"namespace "_s << m_targetNamespace << u" {\n"_s;
225
226 output << u"Q_QMLTYPE_EXPORT void "_s << functionName << u"()\n{"_s;
227 const quint8 majorVersion = m_moduleVersion.majorVersion();
228 const quint8 minorVersion = m_moduleVersion.minorVersion();
229
230 for (const auto &version : m_pastMajorVersions) {
231 output << uR"(
232 qmlRegisterModule("%1", %2, 0);
233 qmlRegisterModule("%1", %2, 254);)"_s.arg(a: m_module)
234 .arg(a: version);
235 }
236
237 if (minorVersion != 0) {
238 output << uR"(
239 qmlRegisterModule("%1", %2, 0);)"_s.arg(a: m_module)
240 .arg(a: majorVersion);
241 }
242
243 output << uR"(
244 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED)"_s;
245
246 QVector<QAnyStringView> typesRegisteredAnonymously;
247
248 const auto fillTypesRegisteredAnonymously = [&](const auto &members, QAnyStringView typeName) {
249 bool foundRevisionEntry = false;
250 for (const auto &entry : members) {
251 if (entry.revision.isValid()) {
252 foundRevisionEntry = true;
253 break;
254 }
255 }
256
257 if (!foundRevisionEntry)
258 return false;
259
260 if (typesRegisteredAnonymously.contains(t: typeName))
261 return true;
262
263 typesRegisteredAnonymously.append(t: typeName);
264
265 if (m_followForeignVersioning) {
266 output << uR"(
267 qmlRegisterAnonymousTypesAndRevisions<%1>("%2", %3);)"_s.arg(args: typeName.toString(), args: m_module)
268 .arg(a: majorVersion);
269 return true;
270 }
271
272 for (const auto &version
273 : m_pastMajorVersions + decltype(m_pastMajorVersions){ majorVersion }) {
274 output << uR"(
275 qmlRegisterAnonymousType<%1, 254>("%2", %3);)"_s.arg(args: typeName.toString(), args: m_module)
276 .arg(a: version);
277 }
278
279 return true;
280 };
281
282
283 QHash<QString, QList<ExclusiveVersionRange>> qmlElementInfos;
284
285 for (const MetaType &classDef : std::as_const(t: m_types)) {
286
287 // Do not generate C++ registrations for JavaScript types.
288 if (classDef.inputFile().isEmpty())
289 continue;
290
291 QString className = classDef.qualifiedClassName().toString();
292 QString targetName = className;
293
294 // If either the foreign or the local part is a namespace we need to
295 // generate a namespace registration.
296 bool targetIsNamespace = classDef.kind() == MetaType::Kind::Namespace;
297
298 QAnyStringView extendedName;
299 QList<QString> qmlElementNames;
300 QTypeRevision addedIn;
301 QTypeRevision removedIn;
302
303 for (const ClassInfo &v : classDef.classInfos()) {
304 const QAnyStringView name = v.name;
305 if (name == S_ELEMENT) {
306 qmlElementNames.append(t: v.value.toString());
307 } else if (name == S_FOREIGN) {
308 targetName = v.value.toString();
309 } else if (name == S_FOREIGN_IS_NAMESPACE) {
310 targetIsNamespace = targetIsNamespace || (v.value == S_TRUE);
311 } else if (name == S_EXTENDED) {
312 extendedName = v.value;
313 } else if (name == S_ADDED_IN_VERSION) {
314 int version = toInt(string: v.value);
315 addedIn = QTypeRevision::fromEncodedVersion(value: version);
316 addedIn = handleInMinorVersion(revision: addedIn, majorVersion);
317 } else if (name == S_REMOVED_IN_VERSION) {
318 int version = toInt(string: v.value);
319 removedIn = QTypeRevision::fromEncodedVersion(value: version);
320 removedIn = handleInMinorVersion(revision: removedIn, majorVersion);
321 }
322 }
323
324 for (QString qmlElementName : std::as_const(t&: qmlElementNames)) {
325 if (qmlElementName == S_ANONYMOUS)
326 continue;
327 if (qmlElementName == S_AUTO)
328 qmlElementName = className;
329 qmlElementInfos[qmlElementName].append(t: {
330 .fileName: classDef.inputFile(),
331 .claimerName: className,
332 .addedIn: addedIn,
333 .removedIn: removedIn
334 });
335 }
336
337 // We want all related metatypes to be registered by name, so that we can look them up
338 // without including the C++ headers. That's the reason for the QMetaType(foo).id() calls.
339
340 const QList<QAnyStringView> namespaces
341 = MetaTypesJsonProcessor::namespaces(classDef);
342
343 const FoundType target = QmlTypesClassDescription::findType(
344 types: m_types, foreign: m_foreignTypes, name: targetName, namespaces);
345
346 // The target may be in a namespace we've resolved already.
347 // Update the targetName accordingly.
348 if (!target.native.isEmpty())
349 targetName = target.native.qualifiedClassName().toString();
350
351 if (targetIsNamespace) {
352 // We need to figure out if the _target_ is a namespace. If not, it already has a
353 // QMetaType and we don't need to generate one.
354
355 QString targetTypeName = targetName;
356
357 if (!target.javaScript.isEmpty() && target.native.isEmpty())
358 warning(classDef: target.javaScript) << "JavaScript type cannot be used as namespace";
359
360 if (target.native.kind() == MetaType::Kind::Object)
361 targetTypeName += " *"_L1;
362
363 // If there is no foreign type, the local one is a namespace.
364 // Otherwise, only do metaTypeForNamespace if the target _metaobject_ is a namespace.
365 // Not if we merely consider it to be a namespace for QML purposes.
366 if (className == targetName || target.native.kind() == MetaType::Kind::Namespace) {
367 output << uR"(
368 {
369 Q_CONSTINIT static auto metaType = QQmlPrivate::metaTypeForNamespace(
370 [](const QtPrivate::QMetaTypeInterface *) {return &%1::staticMetaObject;},
371 "%2");
372 QMetaType(&metaType).id();
373 })"_s.arg(args&: targetName, args&: targetTypeName);
374 } else {
375 Q_ASSERT(!targetTypeName.isEmpty());
376 output << u"\n QMetaType::fromType<%1>().id();"_s.arg(a: targetTypeName);
377 }
378
379 auto metaObjectPointer = [](QAnyStringView name) -> QString {
380 QString result;
381 const QLatin1StringView staticMetaObject = "::staticMetaObject"_L1;
382 result.reserve(asize: 1 + name.length() + staticMetaObject.length());
383 result.append(c: '&'_L1);
384 name.visit(v: [&](auto view) { result.append(view); });
385 result.append(s: staticMetaObject);
386 return result;
387 };
388
389 if (!qmlElementNames.isEmpty()) {
390 output << uR"(
391 qmlRegisterNamespaceAndRevisions(%1, "%2", %3, nullptr, %4, %5);)"_s
392 .arg(args: metaObjectPointer(targetName), args: m_module)
393 .arg(a: majorVersion)
394 .arg(args: metaObjectPointer(className),
395 args: extendedName.isEmpty() ? QStringLiteral("nullptr")
396 : metaObjectPointer(extendedName));
397 }
398 } else {
399 if (!qmlElementNames.isEmpty()) {
400 auto checkRevisions = [&](const auto &array, QLatin1StringView type) {
401 for (auto it = array.begin(); it != array.end(); ++it) {
402 if (!it->revision.isValid())
403 continue;
404
405 QTypeRevision revision = it->revision;
406 if (m_moduleVersion < revision) {
407 warning(classDef)
408 << className << "is trying to register" << type
409 << it->name
410 << "with future version" << revision
411 << "when module version is only" << m_moduleVersion;
412 }
413 }
414 };
415
416 const Method::Container methods = classDef.methods();
417 const Property::Container properties = classDef.properties();
418
419 if (m_moduleVersion.isValid()) {
420 checkRevisions(properties, S_PROPERTY);
421 checkRevisions(methods, S_METHOD);
422 }
423
424 output << uR"(
425 qmlRegisterTypesAndRevisions<%1>("%2", %3);)"_s.arg(args&: className, args: m_module).arg(a: majorVersion);
426
427 const BaseType::Container superClasses = classDef.superClasses();
428
429 for (const BaseType &object : classDef.superClasses()) {
430 if (object.access != Access::Public)
431 continue;
432
433 QAnyStringView superClassName = object.name;
434
435 QVector<QAnyStringView> classesToCheck;
436
437 auto checkForRevisions = [&](QAnyStringView typeName) -> void {
438 auto typeAsMap = findType(name: typeName);
439
440 if (typeAsMap.isEmpty()) {
441 typeAsMap = findTypeForeign(name: typeName);
442 if (typeAsMap.isEmpty())
443 return;
444
445 if (!fillTypesRegisteredAnonymously(
446 typeAsMap.properties(), typeName)) {
447 if (!fillTypesRegisteredAnonymously(
448 typeAsMap.sigs(), typeName)) {
449 fillTypesRegisteredAnonymously(
450 typeAsMap.methods(), typeName);
451 }
452 }
453 }
454
455 for (const BaseType &object : typeAsMap.superClasses()) {
456 if (object.access == Access::Public)
457 classesToCheck << object.name;
458 }
459 };
460
461 checkForRevisions(superClassName);
462
463 while (!classesToCheck.isEmpty())
464 checkForRevisions(classesToCheck.takeFirst());
465 }
466 } else {
467 Q_ASSERT(!className.isEmpty());
468 output << uR"(
469 QMetaType::fromType<%1%2>().id();)"_s.arg(
470 args&: className, args: classDef.kind() == MetaType::Kind::Object ? u" *" : u"");
471 }
472 }
473
474 const auto enums = target.native.enums();
475 for (const auto &enumerator : enums) {
476 output << uR"(
477 QMetaType::fromType<%1::%2>().id();)"_s.arg(
478 args&: targetName, args: enumerator.name.toString());
479 if (!enumerator.alias.isEmpty()) {
480 output << uR"(
481 QMetaType::fromType<%1::%2>().id();)"_s.arg(
482 args&: targetName, args: enumerator.alias.toString());
483 }
484 }
485 }
486
487 for (const auto [qmlName, exportsForSameQmlName] : qmlElementInfos.asKeyValueRange()) {
488 // needs a least two cpp classes exporting the same qml element to potentially have a
489 // conflict
490 if (exportsForSameQmlName.size() < 2)
491 continue;
492
493 // sort exports by versions to find conflicting exports
494 std::sort(first: exportsForSameQmlName.begin(), last: exportsForSameQmlName.end());
495 auto conflictingExportStartIt = exportsForSameQmlName.cbegin();
496 while (1) {
497 // conflicting versions evaluate to true under operator==
498 conflictingExportStartIt =
499 std::adjacent_find(first: conflictingExportStartIt, last: exportsForSameQmlName.cend());
500 if (conflictingExportStartIt == exportsForSameQmlName.cend())
501 break;
502
503 auto conflictingExportEndIt = std::find_if_not(
504 first: conflictingExportStartIt, last: exportsForSameQmlName.cend(),
505 pred: [=](const auto &x) -> bool { return x == *conflictingExportStartIt; });
506 QString registeringCppClasses = conflictingExportStartIt->claimerName;
507 std::for_each(first: std::next(x: conflictingExportStartIt), last: conflictingExportEndIt,
508 f: [&](const auto &q) {
509 registeringCppClasses += u", %1"_s.arg(conflictingVersionToString(q));
510 });
511 warning(fileName: conflictingExportStartIt->fileName)
512 << qmlName << "is registered multiple times by the following C++ classes:"
513 << registeringCppClasses;
514 conflictingExportStartIt = conflictingExportEndIt;
515 }
516 }
517
518 output << uR"(
519 QT_WARNING_POP
520 qmlRegisterModule("%1", %2, %3);
521}
522
523static const QQmlModuleRegistration %5("%1", %4);
524)"_s.arg(a: m_module)
525 .arg(a: majorVersion)
526 .arg(a: minorVersion)
527 .arg(args: functionName, args: registrationVarName(module: m_module));
528
529 if (!m_targetNamespace.isEmpty())
530 output << u"} // namespace %1\n"_s.arg(a: m_targetNamespace);
531}
532
533bool QmlTypeRegistrar::generatePluginTypes(const QString &pluginTypesFile, bool generatingJSRoot)
534{
535 QmlTypesCreator creator;
536 creator.setOwnTypes(m_types);
537 creator.setForeignTypes(m_foreignTypes);
538 creator.setReferencedTypes(m_referencedTypes);
539 creator.setUsingDeclarations(m_usingDeclarations);
540 creator.setModule(m_module.toUtf8());
541 creator.setVersion(QTypeRevision::fromVersion(majorVersion: m_moduleVersion.majorVersion(), minorVersion: 0));
542 creator.setGeneratingJSRoot(generatingJSRoot);
543
544 return creator.generate(outFileName: pluginTypesFile);
545}
546
547void QmlTypeRegistrar::setModuleNameAndNamespace(const QString &module,
548 const QString &targetNamespace)
549{
550 m_module = module;
551 m_targetNamespace = targetNamespace;
552}
553void QmlTypeRegistrar::setModuleVersions(QTypeRevision moduleVersion,
554 const QList<quint8> &pastMajorVersions,
555 bool followForeignVersioning)
556{
557 m_moduleVersion = moduleVersion;
558 m_pastMajorVersions = pastMajorVersions;
559 m_followForeignVersioning = followForeignVersioning;
560}
561void QmlTypeRegistrar::setIncludes(const QList<QString> &includes)
562{
563 m_includes = includes;
564}
565void QmlTypeRegistrar::setTypes(
566 const QVector<MetaType> &types, const QVector<MetaType> &foreignTypes)
567{
568 m_types = types;
569 m_foreignTypes = foreignTypes;
570}
571void QmlTypeRegistrar::setReferencedTypes(const QList<QAnyStringView> &referencedTypes)
572{
573 m_referencedTypes = referencedTypes;
574}
575
576void QmlTypeRegistrar::setUsingDeclarations(const QList<UsingDeclaration> &usingDeclarations)
577{
578 m_usingDeclarations = usingDeclarations;
579}
580
581QT_END_NAMESPACE
582

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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