# 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) # The CMake version higher than 3.23 doesn't contain the issue if(CMAKE_HOST_WIN32 AND CMAKE_VERSION VERSION_LESS 3.23) set(${out_var} "${CMAKE_COMMAND}" "-DSRC_FILE_PATH=${src_file}" "-DDST_FILE_PATH=${dst_file}" -P "${_qt_6_config_cmake_dir}/QtCopyFileIfDifferent.cmake" PARENT_SCOPE ) else() set(${out_var} "${CMAKE_COMMAND}" -E copy_if_different "${src_file}" "${dst_file}" PARENT_SCOPE ) endif() 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 AND (CMAKE_GENERATOR MATCHES "Xcode" 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()