diff options
author | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-07-16 15:07:51 +0200 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-08-17 12:03:45 +0200 |
commit | 527687a866d1a155314c1b6d4b8a09533cf8211a (patch) | |
tree | 6959a67af694f5e286d1de7a164b32b35035d69e /tools | |
parent | 412be2096cea12ca480cbbd419f3531a41dac3ae (diff) |
CMake: Provide API to allow handling of QML static plugins
This change adds a new -cmake-output command line argument to
qmlimportscanner which outputs its result in a format which is
consumable by CMake.
This change also adds a new CMake package called Qt5QmlImportScanner.
It provides a function called QT5_IMPORT_QML_PLUGINS()
which is useful for projects that use a static build of Qt and which
also use QML plugins.
Calling it with the target name of your application does the
following:
- Runs qmlimportscanner at configure time to find out which QML /
QtQuick plugins are used by your project
- Links the imported QML plugins into the target
- Links the static dependencies of the QML plugins into the target
- Generates a .cpp file that initializes imported QML plugins,
which is subsequently compiled and linked into the given target
When Qt is built in a shared library config, the introduced function
is a no-op.
[ChangeLog][CMake] Added ability to import static qml plugins with
CMake builds using the new QT5_IMPORT_QML_PLUGINS function.
Task-number: QTBUG-38913
Change-Id: Ib9b9a69654eab13dfbe12d10f5cb28ba3c307d1b
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools')
-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 |
4 files changed, 277 insertions, 3 deletions
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) |