diff options
Diffstat (limited to 'cmake/QtFindPackageHelpers.cmake')
-rw-r--r-- | cmake/QtFindPackageHelpers.cmake | 365 |
1 files changed, 318 insertions, 47 deletions
diff --git a/cmake/QtFindPackageHelpers.cmake b/cmake/QtFindPackageHelpers.cmake index 2ccd61c938..dd10bde75a 100644 --- a/cmake/QtFindPackageHelpers.cmake +++ b/cmake/QtFindPackageHelpers.cmake @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # This function recursively walks transitive link libraries of the given target # and promotes those targets to be IMPORTED_GLOBAL if they are not. # @@ -7,30 +10,53 @@ # Only works if called from qt_find_package(), because the promotion needs to happen in the same # directory scope where the imported target is first created. # -# Uses qt_internal_walk_libs. +# Uses __qt_internal_walk_libs. function(qt_find_package_promote_targets_to_global_scope target) - qt_internal_walk_libs("${target}" _discarded_out_var _discarded_out_var_2 - "qt_find_package_targets_dict" "promote_global") + __qt_internal_walk_libs("${target}" _discarded_out_var _discarded_out_var_2 + "qt_find_package_targets_dict" "promote_global") endfunction() +# As an optimization when using -developer-build, qt_find_package records which +# packages were found during the initial configuration. Then on subsequent +# reconfigurations it skips looking for packages that were not found on the +# initial run. +# For the build system to pick up a newly added qt_find_package call, you need to: +# - Start with a clean build dir +# - Or remove the <builddir>/CMakeCache.txt file and configure from scratch +# - Or remove the QT_INTERNAL_PREVIOUSLY_FOUND_PACKAGES cache variable (by +# editing CMakeCache.txt) and reconfigure. macro(qt_find_package) # Get the target names we expect to be provided by the package. set(find_package_options CONFIG NO_MODULE MODULE REQUIRED) set(options ${find_package_options} MARK_OPTIONAL) set(oneValueArgs MODULE_NAME QMAKE_LIB) - set(multiValueArgs PROVIDED_TARGETS COMPONENTS) + set(multiValueArgs PROVIDED_TARGETS COMPONENTS OPTIONAL_COMPONENTS) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # If some Qt internal project calls qt_find_package(WrapFreeType), but WrapFreeType was already # found as part of a find_dependency() call from a ModuleDependencies.cmake file (or similar), # and the provided target is also found, that means this might have been an unnecessary # qt_find_package() call, because the dependency was already found via some other transitive - # dependency. Return early, so that CMake doesn't fail wiht an error with trying to promote the + # dependency. Return early, so that CMake doesn't fail with an error with trying to promote the # targets to be global. This behavior is not enabled by default, because there are cases # when a regular find_package() (non qt_) can find a package (Freetype -> PNG), and a subsequent # qt_find_package(PNG PROVIDED_TARGET PNG::PNG) still needs to succeed and register the provided # targets. To enable the debugging behavior, set QT_DEBUG_QT_FIND_PACKAGE to 1. set(_qt_find_package_skip_find_package FALSE) + + # Skip looking for packages that were not found on initial configuration, because they likely + # won't be found again, and only waste configuration time. + # Speeds up reconfiguration configuration for certain platforms and repos. + # Due to this behavior being different from what general CMake projects expect, it is only + # done for -developer-builds. + if(QT_INTERNAL_PREVIOUSLY_FOUND_PACKAGES AND + NOT "${ARGV0}" IN_LIST QT_INTERNAL_PREVIOUSLY_FOUND_PACKAGES + AND "${ARGV0}" IN_LIST QT_INTERNAL_PREVIOUSLY_SEARCHED_PACKAGES) + set(_qt_find_package_skip_find_package TRUE) + endif() + + set_property(GLOBAL APPEND PROPERTY _qt_previously_searched_packages "${ARGV0}") + if(QT_DEBUG_QT_FIND_PACKAGE AND ${ARGV0}_FOUND AND arg_PROVIDED_TARGETS) set(_qt_find_package_skip_find_package TRUE) foreach(qt_find_package_target_name ${arg_PROVIDED_TARGETS}) @@ -45,6 +71,12 @@ macro(qt_find_package) endif() endif() + # When configure.cmake is included only to record summary entries, there's no point in looking + # for the packages. + if(__QtFeature_only_record_summary_entries) + set(_qt_find_package_skip_find_package TRUE) + endif() + # Get the version if specified. set(package_version "") if(${ARGC} GREATER_EQUAL 2) @@ -57,7 +89,10 @@ macro(qt_find_package) # Re-append components to forward them. list(APPEND arg_UNPARSED_ARGUMENTS "COMPONENTS;${arg_COMPONENTS}") endif() - # TODO: Handle REQUIRED_COMPONENTS. + if(arg_OPTIONAL_COMPONENTS) + # Re-append optional components to forward them. + list(APPEND arg_UNPARSED_ARGUMENTS "OPTIONAL_COMPONENTS;${arg_OPTIONAL_COMPONENTS}") + endif() # Don't look for packages in PATH if requested to. if(QT_NO_USE_FIND_PACKAGE_SYSTEM_ENVIRONMENT_PATH) @@ -77,16 +112,31 @@ macro(qt_find_package) # in their own way. CMake has FindSQLite3.cmake and with the original # qt_find_package(SQLite3) call it is our intention to use the cmake package # in module mode. - if (${ARGV0}_FOUND AND arg_PROVIDED_TARGETS) - unset(any_target_found) + unset(_qt_any_target_found) + unset(_qt_should_unset_found_var) + if(${ARGV0}_FOUND AND arg_PROVIDED_TARGETS) foreach(expected_target ${arg_PROVIDED_TARGETS}) if (TARGET ${expected_target}) - set(any_target_found TRUE) + set(_qt_any_target_found TRUE) break() endif() endforeach() - if(NOT any_target_found) - unset(${ARGV0}_FOUND) + if(NOT _qt_any_target_found) + set(_qt_should_unset_found_var TRUE) + endif() + endif() + # If we consider the package not to be found, make sure to unset both regular + # and CACHE vars, otherwise CMP0126 set to NEW might cause issues with + # packages not being found correctly. + if(NOT ${ARGV0}_FOUND OR _qt_should_unset_found_var) + unset(${ARGV0}_FOUND) + unset(${ARGV0}_FOUND CACHE) + + # Unset the NOTFOUND ${package}_DIR var that might have been set by the previous + # find_package call, to get rid of "not found" messages in the feature summary + # if the package is found by the next find_package call. + if(DEFINED CACHE{${ARGV0}_DIR} AND NOT ${ARGV0}_DIR) + unset(${ARGV0}_DIR CACHE) endif() endif() endif() @@ -102,13 +152,6 @@ macro(qt_find_package) # E.g. find_package(Qt6 COMPONENTS BuildInternals) followed by # qt_find_package(Qt6 COMPONENTS Core) doesn't end up calling find_package(Qt6Core). if (NOT ${ARGV0}_FOUND AND NOT _qt_find_package_skip_find_package) - # Unset the NOTFOUND ${package}_DIR var that might have been set by the previous - # find_package call, to get rid of "not found" messagees in the feature summary - # if the package is found by the next find_package call. - if(DEFINED CACHE{${ARGV0}_DIR} AND NOT ${ARGV0}_DIR) - unset(${ARGV0}_DIR CACHE) - endif() - # Call original function without our custom arguments. find_package(${arg_UNPARSED_ARGUMENTS}) endif() @@ -121,6 +164,11 @@ macro(qt_find_package) endif() endif() + if(${ARGV0}_FOUND) + # Record that the package was found, so that future reconfigurations can be sped up. + set_property(GLOBAL APPEND PROPERTY _qt_previously_found_packages "${ARGV0}") + endif() + if(${ARGV0}_FOUND AND arg_PROVIDED_TARGETS AND NOT _qt_find_package_skip_find_package) # If package was found, associate each target with its package name. This will be used # later when creating Config files for Qt libraries, to generate correct find_dependency() @@ -148,13 +196,19 @@ macro(qt_find_package) PROPERTY INTERFACE_QT_PACKAGE_COMPONENTS ${components_as_string}) endif() + if(arg_OPTIONAL_COMPONENTS) + string(REPLACE ";" " " components_as_string "${arg_OPTIONAL_COMPONENTS}") + set_property(TARGET ${qt_find_package_target_name} + PROPERTY INTERFACE_QT_PACKAGE_OPTIONAL_COMPONENTS + ${components_as_string}) + endif() + get_property(is_global TARGET ${qt_find_package_target_name} PROPERTY IMPORTED_GLOBAL) qt_internal_should_not_promote_package_target_to_global( "${qt_find_package_target_name}" should_not_promote) if(NOT is_global AND NOT should_not_promote) - set_property(TARGET ${qt_find_package_target_name} PROPERTY - IMPORTED_GLOBAL TRUE) + __qt_internal_promote_target_to_global(${qt_find_package_target_name}) qt_find_package_promote_targets_to_global_scope( "${qt_find_package_target_name}") endif() @@ -174,6 +228,46 @@ macro(qt_find_package) endif() endmacro() +# Save found packages in the cache. They will be read on next reconfiguration to skip looking +# for packages that were not previously found. +# Only applies to -developer-builds by default. +# Can also be opted in or opted out via QT_INTERNAL_SAVE_PREVIOUSLY_FOUND_PACKAGES. +# Opting out will need two reconfigurations to take effect. +function(qt_internal_save_previously_visited_packages) + if(DEFINED QT_INTERNAL_SAVE_PREVIOUSLY_FOUND_PACKAGES) + set(should_save "${QT_INTERNAL_SAVE_PREVIOUSLY_FOUND_PACKAGES}") + else() + if(FEATURE_developer_build OR QT_FEATURE_developer_build) + set(should_save ON) + else() + set(should_save OFF) + endif() + endif() + + if(NOT should_save) + # When the value is flipped to OFF, remove any previously saved packages. + unset(QT_INTERNAL_PREVIOUSLY_FOUND_PACKAGES CACHE) + unset(QT_INTERNAL_PREVIOUSLY_SEARCHED_PACKAGES CACHE) + return() + endif() + + get_property(_qt_previously_found_packages GLOBAL PROPERTY _qt_previously_found_packages) + if(_qt_previously_found_packages) + list(REMOVE_DUPLICATES _qt_previously_found_packages) + set(QT_INTERNAL_PREVIOUSLY_FOUND_PACKAGES "${_qt_previously_found_packages}" CACHE INTERNAL + "List of CMake packages found during configuration using qt_find_package.") + endif() + + get_property(_qt_previously_searched_packages GLOBAL PROPERTY _qt_previously_searched_packages) + if(_qt_previously_searched_packages) + list(REMOVE_DUPLICATES _qt_previously_searched_packages) + set(QT_INTERNAL_PREVIOUSLY_SEARCHED_PACKAGES + "${_qt_previously_searched_packages}" CACHE INTERNAL + "List of CMake packages searched during configuration using qt_find_package." + ) + endif() +endfunction() + # Return qmake library name for the given target, e.g. return "vulkan" for "Vulkan::Vulkan". function(qt_internal_map_target_to_qmake_lib target out_var) set(${out_var} "${QT_QMAKE_LIB_OF_TARGET_${target}}" PARENT_SCOPE) @@ -203,17 +297,17 @@ endfunction() # This function records a dependency between ${main_target_name} and ${dep_target_name} # at the CMake package level. -# E.g. Qt6CoreConfig.cmake needs to find_package(Qt6EntryPoint). +# E.g. Qt6CoreConfig.cmake needs to find_package(Qt6EntryPointPrivate). # main_target_name = Core -# dep_target_name = EntryPoint +# dep_target_name = EntryPointPrivate # This is just a convenience function that deals with Qt targets and their associated packages # instead of raw package names. function(qt_record_extra_qt_package_dependency main_target_name dep_target_name dep_package_version) - # EntryPoint -> Qt6EntryPoint. - qt_internal_module_info(qtfied_target_name "${dep_target_name}") - qt_record_extra_package_dependency("${main_target_name}" "${qtfied_target_name_versioned}" - "${dep_package_version}") + # EntryPointPrivate -> Qt6EntryPointPrivate. + qt_internal_qtfy_target(qtfied_target_name "${dep_target_name}") + qt_record_extra_package_dependency("${main_target_name}" + "${qtfied_target_name_versioned}" "${dep_package_version}") endfunction() # This function records a 'QtFooTools' package dependency for the ${main_target_name} target @@ -249,12 +343,150 @@ function(qt_record_extra_qt_main_tools_package_dependency main_target_name dep_non_versioned_package_name dep_package_version) # WaylandScannerTools -> Qt6WaylandScannerTools. - qt_internal_module_info(qtfied_package_name "${dep_non_versioned_package_name}") + qt_internal_qtfy_target(qtfied_package_name "${dep_non_versioned_package_name}") qt_record_extra_main_tools_package_dependency( "${main_target_name}" "${qtfied_package_name_versioned}" "${dep_package_version}") endfunction() -# This function stores the list of Qt modules a library depend on, +# Record an extra 3rd party target as a dependency for ${main_target_name}. +# +# Adds a find_package(${dep_target_package_name}) in ${main_target_name}Dependencies.cmake. +# +# Needed to record a dependency on the package that provides WrapVulkanHeaders::WrapVulkanHeaders. +# The package version, components, whether the package is optional, etc, are queried from the +# ${dep_target} target properties. +# Usually these are set at the qt_find_package() call site of a configure.cmake file e.g. using +# Qt's MARK_OPTIONAL option. +function(qt_record_extra_third_party_dependency main_target_name dep_target) + if(NOT TARGET "${main_target_name}") + qt_get_tool_target_name(main_target_name "${main_target_name}") + endif() + if(TARGET "${main_target_name}") + get_target_property(extra_deps "${main_target_name}" _qt_extra_third_party_dep_targets) + if(NOT extra_deps) + set(extra_deps "") + endif() + + list(APPEND extra_deps "${dep_target}") + set_target_properties("${main_target_name}" PROPERTIES _qt_extra_third_party_dep_targets + "${extra_deps}") + endif() +endfunction() + +# Sets out_var to TRUE if the non-namespaced ${lib} target is exported as part of Qt6Targets.cmake. +function(qt_internal_is_lib_part_of_qt6_package lib out_var) + if (lib STREQUAL "Platform" + OR lib STREQUAL "GlobalConfig" + OR lib STREQUAL "GlobalConfigPrivate" + OR lib STREQUAL "PlatformModuleInternal" + OR lib STREQUAL "PlatformPluginInternal" + OR lib STREQUAL "PlatformToolInternal" + OR lib STREQUAL "PlatformCommonInternal" + ) + set(${out_var} "TRUE" PARENT_SCOPE) + else() + set(${out_var} "FALSE" PARENT_SCOPE) + endif() +endfunction() + +# Try to get the CMake package version of a Qt target. +# +# Query the target's _qt_package_version property, or try to read it from the CMake package version +# variable set from calling find_package(Qt6${target}). +# Not all targets will have a find_package _VERSION variable, for example if the target is an +# executable. +# A heuristic is used to handle QtFooPrivate module targets. +# If no version can be found, fall back to ${PROJECT_VERSION} and issue a warning. +function(qt_internal_get_package_version_of_target target package_version_out_var) + qt_internal_is_lib_part_of_qt6_package("${target}" is_part_of_qt6) + + if(is_part_of_qt6) + # When building qtbase, Qt6_VERSION is not set (unless examples are built in-tree, + # non-ExternalProject). Use the Platform target's version instead which would be the + # equivalent. + if(TARGET "${QT_CMAKE_EXPORT_NAMESPACE}::Platform") + get_target_property(package_version + "${QT_CMAKE_EXPORT_NAMESPACE}::Platform" _qt_package_version) + endif() + if(NOT package_version) + set(package_version "${${QT_CMAKE_EXPORT_NAMESPACE}_VERSION}") + endif() + else() + # Try to get the version from the target. + # Try the Private target first and if it doesn't exist, try the non-Private target later. + if(TARGET "${QT_CMAKE_EXPORT_NAMESPACE}::${target}") + get_target_property(package_version + "${QT_CMAKE_EXPORT_NAMESPACE}::${target}" _qt_package_version) + endif() + + # Try to get the version from the corresponding package version variable. + if(NOT package_version) + set(package_version "${${QT_CMAKE_EXPORT_NAMESPACE}${target}_VERSION}") + endif() + + # Try non-Private target. + if(NOT package_version AND target MATCHES "(.*)Private$") + set(target "${CMAKE_MATCH_1}") + endif() + + if(NOT package_version AND TARGET "${QT_CMAKE_EXPORT_NAMESPACE}::${target}") + get_target_property(package_version + "${QT_CMAKE_EXPORT_NAMESPACE}::${target}" _qt_package_version) + endif() + + if(NOT package_version) + set(package_version "${${QT_CMAKE_EXPORT_NAMESPACE}${target}_VERSION}") + endif() + endif() + + if(NOT package_version) + set(package_version "${PROJECT_VERSION}") + if(FEATURE_developer_build) + message(WARNING + "Could not determine package version of target ${target}. " + "Defaulting to project version ${PROJECT_VERSION}.") + endif() + endif() + + set(${package_version_out_var} "${package_version}" PARENT_SCOPE) +endfunction() + +# Get the CMake package name that contains / exported the Qt module target. +function(qt_internal_get_package_name_of_target target package_name_out_var) + qt_internal_is_lib_part_of_qt6_package("${target}" is_part_of_qt6) + + if(is_part_of_qt6) + set(package_name "${INSTALL_CMAKE_NAMESPACE}") + else() + # Get the package name from the module's target property. + # If not set, fallback to a name based on the target name. + # + # TODO: Remove fallback once sufficient time has passed, aka all developers updated + # their builds not to contain stale FooDependencies.cmakes files without the + # _qt_package_name property. + set(package_name "") + set(package_name_default "${INSTALL_CMAKE_NAMESPACE}${target}") + set(target_namespaced "${QT_CMAKE_EXPORT_NAMESPACE}::${target}") + if(TARGET "${target_namespaced}") + get_target_property(package_name_from_prop "${target_namespaced}" _qt_package_name) + if(package_name_from_prop) + set(package_name "${package_name_from_prop}") + endif() + endif() + if(NOT package_name) + message(WARNING + "Could not find target ${target_namespaced} to query its package name. " + "Defaulting to package name ${package_name_default}. Consider re-arranging the " + "project structure to ensure the target exists by this point." + ) + set(package_name "${package_name_default}") + endif() + endif() + + set(${package_name_out_var} "${package_name}" PARENT_SCOPE) +endfunction() + +# This function stores the list of Qt targets a library depend on, # along with their version info, for usage in ${target}Depends.cmake file function(qt_register_target_dependencies target public_libs private_libs) get_target_property(target_deps "${target}" _qt_target_deps) @@ -262,31 +494,74 @@ function(qt_register_target_dependencies target public_libs private_libs) set(target_deps "") endif() - # Only process private dependencies if target is a static library get_target_property(target_type ${target} TYPE) set(lib_list ${public_libs}) - if (target_type STREQUAL "STATIC_LIBRARY") + + set(target_is_shared FALSE) + set(target_is_static FALSE) + if(target_type STREQUAL "SHARED_LIBRARY") + set(target_is_shared TRUE) + elseif(target_type STREQUAL "STATIC_LIBRARY") + set(target_is_static TRUE) + endif() + + # Record 'Qt::Foo'-like private dependencies of static library targets, this will be used to + # generate find_dependency() calls. + # + # Private static library dependencies will become $<LINK_ONLY:> dependencies in + # INTERFACE_LINK_LIBRARIES. + if(target_is_static) list(APPEND lib_list ${private_libs}) endif() foreach(lib IN LISTS lib_list) - if ("${lib}" MATCHES "^Qt::(.*)") + if("${lib}" MATCHES "^Qt::(.*)") set(lib "${CMAKE_MATCH_1}") - if (lib STREQUAL Platform - OR lib STREQUAL GlobalConfig - OR lib STREQUAL GlobalConfigPrivate - OR lib STREQUAL PlatformModuleInternal - OR lib STREQUAL PlatformPluginInternal - OR lib STREQUAL PlatformToolInternal) - list(APPEND target_deps "Qt6\;${PROJECT_VERSION}") - elseif ("${lib}" MATCHES "(.*)Private") - list(APPEND target_deps "${INSTALL_CMAKE_NAMESPACE}${CMAKE_MATCH_1}\;${PROJECT_VERSION}") - else() - list(APPEND target_deps "${INSTALL_CMAKE_NAMESPACE}${lib}\;${PROJECT_VERSION}") - endif() + elseif("${lib}" MATCHES "^${QT_CMAKE_EXPORT_NAMESPACE}::(.*)") + set(lib "${CMAKE_MATCH_1}") + else() + set(lib "") + endif() + + if(lib) + qt_internal_get_package_name_of_target("${lib}" package_name) + qt_internal_get_package_version_of_target("${lib}" package_version) + list(APPEND target_deps "${package_name}\;${package_version}") endif() endforeach() + # Record 'Qt::Foo'-like shared private dependencies of shared library targets. + # + # Private shared library dependencies are listed in the target's + # IMPORTED_LINK_DEPENDENT_LIBRARIES and used in rpath-link calculation. + # See QTBUG-86533 for some details. + # We filter out static libraries and common platform targets, but include both SHARED and + # INTERFACE libraries. INTERFACE libraries in most cases will be FooPrivate libraries. + if(target_is_shared AND private_libs) + foreach(lib IN LISTS private_libs) + set(lib_namespaced "${lib}") + if("${lib}" MATCHES "^Qt::(.*)") + set(lib "${CMAKE_MATCH_1}") + elseif("${lib}" MATCHES "^${QT_CMAKE_EXPORT_NAMESPACE}::(.*)") + set(lib "${CMAKE_MATCH_1}") + else() + set(lib "") + endif() + + if(lib) + set(lib "${CMAKE_MATCH_1}") + + qt_internal_is_lib_part_of_qt6_package("${lib}" is_part_of_qt6) + get_target_property(lib_type "${lib_namespaced}" TYPE) + if(NOT lib_type STREQUAL "STATIC_LIBRARY" AND NOT is_part_of_qt6) + qt_internal_get_package_name_of_target("${lib}" package_name) + qt_internal_get_package_version_of_target("${lib}" package_version) + list(APPEND target_deps "${package_name}\;${package_version}") + endif() + endif() + endforeach() + endif() + set_target_properties("${target}" PROPERTIES _qt_target_deps "${target_deps}") endfunction() @@ -295,7 +570,3 @@ function(qt_internal_should_not_promote_package_target_to_global target out_var) get_property(should_not_promote TARGET "${target}" PROPERTY _qt_no_promote_global) set("${out_var}" "${should_not_promote}" PARENT_SCOPE) endfunction() - -function(qt_internal_disable_find_package_global_promotion target) - set_target_properties("${target}" PROPERTIES _qt_no_promote_global TRUE) -endfunction() |