1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <private/qqmljslexer_p.h>
30#include <private/qqmljsparser_p.h>
31#include <private/qqmljsast_p.h>
32#include <private/qv4codegen_p.h>
33#include <private/qv4staticvalue_p.h>
34#include <private/qqmlirbuilder_p.h>
35#include <private/qqmljsdiagnosticmessage_p.h>
36
37#include <QtCore/QCoreApplication>
38#include <QtCore/QDir>
39#include <QtCore/QDirIterator>
40#include <QtCore/QFile>
41#include <QtCore/QFileInfo>
42#include <QtCore/QSet>
43#include <QtCore/QStringList>
44#include <QtCore/QMetaObject>
45#include <QtCore/QMetaProperty>
46#include <QtCore/QVariant>
47#include <QtCore/QJsonObject>
48#include <QtCore/QJsonArray>
49#include <QtCore/QJsonDocument>
50#include <QtCore/QLibraryInfo>
51
52#include <resourcefilemapper.h>
53
54#include <iostream>
55#include <algorithm>
56
57QT_USE_NAMESPACE
58
59namespace {
60
61QStringList g_qmlImportPaths;
62
63inline QString typeLiteral() { return QStringLiteral("type"); }
64inline QString versionLiteral() { return QStringLiteral("version"); }
65inline QString nameLiteral() { return QStringLiteral("name"); }
66inline QString relativePathLiteral() { return QStringLiteral("relativePath"); }
67inline QString pluginsLiteral() { return QStringLiteral("plugins"); }
68inline QString pathLiteral() { return QStringLiteral("path"); }
69inline QString classnamesLiteral() { return QStringLiteral("classnames"); }
70inline QString dependenciesLiteral() { return QStringLiteral("dependencies"); }
71inline QString moduleLiteral() { return QStringLiteral("module"); }
72inline QString javascriptLiteral() { return QStringLiteral("javascript"); }
73inline QString directoryLiteral() { return QStringLiteral("directory"); }
74
75void printUsage(const QString &appNameIn)
76{
77 const std::string appName = appNameIn.toStdString();
78#ifndef QT_BOOTSTRAPPED
79 const QString qmlPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
80#else
81 const QString qmlPath = QStringLiteral("/home/user/dev/qt-install/qml");
82#endif
83 std::cerr
84 << "Usage: " << appName << " -rootPath path/to/app/qml/directory -importPath path/to/qt/qml/directory\n"
85 " " << appName << " -qmlFiles file1 file2 -importPath path/to/qt/qml/directory\n"
86 " " << appName << " -qrcFiles file1.qrc file2.qrc -importPath path/to/qt/qml/directory\n\n"
87 "Example: " << appName << " -rootPath . -importPath "
88 << QDir::toNativeSeparators(pathName: qmlPath).toStdString()
89 << '\n';
90}
91
92QVariantList findImportsInAst(QQmlJS::AST::UiHeaderItemList *headerItemList, const QString &path)
93{
94 QVariantList imports;
95
96 // Extract uri and version from the imports (which look like "import Foo.Bar 1.2.3")
97 for (QQmlJS::AST::UiHeaderItemList *headerItemIt = headerItemList; headerItemIt; headerItemIt = headerItemIt->next) {
98 QVariantMap import;
99 QQmlJS::AST::UiImport *importNode = QQmlJS::AST::cast<QQmlJS::AST::UiImport *>(ast: headerItemIt->headerItem);
100 if (!importNode)
101 continue;
102 // Handle directory imports
103 if (!importNode->fileName.isEmpty()) {
104 QString name = importNode->fileName.toString();
105 import[nameLiteral()] = name;
106 if (name.endsWith(s: QLatin1String(".js"))) {
107 import[typeLiteral()] = javascriptLiteral();
108 } else {
109 import[typeLiteral()] = directoryLiteral();
110 }
111
112 import[pathLiteral()] = QDir::cleanPath(path: path + QLatin1Char('/') + name);
113 } else {
114 // Walk the id chain ("Foo" -> "Bar" -> etc)
115 QString name;
116 QQmlJS::AST::UiQualifiedId *uri = importNode->importUri;
117 while (uri) {
118 name.append(s: uri->name);
119 name.append(c: QLatin1Char('.'));
120 uri = uri->next;
121 }
122 name.chop(n: 1); // remove trailing "."
123#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
124 if (name.startsWith(s: QLatin1String("QtQuick.Controls")) && name.endsWith(s: QLatin1String("impl")))
125 continue;
126#endif
127 if (!name.isEmpty())
128 import[nameLiteral()] = name;
129 import[typeLiteral()] = moduleLiteral();
130 auto versionString = importNode->version ? QString::number(importNode->version->majorVersion) + QLatin1Char('.') + QString::number(importNode->version->minorVersion) : QString();
131 import[versionLiteral()] = versionString;
132 }
133
134 imports.append(t: import);
135 }
136
137 return imports;
138}
139
140// Read the qmldir file, extract a list of plugins by
141// parsing the "plugin" and "classname" lines.
142QVariantMap pluginsForModulePath(const QString &modulePath) {
143 QFile qmldirFile(modulePath + QLatin1String("/qmldir"));
144 if (!qmldirFile.exists())
145 return QVariantMap();
146
147 qmldirFile.open(flags: QIODevice::ReadOnly | QIODevice::Text);
148
149 // A qml import may contain several plugins
150 QString plugins;
151 QString classnames;
152 QStringList dependencies;
153 QByteArray line;
154 do {
155 line = qmldirFile.readLine();
156 if (line.startsWith(c: "plugin")) {
157 plugins += QString::fromUtf8(str: line.split(sep: ' ').at(i: 1));
158 plugins += QLatin1Char(' ');
159 } else if (line.startsWith(c: "classname")) {
160 classnames += QString::fromUtf8(str: line.split(sep: ' ').at(i: 1));
161 classnames += QLatin1Char(' ');
162 } else if (line.startsWith(c: "depends")) {
163 const QList<QByteArray> dep = line.split(sep: ' ');
164 if (dep.length() != 3)
165 std::cerr << "depends: expected 2 arguments: module identifier and version" << std::endl;
166 else
167 dependencies << QString::fromUtf8(str: dep[1]) + QLatin1Char(' ') + QString::fromUtf8(str: dep[2]).simplified();
168 }
169
170 } while (line.length() > 0);
171
172 QVariantMap pluginInfo;
173 pluginInfo[pluginsLiteral()] = plugins.simplified();
174 pluginInfo[classnamesLiteral()] = classnames.simplified();
175 if (dependencies.length())
176 pluginInfo[dependenciesLiteral()] = dependencies;
177 return pluginInfo;
178}
179
180// Search for a given qml import in g_qmlImportPaths and return a pair
181// of absolute / relative paths (for deployment).
182QPair<QString, QString> resolveImportPath(const QString &uri, const QString &version)
183{
184 const QLatin1Char dot('.');
185 const QLatin1Char slash('/');
186 const QStringList parts = uri.split(sep: dot, behavior: Qt::SkipEmptyParts);
187
188 QString ver = version;
189 while (true) {
190 for (const QString &qmlImportPath : qAsConst(t&: g_qmlImportPaths)) {
191 // Search for the most specific version first, and search
192 // also for the version in parent modules. For example:
193 // - qml/QtQml/Models.2.0
194 // - qml/QtQml.2.0/Models
195 // - qml/QtQml/Models.2
196 // - qml/QtQml.2/Models
197 // - qml/QtQml/Models
198 if (ver.isEmpty()) {
199 QString relativePath = parts.join(sep: slash);
200 if (relativePath.endsWith(c: slash))
201 relativePath.chop(n: 1);
202 const QString candidatePath = QDir::cleanPath(path: qmlImportPath + slash + relativePath);
203 if (QDir(candidatePath).exists())
204 return qMakePair(x: candidatePath, y: relativePath); // import found
205 } else {
206 for (int index = parts.count() - 1; index >= 0; --index) {
207 QString relativePath = parts.mid(pos: 0, alength: index + 1).join(sep: slash)
208 + dot + ver + slash + parts.mid(pos: index + 1).join(sep: slash);
209 if (relativePath.endsWith(c: slash))
210 relativePath.chop(n: 1);
211 const QString candidatePath = QDir::cleanPath(path: qmlImportPath + slash + relativePath);
212 if (QDir(candidatePath).exists())
213 return qMakePair(x: candidatePath, y: relativePath); // import found
214 }
215 }
216 }
217
218 // Remove the last version digit; stop if there are none left
219 if (ver.isEmpty())
220 break;
221
222 int lastDot = ver.lastIndexOf(c: dot);
223 if (lastDot == -1)
224 ver.clear();
225 else
226 ver = ver.mid(position: 0, n: lastDot);
227 }
228
229 return QPair<QString, QString>(); // not found
230}
231
232// Find absolute file system paths and plugins for a list of modules.
233QVariantList findPathsForModuleImports(const QVariantList &imports)
234{
235 QVariantList done;
236 QVariantList importsCopy(imports);
237
238 for (int i = 0; i < importsCopy.length(); ++i) {
239 QVariantMap import = qvariant_cast<QVariantMap>(v: importsCopy.at(i));
240 if (import.value(akey: typeLiteral()) == moduleLiteral()) {
241 const QPair<QString, QString> paths =
242 resolveImportPath(uri: import.value(akey: nameLiteral()).toString(), version: import.value(akey: versionLiteral()).toString());
243 if (!paths.first.isEmpty()) {
244 import.insert(akey: pathLiteral(), avalue: paths.first);
245 import.insert(akey: relativePathLiteral(), avalue: paths.second);
246 }
247 QVariantMap plugininfo = pluginsForModulePath(modulePath: import.value(akey: pathLiteral()).toString());
248 QString plugins = plugininfo.value(akey: pluginsLiteral()).toString();
249 QString classnames = plugininfo.value(akey: classnamesLiteral()).toString();
250 if (!plugins.isEmpty())
251 import.insert(QStringLiteral("plugin"), avalue: plugins);
252 if (!classnames.isEmpty())
253 import.insert(QStringLiteral("classname"), avalue: classnames);
254 if (plugininfo.contains(akey: dependenciesLiteral())) {
255 const QStringList dependencies = plugininfo.value(akey: dependenciesLiteral()).toStringList();
256 for (const QString &line : dependencies) {
257 const auto dep = line.splitRef(sep: QLatin1Char(' '));
258 QVariantMap depImport;
259 depImport[typeLiteral()] = moduleLiteral();
260 depImport[nameLiteral()] = dep[0].toString();
261 depImport[versionLiteral()] = dep[1].toString();
262 importsCopy.append(t: depImport);
263 }
264 }
265 }
266 done.append(t: import);
267 }
268 return done;
269}
270
271// Scan a single qml file for import statements
272QVariantList findQmlImportsInQmlCode(const QString &filePath, const QString &code)
273{
274 QQmlJS::Engine engine;
275 QQmlJS::Lexer lexer(&engine);
276 lexer.setCode(code, /*line = */ lineno: 1);
277 QQmlJS::Parser parser(&engine);
278
279 if (!parser.parse() || !parser.diagnosticMessages().isEmpty()) {
280 // Extract errors from the parser
281 const auto diagnosticMessages = parser.diagnosticMessages();
282 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
283 std::cerr << QDir::toNativeSeparators(filePath).toStdString() << ':'
284 << m.loc.startLine << ':' << m.message.toStdString() << std::endl;
285 }
286 return QVariantList();
287 }
288 return findImportsInAst(parser.ast()->headers, filePath);
289}
290
291// Scan a single qml file for import statements
292QVariantList findQmlImportsInQmlFile(const QString &filePath)
293{
294 QFile file(filePath);
295 if (!file.open(flags: QIODevice::ReadOnly)) {
296 std::cerr << "Cannot open input file " << QDir::toNativeSeparators(pathName: file.fileName()).toStdString()
297 << ':' << file.errorString().toStdString() << std::endl;
298 return QVariantList();
299 }
300 QString code = QString::fromUtf8(str: file.readAll());
301 return findQmlImportsInQmlCode(filePath, code);
302}
303
304struct ImportCollector : public QQmlJS::Directives
305{
306 QVariantList imports;
307
308 void importFile(const QString &jsfile, const QString &module, int line, int column) override
309 {
310 QVariantMap entry;
311 entry[typeLiteral()] = javascriptLiteral();
312 entry[pathLiteral()] = jsfile;
313 imports << entry;
314
315 Q_UNUSED(module);
316 Q_UNUSED(line);
317 Q_UNUSED(column);
318 }
319
320 void importModule(const QString &uri, const QString &version, const QString &module, int line, int column) override
321 {
322 QVariantMap entry;
323 if (uri.contains(c: QLatin1Char('/'))) {
324 entry[typeLiteral()] = directoryLiteral();
325 entry[nameLiteral()] = uri;
326 } else {
327 entry[typeLiteral()] = moduleLiteral();
328 entry[nameLiteral()] = uri;
329 entry[versionLiteral()] = version;
330 }
331 imports << entry;
332
333 Q_UNUSED(module);
334 Q_UNUSED(line);
335 Q_UNUSED(column);
336 }
337};
338
339// Scan a single javascrupt file for import statements
340QVariantList findQmlImportsInJavascriptFile(const QString &filePath)
341{
342 QFile file(filePath);
343 if (!file.open(flags: QIODevice::ReadOnly)) {
344 std::cerr << "Cannot open input file " << QDir::toNativeSeparators(pathName: file.fileName()).toStdString()
345 << ':' << file.errorString().toStdString() << std::endl;
346 return QVariantList();
347 }
348
349 QString sourceCode = QString::fromUtf8(str: file.readAll());
350 file.close();
351
352 QQmlJS::Engine ee;
353 ImportCollector collector;
354 ee.setDirectives(&collector);
355 QQmlJS::Lexer lexer(&ee);
356 lexer.setCode(code: sourceCode, /*line*/lineno: 1, /*qml mode*/qmlMode: false);
357 QQmlJS::Parser parser(&ee);
358 parser.parseProgram();
359
360 const auto diagnosticMessages = parser.diagnosticMessages();
361 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages)
362 if (m.isError())
363 return QVariantList();
364
365 return collector.imports;
366}
367
368// Scan a single qml or js file for import statements
369QVariantList findQmlImportsInFile(const QString &filePath)
370{
371 QVariantList imports;
372 if (filePath == QLatin1String("-")) {
373 QFile f;
374 if (f.open(stdin, ioFlags: QIODevice::ReadOnly))
375 imports = findQmlImportsInQmlCode(filePath: QLatin1String("<stdin>"), code: QString::fromUtf8(str: f.readAll()));
376 } else if (filePath.endsWith(s: QLatin1String(".qml"))) {
377 imports = findQmlImportsInQmlFile(filePath);
378 } else if (filePath.endsWith(s: QLatin1String(".js"))) {
379 imports = findQmlImportsInJavascriptFile(filePath);
380 }
381
382 return findPathsForModuleImports(imports);
383}
384
385// Merge two lists of imports, discard duplicates.
386QVariantList mergeImports(const QVariantList &a, const QVariantList &b)
387{
388 QVariantList merged = a;
389 for (const QVariant &variant : b) {
390 if (!merged.contains(t: variant))
391 merged.append(t: variant);
392 }
393 return merged;
394}
395
396// Predicates needed by findQmlImportsInDirectory.
397
398struct isMetainfo {
399 bool operator() (const QFileInfo &x) const {
400 return x.suffix() == QLatin1String("metainfo");
401 }
402};
403
404struct pathStartsWith {
405 pathStartsWith(const QString &path) : _path(path) {}
406 bool operator() (const QString &x) const {
407 return _path.startsWith(s: x);
408 }
409 const QString _path;
410};
411
412
413
414// Scan all qml files in directory for import statements
415QVariantList findQmlImportsInDirectory(const QString &qmlDir)
416{
417 QVariantList ret;
418 if (qmlDir.isEmpty())
419 return ret;
420
421 QDirIterator iterator(qmlDir, QDir::AllDirs | QDir::NoDotDot, QDirIterator::Subdirectories);
422 QStringList blacklist;
423
424 while (iterator.hasNext()) {
425 iterator.next();
426 const QString path = iterator.filePath();
427 const QFileInfoList entries = QDir(path).entryInfoList();
428
429 // Skip designer related stuff
430 if (std::find_if(first: entries.cbegin(), last: entries.cend(), pred: isMetainfo()) != entries.cend()) {
431 blacklist << path;
432 continue;
433 }
434
435 if (std::find_if(first: blacklist.cbegin(), last: blacklist.cend(), pred: pathStartsWith(path)) != blacklist.cend())
436 continue;
437
438 // Skip obvious build output directories
439 if (path.contains(s: QLatin1String("Debug-iphoneos")) || path.contains(s: QLatin1String("Release-iphoneos")) ||
440 path.contains(s: QLatin1String("Debug-iphonesimulator")) || path.contains(s: QLatin1String("Release-iphonesimulator"))
441#ifdef Q_OS_WIN
442 || path.endsWith(QLatin1String("/release")) || path.endsWith(QLatin1String("/debug"))
443#endif
444 ){
445 continue;
446 }
447
448 for (const QFileInfo &x : entries)
449 if (x.isFile())
450 ret = mergeImports(a: ret, b: findQmlImportsInFile(filePath: x.absoluteFilePath()));
451 }
452 return ret;
453}
454
455QSet<QString> importModulePaths(const QVariantList &imports) {
456 QSet<QString> ret;
457 for (const QVariant &importVariant : imports) {
458 QVariantMap import = qvariant_cast<QVariantMap>(v: importVariant);
459 QString path = import.value(akey: pathLiteral()).toString();
460 QString type = import.value(akey: typeLiteral()).toString();
461 if (type == moduleLiteral() && !path.isEmpty())
462 ret.insert(value: QDir(path).canonicalPath());
463 }
464 return ret;
465}
466
467// Find qml imports recursively from a root set of qml files.
468// The directories in qmlDirs are searched recursively.
469// The files in qmlFiles parsed directly.
470QVariantList findQmlImportsRecursively(const QStringList &qmlDirs, const QStringList &scanFiles)
471{
472 QVariantList ret;
473
474 // Scan all app root qml directories for imports
475 for (const QString &qmlDir : qmlDirs) {
476 QVariantList imports = findQmlImportsInDirectory(qmlDir);
477 ret = mergeImports(a: ret, b: imports);
478 }
479
480 // Scan app qml files for imports
481 for (const QString &file : scanFiles) {
482 QVariantList imports = findQmlImportsInFile(filePath: file);
483 ret = mergeImports(a: ret, b: imports);
484 }
485
486 // Get the paths to the imports found in the app qml
487 QSet<QString> toVisit = importModulePaths(imports: ret);
488
489 // Recursively scan for import dependencies.
490 QSet<QString> visited;
491 while (!toVisit.isEmpty()) {
492 QString qmlDir = *toVisit.begin();
493 toVisit.erase(i: toVisit.begin());
494 visited.insert(value: qmlDir);
495
496 QVariantList imports = findQmlImportsInDirectory(qmlDir);
497 ret = mergeImports(a: ret, b: imports);
498
499 QSet<QString> candidatePaths = importModulePaths(imports: ret);
500 candidatePaths.subtract(other: visited);
501 toVisit.unite(other: candidatePaths);
502 }
503 return ret;
504}
505
506
507QString generateCmakeIncludeFileContent(const QVariantList &importList) {
508 // The function assumes that "list" is a QVariantList with 0 or more QVariantMaps, where
509 // each map contains QString -> QVariant<QString> mappings. This matches with the structure
510 // that qmake parses for static qml plugin auto imporitng.
511 // So: [ {"a": "a","b": "b"}, {"c": "c"} ]
512 QString content;
513 QTextStream s(&content);
514 int importsCount = 0;
515 for (const QVariant &importVariant: importList) {
516 if (static_cast<QMetaType::Type>(importVariant.userType()) == QMetaType::QVariantMap) {
517 s << QStringLiteral("set(qml_import_scanner_import_") << importsCount
518 << QStringLiteral(" \"");
519
520 const QMap<QString, QVariant> &importDict = importVariant.toMap();
521 for (auto it = importDict.cbegin(); it != importDict.cend(); ++it) {
522 s << it.key().toUpper() << QLatin1Char(';')
523 << it.value().toString() << QLatin1Char(';');
524 }
525 s << QStringLiteral("\")\n");
526 ++importsCount;
527 }
528 }
529 if (importsCount >= 0) {
530 content.prepend(s: QString(QStringLiteral("set(qml_import_scanner_imports_count %1)\n"))
531 .arg(a: importsCount));
532 }
533 return content;
534}
535
536} // namespace
537
538int main(int argc, char *argv[])
539{
540 QCoreApplication app(argc, argv);
541 QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
542 QStringList args = app.arguments();
543 const QString appName = QFileInfo(app.applicationFilePath()).baseName();
544 if (args.size() < 2) {
545 printUsage(appNameIn: appName);
546 return 1;
547 }
548
549 QStringList qmlRootPaths;
550 QStringList scanFiles;
551 QStringList qmlImportPaths;
552 QStringList qrcFiles;
553 bool generateCmakeContent = false;
554
555 int i = 1;
556 while (i < args.count()) {
557 const QString &arg = args.at(i);
558 ++i;
559 QStringList *argReceiver = nullptr;
560 if (!arg.startsWith(c: QLatin1Char('-')) || arg == QLatin1String("-")) {
561 qmlRootPaths += arg;
562 } else if (arg == QLatin1String("-rootPath")) {
563 if (i >= args.count())
564 std::cerr << "-rootPath requires an argument\n";
565 argReceiver = &qmlRootPaths;
566 } else if (arg == QLatin1String("-qmlFiles")) {
567 if (i >= args.count())
568 std::cerr << "-qmlFiles requires an argument\n";
569 argReceiver = &scanFiles;
570 } else if (arg == QLatin1String("-jsFiles")) {
571 if (i >= args.count())
572 std::cerr << "-jsFiles requires an argument\n";
573 argReceiver = &scanFiles;
574 } else if (arg == QLatin1String("-importPath")) {
575 if (i >= args.count())
576 std::cerr << "-importPath requires an argument\n";
577 argReceiver = &qmlImportPaths;
578 } else if (arg == QLatin1String("-cmake-output")) {
579 generateCmakeContent = true;
580 } else if (arg == QLatin1String("-qrcFiles")) {
581 argReceiver = &qrcFiles;
582 } else {
583 std::cerr << qPrintable(appName) << ": Invalid argument: \""
584 << qPrintable(arg) << "\"\n";
585 return 1;
586 }
587
588 while (i < args.count()) {
589 const QString arg = args.at(i);
590 if (arg.startsWith(c: QLatin1Char('-')) && arg != QLatin1String("-"))
591 break;
592 ++i;
593 if (arg != QLatin1String("-") && !QFile::exists(fileName: arg)) {
594 std::cerr << qPrintable(appName) << ": No such file or directory: \""
595 << qPrintable(arg) << "\"\n";
596 return 1;
597 } else {
598 *argReceiver += arg;
599 }
600 }
601 }
602
603 if (!qrcFiles.isEmpty())
604 scanFiles << ResourceFileMapper(qrcFiles).qmlCompilerFiles(fo: ResourceFileMapper::FileOutput::AbsoluteFilePath);
605
606 g_qmlImportPaths = qmlImportPaths;
607
608 // Find the imports!
609 QVariantList imports = findQmlImportsRecursively(qmlDirs: qmlRootPaths, scanFiles);
610
611 QByteArray content;
612 if (generateCmakeContent) {
613 // Convert to CMake code
614 content = generateCmakeIncludeFileContent(importList: imports).toUtf8();
615 } else {
616 // Convert to JSON
617 content = QJsonDocument(QJsonArray::fromVariantList(list: imports)).toJson();
618 }
619
620 std::cout << content.constData() << std::endl;
621 return 0;
622}
623

source code of qtdeclarative/tools/qmlimportscanner/main.cpp