qt5base-lts/cmake/QtSeparateDebugInfo.cmake
Alexandru Croitor 939a8281e6 CMake: Fix picking of the binary_for_strip project location
Fix binary_for_strip project not being found when the following
conditions were met:
- building a repo other than qtbase
- qtbase sources are not available on the machine
(usually happens in CI where only the current repo sources are
available).

The issue was that QT_CMAKE_DIR would always be defined, regardless of
which repo was being built and the system would incorrectly assume
the location of the project files.

The fix is to always pick up the sources from qtbase's source dir if
they are available (this time done with an appropriate check),
otherwise use the installed files.

Note that the behavior of always using the qtbase sources if available
is not exactly the best, but it is a more general issue that affects
other code too.
In the name of consistency, make it so for the binary_for_strip
project as well, but add TODOs in code to address the situation
in a separate change.

Amends 39f657032b

Pick-to: 6.2 6.3
Fixes: QTBUG-102064
Task-number: QTBUG-88090
Task-number: QTBUG-101653
Change-Id: I0649f945e9ff0ab1f597c51bb5ab389fa665c021
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Jörg Bornemann <joerg.bornemann@qt.io>
2022-03-29 14:56:14 +02:00

301 lines
12 KiB
CMake

include(CMakeFindBinUtils)
if(CMAKE_VERSION VERSION_LESS 3.17.0)
set(CMAKE_CURRENT_FUNCTION_LIST_DIR ${CMAKE_CURRENT_LIST_DIR})
endif()
# Builds a shared library which will have strip run on it.
function(qt_internal_try_compile_binary_for_strip binary_out_var)
# Need to find the config.tests files depending whether the qtbase sources are available.
# This mirrors the logic in qt_set_up_build_internals_paths.
# TODO: Clean this up, together with qt_set_up_build_internals_paths to only use the
# the qtbase sources when building qtbase. And perhaps also when doing a non-prefix
# developer-build.
set(config_test_dir "config.tests/binary_for_strip")
set(qtbase_config_test_dir "${QT_SOURCE_TREE}/${config_test_dir}")
set(installed_config_test_dir
"${_qt_cmake_dir}/${QT_CMAKE_EXPORT_NAMESPACE}/${config_test_dir}")
# qtbase sources available, always use them, regardless of prefix or non-prefix builds.
if(EXISTS "${qtbase_config_test_dir}")
set(src_dir "${qtbase_config_test_dir}")
# qtbase sources unavailable, use installed files.
elseif(EXISTS "${installed_config_test_dir}")
set(src_dir "${installed_config_test_dir}")
else()
message(FATAL_ERROR "Can't find binary_for_strip config test project.")
endif()
# Make sure the built project files are not installed when doing an in-source build (like it
# happens in Qt's CI) by choosing a build dir that does not coincide with the installed
# source dir. Otherwise the config test binaries will be packaged up, which we don't want.
set(binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${config_test_dir}_built")
set(flags "")
qt_get_platform_try_compile_vars(platform_try_compile_vars)
list(APPEND flags ${platform_try_compile_vars})
# CI passes the project dir of the Qt repository as absolute path without drive letter:
# \Users\qt\work\qt\qtbase
# Ensure that arg_PROJECT_PATH is an absolute path with drive letter:
# C:/Users/qt/work/qt/qtbase
# This works around CMake upstream issue #22534.
if(CMAKE_HOST_WIN32)
get_filename_component(src_dir "${src_dir}" REALPATH)
endif()
# Build a real binary that strip can be run on.
try_compile(QT_INTERNAL_BUILT_BINARY_FOR_STRIP
"${binary_dir}"
"${src_dir}"
binary_for_strip # project name
OUTPUT_VARIABLE build_output
CMAKE_FLAGS ${flags}
)
# Retrieve the binary path from the build output.
string(REGEX REPLACE ".+###(.+)###.+" "\\1" output_binary_path "${build_output}")
if(NOT EXISTS "${output_binary_path}")
message(FATAL_ERROR "Extracted binary path for strip does not exist: ${output_binary_path}")
endif()
set(${binary_out_var} "${output_binary_path}" PARENT_SCOPE)
endfunction()
# When using the MinGW 11.2.0 toolchain, cmake --install --strip as used by
# qt-cmake-private-intstall.cmake, removes the .gnu_debuglink section in binaries and thus
# breaks the separate debug info feature.
#
# Generate a wrapper shell script that passes an option to keep the debug section.
# The wrapper is used when targeting Linux or MinGW with a shared Qt build.
# The check to see if the option is supported by 'strip', is done once for every repo configured,
# because different machines might have different strip versions installed, without support for
# the option we need.
#
# Once CMake supports custom strip arguments, we can remove the part that creates a shell wrapper.
# https://gitlab.kitware.com/cmake/cmake/-/issues/23346
function(qt_internal_generate_binary_strip_wrapper)
# Return early if check was done already, if explicitly skipped, or when building a static Qt.
if(DEFINED CACHE{QT_INTERNAL_STRIP_SUPPORTS_KEEP_SECTION}
OR QT_NO_STRIP_WRAPPER
OR (NOT QT_BUILD_SHARED_LIBS)
)
return()
endif()
# To make reconfiguration more robust when QT_INTERNAL_STRIP_SUPPORTS_KEEP_SECTION is manually
# removed, make sure to always find the original strip first, by first removing the cached var
# and then finding the binary again.
unset(CMAKE_STRIP CACHE)
include(CMakeFindBinUtils)
# Target Linux and MinGW.
if((UNIX OR MINGW)
AND NOT APPLE
AND CMAKE_STRIP)
# Getting path to a binary we can run strip on.
qt_internal_try_compile_binary_for_strip(valid_binary_path)
# The strip arguments are used both for the execute_process test and also as content
# in the file created by configure_file.
set(strip_arguments "--keep-section=.gnu_debuglink")
# Check if the option is supported.
message(STATUS "Performing Test strip --keep-section")
execute_process(
COMMAND
"${CMAKE_STRIP}" ${strip_arguments} "${valid_binary_path}"
OUTPUT_VARIABLE strip_probe_output
ERROR_VARIABLE strip_probe_output
RESULT_VARIABLE strip_result_var
)
# A successful strip of a binary should have a '0' exit code.
if(NOT strip_result_var STREQUAL "0")
set(keep_section_supported FALSE)
else()
set(keep_section_supported TRUE)
endif()
# Cache the result.
set(QT_INTERNAL_STRIP_SUPPORTS_KEEP_SECTION "${keep_section_supported}" CACHE BOOL
"strip supports --keep-section")
message(DEBUG
"qt_internal_generate_binary_strip_wrapper:\n"
"original strip: ${CMAKE_STRIP}\n"
"strip probe output: ${strip_probe_output}\n"
"strip result: ${strip_result_var}\n"
"keep section supported: ${keep_section_supported}\n"
)
message(STATUS "Performing Test strip --keep-section - ${keep_section_supported}")
# If the option is not supported, don't generate a wrapper and just use the stock binary.
if(NOT keep_section_supported)
return()
endif()
set(wrapper_extension "")
if(NOT CMAKE_HOST_UNIX)
set(wrapper_extension ".bat")
endif()
set(script_name "qt-internal-strip")
# the libexec literal is used on purpose for the source, so the file is found
# on Windows hosts.
set(wrapper_rel_path "libexec/${script_name}${wrapper_extension}.in")
# Need to find the libexec input file depending whether the qtbase sources are available.
# This mirrors the logic in qt_set_up_build_internals_paths.
# TODO: Clean this up, together with qt_set_up_build_internals_paths to only use the
# the qtbase sources when building qtbase. And perhaps also when doing a non-prefix
# developer-build.
set(qtbase_wrapper_in_path "${QT_SOURCE_TREE}/${wrapper_rel_path}")
set(installed_wrapper_in_path
"${_qt_cmake_dir}/${QT_CMAKE_EXPORT_NAMESPACE}/${wrapper_rel_path}")
# qtbase sources available, always use them, regardless of prefix or non-prefix builds.
if(EXISTS "${qtbase_wrapper_in_path}")
set(wrapper_in "${qtbase_wrapper_in_path}")
# qtbase sources unavailable, use installed files.
elseif(EXISTS "${installed_wrapper_in_path}")
set(wrapper_in "${installed_wrapper_in_path}")
else()
message(FATAL_ERROR "Can't find ${script_name}${wrapper_extension}.in file.")
endif()
set(wrapper_out "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/${script_name}${wrapper_extension}")
set(original_strip "${CMAKE_STRIP}")
configure_file("${wrapper_in}" "${wrapper_out}" @ONLY)
# Backup the original strip path for informational purposes.
set(QT_INTERNAL_ORIGINAL_STRIP "${original_strip}" CACHE INTERNAL "Original strip binary")
# Override the strip binary to be used by CMake install target.
set(CMAKE_STRIP "${wrapper_out}" CACHE INTERNAL "Custom Qt strip wrapper")
endif()
endfunction()
# Enable separate debug information for the given target
function(qt_enable_separate_debug_info target installDestination)
set(flags QT_EXECUTABLE)
set(options)
set(multiopts ADDITIONAL_INSTALL_ARGS)
cmake_parse_arguments(arg "${flags}" "${options}" "${multiopts}" ${ARGN})
if (NOT QT_FEATURE_separate_debug_info)
return()
endif()
if (NOT UNIX AND NOT MINGW)
return()
endif()
get_target_property(target_type ${target} TYPE)
if (NOT target_type STREQUAL "MODULE_LIBRARY" AND
NOT target_type STREQUAL "SHARED_LIBRARY" AND
NOT target_type STREQUAL "EXECUTABLE")
return()
endif()
get_property(target_source_dir TARGET ${target} PROPERTY SOURCE_DIR)
get_property(skip_separate_debug_info DIRECTORY "${target_source_dir}" PROPERTY _qt_skip_separate_debug_info)
if (skip_separate_debug_info)
return()
endif()
unset(commands)
if(APPLE)
find_program(DSYMUTIL_PROGRAM dsymutil)
set(copy_bin ${DSYMUTIL_PROGRAM})
set(strip_bin ${CMAKE_STRIP})
set(debug_info_suffix dSYM)
set(copy_bin_out_arg --flat -o)
set(strip_args -S)
else()
set(copy_bin ${CMAKE_OBJCOPY})
set(strip_bin ${CMAKE_OBJCOPY})
if(QNX)
set(debug_info_suffix sym)
set(debug_info_keep --keep-file-symbols)
set(strip_args "--strip-debug -R.ident")
else()
set(debug_info_suffix debug)
set(debug_info_keep --only-keep-debug)
set(strip_args --strip-debug)
endif()
endif()
if(APPLE)
get_target_property(is_framework ${target} FRAMEWORK)
if(is_framework)
qt_internal_get_framework_info(fw ${target})
set(debug_info_bundle_dir "$<TARGET_BUNDLE_DIR:${target}>.${debug_info_suffix}")
set(BUNDLE_ID ${fw_name})
else()
set(debug_info_bundle_dir "$<TARGET_FILE:${target}>.${debug_info_suffix}")
set(BUNDLE_ID ${target})
endif()
set(debug_info_contents_dir "${debug_info_bundle_dir}/Contents")
set(debug_info_target_dir "${debug_info_contents_dir}/Resources/DWARF")
configure_file(
"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/QtSeparateDebugInfo.Info.plist.in"
"Info.dSYM.plist"
)
list(APPEND commands
COMMAND ${CMAKE_COMMAND} -E make_directory ${debug_info_target_dir}
COMMAND ${CMAKE_COMMAND} -E copy "Info.dSYM.plist" "${debug_info_contents_dir}/Info.plist"
)
set(debug_info_target "${debug_info_target_dir}/$<TARGET_FILE_BASE_NAME:${target}>")
if(arg_QT_EXECUTABLE AND QT_FEATURE_debug_and_release)
qt_get_cmake_configurations(cmake_configs)
foreach(cmake_config ${cmake_configs})
# Make installation optional for targets that are not built by default in this config
if(NOT (cmake_config STREQUAL QT_MULTI_CONFIG_FIRST_CONFIG))
set(install_optional_arg OPTIONAL)
else()
unset(install_optional_arg)
endif()
qt_install(DIRECTORY ${debug_info_bundle_dir}
${arg_ADDITIONAL_INSTALL_ARGS}
${install_optional_arg}
CONFIGURATIONS ${cmake_config}
DESTINATION ${installDestination})
endforeach()
else()
qt_install(DIRECTORY ${debug_info_bundle_dir}
${arg_ADDITIONAL_INSTALL_ARGS}
DESTINATION ${installDestination})
endif()
else()
set(debug_info_target "$<TARGET_FILE_DIR:${target}>/$<TARGET_FILE_BASE_NAME:${target}>.${debug_info_suffix}")
qt_install(FILES ${debug_info_target} DESTINATION ${installDestination})
endif()
list(APPEND commands
COMMAND ${copy_bin} ${debug_info_keep} $<TARGET_FILE:${target}>
${copy_bin_out_arg} ${debug_info_target}
COMMAND ${strip_bin} ${strip_args} $<TARGET_FILE:${target}>
)
if(NOT APPLE)
list(APPEND commands
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink=${debug_info_target} $<TARGET_FILE:${target}>
)
endif()
if(NOT CMAKE_HOST_WIN32)
list(APPEND commands
COMMAND chmod -x ${debug_info_target}
)
endif()
add_custom_command(
TARGET ${target}
POST_BUILD
${commands}
VERBATIM)
endfunction()