From 97b76704ea3b3a062a912656809b17d434f31ffe Mon Sep 17 00:00:00 2001 From: Leander Beernaert Date: Thu, 1 Aug 2019 15:43:36 +0200 Subject: [PATCH] Refactor QML_FILES for add_qml_module It has been decided, that going forward all qml files are to be added to a module via the resource system. This patch does the ground work to make sure all qml modules in the qt codebase follow these new conventions. New properties on targets have been added so that we can't track all the qml related information for later use. To make sure it is still possible to install qml files we added the qt_install_qml_files() command. Pro2cmake has been adjusted to handle the special cases of versioned qml modules (e.g: QtQuick.2). It will now insert a TARGET_PATH override to avoid the default conversion from the URI parameter. Finally, this patch temporarliy disables the quick compiler by moving all relevant code into a dummy function. This will be removed in a follow up patch where the quick compiler will be enable for all qml files present in resource files. Change-Id: I09fe4517fad26ec96122d9c7c777dbfbd214905c Reviewed-by: Simon Hausmann --- cmake/QtBuild.cmake | 229 ++++++++++++++++++++++++++------------- cmake/QtProperties.cmake | 35 ++++++ util/cmake/pro2cmake.py | 65 ++++++++--- 3 files changed, 237 insertions(+), 92 deletions(-) diff --git a/cmake/QtBuild.cmake b/cmake/QtBuild.cmake index 35334353bf8..f0a8c348756 100644 --- a/cmake/QtBuild.cmake +++ b/cmake/QtBuild.cmake @@ -1853,6 +1853,7 @@ endfunction() # # TARGET_PATH: QML target path # QML_FILES: List of QML Files +# RESOURCE_PREFIX: Resource prefix to be prepended to the module's TARGET_PATH # function(add_quick_compiler_target target) @@ -1863,9 +1864,17 @@ Please add QmlTools to your find_package command." endif() qt_parse_all_arguments(arg "add_quick_compiler_target" - "" "TARGET_PATH" "QML_FILES" ${ARGN} + "" "TARGET_PATH;RESOURCE_PREFIX" "QML_FILES" ${ARGN} ) + if (NOT arg_RESOURCE_PREFIX) + message(FATAL_ERROR "add_quick_compiler_target: no resource prefix specified, please specify one using the RESOURCE_PREFIX") + endif() + + if (NOT arg_TARGET_PATH) + message(FATAL_ERROR "add_quick_compiler_target: no target path specified, please specify one using the TARGET_PATH") + endif() + if (arg_QML_FILES) # enable quick compiler and qml loader @@ -1873,7 +1882,7 @@ Please add QmlTools to your find_package command." get_filename_component(file_absolute ${file} ABSOLUTE) file(RELATIVE_PATH file_relative ${CMAKE_CURRENT_SOURCE_DIR} ${file_absolute}) qt_get_relative_resource_path_for_file(alias ${file}) - set(file_resource_path "/qt-project.org/import/${arg_TARGET_PATH}/${alias}") + set(file_resource_path "${arg_RESOURCE_PREFIX}/${arg_TARGET_PATH}/${alias}") list(APPEND file_resource_paths ${file_resource_path}) string(REGEX REPLACE "\.js$" "_js" compiled_file ${file_relative}) string(REGEX REPLACE "\.mjs$" "_mjs" compiled_file ${compiled_file}) @@ -1891,13 +1900,17 @@ Please add QmlTools to your find_package command." target_sources(${target} PRIVATE ${compiled_file}) endforeach() - set(qmlcache_loader_list "${CMAKE_CURRENT_BINARY_DIR}/qmlcache/${target}_qml_loader_file_list.rsp") + # Use hash of the current list of qml files so the generated loader + # cpp file and list should be unique between invocation of this + # function + string(MD5 unique_id "${arg_QML_FILES}") + set(qmlcache_loader_list "${CMAKE_CURRENT_BINARY_DIR}/qmlcache/${unique_id}_${target}_qml_loader_file_list.rsp") file(GENERATE OUTPUT ${qmlcache_loader_list} CONTENT "$" ) - set(qmlcache_loader_file "${CMAKE_CURRENT_BINARY_DIR}/qmlcache/qmlcache_loader.cpp") + set(qmlcache_loader_file "${CMAKE_CURRENT_BINARY_DIR}/qmlcache/${unique_id}_qmlcache_loader.cpp") string(REPLACE "/" "_" resource_name ${arg_TARGET_PATH}) string(REPLACE "." "_" resource_name ${resource_name}) set(resource_name "qmake_${resource_name}.qrc") @@ -1914,56 +1927,142 @@ Please add QmlTools to your find_package command." endif() endfunction() +# Note this function is a temporary implementation that will be removed +# in the next patch. It just aggregates all special processing that used +# to be done on qml files. +function(target_qml_files target) + set(qml_files ${ARGV}) + list(REMOVE_ITEM qml_files ${target}) + + if (NOT qml_files) + message(FATAL_ERROR "Calling target_qml_files without any qml files.") + endif() + + get_target_property(target_path ${target} QT_QML_MODULE_TARGET_PATH) + if (NOT target_path) + message(FATAL_ERROR "Calling target_qml_files on a target has not been created via add_qml_module.") + endif() + + foreach (qml_file IN LISTS qml_files) + if (NOT ${qml_file} MATCHES "\.js$" + AND NOT ${qml_file} MATCHES "\.mjs$" + AND NOT ${qml_file} MATCHES "\.qml$") + message(FATAL_ERROR "target_qml_files, '${qml_file}' is not a valid qml file. Valid extensions are: .js, .mjs and .qml.") + endif() + endforeach() + + + get_target_property(install_qml_files ${target} QT_QML_MODULE_INSTALL_QML_FILES) + get_target_property(embed_qml_files ${target} QT_QML_MODULE_EMBED_QML_FILES) + + # Only generate compiled caches if we are not embedding + if (NOT embed_qml_files AND install_qml_files) + add_qmlcachegen_target(${target} + QML_FILES ${qml_files} + TARGET_PATH ${target_path} + ) + endif() + + if(NOT QT_BUILD_SHARED_LIBS OR embed_qml_files) + get_target_property(prefix ${target} QT_QML_MODULE_RESOURCE_PREFIX) + string(REPLACE "/" "." uri ${target_path}) + string(REPLACE "." "_" uri_target ${uri}) + + add_quick_compiler_target(${target} + TARGET_PATH ${target_path} + QML_FILES ${qml_files} + RESOURCE_PREFIX ${prefix} + ) + endif() + + if (QT_BUILD_SHARED_LIBS AND arg_QML_FILES AND arg_INSTALL_QML_FILES) + qt_copy_or_install(FILES ${arg_QML_FILES} + DESTINATION "${qml_module_install_dir}" + ) + endif() + +endfunction() + + +function(qt_install_qml_files target) + + qt_parse_all_arguments(arg "qt_install_qml_files" + "" "" "FILES" ${ARGN} + ) + + if (NOT arg_FILES) + message(FATAL_ERROR "No files specified for qt_install_qml_files. Please specify them using the FILES parameter.") + endif() + + get_target_property(target_path ${target} QT_QML_MODULE_TARGET_PATH) + if (NOT target_path) + message(FATAL_ERROR "Target ${target} is not a qml module.") + endif() + + + qt_path_join(qml_module_install_dir ${QT_INSTALL_DIR} "${INSTALL_QMLDIR}/${target_path}") + qt_copy_or_install(FILES ${arg_FILES} + DESTINATION ${qml_module_install_dir} + ) + +endfunction() + # This function creates a CMake target for qml modules. It will also make # sure that if no C++ source are present, that qml files show up in the project # in an IDE. Finally, it will also create a custom ${target}_qmltypes which # can be used to generate the respective plugin.qmltypes file. # -# EMBED_QML_FILES: All qml files (.js, .mjs and .qml) will be embedded -# into the binary. -# INSTALL_QML_FILES: All qml files (.js, .mjs and .qml) will be installed with -# the module. +# CPP_PLUGIN: Whether this qml module has any c++ source files. +# URI: Module's uri. # TARGET_PATH: Expected installation path for the Qml Module. Equivalent -# to the module's URI where '.' is replaced with '/'. -# IMPORT_VERSION: Import version for the qml module -# IMPORT_NAME: Override for the default import name used in the ${target}_qmltypes +# to the module's URI where '.' is replaced with '/'. Use this to override the +# default substitution pattern. +# VERSION: Version of the qml module +# NAME: Override for the default import name used in the ${target}_qmltypes # target (optional) +# RESOURCE_PREFIX: Resource import prefix to be prepended to the module's +# target path. # QML_PLUGINDUMP_DEPENDENCIES: Path to a dependencies.json file to be consumed # with the ${target}_qmltypes target (optional) # function(add_qml_module target) + set(qml_module_optional_args + CPP_PLUGIN EMBED_QML_FILES INSTALL_QML_FILES ) set(qml_module_single_args + URI TARGET_PATH - IMPORT_VERSION - IMPORT_NAME + VERSION + NAME + RESOURCE_PREFIX QML_PLUGINDUMP_DEPENDENCIES ) - set(qml_module_multi_args - QML_FILES - ) qt_parse_all_arguments(arg "add_qml_module" "${__add_qt_plugin_optional_args};${qml_module_optional_args}" "${__add_qt_plugin_single_args};${qml_module_single_args}" - "${__add_qt_plugin_multi_args};${qml_module_multi_args}" ${ARGN}) + "${__add_qt_plugin_multi_args}" ${ARGN}) - if (NOT arg_TARGET_PATH) - message(FATAL_ERROR "add_qml_module called without specifying the module's target path. Please specify one using the TARGET_PATH parameter.") + if (NOT arg_URI) + message(FATAL_ERROR "add_qml_module called without specifying the module's uri. Please specify one using the URI parameter.") endif() set(target_path ${arg_TARGET_PATH}) - if (NOT arg_IMPORT_VERSION) - message(FATAL_ERROR "add_qml_module called without specifying the module's import version. Please specify one using the IMPORT_VERSION parameter.") + if (NOT arg_VERSION) + message(FATAL_ERROR "add_qml_module called without specifying the module's import version. Please specify one using the VERSION parameter.") endif() - if (NOT arg_EMBED_QML_FILES AND NOT arg_INSTALL_QML_FILES) - message(FATAL_ERROR "add_qml_module called without EMBED_QML_FILES or INSTALL_QML_FILES. Please specify at least one of these options.") + if (NOT arg_RESOURCE_PREFIX) + message(FATAL_ERROR "add_qml_module called without specifying the module's import prefix. Prease specify one using the RESOURCE_PREFIX parameter.") + endif() + + if (NOT arg_TARGET_PATH) + string(REPLACE "." "/" arg_TARGET_PATH ${arg_URI}) endif() qt_remove_args(plugin_args @@ -1983,46 +2082,36 @@ function(add_qml_module target) # If we have no sources, but qml files, create a custom target so the # qml file will be visibile in an IDE. - if (NOT arg_SOURCES AND arg_QML_FILES) + if (NOT arg_CPP_PLUGIN) add_custom_target(${target} SOURCES ${arg_QML_FILES} ) - if (arg_EMBED_QML_FILES) - message(FATAL_ERROR "Can't embed qml files in a qml module which does not have any c++ source files.") - endif() else() add_qt_plugin(${target} TYPE qml_plugin QML_TARGET_PATH - "${target_path}" - ${plugin_args} + "${arg_TARGET_PATH}" + ${plugin_args} ) endif() - foreach (qml_file IN LISTS arg_QML_FILES) - if (NOT ${qml_file} MATCHES "\.js$" - AND NOT ${qml_file} MATCHES "\.mjs$" - AND NOT ${qml_file} MATCHES "\.qml$") - message(FATAL_ERROR "add_qml_module, '${qml_file}' is not a valid qml file. Valid extensions are: .js, .mjs and .qml.") - endif() - endforeach() - - # Only generate compiled caches if we are not embedding - if (arg_INSTALL_QML_FILES AND arg_QML_FILES AND NOT arg_EMBED_QML_FILES) - add_qmlcachegen_target(${target} - TARGET_PATH ${arg_TARGET_PATH} - QML_FILES ${arg_QML_FILES} - ) - endif() + # Set target properties + set_target_properties(${target} + PROPERTIES + QT_QML_MODULE_TARGET_PATH ${arg_TARGET_PATH} + QT_QML_MODULE_URI ${arg_URI} + QT_QML_MODULE_RESOURCE_PREFIX ${arg_RESOURCE_PREFIX} + QT_QML_MODULE_VERSION ${arg_VERSION} + ) qt_add_qmltypes_target(${target} - TARGET_PATH "${target_path}" - IMPORT_VERSION "${arg_IMPORT_VERSION}" - IMPORT_NAME "${arg_IMPORT_NAME}" + TARGET_PATH "${arg_TARGET_PATH}" + IMPORT_VERSION "${arg_VERSION}" + IMPORT_NAME "${arg_NAME}" QML_PLUGINDUMP_DEPENDENCIES "${arg_QML_PLUGINDUMP_DEPENDENCIES}") - qt_path_join(qml_module_install_dir ${QT_INSTALL_DIR} "${INSTALL_QMLDIR}/${target_path}") + qt_path_join(qml_module_install_dir ${QT_INSTALL_DIR} "${INSTALL_QMLDIR}/${arg_TARGET_PATH}") set(plugin_types "${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes") if (EXISTS ${plugin_types}) qt_copy_or_install(FILES ${plugin_types} @@ -2033,50 +2122,34 @@ function(add_qml_module target) # plugin.qmltypes when present should also be copied to the # cmake binary dir when doing prefix builds file(COPY ${plugin_types} - DESTINATION "${QT_BUILD_DIR}/${INSTALL_QMLDIR}/${target_path}" + DESTINATION "${QT_BUILD_DIR}/${INSTALL_QMLDIR}/${arg_TARGET_PATH}" ) endif() endif() + if (NOT QT_BUILD_SHARED_LIBS) + # only embed qmldir on static builds. Some qml modules may request + # their qml files to be embedded into their binary + string(REPLACE "." "_" qmldir_resource_name ${arg_URI}) + set(qmldir_resource_name "${qmldir_resource_name}_qmldir") + add_qt_resource(${target} ${uri_target} + FILES "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" + PREFIX "${arg_RESOURCE_PREFIX}/${arg_TARGET_PATH}" + ) + endif() + qt_copy_or_install( FILES "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" DESTINATION "${qml_module_install_dir}" ) + if(QT_WILL_INSTALL) # qmldir should also be copied to the cmake binary dir when doing # prefix builds file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" - DESTINATION "${QT_BUILD_DIR}/${INSTALL_QMLDIR}/${target_path}" - ) - endif() - - if(NOT QT_BUILD_SHARED_LIBS OR arg_EMBED_QML_FILES) - string(REPLACE "/" "." uri ${arg_TARGET_PATH}) - string(REPLACE "." "_" uri_target ${uri}) - # only embed qmldir on static builds. Some qml modules may request - # their qml files to be embedded into their binary - if (NOT QT_BUILD_SHARED_LIBS) - list(APPEND resource_files "${CMAKE_CURRENT_SOURCE_DIR}/qmldir") - endif() - - if (resource_files) - add_qt_resource(${target} ${uri_target} - FILES ${resource_files} - PREFIX "/qt-project.org/import/${target_path}" - ) - endif() - - add_quick_compiler_target(${target} - TARGET_PATH ${arg_TARGET_PATH} - QML_FILES ${arg_QML_FILES} - ) - endif() - - if (QT_BUILD_SHARED_LIBS AND arg_QML_FILES AND arg_INSTALL_QML_FILES) - qt_copy_or_install(FILES ${arg_QML_FILES} - DESTINATION "${qml_module_install_dir}" + DESTINATION "${QT_BUILD_DIR}/${INSTALL_QMLDIR}/${arg_TARGET_PATH}" ) endif() diff --git a/cmake/QtProperties.cmake b/cmake/QtProperties.cmake index 0c3507b9538..5cc8ca7b111 100644 --- a/cmake/QtProperties.cmake +++ b/cmake/QtProperties.cmake @@ -59,3 +59,38 @@ define_property(GLOBAL "" ) +define_property(TARGET + PROPERTY + QT_QML_MODULE_TARGET_PATH + BRIEF_DOCS + "Specifies the target path for a qml module" + FULL_DOCS + "Specifies the target path for a qml module" +) + +define_property(TARGET + PROPERTY + QT_QML_MODULE_URI + BRIEF_DOCS + "Specifies the URI for a qml module" + FULL_DOCS + "Specifies the URI for a qml module" +) + +define_property(TARGET + PROPERTY + QT_QML_MODULE_RESOURCE_PREFIX + BRIEF_DOCS + "Specifies the qml module's resource prefix." + FULL_DOCS + "Specifies the qml module's resource prefix." +) + +define_property(TARGET + PROPERTY + QT_QML_MODULE_VERSION + BRIEF_DOCS + "Specifies the qml module's version." + FULL_DOCS + "Specifies the qml module's version." +) diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py index 80c975cec19..a62b094ee97 100755 --- a/util/cmake/pro2cmake.py +++ b/util/cmake/pro2cmake.py @@ -651,8 +651,8 @@ class Scope(object): return self._evalOps(key, None, [], inherit=inherit) - def get_string(self, key: str, default: str = '') -> str: - v = self.get(key) + def get_string(self, key: str, default: str = '', inherit : bool = False) -> str: + v = self.get(key, inherit = inherit) if len(v) == 0: return default assert len(v) == 1 @@ -1646,6 +1646,8 @@ def write_main_part(cm_fh: typing.IO[str], name: str, typename: str, # Evaluate total condition of all scopes: recursive_evaluate_scope(scope) + is_qml_plugin = any('qml_plugin' == s for s in scope.get('_LOADED')) + if 'exceptions' in scope.get('CONFIG'): extra_lines.append('EXCEPTIONS') @@ -1706,6 +1708,9 @@ def write_main_part(cm_fh: typing.IO[str], name: str, typename: str, write_android_part(cm_fh, name, scopes[0], indent) + if is_qml_plugin: + write_qml_plugin_qml_files(cm_fh, name, scopes[0], indent) + ignored_keys_report = write_ignored_keys(scopes[0], spaces(indent)) if ignored_keys_report: cm_fh.write(ignored_keys_report) @@ -1898,6 +1903,7 @@ def write_plugin(cm_fh, scope, *, indent: int = 0): write_main_part(cm_fh, plugin_name, 'Plugin', plugin_function_name, scope, indent=indent, extra_lines=extra, known_libraries={}, extra_keys=[]) + def write_qml_plugin(cm_fh: typing.IO[str], target: str, scope: Scope, *, @@ -1908,36 +1914,67 @@ def write_qml_plugin(cm_fh: typing.IO[str], indent += 2 scope_config = scope.get('CONFIG') is_embedding_qml_files = False - if 'builtin_resources' in scope_config or 'qt_quick_compiler' in scope_config: - extra_lines.append('EMBED_QML_FILES') - is_embedding_qml_files = True - if not is_embedding_qml_files: - extra_lines.append('INSTALL_QML_FILES') - if 'install_qml_files' in scope_config and is_embedding_qml_files: - extra_lines.append('INSTALL_QML_FILES') + sources = scope.get_files('SOURCES') + if len(sources) != 0: + extra_lines.append('CPP_PLUGIN') target_path = scope.get_string('TARGETPATH') if target_path: - extra_lines.append('TARGET_PATH "{}"'.format(target_path)) + uri = target_path.replace('/','.') + extra_lines.append('URI "{}"'.format(uri)) + # Catch special cases such as foo.QtQuick.2.bar, which when converted + # into a target path via cmake will result in foo/QtQuick/2/bar, which is + # not what we want. So we supply the target path override. + target_path_from_uri = uri.replace('.', '/') + if target_path != target_path_from_uri: + extra_lines.append('TARGET_PATH "{}"'.format(target_path)) + import_version = scope.get_string('IMPORT_VERSION') if import_version: import_version = import_version.replace("$$QT_MINOR_VERSION","${CMAKE_PROJECT_VERSION_MINOR}") - extra_lines.append('IMPORT_VERSION "{}"'.format(import_version)) + extra_lines.append('VERSION "{}"'.format(import_version)) + import_name = scope.get_string('IMPORT_NAME') if import_name: - extra_lines.append('IMPORT_NAME "{}"'.format(import_name)) + extra_lines.append('NAME "{}"'.format(import_name)) plugindump_dep = scope.get_string('QML_PLUGINDUMP_DEPENDENCIES') + if plugindump_dep: extra_lines.append('QML_PLUGINDUMP_DEPENDENCIES "{}"'.format(plugindump_dep)) + # This is only required because of qmldir + extra_lines.append('RESOURCE_PREFIX "/qt-project.org/imports"') + +def write_qml_plugin_qml_files(cm_fh: typing.IO[str], + target: str, + scope: Scope, + indent: int = 0): + qml_files = scope.get_files('QML_FILES', use_vpath=True) if qml_files: - extra_lines.append('QML_FILES\n{}{}'.format( + target_path = scope.get_string('TARGETPATH', inherit=True) + target_path_mangled = target_path.replace('/', '_') + target_path_mangled = target_path_mangled.replace('.', '_') + resource_name = 'qmake_' + target_path_mangled + prefix = '/qt-project.org/imports/' + target_path + cm_fh.write('\n# QML Files\n') + cm_fh.write('{}add_qt_resource({} {}\n{}PREFIX\n{}"{}"\n{}FILES\n{}{}\n)\n'.format( spaces(indent), - '\n{}'.format(spaces(indent)).join(qml_files))) + target, + resource_name, + spaces(indent + 1), + spaces(indent + 2), + prefix, + spaces(indent + 1), + spaces(indent + 2), + '\n{}'.format(spaces(indent + 2)).join(qml_files))) + if 'install_qml_files' in scope.get('CONFIG'): + cm_fh.write('\nqt_install_qml_files({}\n FILES\n {}\n)\n\n'.format( + target, + '\n '.join(qml_files))) def handle_app_or_lib(scope: Scope, cm_fh: typing.IO[str], *,