diff options
Diffstat (limited to 'tools/qmlcachegen')
-rw-r--r-- | tools/qmlcachegen/Qt5QuickCompilerConfig.cmake | 73 | ||||
-rw-r--r-- | tools/qmlcachegen/generateloader.cpp | 390 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcache.prf | 20 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.cpp | 247 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.pro | 15 | ||||
-rw-r--r-- | tools/qmlcachegen/qtquickcompiler.prf | 86 | ||||
-rw-r--r-- | tools/qmlcachegen/resourcefilemapper.cpp | 169 | ||||
-rw-r--r-- | tools/qmlcachegen/resourcefilemapper.h | 50 | ||||
-rw-r--r-- | tools/qmlcachegen/resourcefilter.cpp | 183 |
9 files changed, 1173 insertions, 60 deletions
diff --git a/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake new file mode 100644 index 0000000000..6fe1662995 --- /dev/null +++ b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake @@ -0,0 +1,73 @@ +include(CMakeParseArguments) + +function(QTQUICK_COMPILER_DETERMINE_OUTPUT_FILENAME outvariable filename) + file(RELATIVE_PATH relpath ${CMAKE_CURRENT_SOURCE_DIR} ${filename}) + string(REPLACE ".qml" "_qml" relpath ${relpath}) + string(REPLACE ".js" "_js" relpath ${relpath}) + string(REPLACE "/" "_" relpath ${relpath}) + set(${outvariable} ${CMAKE_CURRENT_BINARY_DIR}/${relpath}.cpp PARENT_SCOPE) +endfunction() + +function(QTQUICK_COMPILER_ADD_RESOURCES outfiles) + set(options) + set(oneValueArgs) + set(multiValueArgs OPTIONS) + + cmake_parse_arguments(_RCC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + find_package(Qt5 COMPONENTS Qml Core) + + set(compiler_path "${_qt5Core_install_prefix}/bin/qmlcachegen") + + get_target_property(rcc_path ${Qt5Core_RCC_EXECUTABLE} IMPORTED_LOCATION) + + set(rcc_files ${_RCC_UNPARSED_ARGUMENTS}) + set(rcc_options ${_RCC_OPTIONS}) + set(filtered_rcc_files) + set(compiler_output) + set(rcc_files_with_compilation_units) + set(loader_flags) + + foreach(_resource ${rcc_files}) + get_filename_component(resource_base ${_resource} NAME_WE) + set(new_resource_file ${CMAKE_CURRENT_BINARY_DIR}/${resource_base}_qmlcache.qrc) + + get_filename_component(input_resource ${_resource} ABSOLUTE) + + execute_process(COMMAND ${compiler_path} -filter-resource-file ${input_resource} -o ${new_resource_file} OUTPUT_VARIABLE remaining_files) + if(remaining_files) + list(APPEND filtered_rcc_files ${new_resource_file}) + list(APPEND loader_flags "--resource-file-mapping=${_resource}=${new_resource_file}") + else() + list(APPEND loader_flags "--resource-file-mapping=${_resource}") + endif() + + set(rcc_file_with_compilation_units) + + exec_program(${rcc_path} ARGS -list ${input_resource} OUTPUT_VARIABLE rcc_contents) + string(REGEX REPLACE "[\r\n]+" ";" rcc_contents ${rcc_contents}) + foreach(it ${rcc_contents}) + get_filename_component(extension ${it} EXT) + if("x${extension}" STREQUAL "x.qml" OR "x${extension}" STREQUAL "x.js" OR "x${extension}" STREQUAL "x.ui.qml") + qtquick_compiler_determine_output_filename(output_file ${it}) + add_custom_command(OUTPUT ${output_file} COMMAND ${compiler_path} ARGS --resource=${input_resource} ${it} -o ${output_file} DEPENDS ${it}) + list(APPEND compiler_output ${output_file}) + set(rcc_file_with_compilation_units ${input_resource}) + endif() + endforeach() + + if(rcc_file_with_compilation_units) + list(APPEND rcc_files_with_compilation_units ${rcc_file_with_compilation_units}) + endif() + endforeach() + + if(rcc_files_with_compilation_units) + set(loader_source ${CMAKE_CURRENT_BINARY_DIR}/qmlcache_loader.cpp) + add_custom_command(OUTPUT ${loader_source} COMMAND ${compiler_path} ARGS ${loader_flags} ${rcc_files_with_compilation_units} -o ${loader_source} DEPENDS ${rcc_files_with_compilation_units}) + list(APPEND compiler_output ${loader_source}) + endif() + + qt5_add_resources(output_resources ${filtered_rcc_files} OPTIONS ${options}) + set(${outfiles} ${output_resources} ${compiler_output} PARENT_SCOPE) +endfunction() + diff --git a/tools/qmlcachegen/generateloader.cpp b/tools/qmlcachegen/generateloader.cpp new file mode 100644 index 0000000000..1a0b987c64 --- /dev/null +++ b/tools/qmlcachegen/generateloader.cpp @@ -0,0 +1,390 @@ +/**************************************************************************** +** +** 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 <QByteArray> +#include <QString> +#include <QStringList> +#include <QTextStream> +#include <QVector> +#include <QtEndian> +#include <QStack> +#include <QFileInfo> +#include <QSaveFile> + +QString symbolNamespaceForPath(const QString &relativePath) +{ + QFileInfo fi(relativePath); + QString symbol = fi.path(); + if (symbol.length() == 1 && symbol.startsWith(QLatin1Char('.'))) { + symbol.clear(); + } else { + symbol.replace(QLatin1Char('/'), QLatin1Char('_')); + symbol += QLatin1Char('_'); + } + symbol += fi.baseName(); + symbol += QLatin1Char('_'); + symbol += fi.suffix(); + symbol.replace(QLatin1Char('.'), QLatin1Char('_')); + symbol.replace(QLatin1Char('+'), QLatin1Char('_')); + symbol.replace(QLatin1Char('-'), QLatin1Char('_')); + return symbol; +} + +struct VirtualDirectoryEntry +{ + QString name; + QVector<VirtualDirectoryEntry*> dirEntries; + int firstChildIndex = -1; // node index inside generated data + bool isDirectory = true; + + VirtualDirectoryEntry() + {} + + ~VirtualDirectoryEntry() + { + qDeleteAll(dirEntries); + } + + VirtualDirectoryEntry *append(const QString &name) + { + for (QVector<VirtualDirectoryEntry*>::Iterator it = dirEntries.begin(), end = dirEntries.end(); + it != end; ++it) { + if ((*it)->name == name) + return *it; + } + + VirtualDirectoryEntry *subEntry = new VirtualDirectoryEntry; + subEntry->name = name; + dirEntries.append(subEntry); + return subEntry; + } + + void appendEmptyFile(const QString &name) + { + VirtualDirectoryEntry *subEntry = new VirtualDirectoryEntry; + subEntry->name = name; + subEntry->isDirectory = false; + dirEntries.append(subEntry); + } + + bool isEmpty() const { return dirEntries.isEmpty(); } +}; + +struct DataStream +{ + DataStream(QVector<unsigned char > *data = nullptr) + : data(data) + {} + + qint64 currentOffset() const { return data->size(); } + + DataStream &operator<<(quint16 value) + { + unsigned char d[2]; + qToBigEndian(value, d); + data->append(d[0]); + data->append(d[1]); + return *this; + } + DataStream &operator<<(quint32 value) + { + unsigned char d[4]; + qToBigEndian(value, d); + data->append(d[0]); + data->append(d[1]); + data->append(d[2]); + data->append(d[3]); + return *this; + } +private: + QVector<unsigned char> *data; +}; + +static bool resource_sort_order(const VirtualDirectoryEntry *lhs, const VirtualDirectoryEntry *rhs) +{ + return qt_hash(lhs->name) < qt_hash(rhs->name); +} + +struct ResourceTree +{ + ResourceTree() + {} + + void serialize(VirtualDirectoryEntry &root, QVector<unsigned char> *treeData, QVector<unsigned char> *stringData) + { + treeStream = DataStream(treeData); + stringStream = DataStream(stringData); + + QStack<VirtualDirectoryEntry *> directories; + + { + directories.push(&root); + while (!directories.isEmpty()) { + VirtualDirectoryEntry *entry = directories.pop(); + registerString(entry->name); + if (entry->isDirectory) + directories << entry->dirEntries; + } + } + + { + quint32 currentDirectoryIndex = 1; + directories.push(&root); + while (!directories.isEmpty()) { + VirtualDirectoryEntry *entry = directories.pop(); + entry->firstChildIndex = currentDirectoryIndex; + currentDirectoryIndex += entry->dirEntries.count(); + std::sort(entry->dirEntries.begin(), entry->dirEntries.end(), resource_sort_order); + + for (QVector<VirtualDirectoryEntry*>::ConstIterator child = entry->dirEntries.constBegin(), end = entry->dirEntries.constEnd(); + child != end; ++child) { + if ((*child)->isDirectory) + directories << *child; + } + } + } + + { + writeTreeEntry(&root); + directories.push(&root); + while (!directories.isEmpty()) { + VirtualDirectoryEntry *entry = directories.pop(); + + for (QVector<VirtualDirectoryEntry*>::ConstIterator child = entry->dirEntries.constBegin(), end = entry->dirEntries.constEnd(); + child != end; ++child) { + writeTreeEntry(*child); + if ((*child)->isDirectory) + directories << (*child); + } + } + } + } + +private: + DataStream treeStream; + DataStream stringStream; + QHash<QString, qint64> stringOffsets; + + void registerString(const QString &name) + { + if (stringOffsets.contains(name)) + return; + const qint64 offset = stringStream.currentOffset(); + stringOffsets.insert(name, offset); + + stringStream << quint16(name.length()) + << quint32(qt_hash(name)); + for (int i = 0; i < name.length(); ++i) + stringStream << quint16(name.at(i).unicode()); + } + + void writeTreeEntry(VirtualDirectoryEntry *entry) + { + treeStream << quint32(stringOffsets.value(entry->name)) + << quint16(entry->isDirectory ? 0x2 : 0x0); // Flags: File or Directory + + if (entry->isDirectory) { + treeStream << quint32(entry->dirEntries.count()) + << quint32(entry->firstChildIndex); + } else { + treeStream << quint16(QLocale::AnyCountry) << quint16(QLocale::C) + << quint32(0x0); + } + } +}; + +static QByteArray generateResourceDirectoryTree(QTextStream &code, const QStringList &qrcFiles) +{ + QByteArray call; + if (qrcFiles.isEmpty()) + return call; + + VirtualDirectoryEntry resourceDirs; + resourceDirs.name = QStringLiteral("/"); + + foreach (const QString &entry, qrcFiles) { + const QStringList segments = entry.split(QLatin1Char('/'), QString::SkipEmptyParts); + + VirtualDirectoryEntry *dirEntry = &resourceDirs; + + for (int i = 0; i < segments.count() - 1; ++i) + dirEntry = dirEntry->append(segments.at(i)); + dirEntry->appendEmptyFile(segments.last()); + } + + if (resourceDirs.isEmpty()) + return call; + + QVector<unsigned char> names; + QVector<unsigned char> tree; + ResourceTree().serialize(resourceDirs, &tree, &names); + + code << "static const unsigned char qt_resource_tree[] = {\n"; + for (int i = 0; i < tree.count(); ++i) { + code << uint(tree.at(i)); + if (i < tree.count() - 1) + code << ','; + if (i % 16 == 0) + code << '\n'; + } + code << "};\n"; + + code << "static const unsigned char qt_resource_names[] = {\n"; + for (int i = 0; i < names.count(); ++i) { + code << uint(names.at(i)); + if (i < names.count() - 1) + code << ','; + if (i % 16 == 0) + code << '\n'; + } + code << "};\n"; + + code << "static const unsigned char qt_resource_empty_payout[] = { 0, 0, 0, 0, 0 };\n"; + + code << "QT_BEGIN_NAMESPACE\n"; + code << "extern Q_CORE_EXPORT bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);\n"; + code << "QT_END_NAMESPACE\n"; + + call = "QT_PREPEND_NAMESPACE(qRegisterResourceData)(/*version*/0x01, qt_resource_tree, qt_resource_names, qt_resource_empty_payout);\n"; + + return call; +} + +static QString qtResourceNameForFile(const QString &fileName) +{ + QFileInfo fi(fileName); + QString name = fi.completeBaseName(); + if (name.isEmpty()) + name = fi.fileName(); + name.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_")); + return name; +} + +bool generateLoader(const QStringList &compiledFiles, const QString &outputFileName, const QStringList &resourceFileMappings, QString *errorString) +{ + QByteArray generatedLoaderCode; + + { + QTextStream stream(&generatedLoaderCode); + stream << "#include <QtQml/qqmlprivate.h>\n"; + stream << "#include <QtCore/qdir.h>\n"; + stream << "#include <QtCore/qurl.h>\n"; + stream << "\n"; + + QByteArray resourceRegisterCall = generateResourceDirectoryTree(stream, compiledFiles); + + stream << "namespace QmlCacheGeneratedCode {\n"; + for (int i = 0; i < compiledFiles.count(); ++i) { + const QString compiledFile = compiledFiles.at(i); + const QString ns = symbolNamespaceForPath(compiledFile); + stream << "namespace " << symbolNamespaceForPath(compiledFile) << " { \n"; + stream << " extern const unsigned char qmlData[];\n"; + stream << " const QQmlPrivate::CachedQmlUnit unit = {\n"; + stream << " reinterpret_cast<const QV4::CompiledData::Unit*>(&qmlData), nullptr, nullptr\n"; + stream << " };\n"; + stream << "}\n"; + } + + stream << "\n}\n"; + stream << "namespace {\n"; + + stream << "struct Registry {\n"; + stream << " Registry();\n"; + stream << " QHash<QString, const QQmlPrivate::CachedQmlUnit*> resourcePathToCachedUnit;\n"; + stream << " static const QQmlPrivate::CachedQmlUnit *lookupCachedUnit(const QUrl &url);\n"; + stream << "};\n\n"; + stream << "Q_GLOBAL_STATIC(Registry, unitRegistry);\n"; + stream << "\n\n"; + + stream << "Registry::Registry() {\n"; + + for (int i = 0; i < compiledFiles.count(); ++i) { + const QString qrcFile = compiledFiles.at(i); + const QString ns = symbolNamespaceForPath(qrcFile); + stream << " resourcePathToCachedUnit.insert(QStringLiteral(\"" << qrcFile << "\"), &QmlCacheGeneratedCode::" << ns << "::unit);\n"; + } + + stream << " QQmlPrivate::RegisterQmlUnitCacheHook registration;\n"; + stream << " registration.version = 0;\n"; + stream << " registration.lookupCachedQmlUnit = &lookupCachedUnit;\n"; + stream << " QQmlPrivate::qmlregister(QQmlPrivate::QmlUnitCacheHookRegistration, ®istration);\n"; + + if (!resourceRegisterCall.isEmpty()) + stream << resourceRegisterCall; + + stream << "}\n"; + stream << "const QQmlPrivate::CachedQmlUnit *Registry::lookupCachedUnit(const QUrl &url) {\n"; + stream << " if (url.scheme() != QLatin1String(\"qrc\"))\n"; + stream << " return nullptr;\n"; + stream << " QString resourcePath = QDir::cleanPath(url.path());\n"; + stream << " if (resourcePath.isEmpty())\n"; + stream << " return nullptr;\n"; + stream << " if (!resourcePath.startsWith(QLatin1Char('/')))\n"; + stream << " resourcePath.prepend(QLatin1Char('/'));\n"; + stream << " return unitRegistry()->resourcePathToCachedUnit.value(resourcePath, nullptr);\n"; + stream << "}\n"; + stream << "}\n"; + + for (const QString &mapping: resourceFileMappings) { + QString originalResourceFile = mapping; + QString newResourceFile; + const int mappingSplit = originalResourceFile.indexOf(QLatin1Char('=')); + if (mappingSplit != -1) { + newResourceFile = originalResourceFile.mid(mappingSplit + 1); + originalResourceFile.truncate(mappingSplit); + } + + const QString function = QLatin1String("qInitResources_") + qtResourceNameForFile(originalResourceFile); + + stream << QStringLiteral("int QT_MANGLE_NAMESPACE(%1)() {\n").arg(function); + stream << " ::unitRegistry();\n"; + if (!newResourceFile.isEmpty()) + stream << " Q_INIT_RESOURCE(" << qtResourceNameForFile(newResourceFile) << ");\n"; + stream << " return 1;\n"; + stream << "}\n"; + stream << "Q_CONSTRUCTOR_FUNCTION(QT_MANGLE_NAMESPACE(" << function << "));\n"; + } + } + + QSaveFile f(outputFileName); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + *errorString = f.errorString(); + return false; + } + + if (f.write(generatedLoaderCode) != generatedLoaderCode.size()) { + *errorString = f.errorString(); + return false; + } + + if (!f.commit()) { + *errorString = f.errorString(); + return false; + } + + return true; +} diff --git a/tools/qmlcachegen/qmlcache.prf b/tools/qmlcachegen/qmlcache.prf index 330da358b7..537eaf62ea 100644 --- a/tools/qmlcachegen/qmlcache.prf +++ b/tools/qmlcachegen/qmlcache.prf @@ -3,28 +3,10 @@ static { return() } -android { - message("QML cache generation ahead of time is not supported on Android") - return() -} - qtPrepareTool(QML_CACHEGEN, qmlcachegen, _ARCH_CHECK) isEmpty(TARGETPATH): error("Must set TARGETPATH (QML import name) for ahead-of-time QML cache generation") -!isEmpty(QT_TARGET_ARCH):QML_CACHEGEN_ARCH=$$QT_TARGET_ARCH -else:QML_CACHEGEN_ARCH=$$QT_ARCH - -!isEmpty(QT_TARGET_BUILDABI):QML_CACHEGEN_ABI=$$QT_TARGET_BUILDABI -else:QML_CACHEGEN_ABI=$$QT_BUILDABI - -QML_CACHEGEN_ARGS=--target-architecture=$$QML_CACHEGEN_ARCH --target-abi=$$QML_CACHEGEN_ABI - -!system($$QML_CACHEGEN_ARCH_CHECK $$QML_CACHEGEN_ARGS --check-if-supported) { - message("QML cache generation requested but target architecture $$QML_CACHEGEN_ARCH is not supported.") - return() -} - load(qt_build_paths) prefix_build: QMLCACHE_DESTDIR = $$MODULE_BASE_OUTDIR/qml/$$TARGETPATH @@ -50,7 +32,7 @@ qmlcacheinst.CONFIG = no_check_exist qmlcachegen.input = CACHEGEN_FILES qmlcachegen.output = ${QMAKE_FUNC_FILE_IN_qmlCacheOutputFileName} qmlcachegen.CONFIG = no_link target_predeps -qmlcachegen.commands = $$QML_CACHEGEN $$QML_CACHEGEN_ARGS -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} +qmlcachegen.commands = $$QML_CACHEGEN -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} qmlcachegen.name = Generate QML Cache ${QMAKE_FILE_IN} qmlcachegen.variable_out = GENERATED_FILES diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index d8af157b8e..9c97ef7694 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -33,10 +33,34 @@ #include <QFileInfo> #include <QDateTime> #include <QHashFunctions> +#include <QSaveFile> #include <private/qqmlirbuilder_p.h> #include <private/qqmljsparser_p.h> +#include "resourcefilemapper.h" + +int filterResourceFile(const QString &input, const QString &output); +bool generateLoader(const QStringList &compiledFiles, const QString &output, const QStringList &resourceFileMappings, QString *errorString); +QString symbolNamespaceForPath(const QString &relativePath); + +QSet<QString> illegalNames; + +void setupIllegalNames() +{ + // #### this in incomplete + illegalNames.insert(QStringLiteral("Math")); + illegalNames.insert(QStringLiteral("Array")); + illegalNames.insert(QStringLiteral("String")); + illegalNames.insert(QStringLiteral("Function")); + illegalNames.insert(QStringLiteral("Boolean")); + illegalNames.insert(QStringLiteral("Number")); + illegalNames.insert(QStringLiteral("Date")); + illegalNames.insert(QStringLiteral("RegExp")); + illegalNames.insert(QStringLiteral("Error")); + illegalNames.insert(QStringLiteral("Object")); +} + struct Error { QString message; @@ -135,10 +159,11 @@ static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, return true; } -static bool compileQmlFile(const QString &inputFileName, const QString &outputFileName, const QString &targetABI, Error *error) +using SaveFunction = std::function<bool (QV4::CompiledData::CompilationUnit *, QString *)>; + +static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFunction, Error *error) { QmlIR::Document irDocument(/*debugMode*/false); - irDocument.jsModule.targetABI = targetABI; QString sourceCode; { @@ -155,7 +180,6 @@ static bool compileQmlFile(const QString &inputFileName, const QString &outputFi } { - QSet<QString> illegalNames; // #### QmlIR::IRBuilder irBuilder(illegalNames); if (!irBuilder.generateFromQml(sourceCode, inputFileName, &irDocument)) { for (const QQmlJS::DiagnosticMessage &parseError: qAsConst(irBuilder.errors)) { @@ -173,7 +197,7 @@ static bool compileQmlFile(const QString &inputFileName, const QString &outputFi QmlIR::JSCodeGen v4CodeGen(irDocument.code, &irDocument.jsGenerator, &irDocument.jsModule, &irDocument.jsParserEngine, irDocument.program, - /*import cache*/0, &irDocument.jsGenerator.stringTable); + /*import cache*/nullptr, &irDocument.jsGenerator.stringTable, illegalNames); v4CodeGen.setUseFastLookups(false); // Disable lookups in non-standalone (aka QML) mode for (QmlIR::Object *object: qAsConst(irDocument.objects)) { if (object->functionsAndExpressions->count == 0) @@ -210,7 +234,7 @@ static bool compileQmlFile(const QString &inputFileName, const QString &outputFi unit->flags |= QV4::CompiledData::Unit::PendingTypeCompilation; irDocument.javaScriptCompilationUnit->data = unit; - if (!irDocument.javaScriptCompilationUnit->saveToDisk(outputFileName, &error->message)) + if (!saveFunction(irDocument.javaScriptCompilationUnit, &error->message)) return false; free(unit); @@ -218,10 +242,9 @@ static bool compileQmlFile(const QString &inputFileName, const QString &outputFi return true; } -static bool compileJSFile(const QString &inputFileName, const QString &outputFileName, const QString &targetABI, Error *error) +static bool compileJSFile(const QString &inputFileName, const QString &inputFileUrl, SaveFunction saveFunction, Error *error) { QmlIR::Document irDocument(/*debugMode*/false); - irDocument.jsModule.targetABI = targetABI; QString sourceCode; { @@ -273,13 +296,13 @@ static bool compileJSFile(const QString &inputFileName, const QString &outputFil } { - irDocument.jsModule.fileName = inputFileName; QmlIR::JSCodeGen v4CodeGen(irDocument.code, &irDocument.jsGenerator, &irDocument.jsModule, &irDocument.jsParserEngine, - irDocument.program, /*import cache*/0, - &irDocument.jsGenerator.stringTable); + irDocument.program, /*import cache*/nullptr, + &irDocument.jsGenerator.stringTable, illegalNames); v4CodeGen.setUseFastLookups(false); // Disable lookups in non-standalone (aka QML) mode - v4CodeGen.generateFromProgram(inputFileName, sourceCode, program, &irDocument.jsModule, QV4::Compiler::GlobalCode); + v4CodeGen.generateFromProgram(inputFileName, inputFileUrl, sourceCode, program, + &irDocument.jsModule, QV4::Compiler::GlobalCode); QList<QQmlJS::DiagnosticMessage> jsErrors = v4CodeGen.errors(); if (!jsErrors.isEmpty()) { for (const QQmlJS::DiagnosticMessage &e: qAsConst(jsErrors)) { @@ -298,7 +321,7 @@ static bool compileJSFile(const QString &inputFileName, const QString &outputFil unit->flags |= QV4::CompiledData::Unit::StaticData; irDocument.javaScriptCompilationUnit->data = unit; - if (!irDocument.javaScriptCompilationUnit->saveToDisk(outputFileName, &error->message)) { + if (!saveFunction(irDocument.javaScriptCompilationUnit, &error->message)) { engine->setDirectives(oldDirs); return false; } @@ -309,6 +332,81 @@ static bool compileJSFile(const QString &inputFileName, const QString &outputFil return true; } +static bool saveUnitAsCpp(const QString &inputFileName, const QString &outputFileName, QV4::CompiledData::CompilationUnit *unit, QString *errorString) +{ + QSaveFile f(outputFileName); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + *errorString = f.errorString(); + return false; + } + + auto writeStr = [&f, errorString](const QByteArray &data) { + if (f.write(data) != data.size()) { + *errorString = f.errorString(); + return false; + } + return true; + }; + + if (!writeStr("// ")) + return false; + + if (!writeStr(inputFileName.toUtf8())) + return false; + + if (!writeStr("\n")) + return false; + + if (!writeStr(QByteArrayLiteral("namespace QmlCacheGeneratedCode {\nnamespace "))) + return false; + + if (!writeStr(symbolNamespaceForPath(inputFileName).toUtf8())) + return false; + + if (!writeStr(QByteArrayLiteral(" {\nextern const unsigned char qmlData alignas(16) [] = {\n"))) + return false; + + QByteArray hexifiedData; + { + QByteArray modifiedUnit; + modifiedUnit.resize(unit->data->unitSize); + memcpy(modifiedUnit.data(), unit->data, unit->data->unitSize); + const char *dataPtr = modifiedUnit.data(); + QV4::CompiledData::Unit *unitPtr; + memcpy(&unitPtr, &dataPtr, sizeof(unitPtr)); + unitPtr->flags |= QV4::CompiledData::Unit::StaticData; + + QTextStream stream(&hexifiedData); + const uchar *begin = reinterpret_cast<const uchar *>(modifiedUnit.constData()); + const uchar *end = begin + unit->data->unitSize; + stream << hex; + int col = 0; + for (const uchar *data = begin; data < end; ++data, ++col) { + if (data > begin) + stream << ','; + if (col % 8 == 0) { + stream << '\n'; + col = 0; + } + stream << "0x" << *data; + } + stream << '\n'; + }; + + if (!writeStr(hexifiedData)) + return false; + + if (!writeStr("};\n}\n}\n")) + return false; + + if (!f.commit()) { + *errorString = f.errorString(); + return false; + } + + return true; +} + int main(int argc, char **argv) { // Produce reliably the same output for the same input by disabling QHash's random seeding. @@ -322,11 +420,14 @@ int main(int argc, char **argv) parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption targetArchitectureOption(QStringLiteral("target-architecture"), QCoreApplication::translate("main", "Target architecture"), QCoreApplication::translate("main", "architecture")); - parser.addOption(targetArchitectureOption); - - QCommandLineOption targetABIOption(QStringLiteral("target-abi"), QCoreApplication::translate("main", "Target architecture binary interface"), QCoreApplication::translate("main", "abi")); - parser.addOption(targetABIOption); + QCommandLineOption filterResourceFileOption(QStringLiteral("filter-resource-file"), QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead")); + parser.addOption(filterResourceFileOption); + QCommandLineOption resourceFileMappingOption(QStringLiteral("resource-file-mapping"), QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name:new-name")); + parser.addOption(resourceFileMappingOption); + QCommandLineOption resourceOption(QStringLiteral("resource"), QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name")); + parser.addOption(resourceOption); + QCommandLineOption resourcePathOption(QStringLiteral("resource-path"), QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path")); + parser.addOption(resourcePathOption); QCommandLineOption outputFileOption(QStringLiteral("o"), QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name")); parser.addOption(outputFileOption); @@ -337,51 +438,119 @@ int main(int argc, char **argv) parser.addPositionalArgument(QStringLiteral("[qml file]"), QStringLiteral("QML source file to generate cache for.")); + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + parser.process(app); - if (!parser.isSet(targetArchitectureOption)) { - fprintf(stderr, "Target architecture not specified. Please specify with --target-architecture=<arch>\n"); - parser.showHelp(); - return EXIT_FAILURE; - } + enum Output { + GenerateCpp, + GenerateCacheFile, + GenerateLoader + } target = GenerateCacheFile; -// if (parser.isSet(checkIfSupportedOption)) { -// if (isel.isNull()) -// return EXIT_FAILURE; -// else -// return EXIT_SUCCESS; -// } + QString outputFileName; + if (parser.isSet(outputFileOption)) + outputFileName = parser.value(outputFileOption); + + if (outputFileName.endsWith(QLatin1String(".cpp"))) { + target = GenerateCpp; + if (outputFileName.endsWith(QLatin1String("qmlcache_loader.cpp"))) + target = GenerateLoader; + } const QStringList sources = parser.positionalArguments(); if (sources.isEmpty()){ parser.showHelp(); - } else if (sources.count() > 1) { + } else if (sources.count() > 1 && target != GenerateLoader) { fprintf(stderr, "%s\n", qPrintable(QStringLiteral("Too many input files specified: '") + sources.join(QStringLiteral("' '")) + QLatin1Char('\''))); return EXIT_FAILURE; } + const QString inputFile = sources.first(); + if (outputFileName.isEmpty()) + outputFileName = inputFile + QLatin1Char('c'); - Error error; + if (parser.isSet(filterResourceFileOption)) { + return filterResourceFile(inputFile, outputFileName); + } - QString outputFileName = inputFile + QLatin1Char('c'); - if (parser.isSet(outputFileOption)) - outputFileName = parser.value(outputFileOption); + if (target == GenerateLoader) { + ResourceFileMapper mapper(sources); + + Error error; + if (!generateLoader(mapper.qmlCompilerFiles(), outputFileName, parser.values(resourceFileMappingOption), &error.message)) { + error.augment(QLatin1String("Error generating loader stub: ")).print(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + + QString inputFileUrl = inputFile; + + SaveFunction saveFunction; + if (target == GenerateCpp) { + ResourceFileMapper fileMapper(parser.values(resourceOption)); + QString inputResourcePath = parser.value(resourcePathOption); + + if (!inputResourcePath.isEmpty() && !fileMapper.isEmpty()) { + fprintf(stderr, "--%s and --%s are mutually exclusive.\n", + qPrintable(resourcePathOption.names().first()), + qPrintable(resourceOption.names().first())); + return EXIT_FAILURE; + } + + // If the user didn't specify the resource path corresponding to the file on disk being + // compiled, try to determine it from the resource file, if one was supplied. + if (inputResourcePath.isEmpty()) { + const QStringList resourcePaths = fileMapper.resourcePaths(inputFile); + if (resourcePaths.isEmpty()) { + fprintf(stderr, "No resource path for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } + + if (resourcePaths.size() != 1) { + fprintf(stderr, "Multiple resource paths for file %s. " + "Use the --%s option to disambiguate:\n", + qPrintable(inputFile), + qPrintable(resourcePathOption.names().first())); + for (const QString &resourcePath: resourcePaths) + fprintf(stderr, "\t%s\n", qPrintable(resourcePath)); + return EXIT_FAILURE; + } + + inputResourcePath = resourcePaths.first(); + } + + inputFileUrl = QStringLiteral("qrc://") + inputResourcePath; + + saveFunction = [inputResourcePath, outputFileName](QV4::CompiledData::CompilationUnit *unit, QString *errorString) { + return saveUnitAsCpp(inputResourcePath, outputFileName, unit, errorString); + }; + + } else { + saveFunction = [outputFileName](QV4::CompiledData::CompilationUnit *unit, QString *errorString) { + return unit->saveToDisk(outputFileName, errorString); + }; + } + + setupIllegalNames(); - const QString targetABI = parser.value(targetABIOption); if (inputFile.endsWith(QLatin1String(".qml"))) { - if (!compileQmlFile(inputFile, outputFileName, targetABI, &error)) { + Error error; + if (!compileQmlFile(inputFile, saveFunction, &error)) { error.augment(QLatin1String("Error compiling qml file: ")).print(); return EXIT_FAILURE; } } else if (inputFile.endsWith(QLatin1String(".js"))) { - if (!compileJSFile(inputFile, outputFileName, targetABI, &error)) { - error.augment(QLatin1String("Error compiling qml file: ")).print(); + Error error; + if (!compileJSFile(inputFile, inputFileUrl, saveFunction, &error)) { + error.augment(QLatin1String("Error compiling js 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)); } - + 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 index be92ef435e..391f0c3889 100644 --- a/tools/qmlcachegen/qmlcachegen.pro +++ b/tools/qmlcachegen/qmlcachegen.pro @@ -3,14 +3,25 @@ option(host_build) QT = qmldevtools-private DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII -SOURCES = qmlcachegen.cpp +SOURCES = qmlcachegen.cpp \ + resourcefilter.cpp \ + generateloader.cpp \ + resourcefilemapper.cpp TARGET = qmlcachegen -build_integration.files = qmlcache.prf +build_integration.files = qmlcache.prf qtquickcompiler.prf build_integration.path = $$[QT_HOST_DATA]/mkspecs/features prefix_build: INSTALLS += build_integration else: COPIES += build_integration +cmake_build_integration.files = Qt5QuickCompilerConfig.cmake +cmake_build_integration.path = $$[QT_INSTALL_LIBS]/cmake/Qt5QuickCompiler +prefix_build: INSTALLS += cmake_build_integration +else: COPIES += cmake_build_integration + QMAKE_TARGET_DESCRIPTION = QML Cache Generator load(qt_tool) + +HEADERS += \ + resourcefilemapper.h diff --git a/tools/qmlcachegen/qtquickcompiler.prf b/tools/qmlcachegen/qtquickcompiler.prf new file mode 100644 index 0000000000..75e474ba70 --- /dev/null +++ b/tools/qmlcachegen/qtquickcompiler.prf @@ -0,0 +1,86 @@ + +qtPrepareTool(QML_CACHEGEN, qmlcachegen, _FILTER) +qtPrepareTool(QMAKE_RCC, rcc, _DEP) + +QMLCACHE_DIR = .qmlcache + +defineReplace(qmlCacheResourceFileOutputName) { + name = $$relative_path($$1, $$_PRO_FILE_PWD_) + name = $$replace(name,/,_) + name = $$replace(name, \\.qrc$, _qmlcache.qrc) + name = $$replace(name,\.\.,) + name = $$replace(name,-,_) + name = $$absolute_path($$name, $$OUT_PWD) + return($${name}) +} + +# Flatten RESOURCES that may contain individual files or objects +load(resources) + +NEWRESOURCES = +QMLCACHE_RESOURCE_FILES = + +for(res, RESOURCES) { + absRes = $$absolute_path($$res, $$_PRO_FILE_PWD_) + rccContents = $$system($$QMAKE_RCC_DEP -list $$absRes,lines) + contains(rccContents,.*\\.js$)|contains(rccContents,.*\\.qml$) { + new_resource = $$qmlCacheResourceFileOutputName($$res) + mkpath($$dirname(new_resource)) + remaining_files = $$system($$QML_CACHEGEN_FILTER -filter-resource-file -o $$new_resource $$absRes,lines) + !isEmpty(remaining_files) { + NEWRESOURCES += $$new_resource + QMLCACHE_LOADER_FLAGS += --resource-file-mapping=$$absRes=$$new_resource + } else { + QMLCACHE_LOADER_FLAGS += --resource-file-mapping=$$absRes + } + + QMLCACHE_RESOURCE_FILES += $$absRes + + for(candidate, $$list($$rccContents)) { + contains(candidate,.*\\.js$)|contains(candidate,.*\\.qml$) { + QMLCACHE_FILES += $$candidate + } + } + } else { + NEWRESOURCES += $$res + } +} + +RESOURCES = $$NEWRESOURCES + +QMLCACHE_RESOURCE_FLAGS = +for(res, QMLCACHE_RESOURCE_FILES) { + QMLCACHE_RESOURCE_FLAGS += --resource=$$res +} + +defineReplace(qmlCacheOutputName) { + name = $$absolute_path($$1, $$OUT_PWD) + name = $$relative_path($$name, $$_PRO_FILE_PWD_) + name = $$replace(name, \\.qml$, _qml) + name = $$replace(name, \\.js$, _js) + name = $$replace(name,/,_) + name = $$QMLCACHE_DIR/$${name} + return($${name}) +} + +qmlcache.input = QMLCACHE_FILES +qmlcache.output = ${QMAKE_FUNC_FILE_IN_qmlCacheOutputName}$${first(QMAKE_EXT_CPP)} +qmlcache.commands = $$QML_CACHEGEN $$QMLCACHE_RESOURCE_FLAGS $$QMLCACHE_FLAGS -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} +qmlcache.name = qmlcachegen ${QMAKE_FILE_IN} +qmlcache.variable_out = GENERATED_SOURCES +qmlcache.dependency_type = TYPE_C + +qmlcache_loader.input = QMLCACHE_RESOURCE_FILES +qmlcache_loader.output = $$QMLCACHE_DIR/qmlcache_loader.cpp +qmlcache_loader.commands = $$QML_CACHEGEN $$QMLCACHE_LOADER_FLAGS $$QMLCACHE_FLAGS -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} +qmlcache_loader.name = qmlcachengen_loader ${QMAKE_FILE_IN} +qmlcache_loader.variable_out = SOURCES +qmlcache_loader.dependency_type = TYPE_C +qmlcache_loader.CONFIG = combine + +unix:!no_qmlcache_depend { + qmlcache.depends += $$QML_CACHEGEN + qmlcache_loader.depends += $$QML_CACHEGEN +} + +QMAKE_EXTRA_COMPILERS += qmlcache qmlcache_loader diff --git a/tools/qmlcachegen/resourcefilemapper.cpp b/tools/qmlcachegen/resourcefilemapper.cpp new file mode 100644 index 0000000000..c2fd057541 --- /dev/null +++ b/tools/qmlcachegen/resourcefilemapper.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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 "resourcefilemapper.h" + +#include <QFileInfo> +#include <QDir> +#include <QXmlStreamReader> + +ResourceFileMapper::ResourceFileMapper(const QStringList &resourceFiles) +{ + for (const QString &fileName: resourceFiles) { + QFile f(fileName); + if (!f.open(QIODevice::ReadOnly)) + continue; + populateFromQrcFile(f); + } +} + +bool ResourceFileMapper::isEmpty() const +{ + return qrcPathToFileSystemPath.isEmpty(); +} + +QStringList ResourceFileMapper::resourcePaths(const QString &fileName) +{ + const QString absPath = QDir::cleanPath(QDir::current().absoluteFilePath(fileName)); + QHashIterator<QString, QString> it(qrcPathToFileSystemPath); + QStringList resourcePaths; + while (it.hasNext()) { + it.next(); + if (QFileInfo(it.value()) == QFileInfo(absPath)) + resourcePaths.append(it.key()); + } + return resourcePaths; +} + +QStringList ResourceFileMapper::qmlCompilerFiles() const +{ + QStringList files; + for (auto it = qrcPathToFileSystemPath.constBegin(), end = qrcPathToFileSystemPath.constEnd(); + it != end; ++it) { + const QString &qrcPath = it.key(); + const QString suffix = QFileInfo(qrcPath).suffix(); + if (suffix != QStringLiteral("qml") && suffix != QStringLiteral("js")) + continue; + files << qrcPath; + } + return files; +} + +void ResourceFileMapper::populateFromQrcFile(QFile &file) +{ + enum State { + InitialState, + InRCC, + InResource, + InFile + }; + State state = InitialState; + + QDir qrcDir = QFileInfo(file).absoluteDir(); + + QString prefix; + QString currentFileName; + QXmlStreamAttributes currentFileAttributes; + + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: + if (reader.name() == QStringLiteral("RCC")) { + if (state != InitialState) + return; + state = InRCC; + continue; + } else if (reader.name() == QStringLiteral("qresource")) { + if (state != InRCC) + return; + state = InResource; + QXmlStreamAttributes attributes = reader.attributes(); + if (attributes.hasAttribute(QStringLiteral("prefix"))) + prefix = attributes.value(QStringLiteral("prefix")).toString(); + if (!prefix.startsWith(QLatin1Char('/'))) + prefix.prepend(QLatin1Char('/')); + if (!prefix.endsWith(QLatin1Char('/'))) + prefix.append(QLatin1Char('/')); + continue; + } else if (reader.name() == QStringLiteral("file")) { + if (state != InResource) + return; + state = InFile; + currentFileAttributes = reader.attributes(); + continue; + } + return; + + case QXmlStreamReader::EndElement: + if (reader.name() == QStringLiteral("file")) { + if (state != InFile) + return; + state = InResource; + continue; + } else if (reader.name() == QStringLiteral("qresource")) { + if (state != InResource) + return; + state = InRCC; + continue; + } else if (reader.name() == QStringLiteral("RCC")) { + if (state != InRCC) + return; + state = InitialState; + continue; + } + return; + + case QXmlStreamReader::Characters: { + if (reader.isWhitespace()) + break; + if (state != InFile) + return; + currentFileName = reader.text().toString(); + if (currentFileName.isEmpty()) + continue; + + const QString fsPath = QDir::cleanPath(qrcDir.absoluteFilePath(currentFileName)); + + if (currentFileAttributes.hasAttribute(QStringLiteral("alias"))) + currentFileName = currentFileAttributes.value(QStringLiteral("alias")).toString(); + + currentFileName = QDir::cleanPath(currentFileName); + while (currentFileName.startsWith(QLatin1String("../"))) + currentFileName.remove(0, 3); + + const QString qrcPath = prefix + currentFileName; + if (QFile::exists(fsPath)) + qrcPathToFileSystemPath.insert(qrcPath, fsPath); + continue; + } + + default: break; + } + } +} diff --git a/tools/qmlcachegen/resourcefilemapper.h b/tools/qmlcachegen/resourcefilemapper.h new file mode 100644 index 0000000000..2e0ab45171 --- /dev/null +++ b/tools/qmlcachegen/resourcefilemapper.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef RESOURCEFILEMAPPER_H +#define RESOURCEFILEMAPPER_H + +#include <QStringList> +#include <QHash> +#include <QFile> + +struct ResourceFileMapper +{ + ResourceFileMapper(const QStringList &resourceFiles); + + bool isEmpty() const; + + QStringList resourcePaths(const QString &fileName); + QStringList qmlCompilerFiles() const; + +private: + void populateFromQrcFile(QFile &file); + + QHash<QString, QString> qrcPathToFileSystemPath; +}; + +#endif // RESOURCEFILEMAPPER_H diff --git a/tools/qmlcachegen/resourcefilter.cpp b/tools/qmlcachegen/resourcefilter.cpp new file mode 100644 index 0000000000..196dbd4a75 --- /dev/null +++ b/tools/qmlcachegen/resourcefilter.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** 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 <QString> +#include <QXmlStreamReader> +#include <QFile> +#include <QDir> + +int filterResourceFile(const QString &input, const QString &output) +{ + enum State { + InitialState, + InRCC, + InResource, + InFile + }; + State state = InitialState; + + QString prefix; + QString currentFileName; + QXmlStreamAttributes fileAttributes; + + QFile file(input); + if (!file.open(QIODevice::ReadOnly)) { + fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(input)); + return EXIT_FAILURE; + } + + QDir inputDirectory = QFileInfo(file).absoluteDir(); + QDir outputDirectory = QFileInfo(output).absoluteDir(); + + QString outputString; + QXmlStreamWriter writer(&outputString); + writer.setAutoFormatting(true); + + QStringList remainingFiles; + + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartDocument: { + QStringRef version = reader.documentVersion(); + if (!version.isEmpty()) + writer.writeStartDocument(version.toString()); + else + writer.writeStartDocument(); + break; + } + case QXmlStreamReader::EndDocument: + writer.writeEndDocument(); + break; + case QXmlStreamReader::StartElement: + if (reader.name() == QStringLiteral("RCC")) { + if (state != InitialState) { + fprintf(stderr, "Unexpected RCC tag in line %d\n", int(reader.lineNumber())); + return EXIT_FAILURE; + } + state = InRCC; + } else if (reader.name() == QStringLiteral("qresource")) { + if (state != InRCC) { + fprintf(stderr, "Unexpected qresource tag in line %d\n", int(reader.lineNumber())); + return EXIT_FAILURE; + } + state = InResource; + QXmlStreamAttributes attributes = reader.attributes(); + if (attributes.hasAttribute(QStringLiteral("prefix"))) + prefix = attributes.value(QStringLiteral("prefix")).toString(); + if (!prefix.startsWith(QLatin1Char('/'))) + prefix.prepend(QLatin1Char('/')); + if (!prefix.endsWith(QLatin1Char('/'))) + prefix.append(QLatin1Char('/')); + } else if (reader.name() == QStringLiteral("file")) { + if (state != InResource) { + fprintf(stderr, "Unexpected file tag in line %d\n", int(reader.lineNumber())); + return EXIT_FAILURE; + } + state = InFile; + fileAttributes = reader.attributes(); + continue; + } + writer.writeStartElement(reader.name().toString()); + writer.writeAttributes(reader.attributes()); + continue; + + case QXmlStreamReader::EndElement: + if (reader.name() == QStringLiteral("file")) { + if (state != InFile) { + fprintf(stderr, "Unexpected end of file tag in line %d\n", int(reader.lineNumber())); + return EXIT_FAILURE; + } + state = InResource; + continue; + } else if (reader.name() == QStringLiteral("qresource")) { + if (state != InResource) { + fprintf(stderr, "Unexpected end of qresource tag in line %d\n", int(reader.lineNumber())); + return EXIT_FAILURE; + } + state = InRCC; + } else if (reader.name() == QStringLiteral("RCC")) { + if (state != InRCC) { + fprintf(stderr, "Unexpected end of RCC tag in line %d\n", int(reader.lineNumber())); + return EXIT_FAILURE; + } + state = InitialState; + } + writer.writeEndElement(); + continue; + + case QXmlStreamReader::Characters: + if (reader.isWhitespace()) + break; + if (state != InFile) + return EXIT_FAILURE; + currentFileName = reader.text().toString(); + if (currentFileName.isEmpty()) + continue; + + if (!currentFileName.endsWith(QStringLiteral(".qml")) && !currentFileName.endsWith(QStringLiteral(".js"))) { + writer.writeStartElement(QStringLiteral("file")); + + if (!fileAttributes.hasAttribute(QStringLiteral("alias"))) + fileAttributes.append(QStringLiteral("alias"), currentFileName); + + currentFileName = inputDirectory.absoluteFilePath(currentFileName); + currentFileName = outputDirectory.relativeFilePath(currentFileName); + + remainingFiles << currentFileName; + + writer.writeAttributes(fileAttributes); + writer.writeCharacters(currentFileName); + writer.writeEndElement(); + } + continue; + + default: break; + } + } + + if (!remainingFiles.isEmpty()) { + QFile outputFile(output); + if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(output)); + return EXIT_FAILURE; + } + const QByteArray outputStringUtf8 = outputString.toUtf8(); + if (outputFile.write(outputStringUtf8) != outputStringUtf8.size()) + return EXIT_FAILURE; + + outputFile.close(); + if (outputFile.error() != QFileDevice::NoError) + return EXIT_FAILURE; + + // The build system expects this output if we wrote a qrc file and no output + // if no files remain. + fprintf(stdout, "New resource file written with %d files.\n", remainingFiles.count()); + } + + return EXIT_SUCCESS; +} |