diff options
-rw-r--r-- | tests/auto/cmake/CMakeLists.txt | 12 | ||||
-rw-r--r-- | tests/auto/cmake/qmlimportscanner/CMakeLists.txt | 18 | ||||
-rw-r--r-- | tests/auto/cmake/qmlimportscanner/main.cpp | 53 | ||||
-rw-r--r-- | tests/auto/cmake/qmlimportscanner/main.qml | 5 | ||||
-rw-r--r-- | tests/auto/cmake/qmlimportscanner/qis_test.qrc | 6 | ||||
-rw-r--r-- | tools/qmlimportscanner/Qt5QmlImportScannerConfig.cmake.in | 184 | ||||
-rw-r--r-- | tools/qmlimportscanner/Qt5QmlImportScannerTemplate.cpp.in | 5 | ||||
-rw-r--r-- | tools/qmlimportscanner/main.cpp | 46 | ||||
-rw-r--r-- | tools/qmlimportscanner/qmlimportscanner.pro | 45 |
9 files changed, 371 insertions, 3 deletions
diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index f304a99705..bda5d626a9 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -27,3 +27,15 @@ add_test(qtquickcompiler ${CMAKE_CTEST_COMMAND} --build-options "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" ${BUILD_OPTIONS_LIST} --test-command qqc_test ) + +add_test(qmlimportscanner ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/qmlimportscanner/" + "${CMAKE_CURRENT_BINARY_DIR}/qmlimportscanner" + --build-config "${CMAKE_BUILD_TYPE}" + --build-generator ${CMAKE_GENERATOR} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-project qis_test + --build-options "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" ${BUILD_OPTIONS_LIST} + --test-command qis_test +) diff --git a/tests/auto/cmake/qmlimportscanner/CMakeLists.txt b/tests/auto/cmake/qmlimportscanner/CMakeLists.txt new file mode 100644 index 0000000000..354b0f8dfc --- /dev/null +++ b/tests/auto/cmake/qmlimportscanner/CMakeLists.txt @@ -0,0 +1,18 @@ + +cmake_minimum_required(VERSION 3.1) +project(qis_test) + +find_package(Qt5Qml 5.0.0 REQUIRED) +find_package(Qt5Gui 5.0.0 REQUIRED) +find_package(Qt5Test 5.0.0 REQUIRED) +find_package(Qt5QmlImportScanner REQUIRED) + +set(CMAKE_CXXFLAGS "${CMAKE_CXXFLAGS} ${Qt5Core_EXECUTABLE_COMPILE_FLAGS}") + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +add_executable(qis_test "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/qis_test.qrc") +target_link_libraries(qis_test PRIVATE Qt5::Gui Qt5::Qml Qt5::Test) +qt5_import_qml_plugins(qis_test) diff --git a/tests/auto/cmake/qmlimportscanner/main.cpp b/tests/auto/cmake/qmlimportscanner/main.cpp new file mode 100644 index 0000000000..370b10e113 --- /dev/null +++ b/tests/auto/cmake/qmlimportscanner/main.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite 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 <QtCore> +#include <QtQml> +#include <QtTest> + +class tst_QQC : public QObject +{ + Q_OBJECT +private slots: + void staticBuildTest(); +}; + +void tst_QQC::staticBuildTest() +{ +#ifdef QT_STATIC + QQmlEngine engine; + QQmlComponent component(&engine, QUrl("qrc:/main.qml")); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->property("success").toInt(), 42); +#endif +} + +QTEST_MAIN(tst_QQC) + +#include "main.moc" diff --git a/tests/auto/cmake/qmlimportscanner/main.qml b/tests/auto/cmake/qmlimportscanner/main.qml new file mode 100644 index 0000000000..e0101958ea --- /dev/null +++ b/tests/auto/cmake/qmlimportscanner/main.qml @@ -0,0 +1,5 @@ +import QtQml 2.0 +import QtQuick 2.0 +QtObject { + property int success: 42 +} diff --git a/tests/auto/cmake/qmlimportscanner/qis_test.qrc b/tests/auto/cmake/qmlimportscanner/qis_test.qrc new file mode 100644 index 0000000000..1f88fc4e71 --- /dev/null +++ b/tests/auto/cmake/qmlimportscanner/qis_test.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> +<file>./main.qml</file> +<file alias="main.cpp">./main.cpp</file> +</qresource> +</RCC> diff --git a/tools/qmlimportscanner/Qt5QmlImportScannerConfig.cmake.in b/tools/qmlimportscanner/Qt5QmlImportScannerConfig.cmake.in new file mode 100644 index 0000000000..6cdfaf8f6f --- /dev/null +++ b/tools/qmlimportscanner/Qt5QmlImportScannerConfig.cmake.in @@ -0,0 +1,184 @@ +include(CMakeParseArguments) + +function(QT5_IMPORT_QML_PLUGINS target) +!!IF !isEmpty(CMAKE_STATIC_TYPE) + set(options) + set(oneValueArgs \"PATH_TO_SCAN\") + set(multiValueArgs) + + cmake_parse_arguments(arg \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN}) + if(NOT arg_PATH_TO_SCAN) + set(arg_PATH_TO_SCAN \"${CMAKE_CURRENT_SOURCE_DIR}\") + endif() + + # Find location of qmlimportscanner. + find_package(Qt5 COMPONENTS Core) +!!IF isEmpty(CMAKE_BIN_DIR_IS_ABSOLUTE) + set(tool_path + \"${_qt5Core_install_prefix}/$${CMAKE_BIN_DIR}qmlimportscanner$$CMAKE_BIN_SUFFIX\") +!!ELSE + set(tool_path \"$${CMAKE_BIN_DIR}qmlimportscanner$$CMAKE_BIN_SUFFIX\") +!!ENDIF + if(NOT EXISTS \"${tool_path}\" ) + message(FATAL_ERROR \"The package \\\"Qt5QmlImportScannerConfig\\\" references the file + \\\"${tool_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. +\") + endif() + + # Find location of qml dir. +!!IF isEmpty(CMAKE_QML_DIR_IS_ABSOLUTE) + set(qml_path \"${_qt5Core_install_prefix}/$${CMAKE_QML_DIR}\") +!!ELSE + set(qml_path \"$${CMAKE_QML_DIR}\") +!!ENDIF + + # Small macro to avoid duplicating code in two different loops. + macro(_qt5_QmlImportScanner_parse_entry) + set(entry_name \"qml_import_scanner_import_${idx}\") + cmake_parse_arguments(\"entry\" + \"\" + \"CLASSNAME;NAME;PATH;PLUGIN;RELATIVEPATH;TYPE;VERSION;\" \"\" + ${${entry_name}}) + endmacro() + + # Macro used to populate the dependency link flags for a certain configuriation (debug vs + # release) of a plugin. + macro(_qt5_link_to_QmlImportScanner_library_dependencies Plugin Configuration PluginLocation + IsDebugAndRelease) + + set_property(TARGET \"${Plugin}\" APPEND PROPERTY IMPORTED_CONFIGURATIONS ${Configuration}) + set(_imported_location \"${PluginLocation}\") + _qt5_Core_check_file_exists(\"${_imported_location}\") + set_target_properties(\"${Plugin}\" PROPERTIES + \"IMPORTED_LOCATION_${Configuration}\" \"${_imported_location}\" + ) + + set(_static_deps + ${_Qt5${entry_PLUGIN}_STATIC_${Configuration}_LIB_DEPENDENCIES} + ) + + if(NOT "${IsDebugAndRelease}") + set(_genex_condition \"1\") + else() + if("${Configuration}" STREQUAL "DEBUG") + set(_genex_condition \"$<CONFIG:Debug>\") + else() + set(_genex_condition \"$<NOT:$<CONFIG:Debug>>\") + endif() + endif() + if(_static_deps) + set(_static_deps_genex \"$<${_genex_condition}:${_static_deps}>\") + target_link_libraries(${imported_target} INTERFACE \"${_static_deps_genex}\") + endif() + + set(_static_link_flags \"${_Qt5${entry_PLUGIN}_STATIC_${Configuration}_LINK_FLAGS}\") + if(NOT CMAKE_VERSION VERSION_LESS \"3.13\" AND _static_link_flags) + set(_static_link_flags_genex \"$<${_genex_condition}:${_static_link_flags}>\") + target_link_options(${imported_target} INTERFACE \"${_static_link_flags_genex}\") + endif() + endmacro() + + # Run qmlimportscanner and include the generated cmake file. + set(qml_imports_file_path + \"${CMAKE_CURRENT_BINARY_DIR}/Qt5_QmlPlugins_Imports_${target}.cmake\") + + message(STATUS \"Running qmlimportscanner to find used QML plugins. \") + execute_process(COMMAND + \"${tool_path}\" \"${arg_PATH_TO_SCAN}\" -importPath \"${qml_path}\" + -cmake-output + OUTPUT_FILE \"${qml_imports_file_path}\") + + include(\"${qml_imports_file_path}\" OPTIONAL RESULT_VARIABLE qml_imports_file_path_found) + if(NOT qml_imports_file_path_found) + message(FATAL_ERROR \"Could not find ${qml_imports_file_path} which was supposed to be generated by qmlimportscanner.\") + endif() + + # Parse the generate cmake file. + # It is possible for the scanner to find no usage of QML, in which case the import count is 0. + if(qml_import_scanner_imports_count) + set(added_plugins \"\") + foreach(idx RANGE \"${qml_import_scanner_imports_count}\") + _qt5_QmlImportScanner_parse_entry() + if(entry_PATH AND entry_PLUGIN) + # Sometimes a plugin appears multiple times with different versions. + # Make sure to process it only once. + list(FIND added_plugins \"${entry_PLUGIN}\" _index) + if(NOT _index EQUAL -1) + continue() + endif() + list(APPEND added_plugins \"${entry_PLUGIN}\") + + # Add an imported target that will contain the link libraries and link options read + # from one plugin prl file. This target will point to the actual plugin and contain + # static dependency libraries and link flags. + # By creating a target for each qml plugin, CMake will take care of link flag + # deduplication. + set(imported_target \"${target}_QmlImport_${entry_PLUGIN}\") + add_library(\"${imported_target}\" MODULE IMPORTED) + target_link_libraries(\"${target}\" PRIVATE \"${imported_target}\") + + # Read static library dependencies from the plugin .prl file. + # And then set the link flags to the library dependencies extracted from the .prl + # file. +!!IF !isEmpty(CMAKE_RELEASE_TYPE) + _qt5_Core_process_prl_file( + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_RELEASE}.prl\" RELEASE + _Qt5${entry_PLUGIN}_STATIC_RELEASE_LIB_DEPENDENCIES + _Qt5${entry_PLUGIN}_STATIC_RELEASE_LINK_FLAGS + ) + _qt5_link_to_QmlImportScanner_library_dependencies( + \"${imported_target}\" + RELEASE + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_RELEASE}.$$QMAKE_EXTENSION_STATICLIB\" + $${CMAKE_DEBUG_AND_RELEASE}) +!!ENDIF + +!!IF !isEmpty(CMAKE_DEBUG_TYPE) + _qt5_Core_process_prl_file( + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_DEBUG}.prl\" DEBUG + _Qt5${entry_PLUGIN}_STATIC_DEBUG_LIB_DEPENDENCIES + _Qt5${entry_PLUGIN}_STATIC_DEBUG_LINK_FLAGS + ) + _qt5_link_to_QmlImportScanner_library_dependencies( + \"${imported_target}\" + DEBUG + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_DEBUG}.$$QMAKE_EXTENSION_STATICLIB\" + $${CMAKE_DEBUG_AND_RELEASE}) +!!ENDIF + endif() + endforeach() + + # Generate content for plugin initialization cpp file. + set(added_imports \"\") + set(qt5_qml_import_cpp_file_content \"\") + foreach(idx RANGE \"${qml_import_scanner_imports_count}\") + _qt5_QmlImportScanner_parse_entry() + if(entry_PLUGIN) + if(entry_CLASSNAME) + list(FIND added_imports \"${entry_PLUGIN}\" _index) + if(_index EQUAL -1) + string(APPEND qt5_qml_import_cpp_file_content + \"Q_IMPORT_PLUGIN(${entry_CLASSNAME})\n\") + list(APPEND added_imports \"${entry_PLUGIN}\") + endif() + else() + message(FATAL_ERROR + \"Plugin ${entry_PLUGIN} is missing a classname entry, please add one to the qmldir file.\") + endif() + endif() + endforeach() + + # Write to the generated file, and include it as a source for the given target. + set(generated_import_cpp_path + \"${CMAKE_CURRENT_BINARY_DIR}/Qt5_QmlPlugins_Imports_${target}.cpp\") + configure_file(\"${Qt5QmlImportScanner_DIR}/Qt5QmlImportScannerTemplate.cpp.in\" + \"${generated_import_cpp_path}\" + @ONLY) + target_sources(${target} PRIVATE \"${generated_import_cpp_path}\") + endif() +!!ENDIF // !isEmpty(CMAKE_STATIC_TYPE) +endfunction() diff --git a/tools/qmlimportscanner/Qt5QmlImportScannerTemplate.cpp.in b/tools/qmlimportscanner/Qt5QmlImportScannerTemplate.cpp.in new file mode 100644 index 0000000000..4ed747a555 --- /dev/null +++ b/tools/qmlimportscanner/Qt5QmlImportScannerTemplate.cpp.in @@ -0,0 +1,5 @@ +// This file is autogenerated by CMake. It imports static plugin classes for +// static plugins used by QML imports. +#include <QtPlugin> + +@qt5_qml_import_cpp_file_content@ diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index 05d1f7fdc0..6d48f6203d 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -496,6 +496,36 @@ QVariantList findQmlImportsRecursively(const QStringList &qmlDirs, const QString return ret; } + +QString generateCmakeIncludeFileContent(const QVariantList &importList) { + // The function assumes that "list" is a QVariantList with 0 or more QVariantMaps, where + // each map contains QString -> QVariant<QString> mappings. This matches with the structure + // that qmake parses for static qml plugin auto imporitng. + // So: [ {"a": "a","b": "b"}, {"c": "c"} ] + QString content; + QTextStream s(&content); + int importsCount = 0; + for (const QVariant &importVariant: importList) { + if (static_cast<QMetaType::Type>(importVariant.type()) == QMetaType::QVariantMap) { + s << QStringLiteral("set(qml_import_scanner_import_") << importsCount + << QStringLiteral(" \""); + + const QMap<QString, QVariant> &importDict = importVariant.toMap(); + for (auto it = importDict.cbegin(); it != importDict.cend(); ++it) { + s << it.key().toUpper() << QLatin1Char(';') + << it.value().toString() << QLatin1Char(';'); + } + s << QStringLiteral("\")\n"); + ++importsCount; + } + } + if (importsCount >= 0) { + content.prepend(QString(QStringLiteral("set(qml_import_scanner_imports_count %1)\n")) + .arg(importsCount)); + } + return content; +} + } // namespace int main(int argc, char *argv[]) @@ -512,6 +542,7 @@ int main(int argc, char *argv[]) QStringList qmlRootPaths; QStringList scanFiles; QStringList qmlImportPaths; + bool generateCmakeContent = false; int i = 1; while (i < args.count()) { @@ -536,6 +567,8 @@ int main(int argc, char *argv[]) if (i >= args.count()) std::cerr << "-importPath requires an argument\n"; argReceiver = &qmlImportPaths; + } else if (arg == QLatin1String("-cmake-output")) { + generateCmakeContent = true; } else { std::cerr << qPrintable(appName) << ": Invalid argument: \"" << qPrintable(arg) << "\"\n"; @@ -562,8 +595,15 @@ int main(int argc, char *argv[]) // Find the imports! QVariantList imports = findQmlImportsRecursively(qmlRootPaths, scanFiles); - // Convert to JSON - QByteArray json = QJsonDocument(QJsonArray::fromVariantList(imports)).toJson(); - std::cout << json.constData() << std::endl; + QByteArray content; + if (generateCmakeContent) { + // Convert to CMake code + content = generateCmakeIncludeFileContent(imports).toUtf8(); + } else { + // Convert to JSON + content = QJsonDocument(QJsonArray::fromVariantList(imports)).toJson(); + } + + std::cout << content.constData() << std::endl; return 0; } diff --git a/tools/qmlimportscanner/qmlimportscanner.pro b/tools/qmlimportscanner/qmlimportscanner.pro index 0b3a03abf3..a29b582274 100644 --- a/tools/qmlimportscanner/qmlimportscanner.pro +++ b/tools/qmlimportscanner/qmlimportscanner.pro @@ -5,6 +5,51 @@ DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII SOURCES += main.cpp +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 +} + +CMAKE_QML_DIR = $$cmakeRelativePath($$[QT_INSTALL_QML/get], $$[QT_INSTALL_PREFIX]) +contains(CMAKE_QML_DIR, "^\\.\\./.*") { + CMAKE_QML_DIR = $$[QT_INSTALL_QML/get]/ + CMAKE_QML_DIR_IS_ABSOLUTE = True +} +load(qt_build_paths) + +static|staticlib:CMAKE_STATIC_TYPE = true + +# Compute the platform target suffix. +CMAKE_QML_PLUGIN_SUFFIX_RELEASE = +win32: CMAKE_QML_PLUGIN_SUFFIX_DEBUG = d +else:darwin: CMAKE_QML_PLUGIN_SUFFIX_DEBUG = _debug +else: CMAKE_QML_PLUGIN_SUFFIX_DEBUG = + +# Find out which configurations should be handled in the generated Config.cmake file. +CMAKE_DEBUG_TYPE = +CMAKE_RELEASE_TYPE = +if(qtConfig(debug_and_release)|contains(QT_CONFIG, debug, debug|release)): CMAKE_DEBUG_TYPE = debug +if(qtConfig(debug_and_release)|contains(QT_CONFIG, release, debug|release)): CMAKE_RELEASE_TYPE = release + +qtConfig(debug_and_release) { + CMAKE_DEBUG_AND_RELEASE = TRUE +} else { + CMAKE_DEBUG_AND_RELEASE = FALSE +} + +equals(QMAKE_HOST.os, Windows): CMAKE_BIN_SUFFIX = ".exe" +cmake_config_file.input = $$PWD/Qt5QmlImportScannerConfig.cmake.in +cmake_config_file.output = $$MODULE_BASE_OUTDIR/lib/cmake/Qt5QmlImportScanner/Qt5QmlImportScannerConfig.cmake +QMAKE_SUBSTITUTES += cmake_config_file + +cmake_build_integration.files = $$cmake_config_file.output $$PWD/Qt5QmlImportScannerTemplate.cpp.in +cmake_build_integration.path = $$[QT_INSTALL_LIBS]/cmake/Qt5QmlImportScanner +prefix_build: INSTALLS += cmake_build_integration +else: COPIES += cmake_build_integration + QMAKE_TARGET_DESCRIPTION = QML Import Scanner load(qt_tool) |