qt5base-lts/cmake/QtPublicWalkLibsHelpers.cmake
Alexandru Croitor 8fc3fcf425 CMake: Add 'collect_targets' mode to __qt_internal_walk_libs
Allow collecting all public dependency CMake targets starting from
a given initial target.

The new mode walks INTERFACE_LINK_LIBRARIES of a shared library
or executable target, as well as the INTERFACE_LINK_LIBRARIES and
LINK_LIBRARIES of a static library target.
Each encountered target (checked with if(TARGET)) is added the output
list.

Note that the private dependencies of a non-static target (like a
shared library or executable) are not walked by the new mode.

Introduce a new function called
__qt_internal_collect_all_target_dependencies which uses the new
mode to collect the full private dependency list of a target.

The final list only contains targets, so no linker flags or file
paths.
This list is useful to do further processing on the targets like
extracting properties from them and running finalizers.

Task-number: QTBUG-92933
Change-Id: I5d96cfa05722d65e2248a344a4f2b0f98a992817
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
2021-05-11 18:57:17 +02:00

294 lines
13 KiB
CMake

# Add libraries to variable ${out_libs_var} in a way that duplicates
# are added at the end. This ensures the library order needed for the
# linker.
function(__qt_internal_merge_libs out_libs_var)
foreach(dep ${ARGN})
list(REMOVE_ITEM ${out_libs_var} ${dep})
list(APPEND ${out_libs_var} ${dep})
endforeach()
set(${out_libs_var} ${${out_libs_var}} PARENT_SCOPE)
endfunction()
# Extracts value from per-target dict key and assigns it to out_var.
# Assumes dict_name to be an existing INTERFACE target.
function(__qt_internal_get_dict_key_values out_var target_infix dict_name dict_key)
get_target_property(values "${dict_name}" "INTERFACE_${target_infix}_${dict_key}")
set(${out_var} "${values}" PARENT_SCOPE)
endfunction()
# Assigns 'values' to per-target dict key, including for aliases of the target.
# Assumes dict_name to be an existing INTERFACE target.
function(__qt_internal_memoize_values_in_dict target dict_name dict_key values)
# Memoize the computed values for the target as well as its aliases.
#
# Aka assigns the contents of ${values} to INTERFACE_Core, INTERFACE_Qt::Core,
# INTERFACE_Qt6::Core.
#
# Yes, i know it's crazy that target names are legal property names.
#
# Assigning for library aliases is needed to avoid multiple recomputation of values.
# Scenario in the context of __qt_internal_walk_libs:
# 'values' are computed for Core target and memoized to INTERFACE_Core.
# When processing Gui, it depends on Qt::Core, but there are no values for INTERFACE_Qt::Core.
set_target_properties(${dict_name} PROPERTIES INTERFACE_${target}_${dict_key} "${values}")
get_target_property(versionless_alias "${target}" "_qt_versionless_alias")
if(versionless_alias)
__qt_internal_get_dict_key_values(
versionless_values "${versionless_alias}" "${dict_name}" "${dict_key}")
if(versionless_values MATCHES "-NOTFOUND$")
set_target_properties(${dict_name}
PROPERTIES INTERFACE_${versionless_alias}_${dict_key} "${values}")
endif()
endif()
get_target_property(versionfull_alias "${target}" "_qt_versionfull_alias")
if(versionfull_alias)
__qt_internal_get_dict_key_values(
versionfull_values "${versionfull_alias}" "${dict_name}" "${dict_key}")
if(versionfull_values MATCHES "-NOTFOUND$")
set_target_properties(${dict_name}
PROPERTIES INTERFACE_${versionfull_alias}_${dict_key} "${values}")
endif()
endif()
endfunction()
# Walks a target's public link libraries recursively, and performs some actions (poor man's
# polypmorphism)
#
# Walks INTERFACE_LINK_LIBRARIES for all target types, as well as LINK_LIBRARIES for static
# library targets.
#
# out_var: the name of the variable where the result will be assigned. The result is a list of
# libraries, mostly in generator expression form.
# rcc_objects_out_var: the name of the variable where the collected rcc object files will be
# assigned (for the initial target and its dependencies)
# dict_name: used for caching the results, and preventing the same target from being processed
# twice
# operation: a string to tell the function what additional behaviors to execute.
# 'collect_libs' (default) operation is to collect linker file paths and flags.
# Used for prl file generation.
# 'promote_global' promotes walked imported targets to global scope.
# 'collect_targets' collects all target names (discards framework or link flags)
#
#
function(__qt_internal_walk_libs
target out_var rcc_objects_out_var dict_name operation)
set(collected ${ARGN})
if(target IN_LIST collected)
return()
endif()
list(APPEND collected ${target})
if(target STREQUAL "${QT_CMAKE_EXPORT_NAMESPACE}::EntryPoint")
# We can't (and don't need to) process EntryPoint because it brings in $<TARGET_PROPERTY:prop>
# genexes which get replaced with $<TARGET_PROPERTY:EntryPoint,prop> genexes in the code below
# and that causes 'INTERFACE_LIBRARY targets may only have whitelisted properties.' errors
# with CMake versions equal to or lower than 3.18. These errors are super unintuitive to
# debug because there's no mention that it's happening during a file(GENERATE) call.
return()
endif()
if(NOT TARGET ${dict_name})
add_library(${dict_name} INTERFACE IMPORTED GLOBAL)
endif()
__qt_internal_get_dict_key_values(libs "${target}" "${dict_name}" "libs")
__qt_internal_get_dict_key_values(rcc_objects "${target}" "${dict_name}" "rcc_objects")
if(libs MATCHES "-NOTFOUND$")
unset(libs)
unset(rcc_objects)
get_target_property(target_libs ${target} INTERFACE_LINK_LIBRARIES)
if(NOT target_libs)
unset(target_libs)
endif()
get_target_property(target_type ${target} TYPE)
if(target_type STREQUAL "STATIC_LIBRARY")
get_target_property(link_libs ${target} LINK_LIBRARIES)
if(link_libs)
list(APPEND target_libs ${link_libs})
endif()
endif()
# Need to record the rcc object file info not only for dependencies, but also for
# the current target too. Otherwise the saved information is incomplete for prl static
# build purposes.
get_target_property(main_target_rcc_objects ${target} _qt_rcc_objects)
if(main_target_rcc_objects)
__qt_internal_merge_libs(rcc_objects ${main_target_rcc_objects})
endif()
foreach(lib ${target_libs})
# Cannot use $<TARGET_POLICY:...> in add_custom_command.
# Check the policy now, and replace the generator expression with the value.
while(lib MATCHES "\\$<TARGET_POLICY:([^>]+)>")
cmake_policy(GET ${CMAKE_MATCH_1} value)
if(value STREQUAL "NEW")
set(value "TRUE")
else()
set(value "FALSE")
endif()
string(REPLACE "${CMAKE_MATCH_0}" "${value}" lib "${lib}")
endwhile()
# Fix up $<TARGET_PROPERTY:FOO> expressions that refer to the "current" target.
# Those cannot be used with add_custom_command.
while(lib MATCHES "\\$<TARGET_PROPERTY:([^,>]+)>")
string(REPLACE "${CMAKE_MATCH_0}" "$<TARGET_PROPERTY:${target},${CMAKE_MATCH_1}>"
lib "${lib}")
endwhile()
# Skip static plugins.
set(_is_plugin_marker_genex "\\$<BOOL:QT_IS_PLUGIN_GENEX>")
if(lib MATCHES "${_is_plugin_marker_genex}")
continue()
endif()
# Strip any directory scope tokens.
__qt_internal_strip_target_directory_scope_token("${lib}" lib)
if(lib MATCHES "^\\$<TARGET_OBJECTS:")
# Skip object files.
continue()
elseif(lib MATCHES "^\\$<LINK_ONLY:(.*)>$")
set(lib_target ${CMAKE_MATCH_1})
else()
set(lib_target ${lib})
endif()
# Skip CMAKE_DIRECTORY_ID_SEP. If a target_link_libraries is applied to a target
# that was defined in a different scope, CMake appends and prepends a special directory
# id separator. Filter those out.
if(lib_target MATCHES "^::@")
continue()
elseif(TARGET ${lib_target})
if ("${lib_target}" MATCHES "^Qt::(.*)")
# If both, Qt::Foo and Foo targets exist, prefer the target name without
# namespace. Which one is preferred doesn't really matter. This code exists to
# avoid ending up with both, Qt::Foo and Foo in our dependencies.
set(namespaceless_lib_target "${CMAKE_MATCH_1}")
if(TARGET namespaceless_lib_target)
set(lib_target ${namespaceless_lib_target})
endif()
endif()
get_target_property(lib_target_type ${lib_target} TYPE)
if(lib_target_type STREQUAL "INTERFACE_LIBRARY")
__qt_internal_walk_libs(
${lib_target}
lib_libs_${target}
lib_rcc_objects_${target}
"${dict_name}" "${operation}" ${collected})
if(lib_libs_${target})
__qt_internal_merge_libs(libs ${lib_libs_${target}})
set(is_module 0)
endif()
if(lib_rcc_objects_${target})
__qt_internal_merge_libs(rcc_objects ${lib_rcc_objects_${target}})
endif()
elseif(NOT lib_target_type STREQUAL "OBJECT_LIBRARY")
if(operation STREQUAL "collect_targets")
__qt_internal_merge_libs(libs ${lib_target})
else()
__qt_internal_merge_libs(libs "$<TARGET_LINKER_FILE:${lib_target}>")
endif()
get_target_property(target_rcc_objects "${lib_target}" _qt_rcc_objects)
if(target_rcc_objects)
__qt_internal_merge_libs(rcc_objects ${target_rcc_objects})
endif()
__qt_internal_walk_libs(
${lib_target}
lib_libs_${target}
lib_rcc_objects_${target}
"${dict_name}" "${operation}" ${collected})
if(lib_libs_${target})
__qt_internal_merge_libs(libs ${lib_libs_${target}})
endif()
if(lib_rcc_objects_${target})
__qt_internal_merge_libs(rcc_objects ${lib_rcc_objects_${target}})
endif()
endif()
if(operation STREQUAL "promote_global")
set(lib_target_unaliased "${lib_target}")
get_target_property(aliased_target ${lib_target} ALIASED_TARGET)
if(aliased_target)
set(lib_target_unaliased ${aliased_target})
endif()
get_property(is_imported TARGET ${lib_target_unaliased} PROPERTY IMPORTED)
get_property(is_global TARGET ${lib_target_unaliased} PROPERTY IMPORTED_GLOBAL)
# Allow opting out of promotion. This is useful in certain corner cases
# like with WrapLibClang and Threads in qttools.
qt_internal_should_not_promote_package_target_to_global(
"${lib_target_unaliased}" should_not_promote)
if(NOT is_global AND is_imported AND NOT should_not_promote)
set_property(TARGET ${lib_target_unaliased} PROPERTY IMPORTED_GLOBAL TRUE)
endif()
endif()
elseif("${lib_target}" MATCHES "^Qt::(.*)")
message(FATAL_ERROR "The ${CMAKE_MATCH_1} target is mentioned as a dependency for \
${target}, but not declared.")
else()
if(NOT operation STREQUAL "collect_targets")
set(final_lib_name_to_merge "${lib_target}")
if(lib_target MATCHES "/([^/]+).framework$")
set(final_lib_name_to_merge "-framework ${CMAKE_MATCH_1}")
endif()
__qt_internal_merge_libs(libs "${final_lib_name_to_merge}")
endif()
endif()
endforeach()
__qt_internal_memoize_values_in_dict("${target}" "${dict_name}" "libs" "${libs}")
__qt_internal_memoize_values_in_dict("${target}" "${dict_name}"
"rcc_objects" "${rcc_objects}")
endif()
set(${out_var} ${libs} PARENT_SCOPE)
set(${rcc_objects_out_var} ${rcc_objects} PARENT_SCOPE)
endfunction()
# Given ${target}, collect all its private dependencies that are CMake targets.
#
# Discards non-CMake-target dependencies like linker flags or file paths.
# Does nothing when given an interface library.
#
# To be used to extract the full list of target dependencies of a library or executable.
function(__qt_internal_collect_all_target_dependencies target out_var)
set(dep_targets "")
get_target_property(target_type ${target} TYPE)
if(NOT target_type STREQUAL "INTERFACE_LIBRARY")
get_target_property(link_libs ${target} LINK_LIBRARIES)
if(link_libs)
foreach(lib ${link_libs})
if(TARGET "${lib}")
list(APPEND dep_targets "${lib}")
__qt_internal_walk_libs(
"${lib}"
lib_walked_targets
_discarded_out_var
"qt_private_link_library_targets"
"collect_targets")
if(lib_walked_targets)
list(APPEND dep_targets ${lib_walked_targets})
endif()
endif()
endforeach()
endif()
endif()
list(REMOVE_DUPLICATES dep_targets)
set(${out_var} "${dep_targets}" PARENT_SCOPE)
endfunction()