CMake: Set RPATH of deployed plugins on Linux

When deploying into some directory structure where CMAKE_INSTALL_LIBDIR
is different from Qt's lib dir, we need to set the RPATH of installed
plugins such that Qt libraries are found.

We do this using CMake's undocumented file(RPATH_SET) command and pray
that this command is safe to use across current and future CMake
versions.  For CMake versions < 3.21, we use patchelf, which must be
installed on the host system.

The adjustment of rpaths can be turned on explicitly by setting
QT_DEPLOY_FORCE_ADJUST_RPATHS to ON.

The usage of patchelf can be forced by setting QT_DEPLOY_USE_PATCHELF to
ON regardless of the CMake version.

Change-Id: I62ced496b4c12bf6d46735d2af7ff35130148acb
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
This commit is contained in:
Joerg Bornemann 2022-09-29 13:06:12 +02:00
parent 5ca714318c
commit 5430fb2243
4 changed files with 97 additions and 7 deletions

View File

@ -111,6 +111,50 @@ function(_qt_internal_re_escape out_var str)
set(${out_var} ${regex} PARENT_SCOPE)
endfunction()
function(_qt_internal_set_rpath)
if(NOT CMAKE_HOST_UNIX OR CMAKE_HOST_APPLE)
message(WARNING "_qt_internal_set_rpath is not implemented on this platform.")
return()
endif()
set(no_value_options "")
set(single_value_options FILE NEW_RPATH)
set(multi_value_options "")
cmake_parse_arguments(PARSE_ARGV 0 arg
"${no_value_options}" "${single_value_options}" "${multi_value_options}"
)
if(__QT_DEPLOY_USE_PATCHELF)
message(STATUS "Setting runtime path of '${arg_FILE}' to '${arg_NEW_RPATH}'.")
execute_process(
COMMAND ${__QT_DEPLOY_PATCHELF_EXECUTABLE} --set-rpath "${arg_NEW_RPATH}" "${arg_FILE}"
RESULT_VARIABLE process_result
)
if(NOT process_result EQUAL "0")
if(process_result MATCHES "^[0-9]+$")
message(FATAL_ERROR "patchelf failed with exit code ${process_result}.")
else()
message(FATAL_ERROR "patchelf failed: ${process_result}.")
endif()
endif()
else()
# Warning: file(RPATH_SET) is CMake-internal API.
file(RPATH_SET
FILE "${arg_FILE}"
NEW_RPATH "${arg_NEW_RPATH}"
)
endif()
endfunction()
# Store the platform-dependent $ORIGIN marker in out_var.
function(_qt_internal_get_rpath_origin out_var)
if(__QT_DEPLOY_SYSTEM_NAME STREQUAL "Darwin")
set(rpath_origin "@loader_path")
else()
set(rpath_origin "$ORIGIN")
endif()
set(${out_var} ${rpath_origin} PARENT_SCOPE)
endfunction()
function(_qt_internal_generic_deployqt)
set(no_value_options
NO_TRANSLATIONS
@ -196,6 +240,11 @@ function(_qt_internal_generic_deployqt)
FOLLOW_SYMLINK_CHAIN
)
# Determine the runtime path origin marker if necessary.
if(__QT_DEPLOY_MUST_ADJUST_PLUGINS_RPATH)
_qt_internal_get_rpath_origin(rpath_origin)
endif()
# Deploy the Qt plugins.
foreach(file_path IN LISTS __QT_DEPLOY_PLUGINS)
file(RELATIVE_PATH destination
@ -205,6 +254,16 @@ function(_qt_internal_generic_deployqt)
get_filename_component(destination "${destination}" DIRECTORY)
string(PREPEND destination "${QT_DEPLOY_PREFIX}/${arg_PLUGINS_DIR}/")
file(INSTALL ${file_path} DESTINATION ${destination})
if(__QT_DEPLOY_MUST_ADJUST_PLUGINS_RPATH)
get_filename_component(file_name ${file_path} NAME)
file(RELATIVE_PATH rel_lib_dir "${destination}"
"${QT_DEPLOY_PREFIX}/${QT_DEPLOY_LIB_DIR}")
_qt_internal_set_rpath(
FILE "${destination}/${file_name}"
NEW_RPATH "${rpath_origin}/${rel_lib_dir}"
)
endif()
endforeach()
# Deploy translations.

View File

@ -2452,6 +2452,31 @@ function(_qt_internal_setup_deploy_support)
_qt_internal_add_deploy_support("${CMAKE_CURRENT_LIST_DIR}/Qt6CoreDeploySupport.cmake")
# Check whether we will have to adjust the RPATH of plugins.
if("${QT_DEPLOY_FORCE_ADJUST_RPATHS}" STREQUAL "")
set(must_adjust_plugins_rpath "")
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows"
AND NOT CMAKE_INSTALL_LIBDIR STREQUAL QT6_INSTALL_LIBS)
set(must_adjust_plugins_rpath ON)
endif()
else()
set(must_adjust_plugins_rpath "${QT_DEPLOY_FORCE_ADJUST_RPATHS}")
endif()
# Find the patchelf executable if necessary.
if(must_adjust_plugins_rpath)
if(CMAKE_VERSION VERSION_LESS "3.21")
set(QT_DEPLOY_USE_PATCHELF ON)
endif()
if(QT_DEPLOY_USE_PATCHELF)
find_program(QT_DEPLOY_PATCHELF_EXECUTABLE patchelf)
if(NOT QT_DEPLOY_PATCHELF_EXECUTABLE)
message(FATAL_ERROR "The patchelf executable could not be located. "
"Please install patchelf or upgrade CMake to 3.21 or newer.")
endif()
endif()
endif()
file(GENERATE OUTPUT "${QT_DEPLOY_SUPPORT}" CONTENT
"cmake_minimum_required(VERSION 3.16...3.21)
@ -2496,6 +2521,9 @@ set(__QT_DEPLOY_QT_INSTALL_BINS \"${QT6_INSTALL_BINS}\")
set(__QT_DEPLOY_QT_INSTALL_PLUGINS \"${QT6_INSTALL_PLUGINS}\")
set(__QT_DEPLOY_QT_INSTALL_TRANSLATIONS \"${QT6_INSTALL_TRANSLATIONS}\")
set(__QT_DEPLOY_PLUGINS \"\")
set(__QT_DEPLOY_MUST_ADJUST_PLUGINS_RPATH \"${must_adjust_plugins_rpath}\")
set(__QT_DEPLOY_USE_PATCHELF \"${QT_DEPLOY_USE_PATCHELF}\")
set(__QT_DEPLOY_PATCHELF_EXECUTABLE \"${QT_DEPLOY_PATCHELF_EXECUTABLE}\")
# Define the CMake commands to be made available during deployment.
set(__qt_deploy_support_files

View File

@ -46,6 +46,7 @@
cmake_minimum_required(VERSION 3.16)
project(cmake_usage_tests)
include(GNUInstallDirs)
# Building the CMake tests as part of a Qt prefix build + in-tree tests, currently doesn't work.
# Each CMake test will fail with a message like
@ -335,12 +336,16 @@ set(deploy_args
NO_RUN_ENVIRONMENT_PLUGIN_PATH
)
# For now, the test should only pass on Windows and macOS shared and static builds and fail on
# other platforms, because there is no support for runtime dependency deployment
# on those platforms.
set(is_desktop_linux FALSE)
if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING)
set(is_desktop_linux TRUE)
endif()
# For now, the test should only pass on Windows, macOS and desktop Linux shared and static builds
# and fail on other platforms, because there is no support for runtime dependency deployment on
# those platforms.
# With static builds the runtime dependencies are just skipped, but the test should still pass.
if(WIN32 OR (APPLE AND NOT IOS)
OR (UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING))
if(WIN32 OR (APPLE AND NOT IOS) OR is_desktop_linux)
_qt_internal_test_expect_pass(${deploy_args})
else()
_qt_internal_test_expect_fail(${deploy_args})

View File

@ -5,8 +5,6 @@ cmake_minimum_required(VERSION 3.16)
project(deployment_api)
enable_testing()
set(CMAKE_INSTALL_LIBDIR lib) ### temporary hack to make the test pass - remove in next commit!
find_package(Qt6 COMPONENTS REQUIRED Widgets Test)
qt6_standard_project_setup()