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

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