diff options
author | Simon Hausmann <simon.hausmann@qt.io> | 2018-01-22 16:46:03 +0100 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2018-01-28 16:37:26 +0000 |
commit | e5b157275d7b924e1a0f39d27958a7ad1eed9cef (patch) | |
tree | 8d08a8a9fb0c79d77449875cc9e62ee6df99b2ea /tools/qmlcachegen/generateloader.cpp | |
parent | e29047555be764d59c52801553e23767ed367cec (diff) |
Add support for compiling QML/JS files ahead of time in resources
This is bringing over the loading infrastructure from the Qt Quick
Compiler that allows embedding qml/js files in resources and compiling
them ahead of time.
At the moment, the approach of generating one cpp file per qml/js file
and the loader stub is needed because the build system does not support
dynamic resource generation. In addition, as per QTBUG-60961, we must
ensure that the generated data structures are aligned.
To retain compatibility this is enabled via CONFIG += qtquickcompiler,
but we may need to find a new name (but should keep the old one in any
case).
Task-number: QTBUG-60961
Change-Id: Ia9839bf98d3af4c50636b6e06815364a9fc7ee57
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'tools/qmlcachegen/generateloader.cpp')
-rw-r--r-- | tools/qmlcachegen/generateloader.cpp | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/tools/qmlcachegen/generateloader.cpp b/tools/qmlcachegen/generateloader.cpp new file mode 100644 index 0000000000..dd2e244b56 --- /dev/null +++ b/tools/qmlcachegen/generateloader.cpp @@ -0,0 +1,398 @@ +/**************************************************************************** +** +** 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; // node index inside generated data + bool isDirectory; + + VirtualDirectoryEntry() + : firstChildIndex(-1) + , isDirectory(true) + {} + + ~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 = 0) + : 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 <private/qv4compileddata_p.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 << " QV4::CompiledData::CompilationUnit *createCompilationUnit() {\n"; + stream << " QV4::CompiledData::CompilationUnit *unit = new QV4::CompiledData::CompilationUnit;\n"; + stream << " unit->data = reinterpret_cast<const QV4::CompiledData::Unit*>(&qmlData);\n"; + stream << " return unit;\n"; + stream << " }\n"; + stream << " const QQmlPrivate::CachedQmlUnit unit = {\n"; + stream << " reinterpret_cast<const QV4::CompiledData::Unit*>(&qmlData), &createCompilationUnit, 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; +} |