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 if (targetIsNamespace) {
347 // We need to figure out if the _target_ is a namespace. If not, it already has a
348 // QMetaType and we don't need to generate one.
349
350 QString targetTypeName = targetName;
351
352 if (!target.javaScript.isEmpty() && target.native.isEmpty())
353 warning(classDef: target.javaScript) << "JavaScript type cannot be used as namespace";
354
355 if (target.native.kind() == MetaType::Kind::Object)
356 targetTypeName += " *"_L1;
357
358 // If there is no foreign type, the local one is a namespace.
359 // Otherwise, only do metaTypeForNamespace if the target _metaobject_ is a namespace.
360 // Not if we merely consider it to be a namespace for QML purposes.
361 if (className == targetName || target.native.kind() == MetaType::Kind::Namespace) {
362 output << uR"(
363 {
364 Q_CONSTINIT static auto metaType = QQmlPrivate::metaTypeForNamespace(
365 [](const QtPrivate::QMetaTypeInterface *) {return &%1::staticMetaObject;},
366 "%2");
367 QMetaType(&metaType).id();
368 })"_s.arg(args&: targetName, args&: targetTypeName);
369 } else {
370 Q_ASSERT(!targetTypeName.isEmpty());
371 output << u"\n QMetaType::fromType<%1>().id();"_s.arg(a: targetTypeName);
372 }
373
374 auto metaObjectPointer = [](QAnyStringView name) -> QString {
375 QString result;
376 const QLatin1StringView staticMetaObject = "::staticMetaObject"_L1;
377 result.reserve(asize: 1 + name.length() + staticMetaObject.length());
378 result.append(c: '&'_L1);
379 name.visit(v: [&](auto view) { result.append(view); });
380 result.append(s: staticMetaObject);
381 return result;
382 };
383
384 if (!qmlElementNames.isEmpty()) {
385 output << uR"(
386 qmlRegisterNamespaceAndRevisions(%1, "%2", %3, nullptr, %4, %5);)"_s
387 .arg(args: metaObjectPointer(targetName), args: m_module)
388 .arg(a: majorVersion)
389 .arg(args: metaObjectPointer(className),
390 args: extendedName.isEmpty() ? QStringLiteral("nullptr")
391 : metaObjectPointer(extendedName));
392 }
393 } else {
394 if (!qmlElementNames.isEmpty()) {
395 auto checkRevisions = [&](const auto &array, QLatin1StringView type) {
396 for (auto it = array.begin(); it != array.end(); ++it) {
397 if (!it->revision.isValid())
398 continue;
399
400 QTypeRevision revision = it->revision;
401 if (m_moduleVersion < revision) {
402 warning(classDef)
403 << className << "is trying to register" << type
404 << it->name
405 << "with future version" << revision
406 << "when module version is only" << m_moduleVersion;
407 }
408 }
409 };
410
411 const Method::Container methods = classDef.methods();
412 const Property::Container properties = classDef.properties();
413
414 if (m_moduleVersion.isValid()) {
415 checkRevisions(properties, S_PROPERTY);
416 checkRevisions(methods, S_METHOD);
417 }
418
419 output << uR"(
420 qmlRegisterTypesAndRevisions<%1>("%2", %3);)"_s.arg(args&: className, args: m_module).arg(a: majorVersion);
421
422 const BaseType::Container superClasses = classDef.superClasses();
423
424 for (const BaseType &object : classDef.superClasses()) {
425 if (object.access != Access::Public)
426 continue;
427
428 QAnyStringView superClassName = object.name;
429
430 QVector<QAnyStringView> classesToCheck;
431
432 auto checkForRevisions = [&](QAnyStringView typeName) -> void {
433 auto typeAsMap = findType(name: typeName);
434
435 if (typeAsMap.isEmpty()) {
436 typeAsMap = findTypeForeign(name: typeName);
437 if (typeAsMap.isEmpty())
438 return;
439
440 if (!fillTypesRegisteredAnonymously(
441 typeAsMap.properties(), typeName)) {
442 if (!fillTypesRegisteredAnonymously(
443 typeAsMap.sigs(), typeName)) {
444 fillTypesRegisteredAnonymously(
445 typeAsMap.methods(), typeName);
446 }
447 }
448 }
449
450 for (const BaseType &object : typeAsMap.superClasses()) {
451 if (object.access == Access::Public)
452 classesToCheck << object.name;
453 }
454 };
455
456 checkForRevisions(superClassName);
457
458 while (!classesToCheck.isEmpty())
459 checkForRevisions(classesToCheck.takeFirst());
460 }
461 } else {
462 Q_ASSERT(!className.isEmpty());
463 output << uR"(
464 QMetaType::fromType<%1%2>().id();)"_s.arg(
465 args&: className, args: classDef.kind() == MetaType::Kind::Object ? u" *" : u"");
466 }
467 }
468
469 const auto enums = target.native.enums();
470 for (const auto &enumerator : enums) {
471 output << uR"(
472 QMetaType::fromType<%1::%2>().id();)"_s.arg(
473 args&: targetName, args: enumerator.name.toString());
474 if (!enumerator.alias.isEmpty()) {
475 output << uR"(
476 QMetaType::fromType<%1::%2>().id();)"_s.arg(
477 args&: targetName, args: enumerator.alias.toString());
478 }
479 }
480 }
481
482 for (const auto [qmlName, exportsForSameQmlName] : qmlElementInfos.asKeyValueRange()) {
483 // needs a least two cpp classes exporting the same qml element to potentially have a
484 // conflict
485 if (exportsForSameQmlName.size() < 2)
486 continue;
487
488 // sort exports by versions to find conflicting exports
489 std::sort(first: exportsForSameQmlName.begin(), last: exportsForSameQmlName.end());
490 auto conflictingExportStartIt = exportsForSameQmlName.cbegin();
491 while (1) {
492 // conflicting versions evaluate to true under operator==
493 conflictingExportStartIt =
494 std::adjacent_find(first: conflictingExportStartIt, last: exportsForSameQmlName.cend());
495 if (conflictingExportStartIt == exportsForSameQmlName.cend())
496 break;
497
498 auto conflictingExportEndIt = std::find_if_not(
499 first: conflictingExportStartIt, last: exportsForSameQmlName.cend(),
500 pred: [=](const auto &x) -> bool { return x == *conflictingExportStartIt; });
501 QString registeringCppClasses = conflictingExportStartIt->claimerName;
502 std::for_each(first: std::next(x: conflictingExportStartIt), last: conflictingExportEndIt,
503 f: [&](const auto &q) {
504 registeringCppClasses += u", %1"_s.arg(conflictingVersionToString(q));
505 });
506 warning(fileName: conflictingExportStartIt->fileName)
507 << qmlName << "is registered multiple times by the following C++ classes:"
508 << registeringCppClasses;
509 conflictingExportStartIt = conflictingExportEndIt;
510 }
511 }
512
513 output << uR"(
514 QT_WARNING_POP
515 qmlRegisterModule("%1", %2, %3);
516}
517
518static const QQmlModuleRegistration %5("%1", %4);
519)"_s.arg(a: m_module)
520 .arg(a: majorVersion)
521 .arg(a: minorVersion)
522 .arg(args: functionName, args: registrationVarName(module: m_module));
523
524 if (!m_targetNamespace.isEmpty())
525 output << u"} // namespace %1\n"_s.arg(a: m_targetNamespace);
526}
527
528bool QmlTypeRegistrar::generatePluginTypes(const QString &pluginTypesFile, bool generatingJSRoot)
529{
530 QmlTypesCreator creator;
531 creator.setOwnTypes(m_types);
532 creator.setForeignTypes(m_foreignTypes);
533 creator.setReferencedTypes(m_referencedTypes);
534 creator.setUsingDeclarations(m_usingDeclarations);
535 creator.setModule(m_module.toUtf8());
536 creator.setVersion(QTypeRevision::fromVersion(majorVersion: m_moduleVersion.majorVersion(), minorVersion: 0));
537 creator.setGeneratingJSRoot(generatingJSRoot);
538
539 return creator.generate(outFileName: pluginTypesFile);
540}
541
542void QmlTypeRegistrar::setModuleNameAndNamespace(const QString &module,
543 const QString &targetNamespace)
544{
545 m_module = module;
546 m_targetNamespace = targetNamespace;
547}
548void QmlTypeRegistrar::setModuleVersions(QTypeRevision moduleVersion,
549 const QList<quint8> &pastMajorVersions,
550 bool followForeignVersioning)
551{
552 m_moduleVersion = moduleVersion;
553 m_pastMajorVersions = pastMajorVersions;
554 m_followForeignVersioning = followForeignVersioning;
555}
556void QmlTypeRegistrar::setIncludes(const QList<QString> &includes)
557{
558 m_includes = includes;
559}
560void QmlTypeRegistrar::setTypes(
561 const QVector<MetaType> &types, const QVector<MetaType> &foreignTypes)
562{
563 m_types = types;
564 m_foreignTypes = foreignTypes;
565}
566void QmlTypeRegistrar::setReferencedTypes(const QList<QAnyStringView> &referencedTypes)
567{
568 m_referencedTypes = referencedTypes;
569}
570
571void QmlTypeRegistrar::setUsingDeclarations(const QList<UsingDeclaration> &usingDeclarations)
572{
573 m_usingDeclarations = usingDeclarations;
574}
575
576QT_END_NAMESPACE
577

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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