diff options
Diffstat (limited to 'cmake/QtRpathHelpers.cmake')
-rw-r--r-- | cmake/QtRpathHelpers.cmake | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/cmake/QtRpathHelpers.cmake b/cmake/QtRpathHelpers.cmake new file mode 100644 index 0000000000..da6c8715a8 --- /dev/null +++ b/cmake/QtRpathHelpers.cmake @@ -0,0 +1,289 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Returns the platform-specific relative rpath base token, if it's supported. +# If it's not supported, returns the string NO_KNOWN_RPATH_REL_BASE. +function(qt_internal_get_relative_rpath_base_token out_var) + if(APPLE) + set(rpath_rel_base "@loader_path") + elseif(LINUX OR SOLARIS OR FREEBSD OR HURD OR OPENBSD) + set(rpath_rel_base "$ORIGIN") + else() + set(rpath_rel_base "NO_KNOWN_RPATH_REL_BASE") + endif() + set(${out_var} "${rpath_rel_base}" PARENT_SCOPE) +endfunction() + +# Computes a relative rpath between ${rpath} and ${install_location} using tokens +# like $ORIGIN / @loader_path +# Not all platforms support such tokens though, in which case the returned rpath will be invalid. +# +# install_location: a directory relative to CMAKE_INSTALL_PREFIX, where the binary will be installed +# rpath: an rpath to embed, can be either an absolute path or a path relative to +# ${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}. +function(qt_compute_relative_rpath_base rpath install_location out_var) + set(install_lib_dir_absolute "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}") + get_filename_component(rpath_absolute "${rpath}" + ABSOLUTE BASE_DIR "${install_lib_dir_absolute}") + + set(install_location_absolute "${install_location}") + if(NOT IS_ABSOLUTE "${install_location_absolute}") + set(install_location_absolute "${CMAKE_INSTALL_PREFIX}/${install_location}") + endif() + # Compute relative rpath from where the target will be installed, to the place where libraries + # will be placed (INSTALL_LIBDIR). + file(RELATIVE_PATH rpath_relative "${install_location_absolute}" "${rpath_absolute}") + + if("${rpath_relative}" STREQUAL "") + # file(RELATIVE_PATH) returns an empty string if the given absolute paths are equal + set(rpath_relative ".") + endif() + + # Prepend $ORIGIN / @loader_path style tokens (qmake's QMAKE_REL_RPATH_BASE), to make the + # relative rpaths work. qmake does this automatically when generating a project, so it wasn't + # needed in the .prf files, but for CMake we need to prepend them ourselves. + qt_internal_get_relative_rpath_base_token(rpath_rel_base) + if(rpath_rel_base STREQUAL "NO_KNOWN_RPATH_REL_BASE") + message(WARNING "No known RPATH_REL_BASE for target platform.") + endif() + + if(rpath_relative STREQUAL ".") + set(rpath_relative "${rpath_rel_base}") + else() + set(rpath_relative "${rpath_rel_base}/${rpath_relative}") + endif() + + set("${out_var}" "${rpath_relative}" PARENT_SCOPE) +endfunction() + +# Applies necessary rpaths to a target upon target installation. +# No-op when targeting Windows, Android. +# +# Since abf72395411b135054b5820f64f93dfbcda430b8 rpaths are also applied in non-prefix builds, +# to address -rpath-link issues when cross-compiling, although this might not be needed anymore +# due to 606124c5cceba0dd4a406a9278588b58bb9f9800. +# See QTBUG-86533 for the whole saga. +# +# If no RELATIVE_RPATH option is given, embeds an absolute path rpath to +# ${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR} into the target. + +# If RELATIVE_RPATH is given, the INSTALL_PATH value is used to compute the relative path from +# ${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR} to wherever the target will be installed +# (the value of INSTALL_PATH). +# INSTALL_PATH is expected to be a relative directory where the binary / library will be installed. + +# RELATIVE_RPATH is the equivalent of qmake's relative_qt_rpath. +# INSTALL_PATH is used to implement the equivalent of qmake's $$qtRelativeRPathBase(). +# +# QT_DISABLE_RPATH can be set to disable embedding any Qt specific rpaths. +function(qt_apply_rpaths) + # No rpath support for win32 and android. + if(WIN32 OR ANDROID) + return() + endif() + + # Rpaths explicitly disabled (like for uikit), equivalent to qmake's no_qt_rpath. + # Or feature was turned OFF. + if(QT_DISABLE_RPATH OR NOT QT_FEATURE_rpath) + return() + endif() + + cmake_parse_arguments(PARSE_ARGV 0 arg + "RELATIVE_RPATH" + "TARGET;INSTALL_PATH" + "") + _qt_internal_validate_all_args_are_parsed(arg) + + if(NOT arg_TARGET) + message(FATAL_ERROR "No target given to qt_apply_rpaths.") + else() + set(target "${arg_TARGET}") + endif() + + # If a target is not built (which can happen for tools when crosscompiling, we shouldn't try + # to apply properties. + if(NOT TARGET "${target}") + return() + endif() + + # Protect against interface libraries. + get_target_property(target_type "${target}" TYPE) + if (target_type STREQUAL "INTERFACE_LIBRARY") + return() + endif() + + if(NOT arg_INSTALL_PATH) + message(FATAL_ERROR "No INSTALL_PATH given to qt_apply_rpaths.") + endif() + + set(rpaths "") + + # Modify the install path to contain the nested structure of a framework. + get_target_property(is_framework "${target}" FRAMEWORK) + if(is_framework) + qt_internal_get_framework_info(fw ${target}) + if(UIKIT) + # Shallow framework + string(APPEND arg_INSTALL_PATH "/${fw_dir}") + else() + # Full framework + string(APPEND arg_INSTALL_PATH "/${fw_dir}/Versions/Current") + endif() + endif() + + # Same but for an app bundle. + get_target_property(is_bundle "${target}" MACOSX_BUNDLE) + if(is_bundle AND NOT is_framework) + if(UIKIT) + # Shallow bundle + string(APPEND arg_INSTALL_PATH "/${target}.app") + else() + # Full bundle + string(APPEND arg_INSTALL_PATH "/${target}.app/Contents/MacOS") + endif() + endif() + + qt_internal_get_relative_rpath_base_token(rpath_base_token) + if(rpath_base_token STREQUAL "NO_KNOWN_RPATH_REL_BASE") + set(relative_rpath_supported FALSE) + else() + set(relative_rpath_supported TRUE) + endif() + + # Somewhat similar to mkspecs/features/qt.prf + # Embed either an absolute path to the installed Qt lib dir, or a relative one, based on + # where ${target} is installed. + # Don't embed relative rpaths if the platform does not support it. + if(arg_RELATIVE_RPATH AND relative_rpath_supported) + qt_compute_relative_rpath_base( + "${_default_install_rpath}" "${arg_INSTALL_PATH}" relative_rpath) + list(APPEND rpaths "${relative_rpath}") + else() + list(APPEND rpaths "${_default_install_rpath}") + endif() + + # Somewhat similar to mkspecs/features/qt_build_extra.prf. + foreach(rpath ${QT_EXTRA_RPATHS}) + if(IS_ABSOLUTE "${rpath}") + list(APPEND rpaths "${rpath}") + else() + if(relative_rpath_supported) + qt_compute_relative_rpath_base("${rpath}" "${arg_INSTALL_PATH}" relative_rpath) + list(APPEND rpaths "${relative_rpath}") + else() + # Any extra relative rpaths on a platform that does not support relative rpaths, + # need to be transformed into absolute ones. + set(install_lib_dir_absolute "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}") + get_filename_component(rpath_absolute "${rpath}" + ABSOLUTE BASE_DIR "${install_lib_dir_absolute}") + list(APPEND rpaths "${rpath_absolute}") + endif() + endif() + endforeach() + + if(rpaths) + list(REMOVE_DUPLICATES rpaths) + if(QT_WILL_INSTALL) + set(prop_name "INSTALL_RPATH") + else() + set(prop_name "BUILD_RPATH") + endif() + set_property(TARGET "${target}" APPEND PROPERTY "${prop_name}" ${rpaths}) + endif() +endfunction() + +macro(qt_internal_set_default_rpath_settings) + # the default RPATH to be used when installing, but only if it's not a system directory + list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES + "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}" isSystemDir) + if("${isSystemDir}" STREQUAL "-1") + set(_default_install_rpath "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}") + endif("${isSystemDir}" STREQUAL "-1") + + # The default rpath settings for installed targets is empty. + # The rpaths will instead be computed for each target separately using qt_apply_rpaths(). + # Additional rpaths can be passed via QT_EXTRA_RPATHS. + # By default this will include $ORIGIN / @loader_path, so the installation is relocatable. + # Bottom line: No need to pass anything to CMAKE_INSTALL_RPATH. + set(CMAKE_INSTALL_RPATH "" CACHE STRING "RPATH for installed binaries") + + # By default, don't embed auto-determined RPATHs pointing to directories + # outside of the build tree, into the installed binaries. + # This ended up adding rpaths like ${CMAKE_INSTALL_PREFIX}/lib (or /Users/qt/work/install/lib + # into the official libraries created by the CI) into the non-qtbase libraries, plugins, etc. + # + # It should not be necessary, given that qt_apply_rpaths() already adds the necessary rpaths, + # either relocatable ones or absolute ones, depending on what the platform supports. + if(NOT QT_NO_DISABLE_CMAKE_INSTALL_RPATH_USE_LINK_PATH) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) + endif() + + # If Qt is built without rpath support, we should not add "user-project default rpaths" to + # qt qml plugins. Do this by setting QT_NO_QML_PLUGIN_RPATH to TRUE, which is + # then read by qt6_add_qml_plugin. + # We do this as part of the internal API, because we still want to allow user project qml + # plugins to have sensible default rpaths, even if Qt qml plugins were built without support + # for rpaths. + # + # Note that feature evaluation is not done yet in qtbase at this point, so we check both + # feature variable variants. In practice it doesn't really matter, because the variable is only + # read during qtdeclarative configuration time when the feature is already evaluated. + # + # We also make sure not to set it as a cache var just in case somebody wants to override it + # per directory scope. + if(NOT DEFINED QT_NO_QML_PLUGIN_RPATH + AND (QT_DISABLE_RPATH OR (NOT FEATURE_rpath) OR (NOT QT_FEATURE_rpath))) + set(QT_NO_QML_PLUGIN_RPATH "TRUE") + endif() +endmacro() + +# Overrides the CMAKE_STAGING_PREFIX in a subdirectory scope, to stop CMake from rewriting build +# rpaths to point into the original staging prefix, and thus breaking running executables from +# the build directory. +# See details at https://bugreports.qt.io/browse/QTBUG-102592 +# and https://gitlab.kitware.com/cmake/cmake/-/issues/23421 +# +# This macro is only meant to be called in functions like +# qt_internal_add_module / qt_internal_add_tool to ensure the variable is set in the +# subdirectory scope of the calling function, and not in the actual function scope (where the +# variable assignment would have no effect). +# +# This is the best workaround we can currently do, but it comes with the disadvantage that calling +# subdirectory-scoped install targets does not work anymore. +# e.g. calling ninja src/gui/install will try to install to the fake prefix and fail. +# A regular ninja install call works fine. +# +# Usage of this macro assumes there are no binaries or libraries added in the root CMakeLists.txt +# of the project because that would mean the macro is called at root level scope, which would +# break installation. +# +# The implementation has to be a macro, so we can propagate the variable into the calling +# subdirectory scope. The implementation can't use return(). +macro(qt_internal_apply_staging_prefix_build_rpath_workaround) + set(__qt_internal_should_apply_staging_prefix_build_rpath_workaround TRUE) + # Allow an opt out. + if(QT_NO_STAGING_PREFIX_BUILD_RPATH_WORKAROUND) + set(__qt_internal_should_apply_staging_prefix_build_rpath_workaround FALSE) + endif() + + # No need for workaround if CMAKE_STAGING_PREFIX is not set. + if(NOT CMAKE_STAGING_PREFIX) + set(__qt_internal_should_apply_staging_prefix_build_rpath_workaround FALSE) + endif() + + # No rpath support for win32, android, ios, so nothing to do. + if(WIN32 OR ANDROID OR UIKIT) + set(__qt_internal_should_apply_staging_prefix_build_rpath_workaround FALSE) + endif() + + # Set the staging prefix to a non-existent directory, which is unlikely to have permissions + # for installation. + # The verbose directory name is chosen to attract the user's attention in case if they end up + # calling a subdirectory-scope install file. + if(__qt_internal_should_apply_staging_prefix_build_rpath_workaround) + set_property(GLOBAL PROPERTY _qt_internal_staging_prefix_build_rpath_workaround TRUE) + set(CMAKE_STAGING_PREFIX + "/qt_fake_staging_prefix/check_qt_internal_apply_staging_prefix_build_rpath_workaround" + PARENT_SCOPE) + endif() +endmacro() |