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
4#include <QFile>
5#include <QCborArray>
6#include <QCborValue>
7
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
30
31/*!
32 * \brief True if x was removed before y was introduced.
33 * \param o
34 * \return
35 */
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 */
50{
51 return !(x < y) && !(y < x);
52}
53
54bool QmlTypeRegistrar::argumentsFromCommandLineAndFile(QStringList &allArguments,
55 const QStringList &arguments)
56{
57 allArguments.reserve(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(QLatin1Char('@'))) {
61 QString optionsFile = argument;
62 optionsFile.remove(0, 1);
63 if (optionsFile.isEmpty()) {
64 warning(optionsFile) << "The @ option requires an input file";
65 return false;
66 }
67 QFile f(optionsFile);
68 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
69 warning(optionsFile) << "Cannot open options file specified with @";
70 return false;
71 }
72 while (!f.atEnd()) {
73 QString line = QString::fromLocal8Bit(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(baseName) << "No types to register found in library";
89 return EXIT_FAILURE;
90 }
91 QFile headerFile(baseName + u".h");
92 bool ok = headerFile.open(QFile::WriteOnly);
93 if (!ok) {
94 error(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(nonAlNum, QLatin1String("_"));
101
102 auto prefix = QString::fromLatin1(
103 "#ifndef %1_H\n"
104 "#define %1_H\n"
105 "#include <QtQml/qqml.h>\n"
106 "#include <QtQml/qqmlmoduleregistration.h>\n").arg(includeGuard);
107 auto postfix = QString::fromLatin1("\n#endif // %1_H\n").arg(includeGuard);
108
109 const QList<QString> includes = processor.includes();
110 for (const QString &include: includes)
111 prefix += u"\n#include <%1>"_s.arg(include);
112 if (!nameSpace.isEmpty()) {
113 prefix += u"\nnamespace %1 {"_s.arg(nameSpace);
114 postfix.prepend(u"\n} // namespace %1"_s.arg(nameSpace));
115 }
116
117 headerFile.write((prefix + processor.extractRegisteredTypes() + postfix).toUtf8());
118
119 QFile sourceFile(baseName + u".cpp");
120 ok = sourceFile.open(QFile::WriteOnly);
121 if (!ok) {
122 error(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(baseName);
127 code += uR"("moc_%1.cpp")"_s.arg(baseName);
128 sourceFile.write(code.toUtf8());
129 sourceFile.write("\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
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(r.addedIn.majorVersion()).arg(r.addedIn.minorVersion());
160 }
161 if (r.removedIn.isValid()) {
162 s += u" (removed in %1.%2)"_s.arg(r.removedIn.majorVersion())
163 .arg(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(0).toLower();
175 result.erase(std::remove_if(result.begin(), result.end(), specialCharPred), 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(include);
194 output << u"\n# include <%1>"_s.arg(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(nonAlnumRegexp, QStringLiteral("_"));
204
205 QString underscoredModuleAsSymbol = m_module;
206 underscoredModuleAsSymbol.replace(QLatin1Char('.'), QLatin1Char('_'));
207
208 if (underscoredModuleAsSymbol != moduleAsSymbol
209 || underscoredModuleAsSymbol.isEmpty()
210 || underscoredModuleAsSymbol.front().isDigit()) {
211 warning(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(m_module)
234 .arg(version);
235 }
236
237 if (minorVersion != 0) {
238 output << uR"(
239 qmlRegisterModule("%1", %2, 0);)"_s.arg(m_module)
240 .arg(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(typeName))
261 return true;
262
263 typesRegisteredAnonymously.append(typeName);
264
265 if (m_followForeignVersioning) {
266 output << uR"(
267 qmlRegisterAnonymousTypesAndRevisions<%1>("%2", %3);)"_s.arg(typeName.toString(),m_module)
268 .arg(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(typeName.toString(),m_module)
276 .arg(version);
277 }
278
279 return true;
280 };
281
282
283 QHash<QString, QList<ExclusiveVersionRange>> qmlElementInfos;
284
285 for (const MetaType &classDef : std::as_const(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(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(v.value);
315 addedIn = QTypeRevision::fromEncodedVersion(version);
316 addedIn = handleInMinorVersion(addedIn, majorVersion);
317 } else if (name == S_REMOVED_IN_VERSION) {
318 int version = toInt(v.value);
319 removedIn = QTypeRevision::fromEncodedVersion(version);
320 removedIn = handleInMinorVersion(removedIn, majorVersion);
321 }
322 }
323
324 for (QString qmlElementName : std::as_const(qmlElementNames)) {
325 if (qmlElementName == S_ANONYMOUS)
326 continue;
327 if (qmlElementName == S_AUTO)
328 qmlElementName = className;
329 qmlElementInfos[qmlElementName].append({
330 classDef.inputFile(),
331 className,
332 addedIn,
333 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 m_types, m_foreignTypes, 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(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(targetName,targetTypeName);
374 } else {
375 Q_ASSERT(!targetTypeName.isEmpty());
376 output << u"\n QMetaType::fromType<%1>().id();"_s.arg(targetTypeName);
377 }
378
379 auto metaObjectPointer = [](QAnyStringView name) -> QString {
380 QString result;
381 const QLatin1StringView staticMetaObject = "::staticMetaObject"_L1;
382 result.reserve(1 + name.length() + staticMetaObject.length());
383 result.append('&'_L1);
384 name.visit([&](auto view) { result.append(view); });
385 result.append(staticMetaObject);
386 return result;
387 };
388
389 if (!qmlElementNames.isEmpty()) {
390 output << uR"(
391 qmlRegisterNamespaceAndRevisions(%1, "%2", %3, nullptr, %4, %5);)"_s
392 .arg(metaObjectPointer(targetName), m_module)
393 .arg(majorVersion)
394 .arg(metaObjectPointer(className),
395 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(className,m_module).arg(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(typeName);
439
440 if (typeAsMap.isEmpty()) {
441 typeAsMap = findTypeForeign(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
469 // Since we don't have a QML name for this type, it can't refer to another type.
470 Q_ASSERT(className == targetName);
471
472 output << uR"(
473 QMetaType::fromType<%1%2>().id();)"_s.arg(
474 className, classDef.kind() == MetaType::Kind::Object ? u" *" : u"");
475 }
476 }
477
478 const auto enums = target.native.enums();
479 for (const auto &enumerator : enums) {
480 output << uR"(
481 qmlRegisterEnum<%1::%2>("%1::%2");)"_s.arg(
482 targetName, enumerator.name.toString());
483 if (!enumerator.alias.isEmpty()) {
484 output << uR"(
485 qmlRegisterEnum<%1::%2>("%1::%2");)"_s.arg(
486 targetName, enumerator.alias.toString());
487 }
488 }
489 }
490
491 for (const auto [qmlName, exportsForSameQmlName] : qmlElementInfos.asKeyValueRange()) {
492 // needs a least two cpp classes exporting the same qml element to potentially have a
493 // conflict
494 if (exportsForSameQmlName.size() < 2)
495 continue;
496
497 // sort exports by versions to find conflicting exports
498 std::sort(exportsForSameQmlName.begin(), exportsForSameQmlName.end());
499 auto conflictingExportStartIt = exportsForSameQmlName.cbegin();
500 while (1) {
501 // conflicting versions evaluate to true under operator==
502 conflictingExportStartIt =
503 std::adjacent_find(conflictingExportStartIt, exportsForSameQmlName.cend());
504 if (conflictingExportStartIt == exportsForSameQmlName.cend())
505 break;
506
507 auto conflictingExportEndIt = std::find_if_not(
508 conflictingExportStartIt, exportsForSameQmlName.cend(),
509 [=](const auto &x) -> bool { return x == *conflictingExportStartIt; });
510 QString registeringCppClasses = conflictingExportStartIt->claimerName;
511 std::for_each(std::next(conflictingExportStartIt), conflictingExportEndIt,
512 [&](const auto &q) {
513 registeringCppClasses += u", %1"_s.arg(conflictingVersionToString(q));
514 });
515 warning(conflictingExportStartIt->fileName)
516 << qmlName << "is registered multiple times by the following C++ classes:"
517 << registeringCppClasses;
518 conflictingExportStartIt = conflictingExportEndIt;
519 }
520 }
521
522 output << uR"(
523 QT_WARNING_POP
524 qmlRegisterModule("%1", %2, %3);
525}
526
527static const QQmlModuleRegistration %5("%1", %4);
528)"_s.arg(m_module)
529 .arg(majorVersion)
530 .arg(minorVersion)
531 .arg(functionName, registrationVarName(m_module));
532
533 if (!m_targetNamespace.isEmpty())
534 output << u"} // namespace %1\n"_s.arg(m_targetNamespace);
535}
536
537bool QmlTypeRegistrar::generatePluginTypes(const QString &pluginTypesFile, bool generatingJSRoot)
538{
539 QmlTypesCreator creator;
540 creator.setOwnTypes(m_types);
541 creator.setForeignTypes(m_foreignTypes);
542 creator.setReferencedTypes(m_referencedTypes);
543 creator.setUsingDeclarations(m_usingDeclarations);
544 creator.setModule(m_module.toUtf8());
545 creator.setVersion(QTypeRevision::fromVersion(m_moduleVersion.majorVersion(), 0));
546 creator.setGeneratingJSRoot(generatingJSRoot);
547
548 return creator.generate(pluginTypesFile);
549}
550
551void QmlTypeRegistrar::setModuleNameAndNamespace(const QString &module,
552 const QString &targetNamespace)
553{
554 m_module = module;
555 m_targetNamespace = targetNamespace;
556}
557void QmlTypeRegistrar::setModuleVersions(QTypeRevision moduleVersion,
558 const QList<quint8> &pastMajorVersions,
559 bool followForeignVersioning)
560{
561 m_moduleVersion = moduleVersion;
562 m_pastMajorVersions = pastMajorVersions;
563 m_followForeignVersioning = followForeignVersioning;
564}
565void QmlTypeRegistrar::setIncludes(const QList<QString> &includes)
566{
567 m_includes = includes;
568}
570 const QVector<MetaType> &types, const QVector<MetaType> &foreignTypes)
571{
572 m_types = types;
573 m_foreignTypes = foreignTypes;
574}
575void QmlTypeRegistrar::setReferencedTypes(const QList<QAnyStringView> &referencedTypes)
576{
577 m_referencedTypes = referencedTypes;
578}
579
580void QmlTypeRegistrar::setUsingDeclarations(const QList<UsingDeclaration> &usingDeclarations)
581{
582 m_usingDeclarations = usingDeclarations;
583}
584
585QT_END_NAMESPACE
MetaType()=default
bool generatePluginTypes(const QString &pluginTypesFile, bool generatingJSRoot=false)
void setModuleNameAndNamespace(const QString &module, const QString &targetNamespace)
void setTypes(const QVector< MetaType > &types, const QVector< MetaType > &foreignTypes)
void setIncludes(const QList< QString > &includes)
void write(QTextStream &os, QAnyStringView outFileName) const
void setModuleVersions(QTypeRevision moduleVersion, const QList< quint8 > &pastMajorVersions, bool followForeignVersioning)
void setUsingDeclarations(const QList< UsingDeclaration > &usingDeclarations)
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.