1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QCoreApplication> |
30 | #include <QStringList> |
31 | #include <QCommandLineParser> |
32 | #include <QFile> |
33 | #include <QFileInfo> |
34 | #include <QDateTime> |
35 | #include <QHashFunctions> |
36 | #include <QSaveFile> |
37 | #include <QScopedPointer> |
38 | #include <QScopeGuard> |
39 | |
40 | #include <private/qqmlirbuilder_p.h> |
41 | #include <private/qqmljsparser_p.h> |
42 | #include <private/qqmljslexer_p.h> |
43 | |
44 | #include "resourcefilemapper.h" |
45 | |
46 | #include <algorithm> |
47 | |
48 | using namespace QQmlJS; |
49 | |
50 | int filterResourceFile(const QString &input, const QString &output); |
51 | bool generateLoader(const QStringList &compiledFiles, const QString &output, |
52 | const QStringList &resourceFileMappings, QString *errorString); |
53 | QString symbolNamespaceForPath(const QString &relativePath); |
54 | |
55 | QSet<QString> illegalNames; |
56 | |
57 | void setupIllegalNames() |
58 | { |
59 | for (const char **g = QV4::Compiler::Codegen::s_globalNames; *g != nullptr; ++g) |
60 | illegalNames.insert(value: QString::fromLatin1(str: *g)); |
61 | } |
62 | |
63 | struct Error |
64 | { |
65 | QString message; |
66 | void print(); |
67 | Error augment(const QString &contextErrorMessage) const; |
68 | void appendDiagnostics(const QString &inputFileName, const QList<QQmlJS::DiagnosticMessage> &diagnostics); |
69 | void appendDiagnostic(const QString &inputFileName, const DiagnosticMessage &diagnostic); |
70 | }; |
71 | |
72 | void Error::print() |
73 | { |
74 | fprintf(stderr, format: "%s\n" , qPrintable(message)); |
75 | } |
76 | |
77 | Error Error::augment(const QString &contextErrorMessage) const |
78 | { |
79 | Error augmented; |
80 | augmented.message = contextErrorMessage + message; |
81 | return augmented; |
82 | } |
83 | |
84 | QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m) |
85 | { |
86 | QString message; |
87 | message = fileName + QLatin1Char(':') + QString::number(m.loc.startLine) + QLatin1Char(':'); |
88 | if (m.loc.startColumn > 0) |
89 | message += QString::number(m.loc.startColumn) + QLatin1Char(':'); |
90 | |
91 | if (m.isError()) |
92 | message += QLatin1String(" error: " ); |
93 | else |
94 | message += QLatin1String(" warning: " ); |
95 | message += m.message; |
96 | return message; |
97 | } |
98 | |
99 | void Error::appendDiagnostic(const QString &inputFileName, const DiagnosticMessage &diagnostic) |
100 | { |
101 | if (!message.isEmpty()) |
102 | message += QLatin1Char('\n'); |
103 | message += diagnosticErrorMessage(fileName: inputFileName, m: diagnostic); |
104 | } |
105 | |
106 | void Error::appendDiagnostics(const QString &inputFileName, const QList<DiagnosticMessage> &diagnostics) |
107 | { |
108 | for (const QQmlJS::DiagnosticMessage &diagnostic: diagnostics) |
109 | appendDiagnostic(inputFileName, diagnostic); |
110 | } |
111 | |
112 | // Ensure that ListElement objects keep all property assignments in their string form |
113 | static void annotateListElements(QmlIR::Document *document) |
114 | { |
115 | QStringList listElementNames; |
116 | |
117 | for (const QV4::CompiledData::Import *import : qAsConst(t&: document->imports)) { |
118 | const QString uri = document->stringAt(index: import->uriIndex); |
119 | if (uri != QStringLiteral("QtQml.Models" ) && uri != QStringLiteral("QtQuick" )) |
120 | continue; |
121 | |
122 | QString listElementName = QStringLiteral("ListElement" ); |
123 | const QString qualifier = document->stringAt(index: import->qualifierIndex); |
124 | if (!qualifier.isEmpty()) { |
125 | listElementName.prepend(c: QLatin1Char('.')); |
126 | listElementName.prepend(s: qualifier); |
127 | } |
128 | listElementNames.append(t: listElementName); |
129 | } |
130 | |
131 | if (listElementNames.isEmpty()) |
132 | return; |
133 | |
134 | for (QmlIR::Object *object : qAsConst(t&: document->objects)) { |
135 | if (!listElementNames.contains(str: document->stringAt(index: object->inheritedTypeNameIndex))) |
136 | continue; |
137 | for (QmlIR::Binding *binding = object->firstBinding(); binding; binding = binding->next) { |
138 | if (binding->type != QV4::CompiledData::Binding::Type_Script) |
139 | continue; |
140 | binding->stringIndex = document->registerString(str: object->bindingAsString(doc: document, scriptIndex: binding->value.compiledScriptIndex)); |
141 | } |
142 | } |
143 | } |
144 | |
145 | static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, Error *error) |
146 | { |
147 | for (QmlIR::Object *object: qAsConst(t: doc.objects)) { |
148 | for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) { |
149 | if (binding->type != QV4::CompiledData::Binding::Type_Script) |
150 | continue; |
151 | const QString propName = doc.stringAt(index: binding->propertyNameIndex); |
152 | if (!propName.startsWith(s: QLatin1String("on" )) |
153 | || propName.length() < 3 |
154 | || !propName.at(i: 2).isUpper()) |
155 | continue; |
156 | auto compiledFunction = doc.jsModule.functions.value(i: object->runtimeFunctionIndices.at(index: binding->value.compiledScriptIndex)); |
157 | if (!compiledFunction) |
158 | continue; |
159 | if (compiledFunction->usesArgumentsObject == QV4::Compiler::Context::ArgumentsObjectUsed) { |
160 | error->message = QLatin1Char(':') + QString::number(compiledFunction->line) + QLatin1Char(':'); |
161 | if (compiledFunction->column > 0) |
162 | error->message += QString::number(compiledFunction->column) + QLatin1Char(':'); |
163 | |
164 | error->message += QLatin1String(" error: The use of eval() or the use of the arguments object in signal handlers is\n" |
165 | "not supported when compiling qml files ahead of time. That is because it's ambiguous if \n" |
166 | "any signal parameter is called \"arguments\". Similarly the string passed to eval might use\n" |
167 | "\"arguments\". Unfortunately we cannot distinguish between it being a parameter or the\n" |
168 | "JavaScript arguments object at this point.\n" |
169 | "Consider renaming the parameter of the signal if applicable or moving the code into a\n" |
170 | "helper function." ); |
171 | return false; |
172 | } |
173 | } |
174 | } |
175 | return true; |
176 | } |
177 | |
178 | using SaveFunction = std::function<bool(const QV4::CompiledData::SaveableUnitPointer &, QString *)>; |
179 | |
180 | static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFunction, Error *error) |
181 | { |
182 | QmlIR::Document irDocument(/*debugMode*/false); |
183 | |
184 | QString sourceCode; |
185 | { |
186 | QFile f(inputFileName); |
187 | if (!f.open(flags: QIODevice::ReadOnly)) { |
188 | error->message = QLatin1String("Error opening " ) + inputFileName + QLatin1Char(':') + f.errorString(); |
189 | return false; |
190 | } |
191 | sourceCode = QString::fromUtf8(str: f.readAll()); |
192 | if (f.error() != QFileDevice::NoError) { |
193 | error->message = QLatin1String("Error reading from " ) + inputFileName + QLatin1Char(':') + f.errorString(); |
194 | return false; |
195 | } |
196 | } |
197 | |
198 | { |
199 | QmlIR::IRBuilder irBuilder(illegalNames); |
200 | if (!irBuilder.generateFromQml(code: sourceCode, url: inputFileName, output: &irDocument)) { |
201 | error->appendDiagnostics(inputFileName, diagnostics: irBuilder.errors); |
202 | return false; |
203 | } |
204 | } |
205 | |
206 | annotateListElements(document: &irDocument); |
207 | |
208 | { |
209 | QmlIR::JSCodeGen v4CodeGen(&irDocument, illegalNames); |
210 | for (QmlIR::Object *object: qAsConst(t&: irDocument.objects)) { |
211 | if (object->functionsAndExpressions->count == 0) |
212 | continue; |
213 | QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile; |
214 | for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe; foe = foe->next) |
215 | functionsToCompile << *foe; |
216 | const QVector<int> runtimeFunctionIndices = v4CodeGen.generateJSCodeForFunctionsAndBindings(functions: functionsToCompile); |
217 | if (v4CodeGen.hasError()) { |
218 | error->appendDiagnostic(inputFileName, diagnostic: v4CodeGen.error()); |
219 | return false; |
220 | } |
221 | |
222 | QQmlJS::MemoryPool *pool = irDocument.jsParserEngine.pool(); |
223 | object->runtimeFunctionIndices.allocate(pool, vector: runtimeFunctionIndices); |
224 | } |
225 | |
226 | if (!checkArgumentsObjectUseInSignalHandlers(doc: irDocument, error)) { |
227 | *error = error->augment(contextErrorMessage: inputFileName); |
228 | return false; |
229 | } |
230 | |
231 | QmlIR::QmlUnitGenerator generator; |
232 | irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/generateUnitData: false); |
233 | generator.generate(output&: irDocument); |
234 | |
235 | const quint32 saveFlags |
236 | = QV4::CompiledData::Unit::StaticData |
237 | | QV4::CompiledData::Unit::PendingTypeCompilation; |
238 | QV4::CompiledData::SaveableUnitPointer saveable(irDocument.javaScriptCompilationUnit.data, |
239 | saveFlags); |
240 | if (!saveFunction(saveable, &error->message)) |
241 | return false; |
242 | } |
243 | return true; |
244 | } |
245 | |
246 | static bool compileJSFile(const QString &inputFileName, const QString &inputFileUrl, SaveFunction saveFunction, Error *error) |
247 | { |
248 | QV4::CompiledData::CompilationUnit unit; |
249 | |
250 | QString sourceCode; |
251 | { |
252 | QFile f(inputFileName); |
253 | if (!f.open(flags: QIODevice::ReadOnly)) { |
254 | error->message = QLatin1String("Error opening " ) + inputFileName + QLatin1Char(':') + f.errorString(); |
255 | return false; |
256 | } |
257 | sourceCode = QString::fromUtf8(str: f.readAll()); |
258 | if (f.error() != QFileDevice::NoError) { |
259 | error->message = QLatin1String("Error reading from " ) + inputFileName + QLatin1Char(':') + f.errorString(); |
260 | return false; |
261 | } |
262 | } |
263 | |
264 | const bool isModule = inputFileName.endsWith(s: QLatin1String(".mjs" )); |
265 | if (isModule) { |
266 | QList<QQmlJS::DiagnosticMessage> diagnostics; |
267 | // Precompiled files are relocatable and the final location will be set when loading. |
268 | QString url; |
269 | unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode, |
270 | sourceTimeStamp: QDateTime(), diagnostics: &diagnostics); |
271 | error->appendDiagnostics(inputFileName, diagnostics); |
272 | if (!unit.unitData()) |
273 | return false; |
274 | } else { |
275 | QmlIR::Document irDocument(/*debugMode*/false); |
276 | |
277 | QQmlJS::Engine *engine = &irDocument.jsParserEngine; |
278 | QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument); |
279 | QQmlJS::Directives *oldDirs = engine->directives(); |
280 | engine->setDirectives(&directivesCollector); |
281 | auto directivesGuard = qScopeGuard(f: [engine, oldDirs]{ |
282 | engine->setDirectives(oldDirs); |
283 | }); |
284 | |
285 | QQmlJS::AST::Program *program = nullptr; |
286 | |
287 | { |
288 | QQmlJS::Lexer lexer(engine); |
289 | lexer.setCode(code: sourceCode, /*line*/lineno: 1, /*parseAsBinding*/qmlMode: false); |
290 | QQmlJS::Parser parser(engine); |
291 | |
292 | bool parsed = parser.parseProgram(); |
293 | |
294 | error->appendDiagnostics(inputFileName, diagnostics: parser.diagnosticMessages()); |
295 | |
296 | if (!parsed) |
297 | return false; |
298 | |
299 | program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); |
300 | if (!program) { |
301 | lexer.setCode(QStringLiteral("undefined;" ), lineno: 1, qmlMode: false); |
302 | parsed = parser.parseProgram(); |
303 | Q_ASSERT(parsed); |
304 | program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); |
305 | Q_ASSERT(program); |
306 | } |
307 | } |
308 | |
309 | { |
310 | QmlIR::JSCodeGen v4CodeGen(&irDocument, illegalNames); |
311 | v4CodeGen.generateFromProgram(fileName: inputFileName, finalUrl: inputFileUrl, sourceCode, ast: program, |
312 | module: &irDocument.jsModule, contextType: QV4::Compiler::ContextType::ScriptImportedByQML); |
313 | if (v4CodeGen.hasError()) { |
314 | error->appendDiagnostic(inputFileName, diagnostic: v4CodeGen.error()); |
315 | return false; |
316 | } |
317 | |
318 | // Precompiled files are relocatable and the final location will be set when loading. |
319 | irDocument.jsModule.fileName.clear(); |
320 | irDocument.jsModule.finalUrl.clear(); |
321 | |
322 | irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/generateUnitData: false); |
323 | QmlIR::QmlUnitGenerator generator; |
324 | generator.generate(output&: irDocument); |
325 | unit = std::move(irDocument.javaScriptCompilationUnit); |
326 | } |
327 | } |
328 | |
329 | return saveFunction(QV4::CompiledData::SaveableUnitPointer(unit.data), &error->message); |
330 | } |
331 | |
332 | static bool saveUnitAsCpp(const QString &inputFileName, const QString &outputFileName, |
333 | const QV4::CompiledData::SaveableUnitPointer &unit, |
334 | QString *errorString) |
335 | { |
336 | #if QT_CONFIG(temporaryfile) |
337 | QSaveFile f(outputFileName); |
338 | #else |
339 | QFile f(outputFileName); |
340 | #endif |
341 | if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate)) { |
342 | *errorString = f.errorString(); |
343 | return false; |
344 | } |
345 | |
346 | auto writeStr = [&f, errorString](const QByteArray &data) { |
347 | if (f.write(data) != data.size()) { |
348 | *errorString = f.errorString(); |
349 | return false; |
350 | } |
351 | return true; |
352 | }; |
353 | |
354 | if (!writeStr("// " )) |
355 | return false; |
356 | |
357 | if (!writeStr(inputFileName.toUtf8())) |
358 | return false; |
359 | |
360 | if (!writeStr("\n" )) |
361 | return false; |
362 | |
363 | if (!writeStr(QByteArrayLiteral("namespace QmlCacheGeneratedCode {\nnamespace " ))) |
364 | return false; |
365 | |
366 | if (!writeStr(symbolNamespaceForPath(relativePath: inputFileName).toUtf8())) |
367 | return false; |
368 | |
369 | if (!writeStr(QByteArrayLiteral(" {\nextern const unsigned char qmlData alignas(16) [] = {\n" ))) |
370 | return false; |
371 | |
372 | unit.saveToDisk<uchar>(writer: [&writeStr](const uchar *begin, quint32 size) { |
373 | QByteArray hexifiedData; |
374 | { |
375 | QTextStream stream(&hexifiedData); |
376 | const uchar *end = begin + size; |
377 | stream << Qt::hex; |
378 | int col = 0; |
379 | for (const uchar *data = begin; data < end; ++data, ++col) { |
380 | if (data > begin) |
381 | stream << ','; |
382 | if (col % 8 == 0) { |
383 | stream << '\n'; |
384 | col = 0; |
385 | } |
386 | stream << "0x" << *data; |
387 | } |
388 | stream << '\n'; |
389 | } |
390 | return writeStr(hexifiedData); |
391 | }); |
392 | |
393 | |
394 | |
395 | if (!writeStr("};\n}\n}\n" )) |
396 | return false; |
397 | |
398 | #if QT_CONFIG(temporaryfile) |
399 | if (!f.commit()) { |
400 | *errorString = f.errorString(); |
401 | return false; |
402 | } |
403 | #endif |
404 | |
405 | return true; |
406 | } |
407 | |
408 | int main(int argc, char **argv) |
409 | { |
410 | // Produce reliably the same output for the same input by disabling QHash's random seeding. |
411 | qSetGlobalQHashSeed(newSeed: 0); |
412 | |
413 | QCoreApplication app(argc, argv); |
414 | QCoreApplication::setApplicationName(QStringLiteral("qmlcachegen" )); |
415 | QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); |
416 | |
417 | QCommandLineParser parser; |
418 | parser.addHelpOption(); |
419 | parser.addVersionOption(); |
420 | |
421 | 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" )); |
422 | parser.addOption(commandLineOption: filterResourceFileOption); |
423 | 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" )); |
424 | parser.addOption(commandLineOption: resourceFileMappingOption); |
425 | 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" )); |
426 | parser.addOption(commandLineOption: resourceOption); |
427 | 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" )); |
428 | parser.addOption(commandLineOption: resourcePathOption); |
429 | |
430 | QCommandLineOption outputFileOption(QStringLiteral("o" ), QCoreApplication::translate(context: "main" , key: "Output file name" ), QCoreApplication::translate(context: "main" , key: "file name" )); |
431 | parser.addOption(commandLineOption: outputFileOption); |
432 | |
433 | parser.addPositionalArgument(QStringLiteral("[qml file]" ), |
434 | QStringLiteral("QML source file to generate cache for." )); |
435 | |
436 | parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); |
437 | |
438 | parser.process(app); |
439 | |
440 | enum Output { |
441 | GenerateCpp, |
442 | GenerateCacheFile, |
443 | GenerateLoader |
444 | } target = GenerateCacheFile; |
445 | |
446 | QString outputFileName; |
447 | if (parser.isSet(option: outputFileOption)) |
448 | outputFileName = parser.value(option: outputFileOption); |
449 | |
450 | if (outputFileName.endsWith(s: QLatin1String(".cpp" ))) { |
451 | target = GenerateCpp; |
452 | if (outputFileName.endsWith(s: QLatin1String("qmlcache_loader.cpp" ))) |
453 | target = GenerateLoader; |
454 | } |
455 | |
456 | const QStringList sources = parser.positionalArguments(); |
457 | if (sources.isEmpty()){ |
458 | parser.showHelp(); |
459 | } else if (sources.count() > 1 && target != GenerateLoader) { |
460 | fprintf(stderr, format: "%s\n" , qPrintable(QStringLiteral("Too many input files specified: '" ) + sources.join(QStringLiteral("' '" )) + QLatin1Char('\''))); |
461 | return EXIT_FAILURE; |
462 | } |
463 | |
464 | const QString inputFile = sources.first(); |
465 | if (outputFileName.isEmpty()) |
466 | outputFileName = inputFile + QLatin1Char('c'); |
467 | |
468 | if (parser.isSet(option: filterResourceFileOption)) { |
469 | return filterResourceFile(input: inputFile, output: outputFileName); |
470 | } |
471 | |
472 | if (target == GenerateLoader) { |
473 | ResourceFileMapper mapper(sources); |
474 | |
475 | Error error; |
476 | if (!generateLoader(compiledFiles: mapper.qmlCompilerFiles(), output: outputFileName, |
477 | resourceFileMappings: parser.values(option: resourceFileMappingOption), errorString: &error.message)) { |
478 | error.augment(contextErrorMessage: QLatin1String("Error generating loader stub: " )).print(); |
479 | return EXIT_FAILURE; |
480 | } |
481 | return EXIT_SUCCESS; |
482 | } |
483 | |
484 | QString inputFileUrl = inputFile; |
485 | |
486 | SaveFunction saveFunction; |
487 | if (target == GenerateCpp) { |
488 | ResourceFileMapper fileMapper(parser.values(option: resourceOption)); |
489 | QString inputResourcePath = parser.value(option: resourcePathOption); |
490 | |
491 | if (!inputResourcePath.isEmpty() && !fileMapper.isEmpty()) { |
492 | fprintf(stderr, format: "--%s and --%s are mutually exclusive.\n" , |
493 | qPrintable(resourcePathOption.names().first()), |
494 | qPrintable(resourceOption.names().first())); |
495 | return EXIT_FAILURE; |
496 | } |
497 | |
498 | // If the user didn't specify the resource path corresponding to the file on disk being |
499 | // compiled, try to determine it from the resource file, if one was supplied. |
500 | if (inputResourcePath.isEmpty()) { |
501 | const QStringList resourcePaths = fileMapper.resourcePaths(fileName: inputFile); |
502 | if (resourcePaths.isEmpty()) { |
503 | fprintf(stderr, format: "No resource path for file: %s\n" , qPrintable(inputFile)); |
504 | return EXIT_FAILURE; |
505 | } |
506 | |
507 | if (resourcePaths.size() != 1) { |
508 | fprintf(stderr, format: "Multiple resource paths for file %s. " |
509 | "Use the --%s option to disambiguate:\n" , |
510 | qPrintable(inputFile), |
511 | qPrintable(resourcePathOption.names().first())); |
512 | for (const QString &resourcePath: resourcePaths) |
513 | fprintf(stderr, format: "\t%s\n" , qPrintable(resourcePath)); |
514 | return EXIT_FAILURE; |
515 | } |
516 | |
517 | inputResourcePath = resourcePaths.first(); |
518 | } |
519 | |
520 | inputFileUrl = QStringLiteral("qrc://" ) + inputResourcePath; |
521 | |
522 | saveFunction = [inputResourcePath, outputFileName]( |
523 | const QV4::CompiledData::SaveableUnitPointer &unit, |
524 | QString *errorString) { |
525 | return saveUnitAsCpp(inputFileName: inputResourcePath, outputFileName, unit, errorString); |
526 | }; |
527 | |
528 | } else { |
529 | saveFunction = [outputFileName](const QV4::CompiledData::SaveableUnitPointer &unit, |
530 | QString *errorString) { |
531 | return unit.saveToDisk<char>( |
532 | writer: [&outputFileName, errorString](const char *data, quint32 size) { |
533 | return QV4::CompiledData::SaveableUnitPointer::writeDataToFile( |
534 | outputFileName, data, size, errorString); |
535 | }); |
536 | }; |
537 | } |
538 | |
539 | setupIllegalNames(); |
540 | |
541 | |
542 | if (inputFile.endsWith(s: QLatin1String(".qml" ))) { |
543 | Error error; |
544 | if (!compileQmlFile(inputFileName: inputFile, saveFunction, error: &error)) { |
545 | error.augment(contextErrorMessage: QLatin1String("Error compiling qml file: " )).print(); |
546 | return EXIT_FAILURE; |
547 | } |
548 | } else if (inputFile.endsWith(s: QLatin1String(".js" )) || inputFile.endsWith(s: QLatin1String(".mjs" ))) { |
549 | Error error; |
550 | if (!compileJSFile(inputFileName: inputFile, inputFileUrl, saveFunction, error: &error)) { |
551 | error.augment(contextErrorMessage: QLatin1String("Error compiling js file: " )).print(); |
552 | return EXIT_FAILURE; |
553 | } |
554 | } else { |
555 | fprintf(stderr, format: "Ignoring %s input file as it is not QML source code - maybe remove from QML_FILES?\n" , qPrintable(inputFile)); |
556 | } |
557 | |
558 | return EXIT_SUCCESS; |
559 | } |
560 | |