From d51d7d4d5c87c37ac0b35e15857761e5e5815cc7 Mon Sep 17 00:00:00 2001 From: Alexey Edelev Date: Fri, 13 May 2022 18:24:22 +0200 Subject: [PATCH] Add support for evaluating genexes when exporting custom target props Properties specified in EXPORT_PROPERTIES have their values exported verbatim, without evaluating generator expression they might contain. This limitation is removed by the introduced functions. They collect properties that needs to be exported and evaluate generator expressions inside the properties using file(GENERATE) before exporting them. The functions generate the ExtraProperties.cmake file that contains set_property calls with exported properties and corresponding values. Change-Id: If32c30a82a62e8bd48bb91f3df21ff2ad8d07243 Reviewed-by: Alexandru Croitor --- cmake/QtModuleConfig.cmake.in | 2 + cmake/QtModuleHelpers.cmake | 5 ++ cmake/QtTargetHelpers.cmake | 154 ++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) diff --git a/cmake/QtModuleConfig.cmake.in b/cmake/QtModuleConfig.cmake.in index 55402f50ca..cdb79f4bbb 100644 --- a/cmake/QtModuleConfig.cmake.in +++ b/cmake/QtModuleConfig.cmake.in @@ -27,6 +27,8 @@ endif() if (NOT QT_NO_CREATE_TARGETS AND @INSTALL_CMAKE_NAMESPACE@@target@_FOUND) include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@Targets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@AdditionalTargetInfo.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@ExtraProperties.cmake" + OPTIONAL) if(NOT QT_NO_CREATE_VERSIONLESS_TARGETS) include("${CMAKE_CURRENT_LIST_DIR}/@INSTALL_CMAKE_NAMESPACE@@target@VersionlessTargets.cmake") endif() diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake index f7c5c4b11b..1c9efc6e2d 100644 --- a/cmake/QtModuleHelpers.cmake +++ b/cmake/QtModuleHelpers.cmake @@ -800,6 +800,11 @@ set(QT_ALLOW_MISSING_TOOLS_PACKAGES TRUE)") EXPORT_NAME_PREFIX ${INSTALL_CMAKE_NAMESPACE}${target} CONFIG_INSTALL_DIR "${config_install_dir}") + qt_internal_export_genex_properties(TARGETS ${target} + EXPORT_NAME_PREFIX ${INSTALL_CMAKE_NAMESPACE}${target} + CONFIG_INSTALL_DIR "${config_install_dir}" + ) + ### fixme: cmake is missing a built-in variable for this. We want to apply it only to modules and plugins # that belong to Qt. if(NOT arg_HEADER_MODULE) diff --git a/cmake/QtTargetHelpers.cmake b/cmake/QtTargetHelpers.cmake index 80dfff7cc1..930fbc884f 100644 --- a/cmake/QtTargetHelpers.cmake +++ b/cmake/QtTargetHelpers.cmake @@ -1142,3 +1142,157 @@ function(qt_internal_get_target_sources_property out_var) endif() set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() + +# This function collects target properties that contain generator expressions and needs to be +# exported. This function is needed since the CMake EXPORT_PROPERTIES property doesn't support +# properties that contain generator expressions. +# Usage: qt_internal_add_genex_properties_export(target properties...) +function(qt_internal_add_genex_properties_export target) + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + + set(config_check_begin "") + set(config_check_end "") + if(is_multi_config) + list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type) + + # The genex snippet is evaluated to '$>>' in the generated cmake file. + # The check is only applicable to the 'main' configuration. If user project doesn't use + # multi-config generator, then the check supposed to return true and the value from the + # 'main' configuration supposed to be used. + string(JOIN "" check_if_config_empty + "$<1:$>" + "$" + "$" + ) + + # The genex snippet is evaluated to '$' in the generated cmake + # file and checks if the config that user uses matches the generated cmake file config. + string(JOIN "" check_user_config + "$<1:$>$" + ) + + # The genex snippet is evaluated to '$<$>:'Property content'> + # for non-main Qt configs and to + # $<$,$>>>:'Property content'> for the + # main Qt config. This guard is required to choose the correct value of the property for the + # user project according to the user config type. + # All genexes need to be escaped properly to protect them from evaluation by the + # file(GENERATE call in the qt_internal_export_genex_properties function. + string(JOIN "" config_check_begin + "$<1:$><" + "$<1:$>:$${check_if_config_empty}>" + "$:" + ) + set(config_check_end "$") + endif() + set(target_name "${QT_CMAKE_EXPORT_NAMESPACE}::${target}") + foreach(property IN LISTS ARGN) + set(target_property_genex "$") + # All properties that contain lists need to be protected of processing by JOIN genex calls. + # So this escapes the semicolons for these list. + set(target_property_list_escape + "$,\;>") + set(property_value + "\"${config_check_begin}${target_property_list_escape}${config_check_end}\"") + set_property(TARGET ${target} APPEND PROPERTY _qt_export_genex_properties_content + "${property} ${property_value}") + endforeach() +endfunction() + +# This function executes generator expressions for the properties that are added by the +# qt_internal_add_genex_properties_export function and sets the calculated values to the +# corresponding properties in the generated ExtraProperties.cmake file. The file then needs to be +# included after the target creation routines in Config.cmake files. It also supports Multi-Config +# builds. +# Arguments: +# EXPORT_NAME_PREFIX: +# The portion of the file name before ExtraProperties.cmake +# CONFIG_INSTALL_DIR: +# Installation location for the file. +# TARGETS: +# The internal target names. +function(qt_internal_export_genex_properties) + set(option_args "") + set(single_args + EXPORT_NAME_PREFIX + CONFIG_INSTALL_DIR + ) + set(multi_args TARGETS) + cmake_parse_arguments(arg "${option_args}" "${single_args}" "${multi_args}" ${ARGN}) + + if(NOT arg_EXPORT_NAME_PREFIX) + message(FATAL_ERROR "qt_internal_export_genex_properties: " + "Missing EXPORT_NAME_PREFIX argument.") + endif() + + if(NOT arg_TARGETS) + message(FATAL_ERROR "qt_internal_export_genex_properties: " + "TARGETS argument must contain at least one target") + endif() + + foreach(target IN LISTS arg_TARGETS) + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + + set(output_file_base_name "${arg_EXPORT_NAME_PREFIX}ExtraProperties") + set(should_append "") + set(config_suffix "") + if(is_multi_config) + list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type) + set(config_suffix "$<$>:-$>") + # If the generated file belongs to the 'main' config type, we should set property + # but not append it. + string(JOIN "" should_append + "$<$>: APPEND>") + endif() + set(file_name "${output_file_base_name}${config_suffix}.cmake") + + qt_path_join(output_file "${arg_CONFIG_INSTALL_DIR}" + "${file_name}") + + if(NOT IS_ABSOLUTE "${output_file}") + qt_path_join(output_file "${QT_BUILD_DIR}" "${output_file}") + endif() + + set(target_name "${QT_CMAKE_EXPORT_NAMESPACE}::${target}") + + string(JOIN "" set_property_begin "set_property(TARGET " + "${target_name}${should_append} PROPERTY " + ) + set(set_property_end ")") + set(set_property_glue "${set_property_end}\n${set_property_begin}") + set(property_list + "$>") + string(JOIN "" set_property_content "${set_property_begin}" + "$" + "${set_property_end}") + + if(is_multi_config) + set(config_includes "") + foreach(config IN LISTS CMAKE_CONFIGURATION_TYPES) + if(NOT first_config_type STREQUAL config) + set(include_file_name + "${output_file_base_name}-${config}.cmake") + list(APPEND config_includes + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${include_file_name}\")") + endif() + endforeach() + list(JOIN config_includes "\n" config_includes_string) + set(config_includes_string + "\n$<$:${config_includes_string}>") + endif() + + file(GENERATE OUTPUT "${output_file}" + CONTENT "$<$:${set_property_content}${config_includes_string}>" + CONDITION "$" + ) + endforeach() + + qt_install(FILES "$<$:${output_file}>" + DESTINATION "${arg_CONFIG_INSTALL_DIR}" + COMPONENT Devel + ) +endfunction()