1// Copyright (C) 2021 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 "qmltccommandlineutils.h"
5#include "qmltcvisitor.h"
6#include "qmltctyperesolver.h"
7
8#include "qmltccompiler.h"
9
10#include <private/qqmljscompiler_p.h>
11#include <private/qqmljsresourcefilemapper_p.h>
12
13#include <QtCore/qcoreapplication.h>
14#include <QtCore/qurl.h>
15#include <QtCore/qhashfunctions.h>
16#include <QtCore/qfileinfo.h>
17#include <QtCore/qlibraryinfo.h>
18#include <QtCore/qcommandlineparser.h>
19#include <QtCore/qregularexpression.h>
20
21#include <QtQml/private/qqmljslexer_p.h>
22#include <QtQml/private/qqmljsparser_p.h>
23#include <QtQml/private/qqmljsengine_p.h>
24#include <QtQml/private/qqmljsastvisitor_p.h>
25#include <QtQml/private/qqmljsast_p.h>
26#include <QtQml/private/qqmljsdiagnosticmessage_p.h>
27
28#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE
29
30using namespace Qt::StringLiterals;
31
32void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
33{
34 for (const QQmlJS::LoggerCategory &category : logger.categories()) {
35 if (category.id() == qmlUnusedImports)
36 continue;
37 logger.setCategoryLevel(id: category.id(), level: QtCriticalMsg);
38 logger.setCategoryIgnored(id: category.id(), error: false);
39 }
40}
41
42int main(int argc, char **argv)
43{
44 // Produce reliably the same output for the same input by disabling QHash's
45 // random seeding.
46 QHashSeed::setDeterministicGlobalSeed();
47 QCoreApplication app(argc, argv);
48 QCoreApplication::setApplicationName(u"qmltc"_s);
49 QCoreApplication::setApplicationVersion(QStringLiteral(QT_VERSION_STR));
50
51 // command-line parsing:
52 QCommandLineParser parser;
53 parser.addHelpOption();
54 parser.addVersionOption();
55
56 QCommandLineOption bareOption {
57 u"bare"_s,
58 QCoreApplication::translate(
59 context: "main", key: "Do not include default import directories. This may be used to run "
60 "qmltc on a project using a different Qt version.")
61 };
62 parser.addOption(commandLineOption: bareOption);
63
64 QCommandLineOption importPathOption {
65 u"I"_s, QCoreApplication::translate(context: "main", key: "Look for QML modules in specified directory"),
66 QCoreApplication::translate(context: "main", key: "import directory")
67 };
68 parser.addOption(commandLineOption: importPathOption);
69 QCommandLineOption qmldirOption {
70 u"i"_s, QCoreApplication::translate(context: "main", key: "Include extra qmldir files"),
71 QCoreApplication::translate(context: "main", key: "qmldir file")
72 };
73 parser.addOption(commandLineOption: qmldirOption);
74 QCommandLineOption outputCppOption {
75 u"impl"_s, QCoreApplication::translate(context: "main", key: "Generated C++ source file path"),
76 QCoreApplication::translate(context: "main", key: "cpp path")
77 };
78 parser.addOption(commandLineOption: outputCppOption);
79 QCommandLineOption outputHOption {
80 u"header"_s, QCoreApplication::translate(context: "main", key: "Generated C++ header file path"),
81 QCoreApplication::translate(context: "main", key: "h path")
82 };
83 parser.addOption(commandLineOption: outputHOption);
84 QCommandLineOption resourceOption {
85 u"resource"_s,
86 QCoreApplication::translate(
87 context: "main", key: "Qt resource file that might later contain one of the compiled files"),
88 QCoreApplication::translate(context: "main", key: "resource file name")
89 };
90 parser.addOption(commandLineOption: resourceOption);
91 QCommandLineOption metaResourceOption {
92 u"meta-resource"_s,
93 QCoreApplication::translate(context: "main", key: "Qt meta information file (in .qrc format)"),
94 QCoreApplication::translate(context: "main", key: "meta file name")
95 };
96 parser.addOption(commandLineOption: metaResourceOption);
97 QCommandLineOption namespaceOption {
98 u"namespace"_s, QCoreApplication::translate(context: "main", key: "Namespace of the generated C++ code"),
99 QCoreApplication::translate(context: "main", key: "namespace")
100 };
101 parser.addOption(commandLineOption: namespaceOption);
102 QCommandLineOption exportOption{ u"export"_s,
103 QCoreApplication::translate(
104 context: "main", key: "Export macro used in the generated C++ code"),
105 QCoreApplication::translate(context: "main", key: "export") };
106 parser.addOption(commandLineOption: exportOption);
107 QCommandLineOption exportIncludeOption{
108 u"exportInclude"_s,
109 QCoreApplication::translate(
110 context: "main", key: "Header defining the export macro to be used in the generated C++ code"),
111 QCoreApplication::translate(context: "main", key: "exportInclude")
112 };
113 parser.addOption(commandLineOption: exportIncludeOption);
114
115 parser.process(app);
116
117 const QStringList sources = parser.positionalArguments();
118 if (sources.size() != 1) {
119 if (sources.isEmpty()) {
120 parser.showHelp();
121 } else {
122 fprintf(stderr, format: "%s\n",
123 qPrintable(u"Too many input files specified: '"_s + sources.join(u"' '"_s)
124 + u'\''));
125 }
126 return EXIT_FAILURE;
127 }
128 const QString inputFile = sources.first();
129
130 QString url = parseUrlArgument(arg: inputFile);
131 if (url.isNull())
132 return EXIT_FAILURE;
133 if (!url.endsWith(s: u".qml")) {
134 fprintf(stderr, format: "Non-QML file passed as input\n");
135 return EXIT_FAILURE;
136 }
137
138 static QRegularExpression nameChecker(u"^[a-zA-Z_][a-zA-Z0-9_]*\\.qml$"_s);
139 if (auto match = nameChecker.match(subject: QUrl(url).fileName()); !match.hasMatch()) {
140 fprintf(stderr,
141 format: "The given QML filename is unsuited for type compilation: the name must consist of "
142 "letters, digits and underscores, starting with "
143 "a letter or an underscore and ending in '.qml'!\n");
144 return EXIT_FAILURE;
145 }
146
147 QString sourceCode = loadUrl(url);
148 if (sourceCode.isEmpty())
149 return EXIT_FAILURE;
150
151 QString implicitImportDirectory = getImplicitImportDirectory(url);
152 if (implicitImportDirectory.isEmpty())
153 return EXIT_FAILURE;
154
155 QStringList importPaths;
156
157 if (parser.isSet(option: resourceOption)) {
158 importPaths.append(t: QLatin1String(":/qt-project.org/imports"));
159 importPaths.append(t: QLatin1String(":/qt/qml"));
160 };
161
162 if (parser.isSet(option: importPathOption))
163 importPaths.append(other: parser.values(option: importPathOption));
164
165 if (!parser.isSet(option: bareOption))
166 importPaths.append(t: QLibraryInfo::path(p: QLibraryInfo::QmlImportsPath));
167
168 QStringList qmldirFiles = parser.values(option: qmldirOption);
169
170 QString outputCppFile;
171 if (!parser.isSet(option: outputCppOption)) {
172 outputCppFile = url.first(n: url.size() - 3) + u"cpp"_s;
173 } else {
174 outputCppFile = parser.value(option: outputCppOption);
175 }
176
177 QString outputHFile;
178 if (!parser.isSet(option: outputHOption)) {
179 outputHFile = url.first(n: url.size() - 3) + u"h"_s;
180 } else {
181 outputHFile = parser.value(option: outputHOption);
182 }
183
184 if (!parser.isSet(option: resourceOption)) {
185 fprintf(stderr, format: "No resource paths for file: %s\n", qPrintable(inputFile));
186 return EXIT_FAILURE;
187 }
188
189 // main logic:
190 QQmlJS::Engine engine;
191 QQmlJS::Lexer lexer(&engine);
192 lexer.setCode(code: sourceCode, /*lineno = */ 1);
193 QQmlJS::Parser qmlParser(&engine);
194 if (!qmlParser.parse()) {
195 const auto diagnosticMessages = qmlParser.diagnosticMessages();
196 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
197 fprintf(stderr, "%s\n",
198 qPrintable(QStringLiteral("%1:%2:%3: %4")
199 .arg(inputFile)
200 .arg(m.loc.startLine)
201 .arg(m.loc.startColumn)
202 .arg(m.message)));
203 }
204 return EXIT_FAILURE;
205 }
206
207 const QStringList resourceFiles = parser.values(option: resourceOption);
208 QQmlJSResourceFileMapper mapper(resourceFiles);
209 const QStringList metaResourceFiles = parser.values(option: metaResourceOption);
210 QQmlJSResourceFileMapper metaDataMapper(metaResourceFiles);
211
212 const auto firstQml = [](const QStringList &paths) {
213 auto it = std::find_if(first: paths.cbegin(), last: paths.cend(),
214 pred: [](const QString &x) { return x.endsWith(s: u".qml"_s); });
215 if (it == paths.cend())
216 return QString();
217 return *it;
218 };
219 // verify that we can map current file to qrc (then use the qrc path later)
220 const QStringList paths = mapper.resourcePaths(filter: QQmlJSResourceFileMapper::localFileFilter(file: url));
221 if (paths.isEmpty()) {
222 fprintf(stderr, format: "Failed to find a resource path for file: %s\n", qPrintable(inputFile));
223 return EXIT_FAILURE;
224 } else if (paths.size() > 1) {
225 bool good = !firstQml(paths).isEmpty();
226 good &= std::any_of(first: paths.cbegin(), last: paths.cend(),
227 pred: [](const QString &x) { return x.endsWith(s: u".h"_s); });
228 if (!good || paths.size() > 2) {
229 fprintf(stderr, format: "Unexpected resource paths for file: %s\n", qPrintable(inputFile));
230 return EXIT_FAILURE;
231 }
232 }
233
234 QmltcCompilerInfo info;
235 info.outputCppFile = parser.value(option: outputCppOption);
236 info.outputHFile = parser.value(option: outputHOption);
237 info.resourcePath = firstQml(paths);
238 info.outputNamespace = parser.value(option: namespaceOption);
239 info.exportMacro = parser.value(option: exportOption);
240 info.exportInclude = parser.value(option: exportIncludeOption);
241
242 if (info.outputCppFile.isEmpty()) {
243 fprintf(stderr, format: "An output C++ file is required. Pass one using --impl");
244 return EXIT_FAILURE;
245 }
246 if (info.outputHFile.isEmpty()) {
247 fprintf(stderr, format: "An output C++ header file is required. Pass one using --header");
248 return EXIT_FAILURE;
249 }
250
251 QQmlJSImporter importer { importPaths, &mapper };
252 importer.setMetaDataMapper(&metaDataMapper);
253 auto createQmltcVisitor = [](const QQmlJSScope::Ptr &root, QQmlJSImporter *importer,
254 QQmlJSLogger *logger, const QString &implicitImportDirectory,
255 const QStringList &qmldirFiles) -> QQmlJSImportVisitor * {
256 return new QmltcVisitor(root, importer, logger, implicitImportDirectory, qmldirFiles);
257 };
258 importer.setImportVisitorCreator(createQmltcVisitor);
259
260 QQmlJSLogger logger;
261 logger.setFileName(url);
262 logger.setCode(sourceCode);
263 setupLogger(logger);
264
265 QmltcVisitor visitor(QQmlJSScope::create(), &importer, &logger,
266 QQmlJSImportVisitor::implicitImportDirectory(localFile: url, mapper: &mapper), qmldirFiles);
267 visitor.setMode(QmltcVisitor::Compile);
268 QmltcTypeResolver typeResolver { &importer };
269 typeResolver.init(visitor: &visitor, program: qmlParser.rootNode());
270
271 if (logger.hasErrors())
272 return EXIT_FAILURE;
273
274 QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings();
275 if (!warnings.isEmpty()) {
276 logger.log(QStringLiteral("Type warnings occurred while compiling file:"), id: qmlImport,
277 srcLocation: QQmlJS::SourceLocation());
278 logger.processMessages(messages: warnings, id: qmlImport);
279 // Log_Import is critical for the compiler
280 return EXIT_FAILURE;
281 }
282
283 QmltcCompiler compiler(url, &typeResolver, &visitor, &logger);
284 compiler.compile(info);
285
286 if (logger.hasErrors())
287 return EXIT_FAILURE;
288
289 return EXIT_SUCCESS;
290}
291

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