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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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