aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmlcachegen
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@qt.io>2016-12-30 16:11:20 +0100
committerSimon Hausmann <simon.hausmann@qt.io>2017-01-31 17:09:15 +0000
commit27c678cea66e3ab092b6788b6e403c232677041f (patch)
treeaa048eacbd299478ac3728ce4f858580b87e18ac /tools/qmlcachegen
parent4eca12e9f5b6e6b09d89cd4438e8557c5b66dbd1 (diff)
Added tool to generate QML cache files
This is an initial preview that will allow generating cache files ahead of time, with some limitations: * There's no support for import dependency resolution * Only ARMv7 is supported as cross-compile target Change-Id: I894237f55ba0c0a71f0ed5dda2ff6f7e5bd6603e Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'tools/qmlcachegen')
-rw-r--r--tools/qmlcachegen/qmlcache.prf9
-rw-r--r--tools/qmlcachegen/qmlcachegen.cpp294
-rw-r--r--tools/qmlcachegen/qmlcachegen.pro24
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)