diff options
author | Morten Johan Sørvig <morten.sorvig@qt.io> | 2018-03-08 18:35:03 +0100 |
---|---|---|
committer | Morten Johan Sørvig <morten.sorvig@qt.io> | 2018-03-08 18:35:03 +0100 |
commit | d9ea4917ca97aeee050a86151fbfa069771b498d (patch) | |
tree | 21fd6960d8a866bf6f5a5a8f9db9be801f8065c1 /tools | |
parent | f9beafddd256cd0b79bf2478a812053ef61241fc (diff) | |
parent | c6a26c248e8abc421b87c3dd6b2466d490ea902e (diff) |
Merge remote-tracking branch 'gerrit/5.11' into wip/webassembly
Change-Id: I729af792166fd2f6c5843ba564e63adf8ae09a97
Diffstat (limited to 'tools')
23 files changed, 1610 insertions, 694 deletions
diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp index 83680a5ba1..73b2700ba4 100644 --- a/tools/qml/main.cpp +++ b/tools/qml/main.cpp @@ -69,8 +69,8 @@ #define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms -static Config *conf = 0; -static QQmlApplicationEngine *qae = 0; +static Config *conf = nullptr; +static QQmlApplicationEngine *qae = nullptr; #if defined(Q_OS_DARWIN) || defined(QT_GUI_LIB) static int exitTimerId = -1; #endif @@ -436,7 +436,7 @@ static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory) int main(int argc, char *argv[]) { getAppFlags(argc, argv); - QCoreApplication *app = 0; + QCoreApplication *app = nullptr; switch (applicationType) { case QmlApplicationTypeCore: app = new QCoreApplication(argc, argv); 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; +} diff --git a/tools/qmleasing/segmentproperties.cpp b/tools/qmleasing/segmentproperties.cpp index 01123d3e97..f37527f863 100644 --- a/tools/qmleasing/segmentproperties.cpp +++ b/tools/qmleasing/segmentproperties.cpp @@ -30,7 +30,7 @@ #include "splineeditor.h" SegmentProperties::SegmentProperties(QWidget *parent) : - QWidget(parent), m_splineEditor(0), m_blockSignals(false) + QWidget(parent), m_splineEditor(nullptr), m_blockSignals(false) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); diff --git a/tools/qmleasing/splineeditor.cpp b/tools/qmleasing/splineeditor.cpp index af5c519b04..2a6081903f 100644 --- a/tools/qmleasing/splineeditor.cpp +++ b/tools/qmleasing/splineeditor.cpp @@ -42,7 +42,7 @@ const int canvasHeight = 320; const int canvasMargin = 160; SplineEditor::SplineEditor(QWidget *parent) : - QWidget(parent), m_pointListWidget(0), m_block(false) + QWidget(parent), m_pointListWidget(nullptr), m_block(false) { setFixedSize(canvasWidth + canvasMargin * 2, canvasHeight + canvasMargin * 2); diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index d596613553..5bd66243e6 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -509,7 +509,7 @@ int main(int argc, char *argv[]) while (i < args.count()) { const QString &arg = args.at(i); ++i; - QStringList *argReceiver = 0; + QStringList *argReceiver = nullptr; if (!arg.startsWith(QLatin1Char('-')) || arg == QLatin1String("-")) { qmlRootPaths += arg; } else if (arg == QLatin1String("-rootPath")) { diff --git a/tools/qmljs/qmljs.cpp b/tools/qmljs/qmljs.cpp index a8dd19228e..ea014f3beb 100644 --- a/tools/qmljs/qmljs.cpp +++ b/tools/qmljs/qmljs.cpp @@ -156,6 +156,5 @@ int main(int argc, char *argv[]) } } - vm.memoryManager->dumpStats(); return EXIT_SUCCESS; } diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp index 422e37fd89..5c5a6a8eb1 100644 --- a/tools/qmlplugindump/main.cpp +++ b/tools/qmlplugindump/main.cpp @@ -300,7 +300,7 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, continue; inObjectInstantiation = tyName; - QObject *object = 0; + QObject *object = nullptr; if (ty.isSingleton()) { QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo(); @@ -403,7 +403,7 @@ public: return exportString; } - void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = 0) + void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) { QSet<QString> implicitSignals; for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { @@ -642,7 +642,7 @@ private: qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); } - void dump(const QMetaProperty &prop, KnownAttributes *knownAttributes = 0) + void dump(const QMetaProperty &prop, KnownAttributes *knownAttributes = nullptr) { int revision = prop.revision(); QByteArray propName = prop.name(); @@ -658,7 +658,7 @@ private: } void dump(const QMetaMethod &meth, const QSet<QString> &implicitSignals, - KnownAttributes *knownAttributes = 0) + KnownAttributes *knownAttributes = nullptr) { if (meth.methodType() == QMetaMethod::Signal) { if (meth.access() != QMetaMethod::Public) @@ -973,7 +973,7 @@ int main(int argc, char *argv[]) sigAction.sa_handler = &sigSegvHandler; sigAction.sa_flags = 0; - sigaction(SIGSEGV, &sigAction, 0); + sigaction(SIGSEGV, &sigAction, nullptr); #endif #ifdef QT_SIMULATOR diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp index 033492b516..3ab6741afa 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.cpp +++ b/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -76,31 +76,36 @@ Q_STATIC_ASSERT(sizeof(features) == QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) : QCoreApplication(argc, argv), m_runMode(LaunchMode), - m_process(0), + m_process(nullptr), m_hostName(QLatin1String("127.0.0.1")), m_port(0), m_pendingRequest(REQUEST_NONE), m_verbose(false), m_recording(true), m_interactive(false), - m_qmlProfilerClient(&m_connection, &m_profilerData), m_connectionAttempts(0) { + m_connection.reset(new QQmlDebugConnection); + m_profilerData.reset(new QmlProfilerData); + m_qmlProfilerClient.reset(new QmlProfilerClient(m_connection.data(), m_profilerData.data())); m_connectTimer.setInterval(1000); connect(&m_connectTimer, &QTimer::timeout, this, &QmlProfilerApplication::tryToConnect); - connect(&m_connection, &QQmlDebugConnection::connected, + connect(m_connection.data(), &QQmlDebugConnection::connected, this, &QmlProfilerApplication::connected); + connect(m_connection.data(), &QQmlDebugConnection::disconnected, + this, &QmlProfilerApplication::disconnected); - connect(&m_qmlProfilerClient, &QmlProfilerClient::enabledChanged, + connect(m_qmlProfilerClient.data(), &QmlProfilerClient::enabledChanged, this, &QmlProfilerApplication::traceClientEnabledChanged); - connect(&m_qmlProfilerClient, &QmlProfilerClient::recordingStarted, + connect(m_qmlProfilerClient.data(), &QmlProfilerClient::traceStarted, this, &QmlProfilerApplication::notifyTraceStarted); - connect(&m_qmlProfilerClient, &QmlProfilerClient::error, + connect(m_qmlProfilerClient.data(), &QmlProfilerClient::error, this, &QmlProfilerApplication::logError); - connect(&m_profilerData, &QmlProfilerData::error, this, &QmlProfilerApplication::logError); - connect(&m_profilerData, &QmlProfilerData::dataReady, + connect(m_profilerData.data(), &QmlProfilerData::error, + this, &QmlProfilerApplication::logError); + connect(m_profilerData.data(), &QmlProfilerData::dataReady, this, &QmlProfilerApplication::traceFinished); } @@ -239,7 +244,7 @@ void QmlProfilerApplication::parseArguments() if (features == 0) parser.showHelp(4); - m_qmlProfilerClient.setFeatures(features); + m_qmlProfilerClient->setRequestedFeatures(features); if (parser.isSet(verbose)) m_verbose = true; @@ -295,10 +300,10 @@ void QmlProfilerApplication::flush() { if (m_recording) { m_pendingRequest = REQUEST_FLUSH; - m_qmlProfilerClient.sendRecordingStatus(false); + m_qmlProfilerClient->sendRecordingStatus(false); } else { - if (m_profilerData.save(m_interactiveOutputFile)) { - m_profilerData.clear(); + if (m_profilerData->save(m_interactiveOutputFile)) { + m_profilerData->clear(); if (!m_interactiveOutputFile.isEmpty()) prompt(tr("Data written to %1.").arg(m_interactiveOutputFile)); else @@ -313,7 +318,7 @@ void QmlProfilerApplication::flush() void QmlProfilerApplication::output() { - if (m_profilerData.save(m_interactiveOutputFile)) { + if (m_profilerData->save(m_interactiveOutputFile)) { if (!m_interactiveOutputFile.isEmpty()) prompt(tr("Data written to %1.").arg(m_interactiveOutputFile)); else @@ -385,12 +390,12 @@ void QmlProfilerApplication::userCommand(const QString &command) if (cmd == Constants::CMD_RECORD || cmd == Constants::CMD_RECORD2) { m_pendingRequest = REQUEST_TOGGLE_RECORDING; - m_qmlProfilerClient.sendRecordingStatus(!m_recording); + m_qmlProfilerClient->sendRecordingStatus(!m_recording); } else if (cmd == Constants::CMD_QUIT || cmd == Constants::CMD_QUIT2) { m_pendingRequest = REQUEST_QUIT; if (m_recording) { prompt(tr("The application is still generating data. Really quit (y/n)?")); - } else if (!m_profilerData.isEmpty()) { + } else if (!m_profilerData->isEmpty()) { prompt(tr("There is still trace data in memory. Really quit (y/n)?")); } else { quit(); @@ -398,7 +403,7 @@ void QmlProfilerApplication::userCommand(const QString &command) } else if (cmd == Constants::CMD_OUTPUT || cmd == Constants::CMD_OUTPUT2) { if (m_recording) { prompt(tr("Cannot output while recording data.")); - } else if (m_profilerData.isEmpty()) { + } else if (m_profilerData->isEmpty()) { prompt(tr("No data was recorded so far.")); } else { m_interactiveOutputFile = args.length() > 0 ? args.at(0).toString() : m_outputFile; @@ -408,14 +413,14 @@ void QmlProfilerApplication::userCommand(const QString &command) } else if (cmd == Constants::CMD_CLEAR || cmd == Constants::CMD_CLEAR2) { if (m_recording) { prompt(tr("Cannot clear data while recording.")); - } else if (m_profilerData.isEmpty()) { + } else if (m_profilerData->isEmpty()) { prompt(tr("No data was recorded so far.")); } else { - m_profilerData.clear(); + m_profilerData->clear(); prompt(tr("Trace data cleared.")); } } else if (cmd == Constants::CMD_FLUSH || cmd == Constants::CMD_FLUSH2) { - if (!m_recording && m_profilerData.isEmpty()) { + if (!m_recording && m_profilerData->isEmpty()) { prompt(tr("No data was recorded so far.")); } else { m_interactiveOutputFile = args.length() > 0 ? args.at(0).toString() : m_outputFile; @@ -443,9 +448,9 @@ void QmlProfilerApplication::notifyTraceStarted() void QmlProfilerApplication::outputData() { - if (!m_profilerData.isEmpty()) { - m_profilerData.save(m_outputFile); - m_profilerData.clear(); + if (!m_profilerData->isEmpty()) { + m_profilerData->save(m_outputFile); + m_profilerData->clear(); } } @@ -454,7 +459,7 @@ void QmlProfilerApplication::run() if (m_runMode == LaunchMode) { if (!m_socketFile.isEmpty()) { logStatus(QString::fromLatin1("Listening on %1 ...").arg(m_socketFile)); - m_connection.startLocalServer(m_socketFile); + m_connection->startLocalServer(m_socketFile); } m_process = new QProcess(this); QStringList arguments; @@ -481,7 +486,7 @@ void QmlProfilerApplication::run() void QmlProfilerApplication::tryToConnect() { - Q_ASSERT(!m_connection.isConnected()); + Q_ASSERT(!m_connection->isConnected()); ++ m_connectionAttempts; if (!m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds @@ -497,7 +502,7 @@ void QmlProfilerApplication::tryToConnect() if (m_socketFile.isEmpty()) { logStatus(QString::fromLatin1("Connecting to %1:%2 ...").arg(m_hostName).arg(m_port)); - m_connection.connectToHost(m_hostName, m_port); + m_connection->connectToHost(m_hostName, m_port); } } @@ -512,6 +517,22 @@ void QmlProfilerApplication::connected() .arg(endpoint).arg(m_recording ? tr("on") : tr("off"))); } +void QmlProfilerApplication::disconnected() +{ + if (m_runMode == AttachMode) { + int exitCode = 0; + if (m_recording) { + logError("Connection dropped while recording, last trace is damaged!"); + exitCode = 2; + } + + if (!m_interactive ) + exit(exitCode); + else + m_qmlProfilerClient->clearAll(); + } +} + void QmlProfilerApplication::processHasOutput() { Q_ASSERT(m_process); @@ -538,7 +559,7 @@ void QmlProfilerApplication::processFinished() if (!m_interactive) exit(exitCode); else - m_qmlProfilerClient.clearPendingData(); + m_qmlProfilerClient->clearAll(); } void QmlProfilerApplication::traceClientEnabledChanged(bool enabled) @@ -547,7 +568,7 @@ void QmlProfilerApplication::traceClientEnabledChanged(bool enabled) logStatus("Trace client is attached."); // blocked server is waiting for recording message from both clients // once the last one is connected, both messages should be sent - m_qmlProfilerClient.sendRecordingStatus(m_recording); + m_qmlProfilerClient->setRecording(m_recording); } } @@ -564,7 +585,7 @@ void QmlProfilerApplication::traceFinished() prompt(tr("Application stopped recording."), false); } - m_qmlProfilerClient.clearPendingData(); + m_qmlProfilerClient->clearEvents(); } void QmlProfilerApplication::prompt(const QString &line, bool ready) diff --git a/tools/qmlprofiler/qmlprofilerapplication.h b/tools/qmlprofiler/qmlprofilerapplication.h index 13f0f041f0..2d00e2b7c5 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.h +++ b/tools/qmlprofiler/qmlprofilerapplication.h @@ -69,6 +69,7 @@ private: void run(); void tryToConnect(); void connected(); + void disconnected(); void processHasOutput(); void processFinished(); @@ -105,9 +106,9 @@ private: bool m_recording; bool m_interactive; - QQmlDebugConnection m_connection; - QmlProfilerClient m_qmlProfilerClient; - QmlProfilerData m_profilerData; + QScopedPointer<QQmlDebugConnection> m_connection; + QScopedPointer<QmlProfilerClient> m_qmlProfilerClient; + QScopedPointer<QmlProfilerData> m_profilerData; QTimer m_connectTimer; uint m_connectionAttempts; }; diff --git a/tools/qmlprofiler/qmlprofilerclient.cpp b/tools/qmlprofiler/qmlprofilerclient.cpp index 0c031db914..b69c7e73e1 100644 --- a/tools/qmlprofiler/qmlprofilerclient.cpp +++ b/tools/qmlprofiler/qmlprofilerclient.cpp @@ -43,36 +43,26 @@ public: QmlProfilerClientPrivate(QQmlDebugConnection *connection, QmlProfilerData *data); QmlProfilerData *data; - - qint64 inProgressRanges; - QStack<qint64> rangeStartTimes[QQmlProfilerDefinitions::MaximumRangeType]; - QStack<QStringList> rangeDatas[QQmlProfilerDefinitions::MaximumRangeType]; - QStack<QQmlEventLocation> rangeLocations[QQmlProfilerDefinitions::MaximumRangeType]; - int rangeCount[QQmlProfilerDefinitions::MaximumRangeType]; - bool enabled; }; QmlProfilerClientPrivate::QmlProfilerClientPrivate(QQmlDebugConnection *connection, QmlProfilerData *data) : - QQmlProfilerClientPrivate(connection), data(data), inProgressRanges(0), enabled(false) + QQmlProfilerClientPrivate(connection, data), data(data), enabled(false) { - ::memset(rangeCount, 0, QQmlProfilerDefinitions::MaximumRangeType * sizeof(int)); } QmlProfilerClient::QmlProfilerClient(QQmlDebugConnection *connection, QmlProfilerData *data) : QQmlProfilerClient(*(new QmlProfilerClientPrivate(connection, data))) { -} - -void QmlProfilerClient::clearPendingData() -{ Q_D(QmlProfilerClient); - for (int i = 0; i < QQmlProfilerDefinitions::MaximumRangeType; ++i) { - d->rangeCount[i] = 0; - d->rangeDatas[i].clear(); - d->rangeLocations[i].clear(); - } + setRequestedFeatures(std::numeric_limits<quint64>::max()); + connect(this, &QQmlProfilerClient::traceStarted, + d->data, &QmlProfilerData::setTraceStartTime); + connect(this, &QQmlProfilerClient::traceFinished, + d->data, &QmlProfilerData::setTraceEndTime); + connect(this, &QQmlProfilerClient::complete, + d->data, &QmlProfilerData::complete); } void QmlProfilerClient::stateChanged(State state) @@ -84,119 +74,4 @@ void QmlProfilerClient::stateChanged(State state) } } -void QmlProfilerClient::traceStarted(qint64 time, int engineId) -{ - Q_UNUSED(engineId); - Q_D(QmlProfilerClient); - d->data->setTraceStartTime(time); - emit recordingStarted(); -} - -void QmlProfilerClient::traceFinished(qint64 time, int engineId) -{ - Q_UNUSED(engineId); - Q_D(QmlProfilerClient); - d->data->setTraceEndTime(time); -} - -void QmlProfilerClient::rangeStart(QQmlProfilerDefinitions::RangeType type, qint64 startTime) -{ - Q_D(QmlProfilerClient); - d->rangeStartTimes[type].push(startTime); - d->inProgressRanges |= (static_cast<qint64>(1) << type); - ++d->rangeCount[type]; -} - -void QmlProfilerClient::rangeData(QQmlProfilerDefinitions::RangeType type, qint64 time, - const QString &data) -{ - Q_UNUSED(time); - Q_D(QmlProfilerClient); - int count = d->rangeCount[type]; - if (count > 0) { - while (d->rangeDatas[type].count() < count) - d->rangeDatas[type].push(QStringList()); - d->rangeDatas[type][count - 1] << data; - } -} - -void QmlProfilerClient::rangeLocation(QQmlProfilerDefinitions::RangeType type, qint64 time, - const QQmlEventLocation &location) -{ - Q_UNUSED(time); - Q_D(QmlProfilerClient); - if (d->rangeCount[type] > 0) - d->rangeLocations[type].push(location); -} - -void QmlProfilerClient::rangeEnd(QQmlProfilerDefinitions::RangeType type, qint64 endTime) -{ - Q_D(QmlProfilerClient); - - if (d->rangeCount[type] == 0) { - emit error(tr("Spurious range end detected.")); - return; - } - - --d->rangeCount[type]; - if (d->inProgressRanges & (static_cast<qint64>(1) << type)) - d->inProgressRanges &= ~(static_cast<qint64>(1) << type); - QStringList data = d->rangeDatas[type].count() ? d->rangeDatas[type].pop() : QStringList(); - QQmlEventLocation location = d->rangeLocations[type].count() ? d->rangeLocations[type].pop() : - QQmlEventLocation(); - qint64 startTime = d->rangeStartTimes[type].pop(); - - if (d->rangeCount[type] == 0 && d->rangeDatas[type].count() + d->rangeStartTimes[type].count() - + d->rangeLocations[type].count() != 0) { - emit error(tr("Incorrectly nested range data")); - return; - } - - d->data->addQmlEvent(type, QQmlProfilerDefinitions::QmlBinding, startTime, endTime - startTime, - data, location); -} - -void QmlProfilerClient::animationFrame(qint64 time, int frameRate, int animationCount, int threadId) -{ - Q_D(QmlProfilerClient); - d->data->addFrameEvent(time, frameRate, animationCount, threadId); -} - -void QmlProfilerClient::sceneGraphEvent(QQmlProfilerDefinitions::SceneGraphFrameType type, - qint64 time, qint64 numericData1, qint64 numericData2, - qint64 numericData3, qint64 numericData4, - qint64 numericData5) -{ - Q_D(QmlProfilerClient); - d->data->addSceneGraphFrameEvent(type, time, numericData1, numericData2, numericData3, - numericData4, numericData5); -} - -void QmlProfilerClient::pixmapCacheEvent(QQmlProfilerDefinitions::PixmapEventType type, qint64 time, - const QString &url, int numericData1, int numericData2) -{ - Q_D(QmlProfilerClient); - d->data->addPixmapCacheEvent(type, time, url, numericData1, numericData2); -} - -void QmlProfilerClient::memoryAllocation(QQmlProfilerDefinitions::MemoryType type, qint64 time, - qint64 amount) -{ - Q_D(QmlProfilerClient); - d->data->addMemoryEvent(type, time, amount); -} - -void QmlProfilerClient::inputEvent(QQmlProfilerDefinitions::InputEventType type, qint64 time, - int a, int b) -{ - Q_D(QmlProfilerClient); - d->data->addInputEvent(type, time, a, b); -} - -void QmlProfilerClient::complete() -{ - Q_D(QmlProfilerClient); - d->data->complete(); -} - #include "moc_qmlprofilerclient.cpp" diff --git a/tools/qmlprofiler/qmlprofilerclient.h b/tools/qmlprofiler/qmlprofilerclient.h index a04a412bb0..b9d8ce241f 100644 --- a/tools/qmlprofiler/qmlprofilerclient.h +++ b/tools/qmlprofiler/qmlprofilerclient.h @@ -29,9 +29,9 @@ #ifndef QMLPROFILERCLIENT_H #define QMLPROFILERCLIENT_H -#include <private/qqmleventlocation_p.h> #include <private/qqmlprofilerclient_p.h> #include <private/qqmlprofilerdefinitions_p.h> +#include <private/qqmlprofilereventlocation_p.h> class QmlProfilerData; class QmlProfilerClientPrivate; @@ -42,32 +42,13 @@ class QmlProfilerClient : public QQmlProfilerClient public: QmlProfilerClient(QQmlDebugConnection *connection, QmlProfilerData *data); - void clearPendingData(); signals: void enabledChanged(bool enabled); - void recordingStarted(); void error(const QString &error); private: void stateChanged(State state) override; - - void traceStarted(qint64 time, int engineId) override; - void traceFinished(qint64 time, int engineId) override; - void rangeStart(QQmlProfilerDefinitions::RangeType type, qint64 startTime) override; - void rangeData(QQmlProfilerDefinitions::RangeType type, qint64 time, const QString &data) override; - void rangeLocation(QQmlProfilerDefinitions::RangeType type, qint64 time, - const QQmlEventLocation &location) override; - void rangeEnd(QQmlProfilerDefinitions::RangeType type, qint64 endTime) override; - void animationFrame(qint64 time, int frameRate, int animationCount, int threadId) override; - void sceneGraphEvent(QQmlProfilerDefinitions::SceneGraphFrameType type, qint64 time, - qint64 numericData1, qint64 numericData2, qint64 numericData3, - qint64 numericData4, qint64 numericData5) override; - void pixmapCacheEvent(QQmlProfilerDefinitions::PixmapEventType type, qint64 time, - const QString &url, int numericData1, int numericData2) override; - void memoryAllocation(QQmlProfilerDefinitions::MemoryType type, qint64 time, qint64 amount) override; - void inputEvent(QQmlProfilerDefinitions::InputEventType type, qint64 time, int a, int b) override; - void complete() override; }; #endif // QMLPROFILERCLIENT_H diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp index 7dcfa4cdaa..32e03298da 100644 --- a/tools/qmlprofiler/qmlprofilerdata.cpp +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -34,6 +34,8 @@ #include <QFile> #include <QXmlStreamReader> #include <QRegularExpression> +#include <QQueue> +#include <QStack> #include <limits> @@ -60,72 +62,13 @@ static const char *MESSAGE_STRINGS[] = { "Complete", "PixmapCache", "SceneGraph", - "MemoryAllocation" + "MemoryAllocation", + "DebugMessage" }; Q_STATIC_ASSERT(sizeof(MESSAGE_STRINGS) == QQmlProfilerDefinitions::MaximumMessage * sizeof(const char *)); -struct QmlRangeEventData { - QmlRangeEventData() {} // never called - QmlRangeEventData(const QString &_displayName, int _detailType, const QString &_eventHashStr, - const QQmlEventLocation &_location, const QString &_details, - QQmlProfilerDefinitions::Message _message, - QQmlProfilerDefinitions::RangeType _rangeType) - : displayName(_displayName), eventHashStr(_eventHashStr), location(_location), - details(_details), message(_message), rangeType(_rangeType), detailType(_detailType) {} - QString displayName; - QString eventHashStr; - QQmlEventLocation location; - QString details; - QQmlProfilerDefinitions::Message message; - QQmlProfilerDefinitions::RangeType rangeType; - int detailType; // can be BindingType, PixmapCacheEventType or SceneGraphFrameType -}; - -struct QmlRangeEventStartInstance { - QmlRangeEventStartInstance() {} // never called - QmlRangeEventStartInstance(qint64 _startTime, qint64 _duration, int _frameRate, - int _animationCount, int _threadId, QmlRangeEventData *_data) - : startTime(_startTime), duration(_duration), frameRate(_frameRate), - animationCount(_animationCount), threadId(_threadId), numericData4(-1), numericData5(-1), - data(_data) - { } - - QmlRangeEventStartInstance(qint64 _startTime, qint64 _numericData1, qint64 _numericData2, - qint64 _numericData3, qint64 _numericData4, qint64 _numericData5, - QmlRangeEventData *_data) - : startTime(_startTime), duration(-1), numericData1(_numericData1), - numericData2(_numericData2), numericData3(_numericData3), numericData4(_numericData4), - numericData5(_numericData5), data(_data) - { } - qint64 startTime; - qint64 duration; - union { - int frameRate; - int inputType; - qint64 numericData1; - }; - union { - int animationCount; - int inputA; - qint64 numericData2; - }; - union { - int threadId; - int inputB; - qint64 numericData3; - }; - qint64 numericData4; - qint64 numericData5; - QmlRangeEventData *data; -}; - -QT_BEGIN_NAMESPACE -Q_DECLARE_TYPEINFO(QmlRangeEventData, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QmlRangeEventStartInstance, Q_MOVABLE_TYPE); -QT_END_NAMESPACE - ///////////////////////////////////////////////////////////////// class QmlProfilerDataPrivate { @@ -133,8 +76,8 @@ public: QmlProfilerDataPrivate(QmlProfilerData *qq){ Q_UNUSED(qq); } // data storage - QHash<QString, QmlRangeEventData *> eventDescriptions; - QVector<QmlRangeEventStartInstance> startInstanceList; + QVector<QQmlProfilerEventType> eventTypes; + QVector<QQmlProfilerEvent> events; qint64 traceStartTime; qint64 traceEndTime; @@ -146,7 +89,7 @@ public: ///////////////////////////////////////////////////////////////// QmlProfilerData::QmlProfilerData(QObject *parent) : - QObject(parent),d(new QmlProfilerDataPrivate(this)) + QQmlProfilerEventReceiver(parent), d(new QmlProfilerDataPrivate(this)) { d->state = Empty; clear(); @@ -160,9 +103,8 @@ QmlProfilerData::~QmlProfilerData() void QmlProfilerData::clear() { - qDeleteAll(d->eventDescriptions); - d->eventDescriptions.clear(); - d->startInstanceList.clear(); + d->eventTypes.clear(); + d->events.clear(); d->traceEndTime = std::numeric_limits<qint64>::min(); d->traceStartTime = std::numeric_limits<qint64>::max(); @@ -171,15 +113,6 @@ void QmlProfilerData::clear() setState(Empty); } -QString QmlProfilerData::getHashStringForQmlEvent(const QQmlEventLocation &location, int eventType) -{ - return QString(QStringLiteral("%1:%2:%3:%4")).arg( - location.filename, - QString::number(location.line), - QString::number(location.column), - QString::number(eventType)); -} - QString QmlProfilerData::qmlRangeTypeAsString(QQmlProfilerDefinitions::RangeType type) { if (type * sizeof(char *) < sizeof(RANGE_TYPE_STRINGS)) @@ -218,20 +151,20 @@ qint64 QmlProfilerData::traceEndTime() const return d->traceEndTime; } -void QmlProfilerData::addQmlEvent(QQmlProfilerDefinitions::RangeType type, - QQmlProfilerDefinitions::BindingType bindingType, - qint64 startTime, - qint64 duration, - const QStringList &data, - const QQmlEventLocation &location) +void QmlProfilerData::addEvent(const QQmlProfilerEvent &event) { setState(AcquiringData); + d->events.append(event); +} + +void QmlProfilerData::addEventType(const QQmlProfilerEventType &type) +{ + QQmlProfilerEventType newType = type; QString details; // generate details string - if (!data.isEmpty()) { - details = data.join(QLatin1Char(' ')).replace( - QLatin1Char('\n'), QLatin1Char(' ')).simplified(); + if (!type.data().isEmpty()) { + details = type.data().simplified(); QRegularExpression rewrite(QStringLiteral("^\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)$")); QRegularExpressionMatch match = rewrite.match(details); if (match.hasMatch()) { @@ -241,223 +174,132 @@ void QmlProfilerData::addQmlEvent(QQmlProfilerDefinitions::RangeType type, details = details.mid(details.lastIndexOf(QLatin1Char('/')) + 1); } - QQmlEventLocation eventLocation = location; - QString displayName, eventHashStr; - // generate hash - if (eventLocation.filename.isEmpty()) { - displayName = tr("<bytecode>"); - eventHashStr = getHashStringForQmlEvent(eventLocation, type); - } else { - const QString filePath = QUrl(eventLocation.filename).path(); - displayName = filePath.midRef( - filePath.lastIndexOf(QLatin1Char('/')) + 1) + - QLatin1Char(':') + QString::number(eventLocation.line); - eventHashStr = getHashStringForQmlEvent(eventLocation, type); - } - - QmlRangeEventData *newEvent; - if (d->eventDescriptions.contains(eventHashStr)) { - newEvent = d->eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlRangeEventData(displayName, bindingType, eventHashStr, location, details, - QQmlProfilerDefinitions::MaximumMessage, type); - d->eventDescriptions.insert(eventHashStr, newEvent); - } - - QmlRangeEventStartInstance rangeEventStartInstance(startTime, duration, -1, -1, -1, newEvent); - - d->startInstanceList.append(rangeEventStartInstance); -} - -void QmlProfilerData::addFrameEvent(qint64 time, int framerate, int animationcount, int threadId) -{ - setState(AcquiringData); - - QString details = tr("Animation Timer Update"); - QString displayName = tr("<Animation Update>"); - QString eventHashStr = displayName; - - QmlRangeEventData *newEvent; - if (d->eventDescriptions.contains(eventHashStr)) { - newEvent = d->eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlRangeEventData(displayName, QQmlProfilerDefinitions::AnimationFrame, - eventHashStr, - QQmlEventLocation(), details, - QQmlProfilerDefinitions::Event, - QQmlProfilerDefinitions::MaximumRangeType); - d->eventDescriptions.insert(eventHashStr, newEvent); - } - - QmlRangeEventStartInstance rangeEventStartInstance(time, -1, framerate, animationcount, - threadId, newEvent); - - d->startInstanceList.append(rangeEventStartInstance); -} - -void QmlProfilerData::addSceneGraphFrameEvent(QQmlProfilerDefinitions::SceneGraphFrameType type, - qint64 time, qint64 numericData1, qint64 numericData2, - qint64 numericData3, qint64 numericData4, - qint64 numericData5) -{ - setState(AcquiringData); - - QString eventHashStr = QString::fromLatin1("SceneGraph:%1").arg(type); - QmlRangeEventData *newEvent; - if (d->eventDescriptions.contains(eventHashStr)) { - newEvent = d->eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlRangeEventData(QStringLiteral("<SceneGraph>"), type, eventHashStr, - QQmlEventLocation(), QString(), - QQmlProfilerDefinitions::SceneGraphFrame, - QQmlProfilerDefinitions::MaximumRangeType); - d->eventDescriptions.insert(eventHashStr, newEvent); - } + newType.setData(details); - QmlRangeEventStartInstance rangeEventStartInstance(time, numericData1, numericData2, - numericData3, numericData4, numericData5, - newEvent); - d->startInstanceList.append(rangeEventStartInstance); -} - -void QmlProfilerData::addPixmapCacheEvent(QQmlProfilerDefinitions::PixmapEventType type, - qint64 time, const QString &location, - int numericData1, int numericData2) -{ - setState(AcquiringData); - - QString filePath = QUrl(location).path(); - - const QString eventHashStr = filePath.midRef(filePath.lastIndexOf(QLatin1Char('/')) + 1) - + QLatin1Char(':') + QString::number(type); - QmlRangeEventData *newEvent; - if (d->eventDescriptions.contains(eventHashStr)) { - newEvent = d->eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlRangeEventData(eventHashStr, type, eventHashStr, - QQmlEventLocation(location, -1, -1), QString(), - QQmlProfilerDefinitions::PixmapCacheEvent, - QQmlProfilerDefinitions::MaximumRangeType); - d->eventDescriptions.insert(eventHashStr, newEvent); + QString displayName; + switch (type.message()) { + case QQmlProfilerDefinitions::Event: { + switch (type.detailType()) { + case QQmlProfilerDefinitions::Mouse: + case QQmlProfilerDefinitions::Key: + displayName = QString::fromLatin1("Input:%1").arg(type.detailType()); + break; + case QQmlProfilerDefinitions::AnimationFrame: + displayName = QString::fromLatin1("AnimationFrame"); + break; + default: + displayName = QString::fromLatin1("Unknown"); + } + break; } - - QmlRangeEventStartInstance rangeEventStartInstance(time, numericData1, numericData2, 0, 0, 0, - newEvent); - d->startInstanceList.append(rangeEventStartInstance); -} - -void QmlProfilerData::addMemoryEvent(QQmlProfilerDefinitions::MemoryType type, qint64 time, - qint64 size) -{ - setState(AcquiringData); - QString eventHashStr = QString::fromLatin1("MemoryAllocation:%1").arg(type); - QmlRangeEventData *newEvent; - if (d->eventDescriptions.contains(eventHashStr)) { - newEvent = d->eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlRangeEventData(eventHashStr, type, eventHashStr, QQmlEventLocation(), - QString(), QQmlProfilerDefinitions::MemoryAllocation, - QQmlProfilerDefinitions::MaximumRangeType); - d->eventDescriptions.insert(eventHashStr, newEvent); + case QQmlProfilerDefinitions::RangeStart: + case QQmlProfilerDefinitions::RangeData: + case QQmlProfilerDefinitions::RangeLocation: + case QQmlProfilerDefinitions::RangeEnd: + case QQmlProfilerDefinitions::Complete: + Q_UNREACHABLE(); + break; + case QQmlProfilerDefinitions::PixmapCacheEvent: { + const QString filePath = QUrl(type.location().filename()).path(); + displayName = filePath.midRef(filePath.lastIndexOf(QLatin1Char('/')) + 1) + + QLatin1Char(':') + QString::number(type.detailType()); + break; } - QmlRangeEventStartInstance rangeEventStartInstance(time, size, 0, 0, 0, 0, newEvent); - d->startInstanceList.append(rangeEventStartInstance); -} - -void QmlProfilerData::addInputEvent(QQmlProfilerDefinitions::InputEventType type, qint64 time, - int a, int b) -{ - setState(AcquiringData); - - QQmlProfilerDefinitions::EventType eventType; - switch (type) { - case QQmlProfilerDefinitions::InputKeyPress: - case QQmlProfilerDefinitions::InputKeyRelease: - case QQmlProfilerDefinitions::InputKeyUnknown: - eventType = QQmlProfilerDefinitions::Key; + case QQmlProfilerDefinitions::SceneGraphFrame: + displayName = QString::fromLatin1("SceneGraph:%1").arg(type.detailType()); break; - default: - eventType = QQmlProfilerDefinitions::Mouse; + case QQmlProfilerDefinitions::MemoryAllocation: + displayName = QString::fromLatin1("MemoryAllocation:%1").arg(type.detailType()); + break; + case QQmlProfilerDefinitions::DebugMessage: + displayName = QString::fromLatin1("DebugMessage:%1").arg(type.detailType()); + break; + case QQmlProfilerDefinitions::MaximumMessage: { + const QQmlProfilerEventLocation eventLocation = type.location(); + // generate hash + if (eventLocation.filename().isEmpty()) { + displayName = QString::fromLatin1("Unknown"); + } else { + const QString filePath = QUrl(eventLocation.filename()).path(); + displayName = filePath.midRef( + filePath.lastIndexOf(QLatin1Char('/')) + 1) + + QLatin1Char(':') + QString::number(eventLocation.line()); + } break; } - - QString eventHashStr = QString::fromLatin1("Input:%1").arg(eventType); - - QmlRangeEventData *newEvent; - if (d->eventDescriptions.contains(eventHashStr)) { - newEvent = d->eventDescriptions[eventHashStr]; - } else { - newEvent = new QmlRangeEventData(QString(), eventType, eventHashStr, QQmlEventLocation(), - QString(), QQmlProfilerDefinitions::Event, - QQmlProfilerDefinitions::MaximumRangeType); - d->eventDescriptions.insert(eventHashStr, newEvent); } - d->startInstanceList.append(QmlRangeEventStartInstance(time, -1, type, a, b, newEvent)); + newType.setDisplayName(displayName); + d->eventTypes.append(newType); } void QmlProfilerData::computeQmlTime() { // compute levels - QHash<int, qint64> endtimesPerLevel; - int minimumLevel = 1; - int level = minimumLevel; - - for (int i = 0; i < d->startInstanceList.count(); i++) { - qint64 st = d->startInstanceList.at(i).startTime; + qint64 level0Start = -1; + int level = 0; - if (d->startInstanceList.at(i).data->rangeType == QQmlProfilerDefinitions::Painting) { + for (const QQmlProfilerEvent &event : qAsConst(d->events)) { + const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); + if (type.message() != QQmlProfilerDefinitions::MaximumMessage) continue; - } - - // general level - if (endtimesPerLevel.value(level) > st) { - level++; - } else { - while (level > minimumLevel && endtimesPerLevel[level-1] <= st) - level--; - } - endtimesPerLevel[level] = st + d->startInstanceList.at(i).duration; - if (level == minimumLevel) { - d->qmlMeasuredTime += d->startInstanceList.at(i).duration; + switch (type.rangeType()) { + case QQmlProfilerDefinitions::Compiling: + case QQmlProfilerDefinitions::Creating: + case QQmlProfilerDefinitions::Binding: + case QQmlProfilerDefinitions::HandlingSignal: + case QQmlProfilerDefinitions::Javascript: + switch (event.rangeStage()) { + case QQmlProfilerDefinitions::RangeStart: + if (level++ == 0) + level0Start = event.timestamp(); + break; + case QQmlProfilerDefinitions::RangeEnd: + if (--level == 0) + d->qmlMeasuredTime += event.timestamp() - level0Start; + break; + default: + break; + } + break; + default: + break; } } } -bool compareStartTimes(const QmlRangeEventStartInstance &t1, const QmlRangeEventStartInstance &t2) +bool compareStartTimes(const QQmlProfilerEvent &t1, const QQmlProfilerEvent &t2) { - return t1.startTime < t2.startTime; + return t1.timestamp() < t2.timestamp(); } void QmlProfilerData::sortStartTimes() { - if (d->startInstanceList.count() < 2) + if (d->events.count() < 2) return; // assuming startTimes is partially sorted // identify blocks of events and sort them with quicksort - QVector<QmlRangeEventStartInstance>::iterator itFrom = d->startInstanceList.end() - 2; - QVector<QmlRangeEventStartInstance>::iterator itTo = d->startInstanceList.end() - 1; + QVector<QQmlProfilerEvent>::iterator itFrom = d->events.end() - 2; + QVector<QQmlProfilerEvent>::iterator itTo = d->events.end() - 1; - while (itFrom != d->startInstanceList.begin() && itTo != d->startInstanceList.begin()) { + while (itFrom != d->events.begin() && itTo != d->events.begin()) { // find block to sort - while ( itFrom != d->startInstanceList.begin() - && itTo->startTime > itFrom->startTime ) { + while (itFrom != d->events.begin() && itTo->timestamp() > itFrom->timestamp()) { --itTo; itFrom = itTo - 1; } // if we're at the end of the list - if (itFrom == d->startInstanceList.begin()) + if (itFrom == d->events.begin()) break; // find block length - while ( itFrom != d->startInstanceList.begin() - && itTo->startTime <= itFrom->startTime ) + while (itFrom != d->events.begin() && itTo->timestamp() <= itFrom->timestamp()) --itFrom; - if (itTo->startTime <= itFrom->startTime) + if (itTo->timestamp() <= itFrom->timestamp()) std::sort(itFrom, itTo + 1, compareStartTimes); else std::sort(itFrom + 1, itTo + 1, compareStartTimes); @@ -479,9 +321,88 @@ void QmlProfilerData::complete() bool QmlProfilerData::isEmpty() const { - return d->startInstanceList.isEmpty(); + return d->events.isEmpty(); } +struct StreamWriter { + QString error; + + StreamWriter(const QString &filename) + { + if (!filename.isEmpty()) { + file.setFileName(filename); + if (!file.open(QIODevice::WriteOnly)) { + error = QmlProfilerData::tr("Could not open %1 for writing").arg(filename); + return; + } + } else { + if (!file.open(stdout, QIODevice::WriteOnly)) { + error = QmlProfilerData::tr("Could not open stdout for writing"); + return; + } + } + + stream.setDevice(&file); + stream.setAutoFormatting(true); + stream.writeStartDocument(); + writeStartElement("trace"); + } + + ~StreamWriter() { + writeEndElement(); + stream.writeEndDocument(); + file.close(); + } + + template<typename Number> + void writeAttribute(const char *name, Number number) + { + stream.writeAttribute(QLatin1String(name), QString::number(number)); + } + + void writeAttribute(const char *name, const char *value) + { + stream.writeAttribute(QLatin1String(name), QLatin1String(value)); + } + + void writeAttribute(const char *name, const QQmlProfilerEvent &event, int i, bool printZero = true) + { + const qint64 number = event.number<qint64>(i); + if (printZero || number != 0) + writeAttribute(name, number); + } + + template<typename Number> + void writeTextElement(const char *name, Number number) + { + writeTextElement(name, QString::number(number)); + } + + void writeTextElement(const char *name, const char *value) + { + stream.writeTextElement(QLatin1String(name), QLatin1String(value)); + } + + void writeTextElement(const char *name, const QString &value) + { + stream.writeTextElement(QLatin1String(name), value); + } + + void writeStartElement(const char *name) + { + stream.writeStartElement(QLatin1String(name)); + } + + void writeEndElement() + { + stream.writeEndElement(); + } + +private: + QFile file; + QXmlStreamWriter stream; +}; + bool QmlProfilerData::save(const QString &filename) { if (isEmpty()) { @@ -489,157 +410,178 @@ bool QmlProfilerData::save(const QString &filename) return false; } - QFile file; - if (!filename.isEmpty()) { - file.setFileName(filename); - if (!file.open(QIODevice::WriteOnly)) { - emit error(tr("Could not open %1 for writing").arg(filename)); - return false; - } - } else { - if (!file.open(stdout, QIODevice::WriteOnly)) { - emit error(tr("Could not open stdout for writing")); - return false; - } + StreamWriter stream(filename); + if (!stream.error.isEmpty()) { + emit error(stream.error); + return false; } - QXmlStreamWriter stream(&file); - stream.setAutoFormatting(true); - stream.writeStartDocument(); - - stream.writeStartElement(QStringLiteral("trace")); - stream.writeAttribute(QStringLiteral("version"), PROFILER_FILE_VERSION); - - stream.writeAttribute(QStringLiteral("traceStart"), QString::number(traceStartTime())); - stream.writeAttribute(QStringLiteral("traceEnd"), QString::number(traceEndTime())); - - stream.writeStartElement(QStringLiteral("eventData")); - stream.writeAttribute(QStringLiteral("totalTime"), QString::number(d->qmlMeasuredTime)); - - const auto eventDescriptionsKeys = d->eventDescriptions.keys(); - for (auto it = d->eventDescriptions.cbegin(), end = d->eventDescriptions.cend(); - it != end; ++it) { - const QmlRangeEventData *eventData = it.value(); - stream.writeStartElement(QStringLiteral("event")); - stream.writeAttribute(QStringLiteral("index"), QString::number( - eventDescriptionsKeys.indexOf(eventData->eventHashStr))); - if (!eventData->displayName.isEmpty()) - stream.writeTextElement(QStringLiteral("displayname"), eventData->displayName); - if (eventData->rangeType != QQmlProfilerDefinitions::MaximumRangeType) - stream.writeTextElement(QStringLiteral("type"), - qmlRangeTypeAsString(eventData->rangeType)); - else - stream.writeTextElement(QStringLiteral("type"), - qmlMessageAsString(eventData->message)); - if (!eventData->location.filename.isEmpty()) - stream.writeTextElement(QStringLiteral("filename"), eventData->location.filename); - if (eventData->location.line >= 0) - stream.writeTextElement(QStringLiteral("line"), - QString::number(eventData->location.line)); - if (eventData->location.column >= 0) - stream.writeTextElement(QStringLiteral("column"), - QString::number(eventData->location.column)); - if (!eventData->details.isEmpty()) - stream.writeTextElement(QStringLiteral("details"), eventData->details); - if (eventData->rangeType == QQmlProfilerDefinitions::Binding) - stream.writeTextElement(QStringLiteral("bindingType"), - QString::number((int)eventData->detailType)); - else if (eventData->message == QQmlProfilerDefinitions::Event) { - switch (eventData->detailType) { + stream.writeAttribute("version", PROFILER_FILE_VERSION); + stream.writeAttribute("traceStart", traceStartTime()); + stream.writeAttribute("traceEnd", traceEndTime()); + + stream.writeStartElement("eventData"); + stream.writeAttribute("totalTime", d->qmlMeasuredTime); + + for (int typeIndex = 0, end = d->eventTypes.size(); typeIndex < end; ++typeIndex) { + const QQmlProfilerEventType &eventData = d->eventTypes.at(typeIndex); + stream.writeStartElement("event"); + stream.writeAttribute("index", typeIndex); + if (!eventData.displayName().isEmpty()) + stream.writeTextElement("displayname", eventData.displayName()); + + stream.writeTextElement("type", + eventData.rangeType() == QQmlProfilerDefinitions::MaximumRangeType + ? qmlMessageAsString(eventData.message()) + : qmlRangeTypeAsString(eventData.rangeType())); + + const QQmlProfilerEventLocation location = eventData.location(); + if (!location.filename().isEmpty()) + stream.writeTextElement("filename", location.filename()); + if (location.line() >= 0) + stream.writeTextElement("line", location.line()); + if (location.column() >= 0) + stream.writeTextElement("column", location.column()); + if (!eventData.data().isEmpty()) + stream.writeTextElement("details", eventData.data()); + if (eventData.rangeType() == QQmlProfilerDefinitions::Binding) + stream.writeTextElement("bindingType", eventData.detailType()); + else if (eventData.message() == QQmlProfilerDefinitions::Event) { + switch (eventData.detailType()) { case QQmlProfilerDefinitions::AnimationFrame: - stream.writeTextElement(QStringLiteral("animationFrame"), - QString::number((int)eventData->detailType)); + stream.writeTextElement("animationFrame", eventData.detailType()); break; case QQmlProfilerDefinitions::Key: - stream.writeTextElement(QStringLiteral("keyEvent"), - QString::number((int)eventData->detailType)); + stream.writeTextElement("keyEvent", eventData.detailType()); break; case QQmlProfilerDefinitions::Mouse: - stream.writeTextElement(QStringLiteral("mouseEvent"), - QString::number((int)eventData->detailType)); + stream.writeTextElement("mouseEvent", eventData.detailType()); break; } - } else if (eventData->message == QQmlProfilerDefinitions::PixmapCacheEvent) - stream.writeTextElement(QStringLiteral("cacheEventType"), - QString::number((int)eventData->detailType)); - else if (eventData->message == QQmlProfilerDefinitions::SceneGraphFrame) - stream.writeTextElement(QStringLiteral("sgEventType"), - QString::number((int)eventData->detailType)); - else if (eventData->message == QQmlProfilerDefinitions::MemoryAllocation) - stream.writeTextElement(QStringLiteral("memoryEventType"), - QString::number((int)eventData->detailType)); + } else if (eventData.message() == QQmlProfilerDefinitions::PixmapCacheEvent) + stream.writeTextElement("cacheEventType", eventData.detailType()); + else if (eventData.message() == QQmlProfilerDefinitions::SceneGraphFrame) + stream.writeTextElement("sgEventType", eventData.detailType()); + else if (eventData.message() == QQmlProfilerDefinitions::MemoryAllocation) + stream.writeTextElement("memoryEventType", eventData.detailType()); stream.writeEndElement(); } stream.writeEndElement(); // eventData - stream.writeStartElement(QStringLiteral("profilerDataModel")); - for (const QmlRangeEventStartInstance &event : qAsConst(d->startInstanceList)) { - stream.writeStartElement(QStringLiteral("range")); - stream.writeAttribute(QStringLiteral("startTime"), QString::number(event.startTime)); - if (event.duration >= 0) - stream.writeAttribute(QStringLiteral("duration"), - QString::number(event.duration)); - stream.writeAttribute(QStringLiteral("eventIndex"), QString::number( - eventDescriptionsKeys.indexOf(event.data->eventHashStr))); - if (event.data->message == QQmlProfilerDefinitions::Event) { - if (event.data->detailType == QQmlProfilerDefinitions::AnimationFrame) { + stream.writeStartElement("profilerDataModel"); + + auto sendEvent = [&](const QQmlProfilerEvent &event, qint64 duration = 0) { + const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); + stream.writeStartElement("range"); + stream.writeAttribute("startTime", event.timestamp()); + if (duration != 0) + stream.writeAttribute("duration", duration); + stream.writeAttribute("eventIndex", event.typeIndex()); + if (type.message() == QQmlProfilerDefinitions::Event) { + if (type.detailType() == QQmlProfilerDefinitions::AnimationFrame) { // special: animation frame - stream.writeAttribute(QStringLiteral("framerate"), QString::number(event.frameRate)); - stream.writeAttribute(QStringLiteral("animationcount"), - QString::number(event.animationCount)); - stream.writeAttribute(QStringLiteral("thread"), QString::number(event.threadId)); - } else if (event.data->detailType == QQmlProfilerDefinitions::Key || - event.data->detailType == QQmlProfilerDefinitions::Mouse) { + stream.writeAttribute("framerate", event, 0); + stream.writeAttribute("animationcount", event, 1); + stream.writeAttribute("thread", event, 2); + } else if (type.detailType() == QQmlProfilerDefinitions::Key || + type.detailType() == QQmlProfilerDefinitions::Mouse) { // numerical value here, to keep the format a bit more compact - stream.writeAttribute(QStringLiteral("type"), - QString::number(event.inputType)); - stream.writeAttribute(QStringLiteral("data1"), - QString::number(event.inputA)); - stream.writeAttribute(QStringLiteral("data2"), - QString::number(event.inputB)); + stream.writeAttribute("type", event, 0); + stream.writeAttribute("data1", event, 1); + stream.writeAttribute("data2", event, 2); } - } else if (event.data->message == QQmlProfilerDefinitions::PixmapCacheEvent) { + } else if (type.message() == QQmlProfilerDefinitions::PixmapCacheEvent) { // special: pixmap cache event - if (event.data->detailType == QQmlProfilerDefinitions::PixmapSizeKnown) { - stream.writeAttribute(QStringLiteral("width"), - QString::number(event.numericData1)); - stream.writeAttribute(QStringLiteral("height"), - QString::number(event.numericData2)); - } else if (event.data->detailType == - QQmlProfilerDefinitions::PixmapReferenceCountChanged || - event.data->detailType == - QQmlProfilerDefinitions::PixmapCacheCountChanged) { - stream.writeAttribute(QStringLiteral("refCount"), - QString::number(event.numericData1)); + if (type.detailType() == QQmlProfilerDefinitions::PixmapSizeKnown) { + stream.writeAttribute("width", event, 0); + stream.writeAttribute("height", event, 1); + } else if (type.detailType() == QQmlProfilerDefinitions::PixmapReferenceCountChanged + || type.detailType() == QQmlProfilerDefinitions::PixmapCacheCountChanged) { + stream.writeAttribute("refCount", event, 1); } - } else if (event.data->message == QQmlProfilerDefinitions::SceneGraphFrame) { - // special: scenegraph frame events - if (event.numericData1 > 0) - stream.writeAttribute(QStringLiteral("timing1"), - QString::number(event.numericData1)); - if (event.numericData2 > 0) - stream.writeAttribute(QStringLiteral("timing2"), - QString::number(event.numericData2)); - if (event.numericData3 > 0) - stream.writeAttribute(QStringLiteral("timing3"), - QString::number(event.numericData3)); - if (event.numericData4 > 0) - stream.writeAttribute(QStringLiteral("timing4"), - QString::number(event.numericData4)); - if (event.numericData5 > 0) - stream.writeAttribute(QStringLiteral("timing5"), - QString::number(event.numericData5)); - } else if (event.data->message == QQmlProfilerDefinitions::MemoryAllocation) { - stream.writeAttribute(QStringLiteral("amount"), QString::number(event.numericData1)); + } else if (type.message() == QQmlProfilerDefinitions::SceneGraphFrame) { + stream.writeAttribute("timing1", event, 0, false); + stream.writeAttribute("timing2", event, 1, false); + stream.writeAttribute("timing3", event, 2, false); + stream.writeAttribute("timing4", event, 3, false); + stream.writeAttribute("timing5", event, 4, false); + } else if (type.message() == QQmlProfilerDefinitions::MemoryAllocation) { + stream.writeAttribute("amount", event, 0); } stream.writeEndElement(); + }; + + QQueue<QQmlProfilerEvent> pointEvents; + QQueue<QQmlProfilerEvent> rangeStarts[QQmlProfilerDefinitions::MaximumRangeType]; + QStack<qint64> rangeEnds[QQmlProfilerDefinitions::MaximumRangeType]; + int level = 0; + + auto sendPending = [&]() { + forever { + int minimum = QQmlProfilerDefinitions::MaximumRangeType; + qint64 minimumTime = std::numeric_limits<qint64>::max(); + for (int i = 0; i < QQmlProfilerDefinitions::MaximumRangeType; ++i) { + const QQueue<QQmlProfilerEvent> &starts = rangeStarts[i]; + if (starts.isEmpty()) + continue; + if (starts.head().timestamp() < minimumTime) { + minimumTime = starts.head().timestamp(); + minimum = i; + } + } + if (minimum == QQmlProfilerDefinitions::MaximumRangeType) + break; + + while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime) + sendEvent(pointEvents.dequeue()); + + sendEvent(rangeStarts[minimum].dequeue(), + rangeEnds[minimum].pop() - minimumTime); + } + }; + + for (const QQmlProfilerEvent &event : qAsConst(d->events)) { + const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); + + if (type.rangeType() != QQmlProfilerDefinitions::MaximumRangeType) { + QQueue<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()]; + switch (event.rangeStage()) { + case QQmlProfilerDefinitions::RangeStart: { + ++level; + starts.enqueue(event); + break; + } + case QQmlProfilerDefinitions::RangeEnd: { + QStack<qint64> &ends = rangeEnds[type.rangeType()]; + if (starts.length() > ends.length()) { + ends.push(event.timestamp()); + if (--level == 0) + sendPending(); + } + break; + } + default: + break; + } + } else { + if (level == 0) + sendEvent(event); + else + pointEvents.enqueue(event); + } } - stream.writeEndElement(); // profilerDataModel - stream.writeEndElement(); // trace - stream.writeEndDocument(); + for (int i = 0; i < QQmlProfilerDefinitions::MaximumRangeType; ++i) { + while (rangeEnds[i].length() < rangeStarts[i].length()) { + rangeEnds[i].push(d->traceEndTime); + --level; + } + } + + sendPending(); + + stream.writeEndElement(); // profilerDataModel - file.close(); return true; } @@ -683,4 +625,9 @@ void QmlProfilerData::setState(QmlProfilerData::State state) return; } +int QmlProfilerData::numLoadedEventTypes() const +{ + return d->eventTypes.length(); +} + #include "moc_qmlprofilerdata.cpp" diff --git a/tools/qmlprofiler/qmlprofilerdata.h b/tools/qmlprofiler/qmlprofilerdata.h index 00ef037071..2be0b73aee 100644 --- a/tools/qmlprofiler/qmlprofilerdata.h +++ b/tools/qmlprofiler/qmlprofilerdata.h @@ -29,13 +29,14 @@ #ifndef QMLPROFILERDATA_H #define QMLPROFILERDATA_H -#include <private/qqmleventlocation_p.h> #include <private/qqmlprofilerdefinitions_p.h> +#include <private/qqmlprofilereventlocation_p.h> +#include <private/qqmlprofilereventreceiver_p.h> #include <QObject> class QmlProfilerDataPrivate; -class QmlProfilerData : public QObject +class QmlProfilerData : public QQmlProfilerEventReceiver { Q_OBJECT public: @@ -49,7 +50,11 @@ public: explicit QmlProfilerData(QObject *parent = 0); ~QmlProfilerData(); - static QString getHashStringForQmlEvent(const QQmlEventLocation &location, int eventType); + int numLoadedEventTypes() const override; + void addEventType(const QQmlProfilerEventType &type) override; + void addEvent(const QQmlProfilerEvent &event) override; + + static QString getHashStringForQmlEvent(const QQmlProfilerEventLocation &location, int eventType); static QString qmlRangeTypeAsString(QQmlProfilerDefinitions::RangeType type); static QString qmlMessageAsString(QQmlProfilerDefinitions::Message type); @@ -61,18 +66,6 @@ public: void clear(); void setTraceEndTime(qint64 time); void setTraceStartTime(qint64 time); - void addQmlEvent(QQmlProfilerDefinitions::RangeType type, - QQmlProfilerDefinitions::BindingType bindingType, - qint64 startTime, qint64 duration, const QStringList &data, - const QQmlEventLocation &location); - void addFrameEvent(qint64 time, int framerate, int animationcount, int threadId); - void addSceneGraphFrameEvent(QQmlProfilerDefinitions::SceneGraphFrameType type, qint64 time, - qint64 numericData1, qint64 numericData2, qint64 numericData3, - qint64 numericData4, qint64 numericData5); - void addPixmapCacheEvent(QQmlProfilerDefinitions::PixmapEventType type, qint64 time, - const QString &location, int numericData1, int numericData2); - void addMemoryEvent(QQmlProfilerDefinitions::MemoryType type, qint64 time, qint64 size); - void addInputEvent(QQmlProfilerDefinitions::InputEventType type, qint64 time, int a, int b); void complete(); bool save(const QString &filename); diff --git a/tools/qmlscene/main.cpp b/tools/qmlscene/main.cpp index fc8b9c5292..bc7fe72d4c 100644 --- a/tools/qmlscene/main.cpp +++ b/tools/qmlscene/main.cpp @@ -145,21 +145,7 @@ struct Options }; Options() - : originalQml(false) - , originalQmlRaster(false) - , maximized(false) - , fullscreen(false) - , transparent(false) - , clip(false) - , versionDetection(true) - , slowAnimations(false) - , quitImmediately(false) - , resizeViewToRootItem(false) - , multisample(false) - , coreProfile(false) - , verbose(false) - , applicationType(DefaultQmlApplicationType) - , textRenderType(QQuickWindow::textRenderType()) + : textRenderType(QQuickWindow::textRenderType()) { // QtWebEngine needs a shared context in order for the GPU thread to // upload textures. @@ -167,22 +153,22 @@ struct Options } QUrl url; - bool originalQml; - bool originalQmlRaster; - bool maximized; - bool fullscreen; - bool transparent; - bool clip; - bool versionDetection; - bool slowAnimations; - bool quitImmediately; - bool resizeViewToRootItem; - bool multisample; - bool coreProfile; - bool verbose; + bool originalQml = false; + bool originalQmlRaster = false; + bool maximized = false; + bool fullscreen = false; + bool transparent = false; + bool clip = false; + bool versionDetection = true; + bool slowAnimations = false; + bool quitImmediately = false; + bool resizeViewToRootItem = false; + bool multisample = false; + bool coreProfile = false; + bool verbose = false; QVector<Qt::ApplicationAttribute> applicationAttributes; QString translationFile; - QmlApplicationType applicationType; + QmlApplicationType applicationType = DefaultQmlApplicationType; QQuickWindow::TextRenderType textRenderType; }; @@ -310,7 +296,7 @@ static void displayFileDialog(Options *options) { #if defined(QT_WIDGETS_LIB) && QT_CONFIG(filedialog) if (options->applicationType == Options::QmlApplicationTypeWidget) { - QString fileName = QFileDialog::getOpenFileName(0, "Open QML file", QString(), "QML Files (*.qml)"); + QString fileName = QFileDialog::getOpenFileName(nullptr, "Open QML file", QString(), "QML Files (*.qml)"); if (!fileName.isEmpty()) { QFileInfo fi(fileName); options->url = QUrl::fromLocalFile(fi.canonicalFilePath()); @@ -640,7 +626,7 @@ int main(int argc, char ** argv) } else { QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel); if (contentItem) { - QQuickView* qxView = new QQuickView(&engine, NULL); + QQuickView* qxView = new QQuickView(&engine, nullptr); window.reset(qxView); // Set window default properties; the qml can still override them if (options.resizeViewToRootItem) diff --git a/tools/qmltime/qmltime.cpp b/tools/qmltime/qmltime.cpp index b337ccac5c..b897d304fc 100644 --- a/tools/qmltime/qmltime.cpp +++ b/tools/qmltime/qmltime.cpp @@ -66,10 +66,10 @@ private: }; QML_DECLARE_TYPE(Timer); -Timer *Timer::m_timer = 0; +Timer *Timer::m_timer = nullptr; Timer::Timer() - : m_component(0) + : m_component(nullptr) , m_willparent(false) , m_item(new QQuickItem) { |