diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmlcachegen/qmlcache.prf | 9 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.cpp | 294 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.pro | 24 |
3 files changed, 327 insertions, 0 deletions
diff --git a/tools/qmlcachegen/qmlcache.prf b/tools/qmlcachegen/qmlcache.prf new file mode 100644 index 0000000000..f151218811 --- /dev/null +++ b/tools/qmlcachegen/qmlcache.prf @@ -0,0 +1,9 @@ +qtPrepareTool(QML_CACHEGEN, qmlcachegen) + +qmlcachegen.input = QML_FILES +qmlcachegen.output = ${QMAKE_FILE_IN}c +qmlcachegen.commands = $$QML_CACHEGEN ${QMAKE_FILE_IN} +qmlcachegen.name = Generate QML Cache ${QMAKE_FILE_IN} +qmlcachegen.variable_out = AUX_QML_FILES + +QMAKE_EXTRA_COMPILERS += qmlcachegen diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp new file mode 100644 index 0000000000..84ad1c7778 --- /dev/null +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QCoreApplication> +#include <QStringList> +#include <QCommandLineParser> +#include <QFile> +#include <QFileInfo> +#include <QDateTime> + +#include <private/qqmlirbuilder_p.h> +#include <private/qv4isel_moth_p.h> +#include <private/qqmljsparser_p.h> + +QT_BEGIN_NAMESPACE +extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed; +QT_END_NAMESPACE + +struct Error +{ + QString message; + void print(); + Error augment(const QString &contextErrorMessage) const; +}; + +void Error::print() +{ + fprintf(stderr, "%s\n", qPrintable(message)); +} + +Error Error::augment(const QString &contextErrorMessage) const +{ + Error augmented; + augmented.message = contextErrorMessage + message; + return augmented; +} + +QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m) +{ + QString message; + message = fileName + QLatin1Char(':') + QString::number(m.loc.startLine) + QLatin1Char(':'); + if (m.loc.startColumn > 0) + message += QString::number(m.loc.startColumn) + QLatin1Char(':'); + + if (m.isError()) + message += QLatin1String(" error: "); + else + message += QLatin1String(" warning: "); + message += m.message; + return message; +} + +static bool compileQmlFile(const QString &inputFileName, QV4::EvalISelFactory *iselFactory, Error *error) +{ + QmlIR::Document irDocument(/*debugMode*/false); + + QString sourceCode; + { + QFile f(inputFileName); + if (!f.open(QIODevice::ReadOnly)) { + error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + sourceCode = QString::fromUtf8(f.readAll()); + if (f.error() != QFileDevice::NoError) { + error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + irDocument.jsModule.sourceTimeStamp = QFileInfo(f).lastModified().toMSecsSinceEpoch(); + } + + { + QSet<QString> illegalNames; // #### + QmlIR::IRBuilder irBuilder(illegalNames); + if (!irBuilder.generateFromQml(sourceCode, inputFileName, &irDocument)) { + for (const QQmlJS::DiagnosticMessage &parseError: qAsConst(irBuilder.errors)) { + if (!error->message.isEmpty()) + error->message += QLatin1Char('\n'); + error->message += diagnosticErrorMessage(inputFileName, parseError); + } + return false; + } + } + + { + QmlIR::JSCodeGen v4CodeGen(inputFileName, irDocument.code, &irDocument.jsModule, &irDocument.jsParserEngine, irDocument.program, /*import cache*/0, &irDocument.jsGenerator.stringTable); + for (QmlIR::Object *object: qAsConst(irDocument.objects)) { + if (object->functionsAndExpressions->count == 0) + continue; + QList<QmlIR::CompiledFunctionOrExpression> functionsToCompile; + for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe; foe = foe->next) { + foe->disableAcceleratedLookups = true; + functionsToCompile << *foe; + } + const QVector<int> runtimeFunctionIndices = v4CodeGen.generateJSCodeForFunctionsAndBindings(functionsToCompile); + QList<QQmlJS::DiagnosticMessage> jsErrors = v4CodeGen.errors(); + if (!jsErrors.isEmpty()) { + for (const QQmlJS::DiagnosticMessage &e: qAsConst(jsErrors)) { + if (!error->message.isEmpty()) + error->message += QLatin1Char('\n'); + error->message += diagnosticErrorMessage(inputFileName, e); + } + return false; + } + + QQmlJS::MemoryPool *pool = irDocument.jsParserEngine.pool(); + object->runtimeFunctionIndices.allocate(pool, runtimeFunctionIndices); + } + + QmlIR::QmlUnitGenerator generator; + + // ### translation binding simplification + + QScopedPointer<QV4::EvalInstructionSelection> isel(iselFactory->create(/*engine*/nullptr, /*executable allocator*/nullptr, &irDocument.jsModule, &irDocument.jsGenerator)); + // Disable lookups in non-standalone (aka QML) mode + isel->setUseFastLookups(false); + irDocument.javaScriptCompilationUnit = isel->compile(/*generate unit*/false); + // ### + QV4::CompiledData::ResolvedTypeReferenceMap dummyDependencies; + QV4::CompiledData::Unit *unit = generator.generate(irDocument, /*engine*/nullptr, dummyDependencies); + unit->flags |= QV4::CompiledData::Unit::StaticData; + irDocument.javaScriptCompilationUnit->data = unit; + + if (!irDocument.javaScriptCompilationUnit->saveToDisk(inputFileName, &error->message)) + return false; + + free(unit); + } + return true; +} + +static bool compileJSFile(const QString &inputFileName, QV4::EvalISelFactory *iselFactory, Error *error) +{ + QmlIR::Document irDocument(/*debugMode*/false); + + QString sourceCode; + { + QFile f(inputFileName); + if (!f.open(QIODevice::ReadOnly)) { + error->message = QLatin1String("Error opening ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + sourceCode = QString::fromUtf8(f.readAll()); + if (f.error() != QFileDevice::NoError) { + error->message = QLatin1String("Error reading from ") + inputFileName + QLatin1Char(':') + f.errorString(); + return false; + } + irDocument.jsModule.sourceTimeStamp = QFileInfo(f).lastModified().toMSecsSinceEpoch(); + } + + QQmlJS::Engine *engine = &irDocument.jsParserEngine; + QmlIR::ScriptDirectivesCollector directivesCollector(engine, &irDocument.jsGenerator); + QQmlJS::Directives *oldDirs = engine->directives(); + engine->setDirectives(&directivesCollector); + + QQmlJS::AST::Program *program = nullptr; + + { + QQmlJS::Lexer lexer(engine); + lexer.setCode(sourceCode, /*line*/1, /*parseAsBinding*/false); + QQmlJS::Parser parser(engine); + + bool parsed = parser.parseProgram(); + + for (const QQmlJS::DiagnosticMessage &parseError: parser.diagnosticMessages()) { + if (!error->message.isEmpty()) + error->message += QLatin1Char('\n'); + error->message += diagnosticErrorMessage(inputFileName, parseError); + } + + if (!parsed) { + engine->setDirectives(oldDirs); + return false; + } + + program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); + if (!program) { + lexer.setCode(QStringLiteral("undefined;"), 1, false); + parsed = parser.parseProgram(); + Q_ASSERT(parsed); + program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); + Q_ASSERT(program); + } + } + + { + QmlIR::JSCodeGen v4CodeGen(inputFileName, irDocument.code, &irDocument.jsModule, &irDocument.jsParserEngine, irDocument.program, /*import cache*/0, &irDocument.jsGenerator.stringTable); + v4CodeGen.generateFromProgram(inputFileName, sourceCode, program, &irDocument.jsModule, QQmlJS::Codegen::GlobalCode); + QList<QQmlJS::DiagnosticMessage> jsErrors = v4CodeGen.errors(); + if (!jsErrors.isEmpty()) { + for (const QQmlJS::DiagnosticMessage &e: qAsConst(jsErrors)) { + if (!error->message.isEmpty()) + error->message += QLatin1Char('\n'); + error->message += diagnosticErrorMessage(inputFileName, e); + } + engine->setDirectives(oldDirs); + return false; + } + + QmlIR::QmlUnitGenerator generator; + + // ### translation binding simplification + + QScopedPointer<QV4::EvalInstructionSelection> isel(iselFactory->create(/*engine*/nullptr, /*executable allocator*/nullptr, &irDocument.jsModule, &irDocument.jsGenerator)); + // Disable lookups in non-standalone (aka QML) mode + isel->setUseFastLookups(false); + irDocument.javaScriptCompilationUnit = isel->compile(/*generate unit*/false); + // ### + QV4::CompiledData::ResolvedTypeReferenceMap dummyDependencies; + QV4::CompiledData::Unit *unit = generator.generate(irDocument, /*engine*/nullptr, dummyDependencies); + unit->flags |= QV4::CompiledData::Unit::StaticData; + irDocument.javaScriptCompilationUnit->data = unit; + + if (!irDocument.javaScriptCompilationUnit->saveToDisk(inputFileName, &error->message)) { + engine->setDirectives(oldDirs); + return false; + } + + free(unit); + } + engine->setDirectives(oldDirs); + return true; +} + +int main(int argc, char **argv) +{ + // Produce reliably the same output for the same input by disabling QHash's random seeding. + qt_qhash_seed.testAndSetRelaxed(-1, 0); + + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral("qmlcachegen")); + QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addPositionalArgument(QStringLiteral("[qml file]"), + QStringLiteral("QML source file to generate cache for.")); + + parser.process(app); + + const QStringList sources = parser.positionalArguments(); + if (sources.count() > 1) { + fprintf(stderr, "%s\n", qPrintable(QStringLiteral("Too many input files specified: '") + sources.join(QStringLiteral("' '")) + QLatin1Char('\''))); + return EXIT_FAILURE; + } + const QString inputFile = sources.first(); + + QV4::Moth::ISelFactory interpreterISelFactory; + + Error error; + + if (inputFile.endsWith(QLatin1String(".qml"))) { + if (!compileQmlFile(inputFile, &interpreterISelFactory, &error)) { + error.augment(QLatin1String("Error compiling qml file: ")).print(); + return EXIT_FAILURE; + } + } else if (inputFile.endsWith(QLatin1String(".js"))) { + if (!compileJSFile(inputFile, &interpreterISelFactory, &error)) { + error.augment(QLatin1String("Error compiling qml file: ")).print(); + return EXIT_FAILURE; + } + } else { + fprintf(stderr, "Ignoring %s input file as it is not QML source code - maybe remove from QML_FILES?\n", qPrintable(inputFile)); } + + + return EXIT_SUCCESS; +} diff --git a/tools/qmlcachegen/qmlcachegen.pro b/tools/qmlcachegen/qmlcachegen.pro new file mode 100644 index 0000000000..48e08b0946 --- /dev/null +++ b/tools/qmlcachegen/qmlcachegen.pro @@ -0,0 +1,24 @@ +option(host_build) + +QT = qmldevtools-private +DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII + +SOURCES = qmlcachegen.cpp +TARGET = qmlcachegen + +BUILD_INTEGRATION = qmlcache.prf +!force_independent { + qmake_integration.input = BUILD_INTEGRATION + qmake_integration.output = $$[QT_HOST_DATA]/mkspecs/features/${QMAKE_FILE_BASE}.prf + qmake_integration.commands = $$QMAKE_COPY ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} + qmake_integration.name = COPY ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} + qmake_integration.CONFIG = no_clean no_link + !contains(TEMPLATE, vc.*): qmake_integration.variable_out = PRE_TARGETDEPS + QMAKE_EXTRA_COMPILERS += qmake_integration +} + +qmake_integration_installs.files = $$BUILD_INTEGRATION +qmake_integration_installs.path = $$[QT_HOST_DATA]/mkspecs/features +INSTALLS += qmake_integration_installs + +load(qt_tool) |