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 | |
27 | static 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 | |
56 | int 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 | |