diff options
Diffstat (limited to 'cmake/QtPrlHelpers.cmake')
-rw-r--r-- | cmake/QtPrlHelpers.cmake | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/cmake/QtPrlHelpers.cmake b/cmake/QtPrlHelpers.cmake new file mode 100644 index 0000000000..7977afd030 --- /dev/null +++ b/cmake/QtPrlHelpers.cmake @@ -0,0 +1,313 @@ +# 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_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() + +# Collects the library dependencies of a target. +# This takes into account transitive usage requirements. +function(qt_collect_libs target out_var) + qt_internal_walk_libs("${target}" "${out_var}" "qt_collect_libs_dict" "collect_libs") + set("${out_var}" "${${out_var}}" PARENT_SCOPE) +endfunction() + +# Walks a target's link libraries recursively, and performs some actions (poor man's polypmorphism) +# +# out_var is the name of the variable where the result will be assigned. The result is a list of +# libraries, mostly in generator expression form. +# dict_name is used for caching the result, and preventing the same target from being processed +# twice +# operation is a string to tell the function what to do +function(qt_internal_walk_libs target out_var dict_name operation) + set(collected ${ARGN}) + if(target IN_LIST collected) + return() + endif() + list(APPEND collected ${target}) + if(NOT TARGET ${dict_name}) + add_library(${dict_name} INTERFACE IMPORTED GLOBAL) + endif() + get_target_property(libs ${dict_name} INTERFACE_${target}) + if(NOT libs) + unset(libs) + 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() + 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 "${dict_name}" "${operation}" ${collected}) + if(lib_libs) + qt_merge_libs(libs ${lib_libs}) + set(is_module 0) + endif() + else() + qt_merge_libs(libs "$<TARGET_FILE:${lib_target}>") + qt_internal_walk_libs( + ${lib_target} lib_libs "${dict_name}" "${operation}" ${collected}) + if(lib_libs) + qt_merge_libs(libs ${lib_libs}) + 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() + else() + 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_merge_libs(libs "${final_lib_name_to_merge}") + endif() + endforeach() + set_target_properties(${dict_name} PROPERTIES INTERFACE_${target} "${libs}") + endif() + set(${out_var} ${libs} PARENT_SCOPE) +endfunction() + +# Generate a qmake .prl file for the given target. +# The install_dir argument is a relative path, for example "lib". +function(qt_generate_prl_file target install_dir) + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL "INTERFACE_LIBRARY") + return() + endif() + + get_target_property(rcc_objects ${target} QT_RCC_OBJECTS) + if(rcc_objects) + if(QT_WILL_INSTALL) + list(TRANSFORM rcc_objects PREPEND "$$[QT_INSTALL_LIBS]/") + endif() + else() + unset(rcc_objects) + endif() + + unset(prl_config) + set(is_static FALSE) + if(target_type STREQUAL "STATIC_LIBRARY") + list(APPEND prl_config static) + set(is_static TRUE) + elseif(target_type STREQUAL "SHARED_LIBRARY") + list(APPEND prl_config shared) + endif() + if (NOT target_type STREQUAL "INTERFACE_LIBRARY") + get_target_property(is_fw ${target} FRAMEWORK) + if(is_fw) + list(APPEND prl_config lib_bundle) + endif() + endif() + list(JOIN prl_config " " prl_config) + + # Generate a preliminary .prl file that contains absolute paths to all libraries + if(MINGW) + # For MinGW, qmake doesn't have a lib prefix in prl files. + set(prefix_for_final_prl_name "") + else() + set(prefix_for_final_prl_name "$<TARGET_FILE_PREFIX:${target}>") + endif() + + # For frameworks, the prl file should be placed under the Resources subdir. + get_target_property(is_framework ${target} FRAMEWORK) + if(is_framework) + get_target_property(fw_version ${target} FRAMEWORK_VERSION) + string(APPEND prefix_for_final_prl_name "Versions/${fw_version}/Resources/") + endif() + + # What follows is a complicated setup for generating configuration-specific + # prl files. It has to be this way, because add_custom_command doesn't support + # generator expressions in OUTPUT or DEPENDS. + # To circumvent that, we create well known file names with file(GENERATE) + # with configuration specific content, which are then fed to add_custom_command + # that uses these genex-less file names. The actual command will extract the info + # from the configuration-specific files, and create a properly named final prl file. + + # The file is named according to a pattern, that is then used in the + # add_custom_command. + set(prl_step1_name_prefix "preliminary_prl_for_${target}_step1_") + set(prl_step1_name_suffix ".prl" ) + qt_path_join(prl_step1_path + "${CMAKE_CURRENT_BINARY_DIR}" + "${prl_step1_name_prefix}$<CONFIG>${prl_step1_name_suffix}") + + # Same, except instead of containing the prl contents, it will contain the final prl file + # name computed via a generator expression. + set(prl_meta_info_name_prefix "preliminary_prl_meta_info_for_${target}_") + set(prl_meta_info_name_suffix ".txt") + qt_path_join(prl_meta_info_path + "${CMAKE_CURRENT_BINARY_DIR}" + "${prl_meta_info_name_prefix}$<CONFIG>${prl_meta_info_name_suffix}") + + # The final prl file name that will be embedded in the file above. + set(final_prl_file_name "${prefix_for_final_prl_name}$<TARGET_FILE_BASE_NAME:${target}>.prl") + qt_path_join(final_prl_file_path "${QT_BUILD_DIR}/${install_dir}" "${final_prl_file_name}") + + # Generate the prl content and its final file name into configuration specific files + # whose names we know, and can be used in add_custom_command. + set(prl_step1_content + "RCC_OBJECTS = ${rcc_objects} +QMAKE_PRL_BUILD_DIR = ${CMAKE_CURRENT_BINARY_DIR} +QMAKE_PRL_TARGET = $<TARGET_FILE_NAME:${target}> +QMAKE_PRL_CONFIG = ${prl_config} +QMAKE_PRL_VERSION = ${PROJECT_VERSION} +") + if(NOT is_static AND WIN32) + # Do nothing. Prl files for shared libraries on Windows shouldn't have the libs listed, + # as per qt_build_config.prf and the conditional CONFIG+=explicitlib assignment. + else() + set(prl_libs "") + qt_collect_libs(${target} prl_libs) + string(APPEND prl_step1_content "QMAKE_PRL_LIBS_FOR_CMAKE = ${prl_libs}\n") + endif() + + file(GENERATE + OUTPUT "${prl_step1_path}" + CONTENT "${prl_step1_content}") + file(GENERATE + OUTPUT "${prl_meta_info_path}" + CONTENT + "FINAL_PRL_FILE_PATH = ${final_prl_file_path}") + + set(library_prefixes ${CMAKE_SHARED_LIBRARY_PREFIX} ${CMAKE_STATIC_LIBRARY_PREFIX}) + set(library_suffixes + ${CMAKE_SHARED_LIBRARY_SUFFIX} + ${CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES} + ${CMAKE_STATIC_LIBRARY_SUFFIX}) + + if(QT_GENERATOR_IS_MULTI_CONFIG) + set(configs ${CMAKE_CONFIGURATION_TYPES}) + else() + set(configs ${CMAKE_BUILD_TYPE}) + endif() + + foreach(config ${configs}) + # Output file for dependency tracking, and which will contain the final content. + qt_path_join(prl_step2_path + "${CMAKE_CURRENT_BINARY_DIR}" "preliminary_prl_for_${target}_step2_${config}.prl") + + # Input dependency names that are constructed for each config manually + # (no genexes allowed). + qt_path_join(prl_step1_path + "${CMAKE_CURRENT_BINARY_DIR}" + "${prl_step1_name_prefix}${config}${prl_step1_name_suffix}") + qt_path_join(prl_meta_info_path + "${CMAKE_CURRENT_BINARY_DIR}" + "${prl_meta_info_name_prefix}${config}${prl_meta_info_name_suffix}") + add_custom_command( + OUTPUT "${prl_step2_path}" + DEPENDS "${prl_step1_path}" + "${prl_meta_info_path}" + "${QT_CMAKE_DIR}/QtFinishPrlFile.cmake" + "${QT_CMAKE_DIR}/QtGenerateLibHelpers.cmake" + COMMAND ${CMAKE_COMMAND} + "-DIN_FILE=${prl_step1_path}" + "-DIN_META_FILE=${prl_meta_info_path}" + "-DOUT_FILE=${prl_step2_path}" + "-DLIBRARY_PREFIXES=${library_prefixes}" + "-DLIBRARY_SUFFIXES=${library_suffixes}" + "-DLINK_LIBRARY_FLAG=${CMAKE_LINK_LIBRARY_FLAG}" + "-DQT_BUILD_LIBDIR=${QT_BUILD_DIR}/${INSTALL_LIBDIR}" + -P "${QT_CMAKE_DIR}/QtFinishPrlFile.cmake" + VERBATIM + COMMENT "Generating prl file for target ${target}" + ) + + # Tell the target to depend on the preliminary prl file, to ensure the custom command + # is executed. As a side-effect, this will also create the final prl file that + # is named appropriately. It should not be specified as a BYPRODUCT. + # This allows proper per-file dependency tracking, without having to resort on a POST_BUILD + # step, which means that relinking would happen as well as transitive rebuilding of any + # dependees. + # This is inspired by https://gitlab.kitware.com/cmake/cmake/-/issues/20842 + target_sources(${target} PRIVATE "${prl_step2_path}") + endforeach() + + # Installation of the .prl file happens globally elsewhere, + # because we have no clue here what the actual file name is. + # What we know however, is the directory where the prl file is created. + # Save that for later, to install all prl files from that directory. + get_property(prl_install_dirs GLOBAL PROPERTY QT_PRL_INSTALL_DIRS) + if(NOT install_dir IN_LIST prl_install_dirs) + set_property(GLOBAL APPEND PROPERTY QT_PRL_INSTALL_DIRS "${install_dir}") + endif() +endfunction() |