diff options
Diffstat (limited to 'cmake/QtPublicCMakeHelpers.cmake')
-rw-r--r-- | cmake/QtPublicCMakeHelpers.cmake | 482 |
1 files changed, 479 insertions, 3 deletions
diff --git a/cmake/QtPublicCMakeHelpers.cmake b/cmake/QtPublicCMakeHelpers.cmake index 35cd4fe1f3..ca091fcf9a 100644 --- a/cmake/QtPublicCMakeHelpers.cmake +++ b/cmake/QtPublicCMakeHelpers.cmake @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # copy_if_different works incorrect in Windows if file size if bigger than 2GB. # See https://gitlab.kitware.com/cmake/cmake/-/issues/23052 and QTBUG-99491 for details. function(_qt_internal_copy_file_if_different_command out_var src_file dst_file) @@ -22,13 +25,486 @@ endfunction() # The function checks if add_custom_command has the support of the DEPFILE argument. function(_qt_internal_check_depfile_support out_var) if(CMAKE_GENERATOR MATCHES "Ninja" OR - CMAKE_VERSION VERSION_GREATER_EQUAL 3.20 AND CMAKE_GENERATOR MATCHES "Makefiles" - OR CMAKE_VERSION VERSION_GREATER_EQUAL 3.21 + (CMAKE_VERSION VERSION_GREATER_EQUAL 3.20 AND CMAKE_GENERATOR MATCHES "Makefiles") + OR (CMAKE_VERSION VERSION_GREATER_EQUAL 3.21 AND (CMAKE_GENERATOR MATCHES "Xcode" - OR CMAKE_GENERATOR MATCHES "Visual Studio ([0-9]+)" AND CMAKE_MATCH_1 GREATER_EQUAL 12)) + OR (CMAKE_GENERATOR MATCHES "Visual Studio ([0-9]+)" AND CMAKE_MATCH_1 GREATER_EQUAL 12) + ) + ) + ) set(${out_var} TRUE) else() set(${out_var} FALSE) endif() set(${out_var} "${${out_var}}" PARENT_SCOPE) endfunction() + +# Checks if the path points to the cmake directory, like lib/cmake. +function(__qt_internal_check_path_points_to_cmake_dir result path) + string(TOUPPER "${QT_CMAKE_EXPORT_NAMESPACE}" export_namespace_upper) + if((INSTALL_LIBDIR AND path MATCHES "/${INSTALL_LIBDIR}/cmake$") OR + (${export_namespace_upper}_INSTALL_LIBS AND + path MATCHES "/${${export_namespace_upper}_INSTALL_LIBS}/cmake$") OR + path MATCHES "/lib/cmake$" + ) + set(${result} TRUE PARENT_SCOPE) + else() + set(${result} FALSE PARENT_SCOPE) + endif() +endfunction() + +# Creates a reverse path to prefix from possible cmake directories. Returns the unchanged path +# if it doesn't point to cmake directory. +function(__qt_internal_reverse_prefix_path_from_cmake_dir result cmake_path) + string(TOUPPER "${QT_CMAKE_EXPORT_NAMESPACE}" export_namespace_upper) + if(INSTALL_LIBDIR AND cmake_path MATCHES "(.+)/${INSTALL_LIBDIR}/cmake$") + if(CMAKE_MATCH_1) + set(${result} "${CMAKE_MATCH_1}" PARENT_SCOPE) + endif() + elseif(${export_namespace_upper}_INSTALL_LIBS AND + cmake_path MATCHES "(.+)/${${export_namespace_upper}_INSTALL_LIBS}/cmake$") + if(CMAKE_MATCH_1) + set(${result} "${CMAKE_MATCH_1}" PARENT_SCOPE) + endif() + elseif(result MATCHES "(.+)/lib/cmake$") + if(CMAKE_MATCH_1) + set(${result} "${CMAKE_MATCH_1}" PARENT_SCOPE) + endif() + else() + set(${result} "${cmake_path}" PARENT_SCOPE) + endif() +endfunction() + +# Returns the possible cmake directories based on prefix_path. +function(__qt_internal_get_possible_cmake_dirs out_paths prefix_path) + set(${out_paths} "") + + if(EXISTS "${prefix_path}/lib/cmake") + list(APPEND ${out_paths} "${prefix_path}/lib/cmake") + endif() + + string(TOUPPER "${QT_CMAKE_EXPORT_NAMESPACE}" export_namespace_upper) + set(next_path "${prefix_path}/${${export_namespace_upper}_INSTALL_LIBS}/cmake") + if(${export_namespace_upper}_INSTALL_LIBS AND EXISTS "${next_path}") + list(APPEND ${out_paths} "${next_path}") + endif() + + set(next_path "${prefix_path}/${INSTALL_LIBDIR}/cmake") + if(INSTALL_LIBDIR AND EXISTS "${next_path}") + list(APPEND ${out_paths} "${next_path}") + endif() + + list(REMOVE_DUPLICATES ${out_paths}) + set(${out_paths} "${${out_paths}}" PARENT_SCOPE) +endfunction() + +# Collect additional package prefix paths to look for Qt packages, both from command line and the +# env variable ${prefixes_var}. The result is stored in ${out_var} and is a list of paths ending +# with "/lib/cmake". +function(__qt_internal_collect_additional_prefix_paths out_var prefixes_var) + if(DEFINED "${out_var}") + return() + endif() + + set(additional_packages_prefix_paths "") + + set(additional_packages_prefixes "") + if(${prefixes_var}) + list(APPEND additional_packages_prefixes ${${prefixes_var}}) + endif() + if(DEFINED ENV{${prefixes_var}} + AND NOT "$ENV{${prefixes_var}}" STREQUAL "") + set(prefixes_from_env "$ENV{${prefixes_var}}") + if(NOT CMAKE_HOST_WIN32) + string(REPLACE ":" ";" prefixes_from_env "${prefixes_from_env}") + endif() + list(APPEND additional_packages_prefixes ${prefixes_from_env}) + endif() + + foreach(additional_path IN LISTS additional_packages_prefixes) + file(TO_CMAKE_PATH "${additional_path}" additional_path) + + # The prefix paths need to end with lib/cmake to ensure the packages are found when + # cross compiling. Search for REROOT_PATH_ISSUE_MARKER in the qt.toolchain.cmake file for + # details. + # We must pass the values via the PATHS options because the main find_package call uses + # NO_DEFAULT_PATH, and thus CMAKE_PREFIX_PATH values are discarded. + # CMAKE_FIND_ROOT_PATH values are not discarded and togegher with the PATHS option, it + # ensures packages from additional prefixes are found. + __qt_internal_check_path_points_to_cmake_dir(is_path_to_cmake "${additional_path}") + if(is_path_to_cmake) + list(APPEND additional_packages_prefix_paths "${additional_path}") + else() + __qt_internal_get_possible_cmake_dirs(additional_cmake_dirs "${additional_path}") + list(APPEND additional_packages_prefix_paths ${additional_cmake_dirs}) + endif() + endforeach() + + set("${out_var}" "${additional_packages_prefix_paths}" PARENT_SCOPE) +endfunction() + +# Collects CMAKE_MODULE_PATH from QT_ADDITIONAL_PACKAGES_PREFIX_PATH +function(__qt_internal_collect_additional_module_paths) + if(__qt_additional_module_paths_set) + return() + endif() + foreach(prefix_path IN LISTS QT_ADDITIONAL_PACKAGES_PREFIX_PATH) + __qt_internal_check_path_points_to_cmake_dir(is_path_to_cmake "${prefix_path}") + if(is_path_to_cmake) + list(APPEND CMAKE_MODULE_PATH "${prefix_path}/${QT_CMAKE_EXPORT_NAMESPACE}") + else() + __qt_internal_get_possible_cmake_dirs(additional_cmake_dirs "${additional_path}") + list(TRANSFORM additional_cmake_dirs APPEND "/${QT_CMAKE_EXPORT_NAMESPACE}") + list(APPEND CMAKE_MODULE_PATH ${additional_cmake_dirs}) + endif() + endforeach() + list(REMOVE_DUPLICATES CMAKE_MODULE_PATH) + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) + set(__qt_additional_module_paths_set TRUE PARENT_SCOPE) +endfunction() + +# Take a list of prefix paths ending with "/lib/cmake", and return a list of absolute paths with +# "/lib/cmake" removed. +function(__qt_internal_prefix_paths_to_roots out_var prefix_paths) + set(result "") + foreach(path IN LISTS prefix_paths) + __qt_internal_reverse_prefix_path_from_cmake_dir(path "${path}") + list(APPEND result "${path}") + endforeach() + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() + +# This function gets all targets below this directory +# +# Multi-value Arguments: +# EXCLUDE list of target types that should be filtered from resulting list. +# +# INCLUDE list of target types that should be filtered from resulting list. +# EXCLUDE has higher priority than INCLUDE. +function(_qt_internal_collect_buildsystem_targets result dir) + cmake_parse_arguments(arg "" "" "EXCLUDE;INCLUDE" ${ARGN}) + + if(NOT _qt_internal_collect_buildsystem_targets_inner) + set(${result} "") + set(_qt_internal_collect_buildsystem_targets_inner TRUE) + endif() + + set(forward_args "") + if(arg_EXCLUDE) + set(forward_args APPEND EXCLUDE ${arg_EXCLUDE}) + endif() + + if(arg_INCLUDE) + set(forward_args APPEND INCLUDE ${arg_INCLUDE}) + endif() + + get_property(subdirs DIRECTORY "${dir}" PROPERTY SUBDIRECTORIES) + + # Make sure that we don't hit endless recursion when running qt-cmake-standalone-test on a + # in-source test dir, where the currently processed directory lists itself in its SUBDIRECTORIES + # property. + # See https://bugreports.qt.io/browse/QTBUG-119998 + # and https://gitlab.kitware.com/cmake/cmake/-/issues/25489 + # Do it only when QT_INTERNAL_IS_STANDALONE_TEST is set, to avoid the possible slowdown when + # processing many subdirectores when configuring all standalone tests rather than just one. + if(QT_INTERNAL_IS_STANDALONE_TEST) + list(REMOVE_ITEM subdirs "${dir}") + endif() + + foreach(subdir IN LISTS subdirs) + _qt_internal_collect_buildsystem_targets(${result} "${subdir}" ${forward_args}) + endforeach() + get_property(sub_targets DIRECTORY "${dir}" PROPERTY BUILDSYSTEM_TARGETS) + set(real_targets "") + if(sub_targets) + foreach(target IN LISTS sub_targets) + get_target_property(target_type ${target} TYPE) + if((NOT arg_INCLUDE OR target_type IN_LIST arg_INCLUDE) AND + (NOT arg_EXCLUDE OR (NOT target_type IN_LIST arg_EXCLUDE))) + list(APPEND real_targets ${target}) + endif() + endforeach() + endif() + set(${result} ${${result}} ${real_targets} PARENT_SCOPE) +endfunction() + +# Add a custom target ${target} that is *not* added to the default build target in a safe way. +# Dependencies must then be added with _qt_internal_add_phony_target_dependencies. +# +# What's "safe" in this context? For the Visual Studio generators, we cannot use add_dependencies, +# because this would enable the dependencies in the default build of the solution. See QTBUG-115166 +# and upstream CMake issue #16668 for details. Instead, we record the dependencies (added with +# _qt_internal_add_phony_target_dependencies) and create the target at the end of the top-level +# directory scope. +# +# This only works if at least CMake 3.19 is used. Older CMake versions will trigger a warning that +# can be turned off with the variable ${WARNING_VARIABLE}. +# +# For other generators, this is just a call to add_custom_target, unless the target already exists, +# followed by add_dependencies. +# +# Use this function for targets that are not part of the default build, i.e. that should be +# triggered by the user. +# +# TARGET_CREATED_HOOK is the name of a function that is called after the target has been created. +# It takes the target's name as first and only argument. +# +# Example: +# _qt_internal_add_phony_target(update_translations +# WARNING_VARIABLE QT_NO_GLOBAL_LUPDATE_TARGET_CREATED_WARNING +# ) +# _qt_internal_add_phony_target_dependencies(update_translations +# narf_lupdate_zort_lupdate +# ) +# +function(_qt_internal_add_phony_target target) + set(no_value_options "") + set(single_value_options + TARGET_CREATED_HOOK + WARNING_VARIABLE + ) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if("${arg_WARNING_VARIABLE}" STREQUAL "") + message(FATAL_ERROR "WARNING_VARIABLE must be provided.") + endif() + if(CMAKE_GENERATOR MATCHES "^Visual Studio ") + if(${CMAKE_VERSION} VERSION_LESS "3.19.0") + if(NOT ${${arg_WARNING_VARIABLE}}) + message(WARNING + "Cannot create target ${target} with this CMake version. " + "Please upgrade to CMake 3.19.0 or newer. " + "Set ${WARNING_VARIABLE} to ON to disable this warning." + ) + endif() + return() + endif() + + get_property(already_deferred GLOBAL PROPERTY _qt_target_${target}_creation_deferred) + if(NOT already_deferred) + cmake_language(EVAL CODE + "cmake_language(DEFER DIRECTORY \"${CMAKE_SOURCE_DIR}\" CALL _qt_internal_add_phony_target_deferred \"${target}\")" + ) + if(DEFINED arg_TARGET_CREATED_HOOK) + set_property(GLOBAL + PROPERTY _qt_target_${target}_creation_hook ${arg_TARGET_CREATED_HOOK} + ) + endif() + endif() + set_property(GLOBAL APPEND PROPERTY _qt_target_${target}_creation_deferred ON) + else() + if(NOT TARGET ${target}) + add_custom_target(${target}) + if(DEFINED arg_TARGET_CREATED_HOOK) + if(CMAKE_VERSION VERSION_LESS "3.19") + set(incfile + "${CMAKE_CURRENT_BINARY_DIR}/.qt_internal_add_phony_target.cmake" + ) + file(WRITE "${incfile}" "${arg_TARGET_CREATED_HOOK}(${target})") + include("${incfile}") + file(REMOVE "${incfile}") + else() + cmake_language(CALL "${arg_TARGET_CREATED_HOOK}" "${target}") + endif() + endif() + endif() + endif() +endfunction() + +# Adds dependencies to a custom target that has been created with +# _qt_internal_add_phony_target. See the docstring at _qt_internal_add_phony_target for +# more details. +function(_qt_internal_add_phony_target_dependencies target) + set(dependencies ${ARGN}) + if(CMAKE_GENERATOR MATCHES "^Visual Studio ") + set_property(GLOBAL APPEND PROPERTY _qt_target_${target}_dependencies ${dependencies}) + + # Exclude the dependencies from the solution's default build to avoid them being enabled + # accidentally should the user add another dependency to them. + set_target_properties(${dependencies} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON) + else() + add_dependencies(${target} ${dependencies}) + endif() +endfunction() + +# Hack for the Visual Studio generator. Create the custom target named ${target} and work +# around the lack of a working add_dependencies by calling 'cmake --build' for every dependency. +function(_qt_internal_add_phony_target_deferred target) + get_property(target_dependencies GLOBAL PROPERTY _qt_target_${target}_dependencies) + set(target_commands "") + foreach(dependency IN LISTS target_dependencies) + list(APPEND target_commands + COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" -t ${dependency} + ) + endforeach() + add_custom_target(${target} ${target_commands}) + get_property(creation_hook GLOBAL PROPERTY _qt_target_${target}_creation_hook) + if(creation_hook) + cmake_language(CALL ${creation_hook} ${target}) + endif() +endfunction() + +# The helper function that checks if module was included multiple times, and has the inconsistent +# set of targets that belong to the module. It's expected that either all 'targets' or none of them +# will be written to the 'targets_not_defined' variable, if the module was not or was +# searched before accordingly. +function(_qt_internal_check_multiple_inclusion targets_not_defined targets) + set(targets_defined "") + set(${targets_not_defined} "") + set(expected_targets "") + foreach(expected_target ${targets}) + list(APPEND expected_targets ${expected_target}) + if(NOT TARGET Qt::${expected_target}) + list(APPEND ${targets_not_defined} ${expected_target}) + endif() + if(TARGET Qt::${expected_target}) + list(APPEND targets_defined ${expected_target}) + endif() + endforeach() + if("${targets_defined}" STREQUAL "${expected_targets}") + set(${targets_not_defined} "" PARENT_SCOPE) + return() + endif() + if(NOT "${targets_defined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined." + "\nTargets Defined: ${targets_defined}\nTargets not yet defined: " + "${${targets_not_defined}}\n" + ) + endif() + set(${targets_not_defined} "${${targets_not_defined}}" PARENT_SCOPE) +endfunction() + +# The function is used when creating version less targets using ALIASes. +function(_qt_internal_create_versionless_alias_targets targets install_namespace) + foreach(target IN LISTS targets) + add_library(Qt::${target} ALIAS ${install_namespace}::${target}) + endforeach() +endfunction() + +# The function is used when creating version less targets from scratch but not using ALIASes. +# It assigns the known properties from the versioned targets to the versionless created in this +# function. This allows versionless targets mimic the versioned. +function(_qt_internal_create_versionless_targets targets install_namespace) + set(known_interface_properties + QT_MAJOR_VERSION + AUTOMOC_MACRO_NAMES + AUTOUIC_OPTIONS + COMPILE_DEFINITIONS + COMPILE_FEATURES + COMPILE_OPTIONS + CXX_MODULE_SETS + HEADER_SETS + HEADER_SETS_TO_VERIFY + INCLUDE_DIRECTORIES + LINK_DEPENDS + LINK_DIRECTORIES + LINK_LIBRARIES + LINK_LIBRARIES_DIRECT + LINK_LIBRARIES_DIRECT_EXCLUDE + LINK_OPTIONS + POSITION_INDEPENDENT_CODE + PRECOMPILE_HEADERS + SOURCES + SYSTEM_INCLUDE_DIRECTORIES + ) + + set(known_qt_exported_properties + MODULE_PLUGIN_TYPES + QT_DISABLED_PRIVATE_FEATURES + QT_DISABLED_PUBLIC_FEATURES + QT_ENABLED_PRIVATE_FEATURES + QT_ENABLED_PUBLIC_FEATURES + QT_QMAKE_PRIVATE_CONFIG + QT_QMAKE_PUBLIC_CONFIG + QT_QMAKE_PUBLIC_QT_CONFIG + ) + + set(known_qt_exported_properties_interface_allowed + _qt_config_module_name + _qt_is_public_module + _qt_module_has_headers + _qt_module_has_private_headers + _qt_module_has_public_headers + _qt_module_has_qpa_headers + _qt_module_has_rhi_headers + _qt_module_include_name + _qt_module_interface_name + _qt_package_name + _qt_package_version + _qt_private_module_target_name + ) + + set(supported_target_types STATIC_LIBRARY MODULE_LIBRARY SHARED_LIBRARY OBJECT_LIBRARY + INTERFACE_LIBRARY) + + foreach(target IN LISTS targets) + if(NOT TARGET ${install_namespace}::${target}) + message(FATAL_ERROR "${install_namespace}::${target} is not a target, can not extend" + " an alias target") + endif() + + get_target_property(type ${install_namespace}::${target} TYPE) + if(NOT type) + message(FATAL_ERROR "Cannot get the ${install_namespace}::${target} target type.") + endif() + + if(NOT "${type}" IN_LIST supported_target_types) + message(AUTHOR_WARNING "${install_namespace}::${target} requires the versionless" + " target creation, but it has incompatible type ${type}.") + continue() + endif() + + string(REPLACE "_LIBRARY" "" creation_type "${type}") + add_library(Qt::${target} ${creation_type} IMPORTED) + + if(NOT "${type}" STREQUAL "INTERFACE_LIBRARY") + foreach(config "" _RELEASE _DEBUG _RELWITHDEBINFO _MINSIZEREL) + get_target_property(target_imported_location + ${install_namespace}::${target} IMPORTED_LOCATION${config}) + if(NOT target_imported_location) + if("${config}" STREQUAL "") + message(FATAL_ERROR "Cannot create versionless target for" + " ${install_namespace}::${target}. IMPORTED_LOCATION property is " + "missing." + ) + else() + continue() + endif() + endif() + set_property(TARGET Qt::${target} PROPERTY + IMPORTED_LOCATION${config} "${target_imported_location}") + endforeach() + + foreach(property IN LISTS known_qt_exported_properties) + get_target_property(exported_property_value + ${install_namespace}::${target} ${property}) + if(exported_property_value) + set_property(TARGET Qt::${target} APPEND PROPERTY + ${property} "${exported_property_value}") + endif() + endforeach() + endif() + + foreach(property IN LISTS known_interface_properties) + get_target_property(interface_property_value + ${install_namespace}::${target} INTERFACE_${property}) + if(interface_property_value) + set_property(TARGET Qt::${target} APPEND PROPERTY + INTERFACE_${property} "${interface_property_value}") + endif() + endforeach() + + foreach(property IN LISTS known_qt_exported_properties_interface_allowed) + get_target_property(exported_property_value + ${install_namespace}::${target} ${property}) + if(exported_property_value) + set_property(TARGET Qt::${target} APPEND PROPERTY + ${property} "${exported_property_value}") + endif() + endforeach() + + set_property(TARGET Qt::${target} PROPERTY _qt_is_versionless_target TRUE) + endforeach() +endfunction() |