1// Copyright (C) 2016 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 <QCoreApplication>
5#include <QStringList>
6#include <QCommandLineParser>
7#include <QFile>
8#include <QFileInfo>
9#include <QDateTime>
10#include <QHashFunctions>
11#include <QSaveFile>
12#include <QScopedPointer>
13#include <QScopeGuard>
14#include <QLibraryInfo>
15#include <QLoggingCategory>
16
17#include <private/qqmlirbuilder_p.h>
18#include <private/qqmljscompiler_p.h>
19#include <private/qqmljslexer_p.h>
20#include <private/qqmljsloadergenerator_p.h>
21#include <private/qqmljsparser_p.h>
22#include <private/qqmljsresourcefilemapper_p.h>
23#include <private/qqmljsutils_p.h>
24#include <private/qresourcerelocater_p.h>
25
26#include <algorithm>
27
28using namespace Qt::Literals::StringLiterals;
29
30static bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QStringList &arguments)
31{
32 allArguments.reserve(asize: arguments.size());
33 for (const QString &argument : arguments) {
34 // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
35 if (argument.startsWith(c: u'@')) {
36 QString optionsFile = argument;
37 optionsFile.remove(i: 0, len: 1);
38 if (optionsFile.isEmpty()) {
39 fprintf(stderr, format: "The @ option requires an input file");
40 return false;
41 }
42 QFile f(optionsFile);
43 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
44 fprintf(stderr, format: "Cannot open options file specified with @");
45 return false;
46 }
47 while (!f.atEnd()) {
48 QString line = QString::fromLocal8Bit(ba: f.readLine().trimmed());
49 if (!line.isEmpty())
50 allArguments << line;
51 }
52 } else {
53 allArguments << argument;
54 }
55 }
56 return true;
57}
58
59int main(int argc, char **argv)
60{
61 // Produce reliably the same output for the same input by disabling QHash's random seeding.
62 QHashSeed::setDeterministicGlobalSeed();
63
64 QCoreApplication app(argc, argv);
65 QCoreApplication::setApplicationName("qmlcachegen"_L1);
66 QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
67
68 QCommandLineParser parser;
69 parser.addHelpOption();
70 parser.addVersionOption();
71
72 QCommandLineOption bareOption("bare"_L1, QCoreApplication::translate(context: "main", key: "Do not include default import directories. This may be used to run qmlcachegen on a project using a different Qt version."));
73 parser.addOption(commandLineOption: bareOption);
74 QCommandLineOption filterResourceFileOption("filter-resource-file"_L1, QCoreApplication::translate(context: "main", key: "Filter out QML/JS files from a resource file that can be cached ahead of time instead"));
75 parser.addOption(commandLineOption: filterResourceFileOption);
76 QCommandLineOption resourceFileMappingOption("resource-file-mapping"_L1, QCoreApplication::translate(context: "main", key: "Path from original resource file to new one"), QCoreApplication::translate(context: "main", key: "old-name=new-name"));
77 parser.addOption(commandLineOption: resourceFileMappingOption);
78 QCommandLineOption resourceOption("resource"_L1, QCoreApplication::translate(context: "main", key: "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate(context: "main", key: "resource-file-name"));
79 parser.addOption(commandLineOption: resourceOption);
80 QCommandLineOption resourcePathOption("resource-path"_L1, QCoreApplication::translate(context: "main", key: "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate(context: "main", key: "resource-path"));
81 parser.addOption(commandLineOption: resourcePathOption);
82 QCommandLineOption resourceNameOption("resource-name"_L1, QCoreApplication::translate(context: "main", key: "Required to generate qmlcache_loader without qrc files. This is the name of the Qt resource the input files belong to."), QCoreApplication::translate(context: "main", key: "compiled-file-list"));
83 parser.addOption(commandLineOption: resourceNameOption);
84 QCommandLineOption directCallsOption("direct-calls"_L1, QCoreApplication::translate(context: "main", key: "This option is ignored."));
85 directCallsOption.setFlags(QCommandLineOption::HiddenFromHelp);
86 parser.addOption(commandLineOption: directCallsOption);
87 QCommandLineOption staticOption("static"_L1, QCoreApplication::translate(context: "main", key: "This option is ignored."));
88 staticOption.setFlags(QCommandLineOption::HiddenFromHelp);
89 parser.addOption(commandLineOption: staticOption);
90 QCommandLineOption importsOption("i"_L1, QCoreApplication::translate(context: "main", key: "Import extra qmldir"), QCoreApplication::translate(context: "main", key: "qmldir file"));
91 parser.addOption(commandLineOption: importsOption);
92 QCommandLineOption importPathOption("I"_L1, QCoreApplication::translate(context: "main", key: "Look for QML modules in specified directory"), QCoreApplication::translate(context: "main", key: "import directory"));
93 parser.addOption(commandLineOption: importPathOption);
94 QCommandLineOption onlyBytecode("only-bytecode"_L1, QCoreApplication::translate(context: "main", key: "Generate only byte code for bindings and functions, no C++ code"));
95 parser.addOption(commandLineOption: onlyBytecode);
96 QCommandLineOption verboseOption("verbose"_L1, QCoreApplication::translate(context: "main", key: "Output compile warnings"));
97 parser.addOption(commandLineOption: verboseOption);
98 QCommandLineOption warningsAreErrorsOption("warnings-are-errors"_L1, QCoreApplication::translate(context: "main", key: "Treat warnings as errors"));
99 parser.addOption(commandLineOption: warningsAreErrorsOption);
100
101 QCommandLineOption validateBasicBlocksOption("validate-basic-blocks"_L1, QCoreApplication::translate(context: "main", key: "Performs checks on the basic blocks of a function compiled ahead of time to validate its structure and coherence"));
102 parser.addOption(commandLineOption: validateBasicBlocksOption);
103
104 QCommandLineOption dumpAotStatsOption("dump-aot-stats"_L1, QCoreApplication::translate(context: "main", key: "Dumps statistics about ahead-of-time compilation of bindings and functions"));
105 parser.addOption(commandLineOption: dumpAotStatsOption);
106 QCommandLineOption moduleIdOption("module-id"_L1, QCoreApplication::translate(context: "main", key: "Identifies the module of the qml file being compiled for aot stats"), QCoreApplication::translate(context: "main", key: "id"));
107 parser.addOption(commandLineOption: moduleIdOption);
108
109 QCommandLineOption outputFileOption("o"_L1, QCoreApplication::translate(context: "main", key: "Output file name"), QCoreApplication::translate(context: "main", key: "file name"));
110 parser.addOption(commandLineOption: outputFileOption);
111
112 parser.addPositionalArgument(name: "[qml file]"_L1, description: "QML source file to generate cache for."_L1);
113
114 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
115
116
117 QStringList arguments;
118 if (!argumentsFromCommandLineAndFile(allArguments&: arguments, arguments: app.arguments()))
119 return EXIT_FAILURE;
120
121 parser.process(arguments);
122
123 enum Output {
124 GenerateCpp,
125 GenerateCacheFile,
126 GenerateLoader,
127 GenerateLoaderStandAlone,
128 } target = GenerateCacheFile;
129
130 QString outputFileName;
131 if (parser.isSet(option: outputFileOption))
132 outputFileName = parser.value(option: outputFileOption);
133
134 if (outputFileName.endsWith(s: ".cpp"_L1)) {
135 target = GenerateCpp;
136 if (outputFileName.endsWith(s: "qmlcache_loader.cpp"_L1))
137 target = GenerateLoader;
138 }
139
140 if (target == GenerateLoader && parser.isSet(option: resourceNameOption))
141 target = GenerateLoaderStandAlone;
142
143 if (parser.isSet(option: dumpAotStatsOption) && !parser.isSet(option: moduleIdOption)) {
144 fprintf(stderr, format: "--dump-aot-stats set without setting --module-id");
145 return EXIT_FAILURE;
146 }
147
148 const QStringList sources = parser.positionalArguments();
149 if (sources.isEmpty()){
150 parser.showHelp();
151 } else if (sources.size() > 1 && (target != GenerateLoader && target != GenerateLoaderStandAlone)) {
152 fprintf(stderr, format: "%s\n", qPrintable("Too many input files specified: '"_L1 + sources.join("' '"_L1) + u'\''));
153 return EXIT_FAILURE;
154 }
155
156 const QString inputFile = !sources.isEmpty() ? sources.first() : QString();
157 if (outputFileName.isEmpty())
158 outputFileName = inputFile + u'c';
159
160 if (parser.isSet(option: filterResourceFileOption))
161 return qRelocateResourceFile(input: inputFile, output: outputFileName);
162
163 if (target == GenerateLoader) {
164 QQmlJSResourceFileMapper mapper(sources);
165
166 QQmlJSCompileError error;
167 if (!qQmlJSGenerateLoader(
168 compiledFiles: mapper.resourcePaths(filter: QQmlJSResourceFileMapper::allQmlJSFilter()),
169 outputFileName, resourceFileMappings: parser.values(option: resourceFileMappingOption), errorString: &error.message)) {
170 error.augment(contextErrorMessage: "Error generating loader stub: "_L1).print();
171 return EXIT_FAILURE;
172 }
173 return EXIT_SUCCESS;
174 }
175
176 if (target == GenerateLoaderStandAlone) {
177 QQmlJSCompileError error;
178 if (!qQmlJSGenerateLoader(compiledFiles: sources, outputFileName,
179 resourceFileMappings: parser.values(option: resourceNameOption), errorString: &error.message)) {
180 error.augment(contextErrorMessage: "Error generating loader stub: "_L1).print();
181 return EXIT_FAILURE;
182 }
183 return EXIT_SUCCESS;
184 }
185 QString inputFileUrl = inputFile;
186
187 QQmlJSSaveFunction saveFunction;
188 QQmlJSResourceFileMapper fileMapper(parser.values(option: resourceOption));
189 QString inputResourcePath = parser.value(option: resourcePathOption);
190
191 // If the user didn't specify the resource path corresponding to the file on disk being
192 // compiled, try to determine it from the resource file, if one was supplied.
193 if (inputResourcePath.isEmpty()) {
194 const QStringList resourcePaths = fileMapper.resourcePaths(
195 filter: QQmlJSResourceFileMapper::localFileFilter(file: inputFile));
196 if (target == GenerateCpp && resourcePaths.isEmpty()) {
197 fprintf(stderr, format: "No resource path for file: %s\n", qPrintable(inputFile));
198 return EXIT_FAILURE;
199 }
200
201 if (resourcePaths.size() == 1) {
202 inputResourcePath = resourcePaths.first();
203 } else if (target == GenerateCpp) {
204 fprintf(stderr, format: "Multiple resource paths for file %s. "
205 "Use the --%s option to disambiguate:\n",
206 qPrintable(inputFile),
207 qPrintable(resourcePathOption.names().first()));
208 for (const QString &resourcePath: resourcePaths)
209 fprintf(stderr, format: "\t%s\n", qPrintable(resourcePath));
210 return EXIT_FAILURE;
211 }
212 }
213
214 if (target == GenerateCpp) {
215 inputFileUrl = "qrc://"_L1 + inputResourcePath;
216 saveFunction = [inputResourcePath, outputFileName](
217 const QV4::CompiledData::SaveableUnitPointer &unit,
218 const QQmlJSAotFunctionMap &aotFunctions,
219 QString *errorString) {
220 return qSaveQmlJSUnitAsCpp(inputFileName: inputResourcePath, outputFileName, unit, aotFunctions, errorString);
221 };
222
223 } else {
224 saveFunction = [outputFileName](const QV4::CompiledData::SaveableUnitPointer &unit,
225 const QQmlJSAotFunctionMap &aotFunctions,
226 QString *errorString) {
227 Q_UNUSED(aotFunctions);
228 return unit.saveToDisk<char>(
229 writer: [&outputFileName, errorString](const char *data, quint32 size) {
230 return QV4::CompiledData::SaveableUnitPointer::writeDataToFile(
231 outputFileName, data, size, errorString);
232 });
233 };
234 }
235
236 if (inputFile.endsWith(s: ".qml"_L1)) {
237 QQmlJSCompileError error;
238 if (target != GenerateCpp || inputResourcePath.isEmpty() || parser.isSet(option: onlyBytecode)) {
239 if (!qCompileQmlFile(inputFileName: inputFile, saveFunction, aotCompiler: nullptr, error: &error,
240 /* storeSourceLocation */ false)) {
241 error.augment(contextErrorMessage: "Error compiling qml file: "_L1).print();
242 return EXIT_FAILURE;
243 }
244
245 if (parser.isSet(option: onlyBytecode)) {
246 QQmlJS::AotStats emptyStats;
247 emptyStats.saveToDisk(filepath: outputFileName + u".aotstats"_s);
248 }
249 } else {
250 QStringList importPaths;
251
252 if (parser.isSet(option: resourceOption)) {
253 importPaths.append(t: ":/qt-project.org/imports"_L1);
254 importPaths.append(t: ":/qt/qml"_L1);
255 };
256
257 if (parser.isSet(option: importPathOption))
258 importPaths.append(other: parser.values(option: importPathOption));
259
260 if (!parser.isSet(option: bareOption))
261 importPaths.append(t: QLibraryInfo::path(p: QLibraryInfo::QmlImportsPath));
262
263 QQmlJSImporter importer(
264 importPaths, parser.isSet(option: resourceOption) ? &fileMapper : nullptr);
265 QQmlJSLogger logger;
266 logger.setFilePath(inputFile);
267
268 // Always trigger the qFatal() on "pragma Strict" violations.
269 logger.setCategoryLevel(id: qmlCompiler, level: QtWarningMsg);
270 logger.setCategoryIgnored(id: qmlCompiler, error: false);
271 logger.setCategoryFatal(id: qmlCompiler, error: true);
272
273 if (!parser.isSet(option: verboseOption) && !parser.isSet(option: warningsAreErrorsOption))
274 logger.setSilent(true);
275
276 QQmlJSAotCompiler cppCodeGen(
277 &importer, u':' + inputResourcePath,
278 QQmlJSUtils::cleanPaths(paths: parser.values(option: importsOption)), &logger);
279
280 if (parser.isSet(option: dumpAotStatsOption)) {
281 QQmlJS::QQmlJSAotCompilerStats::setRecordAotStats(true);
282 QQmlJS::QQmlJSAotCompilerStats::setModuleId(parser.value(option: moduleIdOption));
283 QQmlJS::QQmlJSAotCompilerStats::registerFile(filepath: inputFile);
284 }
285
286 if (parser.isSet(option: validateBasicBlocksOption))
287 cppCodeGen.m_flags.setFlag(flag: QQmlJSAotCompiler::ValidateBasicBlocks);
288
289 if (!qCompileQmlFile(inputFileName: inputFile, saveFunction, aotCompiler: &cppCodeGen, error: &error,
290 /* storeSourceLocation */ true)) {
291 error.augment(contextErrorMessage: "Error compiling qml file: "_L1).print();
292 return EXIT_FAILURE;
293 }
294
295 QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings();
296
297 if (!warnings.isEmpty()) {
298 logger.log(message: "Type warnings occurred while compiling file:"_L1,
299 id: qmlImport, srcLocation: QQmlJS::SourceLocation());
300 logger.processMessages(messages: warnings, id: qmlImport);
301 if (parser.isSet(option: warningsAreErrorsOption))
302 return EXIT_FAILURE;
303 }
304
305 if (parser.isSet(option: dumpAotStatsOption))
306 QQmlJS::QQmlJSAotCompilerStats::instance()->saveToDisk(filepath: outputFileName + u".aotstats"_s);
307 }
308 } else if (inputFile.endsWith(s: ".js"_L1) || inputFile.endsWith(s: ".mjs"_L1)) {
309 QQmlJSCompileError error;
310 if (!qCompileJSFile(inputFileName: inputFile, inputFileUrl, saveFunction, error: &error)) {
311 error.augment(contextErrorMessage: "Error compiling js file: "_L1).print();
312 return EXIT_FAILURE;
313 }
314 } else {
315 fprintf(stderr, format: "Ignoring %s input file as it is not QML source code - maybe remove from QML_FILES?\n", qPrintable(inputFile));
316 if (parser.isSet(option: warningsAreErrorsOption))
317 return EXIT_FAILURE;
318 }
319
320 return EXIT_SUCCESS;
321}
322

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/tools/qmlcachegen/qmlcachegen.cpp