diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qml/main.cpp | 13 | ||||
-rw-r--r-- | tools/qml/qml.pro | 2 | ||||
-rw-r--r-- | tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in (renamed from tools/qmlcachegen/Qt5QuickCompilerConfig.cmake) | 37 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.cpp | 163 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.pro | 16 | ||||
-rw-r--r-- | tools/qmlcachegen/qtquickcompiler.prf | 6 | ||||
-rw-r--r-- | tools/qmlcachegen/resourcefilemapper.cpp | 2 | ||||
-rw-r--r-- | tools/qmlcachegen/resourcefilter.cpp | 4 | ||||
-rw-r--r-- | tools/qmljs/qmljs.cpp | 63 | ||||
-rw-r--r-- | tools/qmlmin/main.cpp | 104 | ||||
-rw-r--r-- | tools/qmlplugindump/main.cpp | 197 | ||||
-rw-r--r-- | tools/qmlpreview/main.cpp | 36 | ||||
-rw-r--r-- | tools/qmlpreview/qmlpreview.pro | 13 | ||||
-rw-r--r-- | tools/qmlpreview/qmlpreviewapplication.cpp | 266 | ||||
-rw-r--r-- | tools/qmlpreview/qmlpreviewapplication.h | 83 | ||||
-rw-r--r-- | tools/qmlprofiler/qmlprofilerapplication.cpp | 6 | ||||
-rw-r--r-- | tools/qmlprofiler/qmlprofilerclient.cpp | 4 | ||||
-rw-r--r-- | tools/qmlprofiler/qmlprofilerclient.h | 2 | ||||
-rw-r--r-- | tools/qmlscene/main.cpp | 12 | ||||
-rw-r--r-- | tools/tools.pro | 24 |
20 files changed, 791 insertions, 262 deletions
diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp index 73b2700ba4..7dfae2b53d 100644 --- a/tools/qml/main.cpp +++ b/tools/qml/main.cpp @@ -55,7 +55,9 @@ #include <QLibraryInfo> #include <qqml.h> #include <qqmldebug.h> -#if QT_CONFIG(animation) + +#include <private/qtqmlglobal_p.h> +#if QT_CONFIG(qml_animation) #include <private/qabstractanimation_p.h> #endif @@ -483,7 +485,7 @@ int main(int argc, char *argv[]) break; else if (arg == QLatin1String("-verbose")) verboseMode = true; -#if QT_CONFIG(animation) +#if QT_CONFIG(qml_animation) else if (arg == QLatin1String("-slow-animations")) QUnifiedTimer::instance()->setSlowModeEnabled(true); else if (arg == QLatin1String("-fixed-animations")) @@ -533,10 +535,9 @@ int main(int argc, char *argv[]) verboseMode = false; #if QT_CONFIG(translation) - //qt_ translations loaded by QQmlApplicationEngine - QString sysLocale = QLocale::system().name(); - - if (!translationFile.isEmpty()) { //Note: installed before QQmlApplicationEngine's automatic translation loading + // Need to be installed before QQmlApplicationEngine's automatic translation loading + // (qt_ translations are loaded there) + if (!translationFile.isEmpty()) { QTranslator translator; if (translator.load(translationFile)) { diff --git a/tools/qml/qml.pro b/tools/qml/qml.pro index 04704f9314..3f41707275 100644 --- a/tools/qml/qml.pro +++ b/tools/qml/qml.pro @@ -1,4 +1,4 @@ -QT = qml core-private +QT = qml-private core-private qtHaveModule(gui): QT += gui qtHaveModule(widgets): QT += widgets diff --git a/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in index 49ba4edde9..a5c531fb0d 100644 --- a/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake +++ b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in @@ -2,9 +2,10 @@ 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}) + string(REPLACE \".qml\" \"_qml\" relpath ${relpath}) + string(REPLACE \".js\" \"_js\" relpath ${relpath}) + string(REPLACE \".mjs\" \"_mjs\" relpath ${relpath}) + string(REPLACE \"/\" \"_\" relpath ${relpath}) set(${outvariable} ${CMAKE_CURRENT_BINARY_DIR}/${relpath}.cpp PARENT_SCOPE) endfunction() @@ -13,11 +14,26 @@ function(QTQUICK_COMPILER_ADD_RESOURCES outfiles) set(oneValueArgs) set(multiValueArgs OPTIONS) - cmake_parse_arguments(_RCC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + cmake_parse_arguments(_RCC \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN}) find_package(Qt5 COMPONENTS Qml Core) - set(compiler_path "${_qt5Core_install_prefix}/bin/qmlcachegen") +!!IF isEmpty(CMAKE_BIN_DIR_IS_ABSOLUTE) + set(compiler_path \"${_qt5Core_install_prefix}/$${CMAKE_BIN_DIR}qmlcachegen$$CMAKE_BIN_SUFFIX\") +!!ELSE + set(compiler_path \"$${CMAKE_BIN_DIR}qmlcachegen$$CMAKE_BIN_SUFFIX\") +!!ENDIF + if(NOT EXISTS \"${compiler_path}\" ) + message(FATAL_ERROR \"The package \\\"Qt5QuickCompilerConfig\\\" references the file + \\\"${compiler_path}\\\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \\\"${CMAKE_CURRENT_LIST_FILE}\\\" +but not all the files it references. +\") + endif() get_target_property(rcc_path ${Qt5Core_RCC_EXECUTABLE} IMPORTED_LOCATION) @@ -37,18 +53,18 @@ function(QTQUICK_COMPILER_ADD_RESOURCES outfiles) 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}") + list(APPEND loader_flags \"--resource-file-mapping=${_resource}=${new_resource_file}\") else() - list(APPEND loader_flags "--resource-file-mapping=${_resource}") + list(APPEND loader_flags \"--resource-file-mapping=${_resource}\") endif() set(rcc_file_with_compilation_units) - execute_process(COMMAND ${rcc_path} -list "${input_resource}" OUTPUT_VARIABLE rcc_contents) - string(REGEX REPLACE "[\r\n]+" ";" rcc_contents ${rcc_contents}) + execute_process(COMMAND ${rcc_path} -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(extension STREQUAL ".qml" OR extension STREQUAL ".js" OR extension STREQUAL ".ui.qml") + if(extension STREQUAL \".qml\" OR extension STREQUAL \".js\" OR extension STREQUAL \".ui.qml\" OR extension STREQUAL \".mjs\") 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}) @@ -70,4 +86,3 @@ function(QTQUICK_COMPILER_ADD_RESOURCES outfiles) qt5_add_resources(output_resources ${filtered_rcc_files} OPTIONS ${options}) set(${outfiles} ${output_resources} ${compiler_output} PARENT_SCOPE) endfunction() - diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index 8a2776e808..762f6e7221 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -34,9 +34,12 @@ #include <QDateTime> #include <QHashFunctions> #include <QSaveFile> +#include <QScopedPointer> +#include <QScopeGuard> #include <private/qqmlirbuilder_p.h> #include <private/qqmljsparser_p.h> +#include <private/qqmljslexer_p.h> #include "resourcefilemapper.h" @@ -66,6 +69,7 @@ struct Error QString message; void print(); Error augment(const QString &contextErrorMessage) const; + void appendDiagnostics(const QString &inputFileName, const QList<QQmlJS::DiagnosticMessage> &diagnostics); }; void Error::print() @@ -95,6 +99,15 @@ QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::Diagnostic return message; } +void Error::appendDiagnostics(const QString &inputFileName, const QList<DiagnosticMessage> &diagnostics) +{ + for (const QQmlJS::DiagnosticMessage &parseError: diagnostics) { + if (!message.isEmpty()) + message += QLatin1Char('\n'); + message += diagnosticErrorMessage(inputFileName, parseError); + } +} + // Ensure that ListElement objects keep all property assignments in their string form static void annotateListElements(QmlIR::Document *document) { @@ -161,7 +174,7 @@ static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, return true; } -using SaveFunction = std::function<bool (QV4::CompiledData::CompilationUnit *, QString *)>; +using SaveFunction = std::function<bool (const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &, QString *)>; static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFunction, Error *error) { @@ -184,11 +197,7 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti { QmlIR::IRBuilder irBuilder(illegalNames); if (!irBuilder.generateFromQml(sourceCode, inputFileName, &irDocument)) { - for (const QQmlJS::DiagnosticMessage &parseError: qAsConst(irBuilder.errors)) { - if (!error->message.isEmpty()) - error->message += QLatin1Char('\n'); - error->message += diagnosticErrorMessage(inputFileName, parseError); - } + error->appendDiagnostics(inputFileName, irBuilder.errors); return false; } } @@ -212,11 +221,7 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti const QVector<int> runtimeFunctionIndices = v4CodeGen.generateJSCodeForFunctionsAndBindings(functionsToCompile); QList<QQmlJS::DiagnosticMessage> jsErrors = v4CodeGen.errors(); if (!jsErrors.isEmpty()) { - for (const QQmlJS::DiagnosticMessage &e: qAsConst(jsErrors)) { - if (!error->message.isEmpty()) - error->message += QLatin1Char('\n'); - error->message += diagnosticErrorMessage(inputFileName, e); - } + error->appendDiagnostics(inputFileName, jsErrors); return false; } @@ -231,10 +236,10 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti QmlIR::QmlUnitGenerator generator; irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false); - QV4::CompiledData::Unit *unit = generator.generate(irDocument); + generator.generate(irDocument); + QV4::CompiledData::Unit *unit = const_cast<QV4::CompiledData::Unit*>(irDocument.javaScriptCompilationUnit->data); unit->flags |= QV4::CompiledData::Unit::StaticData; unit->flags |= QV4::CompiledData::Unit::PendingTypeCompilation; - irDocument.javaScriptCompilationUnit->data = unit; if (!saveFunction(irDocument.javaScriptCompilationUnit, &error->message)) return false; @@ -246,7 +251,8 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti static bool compileJSFile(const QString &inputFileName, const QString &inputFileUrl, SaveFunction saveFunction, Error *error) { - QmlIR::Document irDocument(/*debugMode*/false); + QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit; + QScopedPointer<QV4::CompiledData::Unit, QScopedPointerPodDeleter> unitDataToFree; QString sourceCode; { @@ -262,79 +268,83 @@ static bool compileJSFile(const QString &inputFileName, const QString &inputFile } } - QQmlJS::Engine *engine = &irDocument.jsParserEngine; - QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument); - QQmlJS::Directives *oldDirs = engine->directives(); - engine->setDirectives(&directivesCollector); + const bool isModule = inputFileName.endsWith(QLatin1String(".mjs")); + if (isModule) { + QList<QQmlJS::DiagnosticMessage> diagnostics; + // Precompiled files are relocatable and the final location will be set when loading. + QString url; + unit = QV4::ExecutionEngine::compileModule(/*debugMode*/false, url, sourceCode, QDateTime(), &diagnostics); + error->appendDiagnostics(inputFileName, diagnostics); + if (!unit) + return false; + } else { + QmlIR::Document irDocument(/*debugMode*/false); - QQmlJS::AST::Program *program = nullptr; + QQmlJS::Engine *engine = &irDocument.jsParserEngine; + QmlIR::ScriptDirectivesCollector directivesCollector(&irDocument); + QQmlJS::Directives *oldDirs = engine->directives(); + engine->setDirectives(&directivesCollector); + auto directivesGuard = qScopeGuard([engine, oldDirs]{ + engine->setDirectives(oldDirs); + }); - { - QQmlJS::Lexer lexer(engine); - lexer.setCode(sourceCode, /*line*/1, /*parseAsBinding*/false); - QQmlJS::Parser parser(engine); + QQmlJS::AST::Program *program = nullptr; - bool parsed = parser.parseProgram(); + { + QQmlJS::Lexer lexer(engine); + lexer.setCode(sourceCode, /*line*/1, /*parseAsBinding*/false); + QQmlJS::Parser parser(engine); - for (const QQmlJS::DiagnosticMessage &parseError: parser.diagnosticMessages()) { - if (!error->message.isEmpty()) - error->message += QLatin1Char('\n'); - error->message += diagnosticErrorMessage(inputFileName, parseError); - } + bool parsed = parser.parseProgram(); - if (!parsed) { - engine->setDirectives(oldDirs); - return false; - } + error->appendDiagnostics(inputFileName, parser.diagnosticMessages()); - program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); - if (!program) { - lexer.setCode(QStringLiteral("undefined;"), 1, false); - parsed = parser.parseProgram(); - Q_ASSERT(parsed); - program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); - Q_ASSERT(program); - } - } + if (!parsed) + return false; - { - QmlIR::JSCodeGen v4CodeGen(irDocument.code, &irDocument.jsGenerator, - &irDocument.jsModule, &irDocument.jsParserEngine, - irDocument.program, /*import cache*/nullptr, - &irDocument.jsGenerator.stringTable, illegalNames); - v4CodeGen.setUseFastLookups(false); // Disable lookups in non-standalone (aka QML) mode - 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)) { - if (!error->message.isEmpty()) - error->message += QLatin1Char('\n'); - error->message += diagnosticErrorMessage(inputFileName, e); + program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); + if (!program) { + lexer.setCode(QStringLiteral("undefined;"), 1, false); + parsed = parser.parseProgram(); + Q_ASSERT(parsed); + program = QQmlJS::AST::cast<QQmlJS::AST::Program*>(parser.rootNode()); + Q_ASSERT(program); } - engine->setDirectives(oldDirs); - return false; } - QmlIR::QmlUnitGenerator generator; - - irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false); - QV4::CompiledData::Unit *unit = generator.generate(irDocument); - unit->flags |= QV4::CompiledData::Unit::StaticData; - irDocument.javaScriptCompilationUnit->data = unit; + { + QmlIR::JSCodeGen v4CodeGen(irDocument.code, &irDocument.jsGenerator, + &irDocument.jsModule, &irDocument.jsParserEngine, + irDocument.program, /*import cache*/nullptr, + &irDocument.jsGenerator.stringTable, illegalNames); + v4CodeGen.setUseFastLookups(false); // Disable lookups in non-standalone (aka QML) mode + v4CodeGen.generateFromProgram(inputFileName, inputFileUrl, sourceCode, program, + &irDocument.jsModule, QV4::Compiler::ContextType::Global); + QList<QQmlJS::DiagnosticMessage> jsErrors = v4CodeGen.errors(); + if (!jsErrors.isEmpty()) { + error->appendDiagnostics(inputFileName, jsErrors); + return false; + } - if (!saveFunction(irDocument.javaScriptCompilationUnit, &error->message)) { - engine->setDirectives(oldDirs); - return false; + // Precompiled files are relocatable and the final location will be set when loading. + irDocument.jsModule.fileName.clear(); + irDocument.jsModule.finalUrl.clear(); + + irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false); + QmlIR::QmlUnitGenerator generator; + generator.generate(irDocument); + QV4::CompiledData::Unit *unitData = const_cast<QV4::CompiledData::Unit*>(irDocument.javaScriptCompilationUnit->data); + unitData->flags |= QV4::CompiledData::Unit::StaticData; + unitDataToFree.reset(unitData); + unit = irDocument.javaScriptCompilationUnit; } - - free(unit); } - engine->setDirectives(oldDirs); - return true; + + return saveFunction(unit, &error->message); } -static bool saveUnitAsCpp(const QString &inputFileName, const QString &outputFileName, QV4::CompiledData::CompilationUnit *unit, QString *errorString) +static bool saveUnitAsCpp(const QString &inputFileName, const QString &outputFileName, + const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, QString *errorString) { QSaveFile f(outputFileName); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { @@ -434,9 +444,6 @@ int main(int argc, char **argv) QCommandLineOption outputFileOption(QStringLiteral("o"), QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name")); parser.addOption(outputFileOption); - QCommandLineOption checkIfSupportedOption(QStringLiteral("check-if-supported"), QCoreApplication::translate("main", "Check if cache generate is supported on the specified target architecture")); - parser.addOption(checkIfSupportedOption); - parser.addPositionalArgument(QStringLiteral("[qml file]"), QStringLiteral("QML source file to generate cache for.")); @@ -525,12 +532,12 @@ int main(int argc, char **argv) inputFileUrl = QStringLiteral("qrc://") + inputResourcePath; - saveFunction = [inputResourcePath, outputFileName](QV4::CompiledData::CompilationUnit *unit, QString *errorString) { + saveFunction = [inputResourcePath, outputFileName](const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, QString *errorString) { return saveUnitAsCpp(inputResourcePath, outputFileName, unit, errorString); }; } else { - saveFunction = [outputFileName](QV4::CompiledData::CompilationUnit *unit, QString *errorString) { + saveFunction = [outputFileName](const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, QString *errorString) { return unit->saveToDisk(outputFileName, errorString); }; } @@ -544,7 +551,7 @@ int main(int argc, char **argv) error.augment(QLatin1String("Error compiling qml file: ")).print(); return EXIT_FAILURE; } - } else if (inputFile.endsWith(QLatin1String(".js"))) { + } else if (inputFile.endsWith(QLatin1String(".js")) || inputFile.endsWith(QLatin1String(".mjs"))) { Error error; if (!compileJSFile(inputFile, inputFileUrl, saveFunction, &error)) { error.augment(QLatin1String("Error compiling js file: ")).print(); diff --git a/tools/qmlcachegen/qmlcachegen.pro b/tools/qmlcachegen/qmlcachegen.pro index 391f0c3889..9662690395 100644 --- a/tools/qmlcachegen/qmlcachegen.pro +++ b/tools/qmlcachegen/qmlcachegen.pro @@ -14,7 +14,21 @@ build_integration.path = $$[QT_HOST_DATA]/mkspecs/features prefix_build: INSTALLS += build_integration else: COPIES += build_integration -cmake_build_integration.files = Qt5QuickCompilerConfig.cmake +load(cmake_functions) + +CMAKE_BIN_DIR = $$cmakeRelativePath($$[QT_HOST_BINS], $$[QT_INSTALL_PREFIX]) +contains(CMAKE_BIN_DIR, "^\\.\\./.*") { + CMAKE_BIN_DIR = $$[QT_HOST_BINS]/ + CMAKE_BIN_DIR_IS_ABSOLUTE = True +} + +load(qt_build_paths) + +cmake_config_file.input = $$PWD/Qt5QuickCompilerConfig.cmake.in +cmake_config_file.output = $$MODULE_BASE_OUTDIR/lib/cmake/Qt5QuickCompiler/Qt5QuickCompilerConfig.cmake +QMAKE_SUBSTITUTES += cmake_config_file + +cmake_build_integration.files = $$cmake_config_file.output cmake_build_integration.path = $$[QT_INSTALL_LIBS]/cmake/Qt5QuickCompiler prefix_build: INSTALLS += cmake_build_integration else: COPIES += cmake_build_integration diff --git a/tools/qmlcachegen/qtquickcompiler.prf b/tools/qmlcachegen/qtquickcompiler.prf index d05908560d..608b5c9143 100644 --- a/tools/qmlcachegen/qtquickcompiler.prf +++ b/tools/qmlcachegen/qtquickcompiler.prf @@ -6,6 +6,7 @@ isEmpty(QMLCACHE_DIR): QMLCACHE_DIR = . defineReplace(qmlCacheResourceFileOutputName) { name = $$relative_path($$1, $$_PRO_FILE_PWD_) + contains(name, ^\\.\\..*): name = $$relative_path($$1, $$OUT_PWD) name = $$replace(name,/,_) name = $$replace(name, \\.qrc$, _qmlcache.qrc) name = $$replace(name,\.\.,) @@ -35,7 +36,7 @@ for(res, RESOURCES) { absRes = $$absolute_path($$res, $$_PRO_FILE_PWD_) rccContents = $$system($$QMAKE_RCC_DEP -list $$system_quote($$absRes),lines) - contains(rccContents,.*\\.js$)|contains(rccContents,.*\\.qml$) { + contains(rccContents,.*\\.js$)|contains(rccContents,.*\\.qml$)|contains(rccContents,.*\\.mjs$) { new_resource = $$qmlCacheResourceFileOutputName($$res) mkpath($$dirname(new_resource)) remaining_files = $$system($$QML_CACHEGEN_FILTER -filter-resource-file -o $$system_quote($$new_resource) $$system_quote($$absRes),lines) @@ -49,7 +50,7 @@ for(res, RESOURCES) { QMLCACHE_RESOURCE_FILES += $$absRes for(candidate, rccContents) { - contains(candidate,.*\\.js$)|contains(candidate,.*\\.qml$) { + contains(candidate,.*\\.js$)|contains(candidate,.*\\.qml$)|contains(candidate,.*\\.mjs$) { QMLCACHE_FILES += $$candidate } } @@ -70,6 +71,7 @@ defineReplace(qmlCacheOutputName) { name = $$relative_path($$name, $$_PRO_FILE_PWD_) name = $$replace(name, \\.qml$, _qml) name = $$replace(name, \\.js$, _js) + name = $$replace(name, \\.mjs$, _mjs) name = $$replace(name,/,_) name = $$QMLCACHE_DIR/$${name} return($${name}) diff --git a/tools/qmlcachegen/resourcefilemapper.cpp b/tools/qmlcachegen/resourcefilemapper.cpp index c2fd057541..6a00b39f2e 100644 --- a/tools/qmlcachegen/resourcefilemapper.cpp +++ b/tools/qmlcachegen/resourcefilemapper.cpp @@ -67,7 +67,7 @@ QStringList ResourceFileMapper::qmlCompilerFiles() const it != end; ++it) { const QString &qrcPath = it.key(); const QString suffix = QFileInfo(qrcPath).suffix(); - if (suffix != QStringLiteral("qml") && suffix != QStringLiteral("js")) + if (suffix != QStringLiteral("qml") && suffix != QStringLiteral("js") && suffix != QStringLiteral("mjs")) continue; files << qrcPath; } diff --git a/tools/qmlcachegen/resourcefilter.cpp b/tools/qmlcachegen/resourcefilter.cpp index 196dbd4a75..3ad6e9ca0d 100644 --- a/tools/qmlcachegen/resourcefilter.cpp +++ b/tools/qmlcachegen/resourcefilter.cpp @@ -139,7 +139,9 @@ int filterResourceFile(const QString &input, const QString &output) if (currentFileName.isEmpty()) continue; - if (!currentFileName.endsWith(QStringLiteral(".qml")) && !currentFileName.endsWith(QStringLiteral(".js"))) { + if (!currentFileName.endsWith(QStringLiteral(".qml")) + && !currentFileName.endsWith(QStringLiteral(".js")) + && !currentFileName.endsWith(QStringLiteral(".mjs"))) { writer.writeStartElement(QStringLiteral("file")); if (!fileAttributes.hasAttribute(QStringLiteral("alias"))) diff --git a/tools/qmljs/qmljs.cpp b/tools/qmljs/qmljs.cpp index ea014f3beb..56d4a7d383 100644 --- a/tools/qmljs/qmljs.cpp +++ b/tools/qmljs/qmljs.cpp @@ -37,6 +37,7 @@ #include "private/qv4context_p.h" #include "private/qv4script_p.h" #include "private/qv4string_p.h" +#include "private/qv4module_p.h" #include "private/qqmlbuiltinfunctions_p.h" #include <QtCore/QCoreApplication> @@ -77,14 +78,29 @@ int main(int argc, char *argv[]) args.removeFirst(); bool runAsQml = false; + bool runAsModule = false; bool cache = false; if (!args.isEmpty()) { + if (args.constFirst() == QLatin1String("--jit")) { + qputenv("QV4_JIT_CALL_THRESHOLD", QByteArray("0")); + args.removeFirst(); + } + if (args.constFirst() == QLatin1String("--interpret")) { + qputenv("QV4_FORCE_INTERPRETER", QByteArray("1")); + args.removeFirst(); + } + if (args.constFirst() == QLatin1String("--qml")) { runAsQml = true; args.removeFirst(); } + if (args.constFirst() == QLatin1String("--module")) { + runAsModule = true; + args.removeFirst(); + } + if (args.constFirst() == QLatin1String("--cache")) { cache = true; args.removeFirst(); @@ -104,8 +120,21 @@ int main(int argc, char *argv[]) QV4::GlobalExtensions::init(vm.globalObject, QJSEngine::ConsoleExtension | QJSEngine::GarbageCollectionExtension); for (const QString &fn : qAsConst(args)) { - QFile file(fn); - if (file.open(QFile::ReadOnly)) { + QV4::ScopedValue result(scope); + if (runAsModule) { + auto moduleUnit = vm.loadModule(QUrl::fromLocalFile(QFileInfo(fn).absoluteFilePath())); + if (moduleUnit) { + if (moduleUnit->instantiate(&vm)) + moduleUnit->evaluate(); + } else { + vm.throwError(QStringLiteral("Could not load module file")); + } + } else { + QFile file(fn); + if (!file.open(QFile::ReadOnly)) { + std::cerr << "Error: cannot open file " << fn.toUtf8().constData() << std::endl; + return EXIT_FAILURE; + } QScopedPointer<QV4::Script> script; if (cache && QFile::exists(fn + QLatin1Char('c'))) { QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit = QV4::Compiler::Codegen::createUnitForLoading(); @@ -120,16 +149,15 @@ int main(int argc, char *argv[]) const QString code = QString::fromUtf8(file.readAll()); file.close(); - script.reset(new QV4::Script(ctx, QV4::Compiler::GlobalCode, code, fn)); + script.reset(new QV4::Script(ctx, QV4::Compiler::ContextType::Global, code, fn)); script->parseAsBinding = runAsQml; script->parse(); } - QV4::ScopedValue result(scope); if (!scope.engine->hasException) { const auto unit = script->compilationUnit; - if (cache && unit && !(unit->data->flags & QV4::CompiledData::Unit::StaticData)) { - if (unit->data->sourceTimeStamp == 0) { - const_cast<QV4::CompiledData::Unit*>(unit->data)->sourceTimeStamp = QFileInfo(fn).lastModified().toMSecsSinceEpoch(); + if (cache && unit && !(unit->unitData()->flags & QV4::CompiledData::Unit::StaticData)) { + if (unit->unitData()->sourceTimeStamp == 0) { + const_cast<QV4::CompiledData::Unit*>(unit->unitData())->sourceTimeStamp = QFileInfo(fn).lastModified().toMSecsSinceEpoch(); } QString saveError; if (!unit->saveToDisk(QUrl::fromLocalFile(fn), &saveError)) { @@ -140,20 +168,17 @@ int main(int argc, char *argv[]) result = script->run(); // std::cout << t.elapsed() << " ms. elapsed" << std::endl; } - if (scope.engine->hasException) { - QV4::StackTrace trace; - QV4::ScopedValue ex(scope, scope.engine->catchException(&trace)); - showException(ctx, ex, trace); - return EXIT_FAILURE; - } - if (!result->isUndefined()) { - if (! qgetenv("SHOW_EXIT_VALUE").isEmpty()) - std::cout << "exit value: " << qPrintable(result->toQString()) << std::endl; - } - } else { - std::cerr << "Error: cannot open file " << fn.toUtf8().constData() << std::endl; + } + if (scope.engine->hasException) { + QV4::StackTrace trace; + QV4::ScopedValue ex(scope, scope.engine->catchException(&trace)); + showException(ctx, ex, trace); return EXIT_FAILURE; } + if (!result->isUndefined()) { + if (! qgetenv("SHOW_EXIT_VALUE").isEmpty()) + std::cout << "exit value: " << qPrintable(result->toQString()) << std::endl; + } } return EXIT_SUCCESS; diff --git a/tools/qmlmin/main.cpp b/tools/qmlmin/main.cpp index 5641e6348e..26833d2a08 100644 --- a/tools/qmlmin/main.cpp +++ b/tools/qmlmin/main.cpp @@ -57,15 +57,48 @@ class QmlminLexer: protected Lexer, public Directives QString _fileName; QString _directives; +protected: + QVector<int> _stateStack; + QList<int> _tokens; + QList<QString> _tokenStrings; + int yytoken = -1; + QString yytokentext; + + void lex() { + if (_tokens.isEmpty()) { + _tokens.append(Lexer::lex()); + _tokenStrings.append(tokenText()); + } + + yytoken = _tokens.takeFirst(); + yytokentext = _tokenStrings.takeFirst(); + } + + int lookaheadToken() + { + if (yytoken < 0) + lex(); + return yytoken; + } + + void pushToken(int token) + { + _tokens.prepend(yytoken); + _tokenStrings.prepend(yytokentext); + yytoken = token; + yytokentext = QString(); + } + public: - QmlminLexer(): Lexer(&_engine) {} + QmlminLexer() + : Lexer(&_engine), _stateStack(128) {} virtual ~QmlminLexer() {} QString fileName() const { return _fileName; } bool operator()(const QString &fileName, const QString &code) { - int startToken = T_FEED_JS_PROGRAM; + int startToken = T_FEED_JS_SCRIPT; const QFileInfo fileInfo(fileName); if (fileInfo.suffix().toLower() == QLatin1String("qml")) startToken = T_FEED_UI_PROGRAM; @@ -154,6 +187,24 @@ protected: ruleno == J_SCRIPT_REGEXPLITERAL_RULE2; } + void handleLookaheads(int ruleno) { + if (ruleno == J_SCRIPT_EXPRESSIONSTATEMENTLOOKAHEAD_RULE) { + int token = lookaheadToken(); + if (token == T_LBRACE) + pushToken(T_FORCE_BLOCK); + else if (token == T_FUNCTION || token == T_CLASS || token == T_LET || token == T_CONST) + pushToken(T_FORCE_DECLARATION); + } else if (ruleno == J_SCRIPT_CONCISEBODYLOOKAHEAD_RULE) { + int token = lookaheadToken(); + if (token == T_LBRACE) + pushToken(T_FORCE_BLOCK); + } else if (ruleno == J_SCRIPT_EXPORTDECLARATIONLOOKAHEAD_RULE) { + int token = lookaheadToken(); + if (token == T_FUNCTION || token == T_CLASS) + pushToken(T_FORCE_DECLARATION); + } + } + bool scanRestOfRegExp(int ruleno, QString *restOfRegExp) { if (! scanRegExp(ruleno == J_SCRIPT_REGEXPLITERAL_RULE1 ? Lexer::NoPrefix : Lexer::EqualPrefix)) @@ -187,9 +238,6 @@ protected: class Minify: public QmlminLexer { - QVector<int> _stateStack; - QList<int> _tokens; - QList<QString> _tokenStrings; QString _minifiedCode; int _maxWidth; int _width; @@ -206,7 +254,7 @@ protected: }; Minify::Minify(int maxWidth) - : _stateStack(128), _maxWidth(maxWidth), _width(0) + : _maxWidth(maxWidth), _width(0) { } @@ -250,16 +298,14 @@ void Minify::escape(const QChar &ch, QString *out) bool Minify::parse(int startToken) { int yyaction = 0; - int yytoken = -1; int yytos = -1; - QString yytokentext; QString assembled; _minifiedCode.clear(); _tokens.append(startToken); _tokenStrings.append(QString()); - if (startToken == T_FEED_JS_PROGRAM) { + if (startToken == T_FEED_JS_SCRIPT) { // parse optional pragma directive DiagnosticMessage error; if (scanDirectives(this, &error)) { @@ -282,15 +328,8 @@ bool Minify::parse(int startToken) _stateStack[yytos] = yyaction; again: - if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) { - if (_tokens.isEmpty()) { - _tokens.append(lex()); - _tokenStrings.append(tokenText()); - } - - yytoken = _tokens.takeFirst(); - yytokentext = _tokenStrings.takeFirst(); - } + if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) + lex(); yyaction = t_action(yyaction, yytoken); if (yyaction > 0) { @@ -366,6 +405,8 @@ bool Minify::parse(int startToken) const int ruleno = -yyaction - 1; yytos -= rhs[ruleno]; + handleLookaheads(ruleno); + if (isRegExpRule(ruleno)) { QString restOfRegExp; @@ -398,13 +439,10 @@ bool Minify::parse(int startToken) class Tokenize: public QmlminLexer { - QVector<int> _stateStack; - QList<int> _tokens; - QList<QString> _tokenStrings; QStringList _minifiedCode; public: - Tokenize(); + Tokenize() {} QStringList tokenStream() const; @@ -412,11 +450,6 @@ protected: bool parse(int startToken) override; }; -Tokenize::Tokenize() - : _stateStack(128) -{ -} - QStringList Tokenize::tokenStream() const { return _minifiedCode; @@ -425,15 +458,13 @@ QStringList Tokenize::tokenStream() const bool Tokenize::parse(int startToken) { int yyaction = 0; - int yytoken = -1; int yytos = -1; - QString yytokentext; _minifiedCode.clear(); _tokens.append(startToken); _tokenStrings.append(QString()); - if (startToken == T_FEED_JS_PROGRAM) { + if (startToken == T_FEED_JS_SCRIPT) { // parse optional pragma directive DiagnosticMessage error; if (scanDirectives(this, &error)) { @@ -457,15 +488,8 @@ bool Tokenize::parse(int startToken) _stateStack[yytos] = yyaction; again: - if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) { - if (_tokens.isEmpty()) { - _tokens.append(lex()); - _tokenStrings.append(tokenText()); - } - - yytoken = _tokens.takeFirst(); - yytokentext = _tokenStrings.takeFirst(); - } + if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) + lex(); yyaction = t_action(yyaction, yytoken); if (yyaction > 0) { @@ -484,6 +508,8 @@ bool Tokenize::parse(int startToken) const int ruleno = -yyaction - 1; yytos -= rhs[ruleno]; + handleLookaheads(ruleno); + if (isRegExpRule(ruleno)) { QString restOfRegExp; diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp index 08fc0168dd..f5bd6d2291 100644 --- a/tools/qmlplugindump/main.cpp +++ b/tools/qmlplugindump/main.cpp @@ -76,9 +76,9 @@ namespace { const uint qtQmlMajorVersion = 2; -const uint qtQmlMinorVersion = 2; +const uint qtQmlMinorVersion = QT_VERSION_MINOR; const uint qtQuickMajorVersion = 2; -const uint qtQuickMinorVersion = 8; +const uint qtQuickMinorVersion = QT_VERSION_MINOR; const QString qtQuickQualifiedName = QString::fromLatin1("QtQuick %1.%2") .arg(qtQuickMajorVersion) @@ -121,14 +121,14 @@ void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *met const QMetaObject *meta = object->metaObject(); if (verbose) - std::cerr << "Processing object" << qPrintable( meta->className() ) << std::endl; + std::cerr << "Processing object " << qPrintable( meta->className() ) << std::endl; collectReachableMetaObjects(meta, metas); for (int index = 0; index < meta->propertyCount(); ++index) { QMetaProperty prop = meta->property(index); if (QQmlMetaType::isQObject(prop.userType())) { if (verbose) - std::cerr << " Processing property" << qPrintable( prop.name() ) << std::endl; + std::cerr << " Processing property " << qPrintable( prop.name() ) << std::endl; currentProperty = QString("%1::%2").arg(meta->className(), prop.name()); // if the property was not initialized during construction, @@ -143,7 +143,7 @@ void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *met void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet<const QMetaObject *> *metas) { - collectReachableMetaObjects(ty.metaObject(), metas, ty.isExtendedType()); + collectReachableMetaObjects(ty.baseMetaObject(), metas, ty.isExtendedType()); if (ty.attachedPropertiesType(engine)) collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas); } @@ -211,7 +211,7 @@ QByteArray convertToId(const QMetaObject *mo) void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet<const QMetaObject *>& metas ) { const auto qmlAllTypes = QQmlMetaType::qmlAllTypes(); for (const QQmlType &ty : qmlAllTypes) { - if ( ! metas.contains(ty.metaObject()) ) { + if (!metas.contains(ty.baseMetaObject())) { if (!ty.isComposite()) { collectReachableMetaObjects(engine, ty, &metas); } else { @@ -229,55 +229,20 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, QSet<const QMetaObject *> metas; metas.insert(FriendlyQObject::qtMeta()); - QHash<QByteArray, QSet<QByteArray> > extensions; const auto qmlTypes = QQmlMetaType::qmlTypes(); for (const QQmlType &ty : qmlTypes) { if (!ty.isCreatable()) - noncreatables.insert(ty.metaObject()); + noncreatables.insert(ty.baseMetaObject()); if (ty.isSingleton()) - singletons.insert(ty.metaObject()); + singletons.insert(ty.baseMetaObject()); if (!ty.isComposite()) { - qmlTypesByCppName[ty.metaObject()->className()].insert(ty); - if (ty.isExtendedType()) - extensions[ty.typeName()].insert(ty.metaObject()->className()); + qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty); collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas); } else { qmlTypesByCompositeName[ty.elementName()].insert(ty); } } - // Adjust exports of the base object if there are extensions. - // For each export of a base object there can be a single extension object overriding it. - // Example: QDeclarativeGraphicsWidget overrides the QtQuick/QGraphicsWidget export - // of QGraphicsWidget. - for (auto it = extensions.cbegin(), end = extensions.cend(); it != end; ++it) { - QSet<QQmlType> baseExports = qmlTypesByCppName.value(it.key()); - - const QSet<QByteArray> extensionCppNames = it.value(); - for (const QByteArray &extensionCppName : extensionCppNames) { - const QSet<QQmlType> extensionExports = qmlTypesByCppName.value(extensionCppName); - - // remove extension exports from base imports - // unfortunately the QQmlType pointers don't match, so can't use QSet::subtract - QSet<QQmlType> newBaseExports; - for (const QQmlType &baseExport : qAsConst(baseExports)) { - bool match = false; - for (const QQmlType &extensionExport : extensionExports) { - if (baseExport.qmlTypeName() == extensionExport.qmlTypeName() - && baseExport.majorVersion() == extensionExport.majorVersion() - && baseExport.minorVersion() == extensionExport.minorVersion()) { - match = true; - break; - } - } - if (!match) - newBaseExports.insert(baseExport); - } - baseExports = newBaseExports; - } - qmlTypesByCppName[it.key()] = baseExports; - } - if (creatable) { // find even more QMetaObjects by instantiating QML types and running // over the instances @@ -334,7 +299,7 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, collectReachableMetaObjects(object, &metas); object->deleteLater(); } else { - std::cerr << "Could not create" << qPrintable(tyName) << std::endl; + std::cerr << "Could not create " << qPrintable(tyName) << std::endl; } } } @@ -402,15 +367,7 @@ public: void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) { - QSet<QString> implicitSignals; - for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { - const QMetaProperty &property = meta->property(index); - dump(property, knownAttributes); - if (knownAttributes) - knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), - 0, property.revision()); - implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); - } + QSet<QString> implicitSignals = dumpMetaProperties(meta, 0, knownAttributes); if (meta == &QObject::staticMetaObject) { // for QObject, hide deleteLater() and onDestroyed @@ -530,6 +487,27 @@ public: qml->writeEndObject(); } + QString getDefaultProperty(const QMetaObject *meta) + { + for (int index = meta->classInfoCount() - 1; index >= 0; --index) { + QMetaClassInfo classInfo = meta->classInfo(index); + if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { + return QLatin1String(classInfo.value()); + } + } + return QString(); + } + + struct QmlTypeInfo { + QmlTypeInfo() {} + QmlTypeInfo(const QString &exportString, int revision, const QMetaObject *extendedObject, QByteArray attachedTypeId) + : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {} + QString exportString; + int revision = 0; + const QMetaObject *extendedObject = nullptr; + QByteArray attachedTypeId; + }; + void dump(QQmlEnginePrivate *engine, const QMetaObject *meta, bool isUncreatable, bool isSingleton) { qml->writeStartObject("Component"); @@ -537,29 +515,58 @@ public: QByteArray id = convertToId(meta); qml->writeScriptBinding(QLatin1String("name"), enquote(id)); - for (int index = meta->classInfoCount() - 1 ; index >= 0 ; --index) { - QMetaClassInfo classInfo = meta->classInfo(index); - if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { - qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value()))); - break; + // collect type information + QVector<QmlTypeInfo> typeInfo; + for (QQmlType type : qmlTypesByCppName.value(meta->className())) { + const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr; + QByteArray attachedTypeId; + if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) { + // Can happen when a type is registered that returns itself as attachedPropertiesType() + // because there is no creatable type to attach to. + if (attachedType != meta) + attachedTypeId = convertToId(attachedType); + } + const QString exportString = getExportString(type.qmlTypeName(), type.majorVersion(), type.minorVersion()); + int metaObjectRevision = type.metaObjectRevision(); + if (extendedObject) { + // emulate custom metaobjectrevision out of import + metaObjectRevision = type.majorVersion() * 100 + type.minorVersion(); + } + + QmlTypeInfo info = { exportString, metaObjectRevision, extendedObject, attachedTypeId }; + typeInfo.append(info); + } + + // sort to ensure stable output + std::sort(typeInfo.begin(), typeInfo.end(), [](const QmlTypeInfo &i1, const QmlTypeInfo &i2) { + return i1.revision < i2.revision; + }); + + // determine default property + // TODO: support revisioning of default property + QString defaultProperty = getDefaultProperty(meta); + if (defaultProperty.isEmpty()) { + for (const QmlTypeInfo &iter : typeInfo) { + if (iter.extendedObject) { + defaultProperty = getDefaultProperty(iter.extendedObject); + if (!defaultProperty.isEmpty()) + break; + } } } + if (!defaultProperty.isEmpty()) + qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty)); if (meta->superClass()) qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass()))); - const QSet<QQmlType> qmlTypes = qmlTypesByCppName.value(meta->className()); - if (!qmlTypes.isEmpty()) { - QHash<QString, QQmlType> exports; - - for (const QQmlType &qmlTy : qmlTypes) { - const QString exportString = getExportString(qmlTy.qmlTypeName(), qmlTy.majorVersion(), qmlTy.minorVersion()); - exports.insert(exportString, qmlTy); - } + if (!typeInfo.isEmpty()) { + QMap<QString, QString> exports; // sort exports + for (const QmlTypeInfo &iter : typeInfo) + exports.insert(iter.exportString, QString::number(iter.revision)); - // ensure exports are sorted and don't change order when the plugin is dumped again QStringList exportStrings = exports.keys(); - std::sort(exportStrings.begin(), exportStrings.end()); + QStringList metaObjectRevisions = exports.values(); qml->writeArrayBinding(QLatin1String("exports"), exportStrings); if (isUncreatable) @@ -568,20 +575,12 @@ public: if (isSingleton) qml->writeBooleanBinding(QLatin1String("isSingleton"), true); - // write meta object revisions - QStringList metaObjectRevisions; - for (const QString &exportString : qAsConst(exportStrings)) { - int metaObjectRevision = exports[exportString].metaObjectRevision(); - metaObjectRevisions += QString::number(metaObjectRevision); - } qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions); - if (const QMetaObject *attachedType = (*qmlTypes.begin()).attachedPropertiesType(engine)) { - // Can happen when a type is registered that returns itself as attachedPropertiesType() - // because there is no creatable type to attach to. - if (attachedType != meta) { - qml->writeScriptBinding(QLatin1String("attachedType"), enquote( - convertToId(attachedType))); + for (const QmlTypeInfo &iter : typeInfo) { + if (!iter.attachedTypeId.isEmpty()) { + qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId)); + break; } } } @@ -591,6 +590,12 @@ public: writeMetaContent(meta); + // dump properties from extended metaobjects last + for (auto iter : typeInfo) { + if (iter.extendedObject) + dumpMetaProperties(iter.extendedObject, iter.revision); + } + qml->writeEndObject(); } @@ -639,9 +644,9 @@ private: qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); } - void dump(const QMetaProperty &prop, KnownAttributes *knownAttributes = nullptr) + void dump(const QMetaProperty &prop, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { - int revision = prop.revision(); + int revision = metaRevision ? metaRevision : prop.revision(); QByteArray propName = prop.name(); if (knownAttributes && knownAttributes->knownProperty(propName, revision)) return; @@ -654,6 +659,20 @@ private: qml->writeEndObject(); } + QSet<QString> dumpMetaProperties(const QMetaObject *meta, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) + { + QSet<QString> implicitSignals; + for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { + const QMetaProperty &property = meta->property(index); + dump(property, metaRevision, knownAttributes); + if (knownAttributes) + knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), + 0, property.revision()); + implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); + } + return implicitSignals; + } + void dump(const QMetaMethod &meth, const QSet<QString> &implicitSignals, KnownAttributes *knownAttributes = nullptr) { @@ -822,9 +841,11 @@ static bool getDependencies(const QQmlEngine &engine, const QString &pluginImpor const QString &pluginImportVersion, QStringList *dependencies, bool forceQtQuickDependency) { + QString importScannerExe = QLatin1String("qmlimportscanner"); QFileInfo selfExe(QCoreApplication::applicationFilePath()); - QString command = selfExe.absoluteDir().filePath(QLatin1String("qmlimportscanner") - + selfExe.suffix()); + if (!selfExe.suffix().isEmpty()) + importScannerExe += QLatin1String(".") + selfExe.suffix(); + QString command = selfExe.absoluteDir().filePath(importScannerExe); QStringList commandArgs = QStringList() << QLatin1String("-qmlFiles") @@ -989,7 +1010,7 @@ int main(int argc, char *argv[]) #endif // QT_WIDGETS_LIB QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); - const QStringList args = app->arguments(); + QStringList args = app->arguments(); const QString appName = QFileInfo(app->applicationFilePath()).baseName(); if (args.size() < 2) { printUsage(appName); @@ -1021,6 +1042,10 @@ int main(int argc, char *argv[]) return EXIT_INVALIDARGUMENTS; } dependenciesFile = args.at(iArg); + + // Remove absolute path so that it does not show up in the + // printed command line inside the plugins.qmltypes file. + args[iArg] = QFileInfo(args.at(iArg)).fileName(); } else if (arg == QLatin1String("--merge") || arg == QLatin1String("-merge")) { if (++iArg == args.size()) { diff --git a/tools/qmlpreview/main.cpp b/tools/qmlpreview/main.cpp new file mode 100644 index 0000000000..c7a32da258 --- /dev/null +++ b/tools/qmlpreview/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications 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 "qmlpreviewapplication.h" + +int main(int argc, char *argv[]) +{ + QmlPreviewApplication app(argc, argv); + app.parseArguments(); + return app.exec(); +} diff --git a/tools/qmlpreview/qmlpreview.pro b/tools/qmlpreview/qmlpreview.pro new file mode 100644 index 0000000000..7d443b5f6c --- /dev/null +++ b/tools/qmlpreview/qmlpreview.pro @@ -0,0 +1,13 @@ +QT = network core qmldebug-private +CONFIG += no_import_scan + +SOURCES += \ + main.cpp \ + qmlpreviewapplication.cpp + +HEADERS += \ + qmlpreviewapplication.h + +QMAKE_TARGET_DESCRIPTION = QML Preview + +load(qt_tool) diff --git a/tools/qmlpreview/qmlpreviewapplication.cpp b/tools/qmlpreview/qmlpreviewapplication.cpp new file mode 100644 index 0000000000..618769502c --- /dev/null +++ b/tools/qmlpreview/qmlpreviewapplication.cpp @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qmlpreviewapplication.h" + +#include <QtCore/QStringList> +#include <QtCore/QTextStream> +#include <QtCore/QProcess> +#include <QtCore/QTimer> +#include <QtCore/QDateTime> +#include <QtCore/QFileInfo> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QCommandLineParser> +#include <QtCore/QTemporaryFile> +#include <QtCore/QUrl> + +QmlPreviewApplication::QmlPreviewApplication(int &argc, char **argv) : + QCoreApplication(argc, argv), + m_verbose(false), + m_connectionAttempts(0) +{ + m_connection.reset(new QQmlDebugConnection); + m_qmlPreviewClient.reset(new QQmlPreviewClient(m_connection.data())); + m_connectTimer.setInterval(1000); + + m_loadTimer.setInterval(100); + m_loadTimer.setSingleShot(true); + connect(&m_loadTimer, &QTimer::timeout, this, [this]() { + m_qmlPreviewClient->triggerLoad(QUrl()); + }); + + connect(&m_connectTimer, &QTimer::timeout, this, &QmlPreviewApplication::tryToConnect); + connect(m_connection.data(), &QQmlDebugConnection::connected, &m_connectTimer, &QTimer::stop); + + connect(m_qmlPreviewClient.data(), &QQmlPreviewClient::error, + this, &QmlPreviewApplication::logError); + connect(m_qmlPreviewClient.data(), &QQmlPreviewClient::request, + this, &QmlPreviewApplication::serveRequest); + + connect(&m_watcher, &QFileSystemWatcher::fileChanged, + this, &QmlPreviewApplication::sendFile); + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, + this, &QmlPreviewApplication::sendDirectory); +} + +QmlPreviewApplication::~QmlPreviewApplication() +{ + if (m_process && m_process->state() != QProcess::NotRunning) { + logStatus("Terminating process ..."); + m_process->disconnect(); + m_process->terminate(); + if (!m_process->waitForFinished(1000)) { + logStatus("Killing process ..."); + m_process->kill(); + } + } +} + +void QmlPreviewApplication::parseArguments() +{ + setApplicationName(QLatin1String("qmlpreview")); + setApplicationVersion(QLatin1String(qVersion())); + + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + + parser.setApplicationDescription(QChar::LineFeed + tr( + "The QML Preview tool watches QML and JavaScript files on disk and updates\n" + "the application live with any changes. The application to be previewed\n" + "has to enable QML debugging. See the Qt Creator documentation on how to do\n" + "this for different Qt versions.")); + + QCommandLineOption verbose(QStringList() << QLatin1String("verbose"), + tr("Print debugging output.")); + parser.addOption(verbose); + + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addPositionalArgument(QLatin1String("program"), + tr("The program to be started and profiled."), + QLatin1String("[program]")); + parser.addPositionalArgument(QLatin1String("parameters"), + tr("Parameters for the program to be started."), + QLatin1String("[parameters...]")); + + parser.process(*this); + + QTemporaryFile file; + if (file.open()) + m_socketFile = file.fileName(); + + if (parser.isSet(verbose)) + m_verbose = true; + + m_programArguments = parser.positionalArguments(); + if (!m_programArguments.isEmpty()) + m_programPath = m_programArguments.takeFirst(); + + if (m_programPath.isEmpty()) { + logError(tr("You have to specify a program to start.")); + parser.showHelp(2); + } +} + +int QmlPreviewApplication::exec() +{ + QTimer::singleShot(0, this, &QmlPreviewApplication::run); + return QCoreApplication::exec(); +} + +void QmlPreviewApplication::run() +{ + logStatus(QString("Listening on %1 ...").arg(m_socketFile)); + m_connection->startLocalServer(m_socketFile); + m_process.reset(new QProcess(this)); + QStringList arguments; + arguments << QString("-qmljsdebugger=file:%1,block,services:QmlPreview").arg(m_socketFile); + arguments << m_programArguments; + + m_process->setProcessChannelMode(QProcess::MergedChannels); + connect(m_process.data(), &QIODevice::readyRead, + this, &QmlPreviewApplication::processHasOutput); + connect(m_process.data(), static_cast<void(QProcess::*)(int)>(&QProcess::finished), + this, [this](int){ processFinished(); }); + logStatus(QString("Starting '%1 %2' ...").arg(m_programPath, arguments.join(QLatin1Char(' ')))); + m_process->start(m_programPath, arguments); + if (!m_process->waitForStarted()) { + logError(QString("Could not run '%1': %2").arg(m_programPath, m_process->errorString())); + exit(1); + } + m_connectTimer.start(); +} + +void QmlPreviewApplication::tryToConnect() +{ + Q_ASSERT(!m_connection->isConnected()); + ++m_connectionAttempts; + + if (m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds + logError(QString("No connection received on %1 for %2 seconds ...") + .arg(m_socketFile).arg(m_connectionAttempts)); + } +} + +void QmlPreviewApplication::processHasOutput() +{ + Q_ASSERT(m_process); + while (m_process->bytesAvailable()) { + QTextStream out(stderr); + out << m_process->readAll(); + } +} + +void QmlPreviewApplication::processFinished() +{ + Q_ASSERT(m_process); + int exitCode = 0; + if (m_process->exitStatus() == QProcess::NormalExit) { + logStatus(QString("Process exited (%1).").arg(m_process->exitCode())); + } else { + logError("Process crashed!"); + exitCode = 3; + } + exit(exitCode); +} + +void QmlPreviewApplication::logError(const QString &error) +{ + QTextStream err(stderr); + err << "Error: " << error << endl; +} + +void QmlPreviewApplication::logStatus(const QString &status) +{ + if (!m_verbose) + return; + QTextStream err(stderr); + err << status << endl; +} + +void QmlPreviewApplication::serveRequest(const QString &path) +{ + QFileInfo info(path); + + if (info.isDir()) { + m_qmlPreviewClient->sendDirectory(path, QDir(path).entryList()); + m_watcher.addPath(path); + } else { + QFile file(path); + if (file.open(QIODevice::ReadOnly)) { + m_qmlPreviewClient->sendFile(path, file.readAll()); + m_watcher.addPath(path); + + // Also watch the directory, because editors will rather replace a file than change it. + // Therefore when the file changes, we can't read it, but when the file is re-added we can + // see that from the directory changing. + m_watcher.addPath(info.absolutePath()); + } else { + logStatus(QString("Could not open file %1 for reading: %2").arg(path) + .arg(file.errorString())); + m_qmlPreviewClient->sendError(path); + } + } +} + +bool QmlPreviewApplication::sendFile(const QString &path) +{ + QFile file(path); + if (file.open(QIODevice::ReadOnly)) { + m_qmlPreviewClient->sendFile(path, file.readAll()); + m_pendingFiles.removeAll(path); + // Defer the Load, because files tend to change multiple times in a row. + m_loadTimer.start(); + return true; + } + if (!m_pendingFiles.contains(path)) + m_pendingFiles.append(path); + logStatus(QString("Could not open file %1 for reading: %2").arg(path).arg(file.errorString())); + return false; +} + +void QmlPreviewApplication::sendDirectory(const QString &path) +{ + m_qmlPreviewClient->sendDirectory(path, QDir(path).entryList()); + for (auto it = m_pendingFiles.begin(); it != m_pendingFiles.end();) { + const QString filePath = *it; + QFile file(filePath); + if (file.open(QIODevice::ReadOnly)) { + logStatus(QString("Sending replaced file %1.").arg(filePath)); + m_qmlPreviewClient->sendFile(filePath, file.readAll()); + m_watcher.addPath(filePath); + it = m_pendingFiles.erase(it); + } else { + ++it; + } + } + m_loadTimer.start(); +} diff --git a/tools/qmlpreview/qmlpreviewapplication.h b/tools/qmlpreview/qmlpreviewapplication.h new file mode 100644 index 0000000000..eb363b0eb6 --- /dev/null +++ b/tools/qmlpreview/qmlpreviewapplication.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 QMLPREVIEWAPPLICATION_H +#define QMLPREVIEWAPPLICATION_H + +#include <private/qqmlpreviewclient_p.h> +#include <private/qqmldebugconnection_p.h> + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qprocess.h> +#include <QtCore/qtimer.h> +#include <QtCore/qfilesystemwatcher.h> + +#include <QtNetwork/qabstractsocket.h> + +class QmlPreviewApplication : public QCoreApplication +{ + Q_OBJECT +public: + QmlPreviewApplication(int &argc, char **argv); + ~QmlPreviewApplication(); + + void parseArguments(); + int exec(); + +private: + void run(); + void tryToConnect(); + void processHasOutput(); + void processFinished(); + + void logError(const QString &error); + void logStatus(const QString &status); + + void serveRequest(const QString &request); + bool sendFile(const QString &path); + void sendDirectory(const QString &path); + + QString m_programPath; + QStringList m_programArguments; + QScopedPointer<QProcess> m_process; + bool m_verbose; + + QString m_socketFile; + + QScopedPointer<QQmlDebugConnection> m_connection; + QScopedPointer<QQmlPreviewClient> m_qmlPreviewClient; + QFileSystemWatcher m_watcher; + + QTimer m_loadTimer; + QTimer m_connectTimer; + uint m_connectionAttempts; + + QStringList m_pendingFiles; +}; + +#endif // QMLPREVIEWAPPLICATION_H diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp index 0b63a91e5b..6732766b46 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.cpp +++ b/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -120,6 +120,10 @@ QmlProfilerApplication::~QmlProfilerApplication() logStatus("Killing process ..."); m_process->kill(); } + if (isInteractive()) { + QTextStream err(stderr); + err << endl; + } delete m_process; } @@ -366,7 +370,7 @@ void QmlProfilerApplication::userCommand(const QString &command) m_pendingRequest = REQUEST_NONE; prompt(); } else { - prompt(tr("The application is still generating data. Really quit (y/n)?")); + prompt(tr("Really quit (y/n)?")); } return; } diff --git a/tools/qmlprofiler/qmlprofilerclient.cpp b/tools/qmlprofiler/qmlprofilerclient.cpp index b69c7e73e1..f6cc6f39fe 100644 --- a/tools/qmlprofiler/qmlprofilerclient.cpp +++ b/tools/qmlprofiler/qmlprofilerclient.cpp @@ -57,6 +57,8 @@ QmlProfilerClient::QmlProfilerClient(QQmlDebugConnection *connection, QmlProfile { Q_D(QmlProfilerClient); setRequestedFeatures(std::numeric_limits<quint64>::max()); + connect(this, &QQmlDebugClient::stateChanged, + this, &QmlProfilerClient::onStateChanged); connect(this, &QQmlProfilerClient::traceStarted, d->data, &QmlProfilerData::setTraceStartTime); connect(this, &QQmlProfilerClient::traceFinished, @@ -65,7 +67,7 @@ QmlProfilerClient::QmlProfilerClient(QQmlDebugConnection *connection, QmlProfile d->data, &QmlProfilerData::complete); } -void QmlProfilerClient::stateChanged(State state) +void QmlProfilerClient::onStateChanged(State state) { Q_D(QmlProfilerClient); if ((d->enabled && state != Enabled) || (!d->enabled && state == Enabled)) { diff --git a/tools/qmlprofiler/qmlprofilerclient.h b/tools/qmlprofiler/qmlprofilerclient.h index 30f4a51751..7355688222 100644 --- a/tools/qmlprofiler/qmlprofilerclient.h +++ b/tools/qmlprofiler/qmlprofilerclient.h @@ -48,7 +48,7 @@ signals: void error(const QString &error); private: - void stateChanged(State state) override; + void onStateChanged(State state); }; #endif // QMLPROFILERCLIENT_H diff --git a/tools/qmlscene/main.cpp b/tools/qmlscene/main.cpp index bc7fe72d4c..4d18a868a2 100644 --- a/tools/qmlscene/main.cpp +++ b/tools/qmlscene/main.cpp @@ -51,8 +51,10 @@ #ifdef QT_WIDGETS_LIB #include <QtWidgets/QApplication> +#if QT_CONFIG(filedialog) #include <QtWidgets/QFileDialog> #endif +#endif #include <QtCore/QTranslator> #include <QtCore/QLibraryInfo> @@ -311,7 +313,7 @@ static void displayFileDialog(Options *options) #if QT_CONFIG(translation) static void loadTranslationFile(QTranslator &translator, const QString& directory) { - translator.load(QLatin1String("qml_" )+QLocale::system().name(), directory + QLatin1String("/i18n")); + translator.load(QLocale(), QLatin1String("qml"), QLatin1String("_"), directory + QLatin1String("/i18n")); QCoreApplication::installTranslator(&translator); } #endif @@ -549,12 +551,12 @@ int main(int argc, char ** argv) } #if QT_CONFIG(translation) - QTranslator translator; + QLocale locale; QTranslator qtTranslator; - QString sysLocale = QLocale::system().name(); - if (qtTranslator.load(QLatin1String("qt_") + sysLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + if (qtTranslator.load(locale, QLatin1String("qt"), QLatin1String("_"), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) app->installTranslator(&qtTranslator); - if (translator.load(QLatin1String("qmlscene_") + sysLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + QTranslator translator; + if (translator.load(locale, QLatin1String("qmlscene"), QLatin1String("_"), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) app->installTranslator(&translator); QTranslator qmlTranslator; diff --git a/tools/tools.pro b/tools/tools.pro index 856906cc53..3f5f23eb32 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -1,17 +1,21 @@ TEMPLATE = subdirs QT_FOR_CONFIG += qml-private -SUBDIRS += \ - qmlmin \ - qmlimportscanner -qtConfig(commandlineparser):qtConfig(xmlstreamwriter): SUBDIRS += qmlcachegen +qtConfig(qml-devtools) { + SUBDIRS += \ + qmlmin \ + qmlimportscanner + + qtConfig(commandlineparser):qtConfig(xmlstreamwriter): SUBDIRS += qmlcachegen +} -!android|android_app { +qtConfig(thread):!android|android_app { SUBDIRS += \ - qml \ - qmllint + qml + qtConfig(qml-devtools): SUBDIRS += qmllint qtConfig(qml-profiler): SUBDIRS += qmlprofiler + qtConfig(qml-preview): SUBDIRS += qmlpreview qtHaveModule(quick) { !static: { @@ -30,8 +34,10 @@ qtConfig(commandlineparser):qtConfig(xmlstreamwriter): SUBDIRS += qmlcachegen qtConfig(private_tests): SUBDIRS += qmljs } -qml.depends = qmlimportscanner -qmleasing.depends = qmlimportscanner +qtConfig(qml-devtools) { + qml.depends = qmlimportscanner + qmleasing.depends = qmlimportscanner +} # qmlmin, qmlimportscanner & qmlcachegen are build tools. # qmlscene is needed by the autotests. |