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