Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
17using namespace Qt::Literals;
18using namespace Constants;
19using namespace Constants::MetatypesDotJson;
21using namespace QAnyStringViewUtils;
22
30
37{
38 if (x.removedIn.isValid())
39 return y.addedIn.isValid() ? x.removedIn <= y.addedIn : true;
40 else
41 return false;
42}
43
50{
51 return !(x < y) && !(y < x);
52}
53
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);
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(const QString &baseName, const MetaTypesJsonProcessor &processor)
85{
86 if (processor.types().isEmpty()) {
87 error(baseName) << "No types to register found in library";
88 return EXIT_FAILURE;
89 }
90 QFile headerFile(baseName + u".h");
91 bool ok = headerFile.open(QFile::WriteOnly);
92 if (!ok) {
93 error(headerFile.fileName()) << "Cannot open header file for writing";
94 return EXIT_FAILURE;
95 }
96
97 QString includeGuard = baseName;
98 static const QRegularExpression nonAlNum(QLatin1String("[^a-zA-Z0-9_]"));
99 includeGuard.replace(nonAlNum, QLatin1String("_"));
100
101 auto prefix = QString::fromLatin1(
102 "#ifndef %1_H\n"
103 "#define %1_H\n"
104 "#include <QtQml/qqml.h>\n"
105 "#include <QtQml/qqmlmoduleregistration.h>\n").arg(includeGuard);
106 const QList<QString> includes = processor.includes();
107 for (const QString &include: includes)
108 prefix += u"\n#include <%1>"_s.arg(include);
109 headerFile.write((prefix + processor.extractRegisteredTypes()).toUtf8() + "\n#endif\n");
110
111 QFile sourceFile(baseName + u".cpp");
112 ok = sourceFile.open(QFile::WriteOnly);
113 if (!ok) {
114 error(sourceFile.fileName()) << "Cannot open implementation file for writing";
115 return EXIT_FAILURE;
116 }
117 // the string split is necessaury because cmake's automoc scanner would otherwise pick up the include
118 QString code = u"#include \"%1.h\"\n#include "_s.arg(baseName);
119 code += uR"("moc_%1.cpp")"_s.arg(baseName);
120 sourceFile.write(code.toUtf8());
121 sourceFile.write("\n");
122 return EXIT_SUCCESS;
123}
124
125MetaType QmlTypeRegistrar::findType(QAnyStringView name) const
126{
127 for (const MetaType &type : m_types) {
128 if (type.qualifiedClassName() != name)
129 continue;
130 return type;
131 }
132 return MetaType();
133};
134
135MetaType QmlTypeRegistrar::findTypeForeign(QAnyStringView name) const
136{
137 for (const MetaType &type : m_foreignTypes) {
138 if (type.qualifiedClassName() != name)
139 continue;
140 return type;
141 }
142 return MetaType();
143};
144
146{
147 using namespace Qt::StringLiterals;
148
149 QString s = r.claimerName;
150 if (r.addedIn.isValid()) {
151 s += u" (added in %1.%2)"_s.arg(r.addedIn.majorVersion()).arg(r.addedIn.minorVersion());
152 }
153 if (r.removedIn.isValid()) {
154 s += u" (removed in %1.%2)"_s.arg(r.removedIn.majorVersion())
155 .arg(r.removedIn.minorVersion());
156 }
157 return s;
158};
159
160// Return a name for the registration variable containing the module to
161// avoid clashes in Unity builds.
163{
164 auto specialCharPred = [](QChar c) { return !c.isLetterOrNumber(); };
165 QString result = module;
166 result[0] = result.at(0).toLower();
167 result.erase(std::remove_if(result.begin(), result.end(), specialCharPred), result.end());
168 return result + "Registration"_L1;
169}
170
172{
173 output << uR"(/****************************************************************************
174** Generated QML type registration code
175**
176** WARNING! All changes made in this file will be lost!
177*****************************************************************************/
178
179)"_s;
180
181 output << u"#include <QtQml/qqml.h>\n"_s;
182 output << u"#include <QtQml/qqmlmoduleregistration.h>\n"_s;
183
184 for (const QString &include : m_includes)
185 output << u"\n#include <%1>"_s.arg(include);
186
187 output << u"\n\n"_s;
188
189 // Keep this in sync with _qt_internal_get_escaped_uri in CMake
190 QString moduleAsSymbol = m_module;
191 static const QRegularExpression nonAlnumRegexp(QLatin1String("[^A-Za-z0-9]"));
192 moduleAsSymbol.replace(nonAlnumRegexp, QStringLiteral("_"));
193
194 QString underscoredModuleAsSymbol = m_module;
195 underscoredModuleAsSymbol.replace(QLatin1Char('.'), QLatin1Char('_'));
196
197 if (underscoredModuleAsSymbol != moduleAsSymbol
198 || underscoredModuleAsSymbol.isEmpty()
199 || underscoredModuleAsSymbol.front().isDigit()) {
200 warning(outFileName) << m_module << "is an invalid QML module URI. You cannot import this.";
201 }
202
203 const QString functionName = QStringLiteral("qml_register_types_") + moduleAsSymbol;
204 output << uR"(
205#if !defined(QT_STATIC)
206#define Q_QMLTYPE_EXPORT Q_DECL_EXPORT
207#else
208#define Q_QMLTYPE_EXPORT
209#endif
210)"_s;
211
212 if (!m_targetNamespace.isEmpty())
213 output << u"namespace "_s << m_targetNamespace << u" {\n"_s;
214
215 output << u"Q_QMLTYPE_EXPORT void "_s << functionName << u"()\n{"_s;
216 const quint8 majorVersion = m_moduleVersion.majorVersion();
217 const quint8 minorVersion = m_moduleVersion.minorVersion();
218
219 for (const auto &version : m_pastMajorVersions) {
220 output << uR"(
221 qmlRegisterModule("%1", %2, 0);
222 qmlRegisterModule("%1", %2, 254);)"_s.arg(m_module)
223 .arg(version);
224 }
225
226 if (minorVersion != 0) {
227 output << uR"(
228 qmlRegisterModule("%1", %2, 0);)"_s.arg(m_module)
229 .arg(majorVersion);
230 }
231
232 QVector<QAnyStringView> typesRegisteredAnonymously;
233
234 const auto fillTypesRegisteredAnonymously = [&](const auto &members, QAnyStringView typeName) {
235 bool foundRevisionEntry = false;
236 for (const auto &entry : members) {
237 if (entry.revision.isValid()) {
238 foundRevisionEntry = true;
239 break;
240 }
241 }
242
243 if (!foundRevisionEntry)
244 return false;
245
246 if (typesRegisteredAnonymously.contains(typeName))
247 return true;
248
249 typesRegisteredAnonymously.append(typeName);
250
251 if (m_followForeignVersioning) {
252 output << uR"(
253 qmlRegisterAnonymousTypesAndRevisions<%1>("%2", %3);)"_s.arg(typeName.toString(), m_module)
254 .arg(majorVersion);
255 return true;
256 }
257
258 for (const auto &version
259 : m_pastMajorVersions + decltype(m_pastMajorVersions){ majorVersion }) {
260 output << uR"(
261 qmlRegisterAnonymousType<%1, 254>("%2", %3);)"_s.arg(typeName.toString(), m_module)
262 .arg(version);
263 }
264
265 return true;
266 };
267
268
269 QHash<QString, QList<ExclusiveVersionRange>> qmlElementInfos;
270
271 for (const MetaType &classDef : std::as_const(m_types)) {
272
273 // Do not generate C++ registrations for JavaScript types.
274 if (classDef.inputFile().isEmpty())
275 continue;
276
277 QString className = classDef.qualifiedClassName().toString();
278 QString targetName = className;
279
280 // If either the foreign or the local part is a namespace we need to
281 // generate a namespace registration.
282 bool targetIsNamespace = classDef.kind() == MetaType::Kind::Namespace;
283
284 QAnyStringView extendedName;
285 QList<QString> qmlElementNames;
286 QTypeRevision addedIn;
287 QTypeRevision removedIn;
288
289 for (const ClassInfo &v : classDef.classInfos()) {
290 const QAnyStringView name = v.name;
291 if (name == S_ELEMENT) {
292 qmlElementNames.append(v.value.toString());
293 } else if (name == S_FOREIGN) {
294 targetName = v.value.toString();
295 } else if (name == S_FOREIGN_IS_NAMESPACE) {
296 targetIsNamespace = targetIsNamespace || (v.value == S_TRUE);
297 } else if (name == S_EXTENDED) {
298 extendedName = v.value;
299 } else if (name == S_ADDED_IN_VERSION) {
300 int version = toInt(v.value);
301 addedIn = QTypeRevision::fromEncodedVersion(version);
302 addedIn = handleInMinorVersion(addedIn, majorVersion);
303 } else if (name == S_REMOVED_IN_VERSION) {
304 int version = toInt(v.value);
305 removedIn = QTypeRevision::fromEncodedVersion(version);
306 removedIn = handleInMinorVersion(removedIn, majorVersion);
307 }
308 }
309
310 for (QString qmlElementName : std::as_const(qmlElementNames)) {
311 if (qmlElementName == S_ANONYMOUS)
312 continue;
313 if (qmlElementName == S_AUTO)
314 qmlElementName = className;
315 qmlElementInfos[qmlElementName].append({
316 classDef.inputFile(),
317 className,
318 addedIn,
319 removedIn
320 });
321 }
322
323 // We want all related metatypes to be registered by name, so that we can look them up
324 // without including the C++ headers. That's the reason for the QMetaType(foo).id() calls.
325
326 if (targetIsNamespace) {
327 // We need to figure out if the _target_ is a namespace. If not, it already has a
328 // QMetaType and we don't need to generate one.
329
330 QString targetTypeName = targetName;
331
332 const QList<QAnyStringView> namespaces
334
336 m_types, m_foreignTypes, targetName, namespaces);
337
338 if (!target.javaScript.isEmpty() && target.native.isEmpty())
339 warning(target.javaScript) << "JavaScript type cannot be used as namespace";
340
341 if (target.native.kind() == MetaType::Kind::Object)
342 targetTypeName += " *"_L1;
343
344 // If there is no foreign type, the local one is a namespace.
345 // Otherwise, only do metaTypeForNamespace if the target _metaobject_ is a namespace.
346 // Not if we merely consider it to be a namespace for QML purposes.
347 if (className == targetName || target.native.kind() == MetaType::Kind::Namespace) {
348 output << uR"(
349 {
350 Q_CONSTINIT static auto metaType = QQmlPrivate::metaTypeForNamespace(
351 [](const QtPrivate::QMetaTypeInterface *) {return &%1::staticMetaObject;},
352 "%2");
353 QMetaType(&metaType).id();
354 })"_s.arg(targetName, targetTypeName);
355 } else {
356 Q_ASSERT(!targetTypeName.isEmpty());
357 output << u"\n QMetaType::fromType<%1>().id();"_s.arg(targetTypeName);
358 }
359
360 auto metaObjectPointer = [](QAnyStringView name) -> QString {
362 const QLatin1StringView staticMetaObject = "::staticMetaObject"_L1;
363 result.reserve(1 + name.length() + staticMetaObject.length());
364 result.append('&'_L1);
365 name.visit([&](auto view) { result.append(view); });
366 result.append(staticMetaObject);
367 return result;
368 };
369
370 if (!qmlElementNames.isEmpty()) {
371 output << uR"(
372 qmlRegisterNamespaceAndRevisions(%1, "%2", %3, nullptr, %4, %5);)"_s
373 .arg(metaObjectPointer(targetName), m_module)
374 .arg(majorVersion)
375 .arg(metaObjectPointer(className),
376 extendedName.isEmpty() ? QStringLiteral("nullptr")
377 : metaObjectPointer(extendedName));
378 }
379 } else {
380 if (!qmlElementNames.isEmpty()) {
381 auto checkRevisions = [&](const auto &array, QLatin1StringView type) {
382 for (auto it = array.begin(); it != array.end(); ++it) {
383 if (!it->revision.isValid())
384 continue;
385
386 QTypeRevision revision = it->revision;
387 if (m_moduleVersion < revision) {
388 warning(classDef)
389 << className << "is trying to register" << type
390 << it->name
391 << "with future version" << revision
392 << "when module version is only" << m_moduleVersion;
393 }
394 }
395 };
396
397 const Method::Container methods = classDef.methods();
398 const Property::Container properties = classDef.properties();
399
400 if (m_moduleVersion.isValid()) {
401 checkRevisions(properties, S_PROPERTY);
402 checkRevisions(methods, S_METHOD);
403 }
404
405 output << uR"(
406 qmlRegisterTypesAndRevisions<%1>("%2", %3);)"_s.arg(className, m_module).arg(majorVersion);
407
408 const BaseType::Container superClasses = classDef.superClasses();
409
410 for (const BaseType &object : classDef.superClasses()) {
411 if (object.access != Access::Public)
412 continue;
413
414 QAnyStringView superClassName = object.name;
415
416 QVector<QAnyStringView> classesToCheck;
417
418 auto checkForRevisions = [&](QAnyStringView typeName) -> void {
419 auto typeAsMap = findType(typeName);
420
421 if (typeAsMap.isEmpty()) {
422 typeAsMap = findTypeForeign(typeName);
423 if (typeAsMap.isEmpty())
424 return;
425
426 if (!fillTypesRegisteredAnonymously(
427 typeAsMap.properties(), typeName)) {
428 if (!fillTypesRegisteredAnonymously(
429 typeAsMap.sigs(), typeName)) {
430 fillTypesRegisteredAnonymously(
431 typeAsMap.methods(), typeName);
432 }
433 }
434 }
435
436 for (const BaseType &object : typeAsMap.superClasses()) {
437 if (object.access == Access::Public)
438 classesToCheck << object.name;
439 }
440 };
441
442 checkForRevisions(superClassName);
443
444 while (!classesToCheck.isEmpty())
445 checkForRevisions(classesToCheck.takeFirst());
446 }
447 } else {
448 Q_ASSERT(!className.isEmpty());
449 output << uR"(
450 QMetaType::fromType<%1%2>().id();)"_s.arg(
451 className, classDef.kind() == MetaType::Kind::Object ? u" *" : u"");
452 }
453 }
454 }
455
456 for (const auto [qmlName, exportsForSameQmlName] : qmlElementInfos.asKeyValueRange()) {
457 // needs a least two cpp classes exporting the same qml element to potentially have a
458 // conflict
459 if (exportsForSameQmlName.size() < 2)
460 continue;
461
462 // sort exports by versions to find conflicting exports
463 std::sort(exportsForSameQmlName.begin(), exportsForSameQmlName.end());
464 auto conflictingExportStartIt = exportsForSameQmlName.cbegin();
465 while (1) {
466 // conflicting versions evaluate to true under operator==
467 conflictingExportStartIt =
468 std::adjacent_find(conflictingExportStartIt, exportsForSameQmlName.cend());
469 if (conflictingExportStartIt == exportsForSameQmlName.cend())
470 break;
471
472 auto conflictingExportEndIt = std::find_if_not(
473 conflictingExportStartIt, exportsForSameQmlName.cend(),
474 [=](const auto &x) -> bool { return x == *conflictingExportStartIt; });
475 QString registeringCppClasses = conflictingExportStartIt->claimerName;
476 std::for_each(std::next(conflictingExportStartIt), conflictingExportEndIt,
477 [&](const auto &q) {
478 registeringCppClasses += u", %1"_s.arg(conflictingVersionToString(q));
479 });
480 warning(conflictingExportStartIt->fileName)
481 << qmlName << "is registered multiple times by the following C++ classes:"
482 << registeringCppClasses;
483 conflictingExportStartIt = conflictingExportEndIt;
484 }
485 }
486
487 output << uR"(
488 qmlRegisterModule("%1", %2, %3);
489}
490
491static const QQmlModuleRegistration %5("%1", %4);
492)"_s.arg(m_module)
493 .arg(majorVersion)
494 .arg(minorVersion)
495 .arg(functionName, registrationVarName(m_module));
496
497 if (!m_targetNamespace.isEmpty())
498 output << u"} // namespace %1\n"_s.arg(m_targetNamespace);
499}
500
501bool QmlTypeRegistrar::generatePluginTypes(const QString &pluginTypesFile)
502{
504 creator.setOwnTypes(m_types);
505 creator.setForeignTypes(m_foreignTypes);
506 creator.setReferencedTypes(m_referencedTypes);
507 creator.setModule(m_module.toUtf8());
508 creator.setVersion(QTypeRevision::fromVersion(m_moduleVersion.majorVersion(), 0));
509
510 return creator.generate(pluginTypesFile);
511}
512
514 const QString &targetNamespace)
515{
516 m_module = module;
517 m_targetNamespace = targetNamespace;
518}
520 const QList<quint8> &pastMajorVersions,
521 bool followForeignVersioning)
522{
523 m_moduleVersion = moduleVersion;
524 m_pastMajorVersions = pastMajorVersions;
525 m_followForeignVersioning = followForeignVersioning;
526}
527void QmlTypeRegistrar::setIncludes(const QList<QString> &includes)
528{
529 m_includes = includes;
530}
532 const QVector<MetaType> &types, const QVector<MetaType> &foreignTypes)
533{
534 m_types = types;
535 m_foreignTypes = foreignTypes;
536}
537void QmlTypeRegistrar::setReferencedTypes(const QList<QAnyStringView> &referencedTypes)
538{
539 m_referencedTypes = referencedTypes;
540}
541
static JNINativeMethod methods[]
static QList< QAnyStringView > namespaces(const MetaType &classDef)
\inmodule QtCore
\inmodule QtCore
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
QString fileName() const override
Returns the name set by setFileName() or to the QFile constructors.
Definition qfile.cpp:277
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
constexpr qsizetype length() const noexcept
qsizetype size() const noexcept
Definition qlist.h:397
\inmodule QtCore \reentrant
iterator begin()
Definition qset.h:136
iterator end()
Definition qset.h:140
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5949
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8870
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
QByteArray toUtf8() const &
Definition qstring.h:634
\inmodule QtCore
\inmodule QtCore
static constexpr QTypeRevision fromVersion(Major majorVersion, Minor minorVersion)
Produces a QTypeRevision from the given majorVersion and minorVersion, both of which need to be a val...
constexpr quint8 minorVersion() const
Returns the minor version encoded in the revision.
constexpr bool isValid() const
Returns true if the major version or the minor version is known, otherwise false.
static constexpr QTypeRevision fromEncodedVersion(Integer value)
Produces a QTypeRevision from the given value.
constexpr quint8 majorVersion() const
Returns the major version encoded in the revision.
void setModuleNameAndNamespace(const QString &module, const QString &targetNamespace)
static bool argumentsFromCommandLineAndFile(QStringList &allArguments, const QStringList &arguments)
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)
bool generatePluginTypes(const QString &pluginTypesFile)
void setReferencedTypes(const QList< QAnyStringView > &referencedTypes)
static int runExtract(const QString &baseName, const MetaTypesJsonProcessor &processor)
QSet< QString >::iterator it
QList< QVariant > arguments
QString includeGuard(const QString &filename)
Definition helpers.cpp:20
static constexpr QLatin1StringView S_FOREIGN
static constexpr QLatin1StringView S_FOREIGN_IS_NAMESPACE
static constexpr QLatin1StringView S_EXTENDED
static constexpr QLatin1StringView S_REMOVED_IN_VERSION
static constexpr QLatin1StringView S_ELEMENT
static constexpr QLatin1StringView S_ADDED_IN_VERSION
static constexpr QLatin1StringView S_ANONYMOUS
static constexpr QLatin1StringView S_TRUE
static constexpr QLatin1StringView S_PROPERTY
static constexpr QLatin1StringView S_AUTO
static constexpr QLatin1StringView S_METHOD
Combined button and popup list for selecting options.
static const QCssKnownValue properties[NumProperties - 1]
DBusConnection const char DBusError * error
const char * typeName
GLsizei const GLfloat * v
[13]
GLint GLint GLint GLint GLint x
[0]
GLboolean r
[2]
GLsizei GLenum GLenum * types
GLfloat GLfloat f
GLenum type
GLenum access
GLenum target
GLuint name
GLint y
GLdouble s
[6]
Definition qopenglext.h:235
const GLubyte * c
GLuint entry
GLenum array
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLuint64EXT * result
[6]
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.
QT_BEGIN_NAMESPACE QTypeRevision handleInMinorVersion(QTypeRevision revision, int majorVersion)
QDebug warning(QAnyStringView fileName, int lineNumber)
Members members(const Members &candidates, QTypeRevision maxMajorVersion, Postprocess &&process)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int void * arg
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
unsigned char quint8
Definition qtypes.h:46
static int toInt(const QChar &qc, int R)
QT_BEGIN_NAMESPACE typedef uchar * output
const char className[16]
[1]
Definition qwizard.cpp:100
QItemEditorCreatorBase * creator
QQuickView * view
[0]
QDBusArgument argument
std::vector< Method > Container
\inmodule QtCore \reentrant
Definition qchar.h:18
static FoundType findType(const QVector< MetaType > &types, const QVector< MetaType > &foreign, const QAnyStringView &name, const QList< QAnyStringView > &namespaces)