summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandru Croitor <alexandru.croitor@qt.io>2020-04-15 18:48:26 +0200
committerAlexandru Croitor <alexandru.croitor@qt.io>2020-04-17 15:59:25 +0200
commit67ee92f4d898ee76c40b7efd8e69782a6a4a3754 (patch)
tree57c4934656c2f15a59a70cd53aa0f73fd4fd838d
parente6d880e511db7f12bda1f8ee599246d30b7ebf1b (diff)
CMake: Handle automatic rpath embedding correctly
Instead of using CMAKE_INSTALL_RPATH to embed an absolute path to prefix/libdir into all targets, use the more sophisticated aproach that qmake does. For certain targets (modules, plugins, tools) use relative rpaths. Otherwise embed absolute paths (examples, regular binaries). Installed tests currently have no rpaths. On certain platforms rpaths are not used (Windows, Android, iOS / uikit). Frameworks, app bundles and shallow bundles should also be handled correctly. Additional rpaths can be provided via QT_EXTRA_RPATHS variable (similar to the -R option that configure takes). Automatic embedding can be disabled either via QT_FEATURE_rpath=OFF or QT_DISABLE_RPATH=ON. Note that installed examples are not relocatable at the moment (due to always having an absolute path rpath), so this is a missing feature compared to qmake. This is due to missing information on where examples will be installed, so a relative rpath can not be computed. By default a Qt installation is relocatable, so there is no need to pass -DQT_EXTRA_RPATHS=. like Coin used to do with qmake e.g. -R . Relative rpaths will have the appropriate 'relative base' prefixed to them (e.g $ORIGIN on linux and @loader_path on darwin platforms). There is currently no support for other platforms that might have a different 'relative base' than the ones mentioned above. Any extra rpaths are saved to BuildInternalsExtra which are re-used when building other repositories. configurejson2cmake modified to include correct conditions for the rpath feature. It's very likely that we will need a new qt_add_internal_app() function for gui apps that are to be installed to prefix/bin. For example for Assistant from qttools. Currently such apps use qt_add_executable(). The distinction is necessary to make sure that relative rpaths are embedded into apps, but not executables (which tests are part of). Amends e835a6853b9c0fb7af32798ed8965de3adf0e15b Task-number: QTBUG-83497 Change-Id: I3510f63c0a59489741116cc8ec3ef6a0a7704f25 Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
-rw-r--r--cmake/QtAutoDetect.cmake5
-rw-r--r--cmake/QtBuild.cmake152
-rw-r--r--cmake/QtBuildInternals/QtBuildInternalsConfig.cmake7
-rw-r--r--cmake/QtPostProcess.cmake12
-rw-r--r--configure.cmake7
-rwxr-xr-xutil/cmake/configurejson2cmake.py16
6 files changed, 189 insertions, 10 deletions
diff --git a/cmake/QtAutoDetect.cmake b/cmake/QtAutoDetect.cmake
index d5d93007ba..dd5d8a6bd6 100644
--- a/cmake/QtAutoDetect.cmake
+++ b/cmake/QtAutoDetect.cmake
@@ -153,6 +153,11 @@ function(qt_auto_detect_ios)
message(FATAL_ERROR
"Building Qt for ${CMAKE_SYSTEM_NAME} as shared libraries is not supported.")
endif()
+
+ # Disable qt rpaths for iOS, just like mkspecs/common/uikit.conf does, due to those
+ # bundles not being able to use paths outside the app bundle. Not sure this is strictly
+ # needed though.
+ set(QT_DISABLE_RPATH "OFF" CACHE BOOL "Disable automatic Qt rpath handling." FORCE)
endif()
endfunction()
diff --git a/cmake/QtBuild.cmake b/cmake/QtBuild.cmake
index 765b5fb961..d4b7924646 100644
--- a/cmake/QtBuild.cmake
+++ b/cmake/QtBuild.cmake
@@ -106,13 +106,12 @@ if("${isSystemDir}" STREQUAL "-1")
set(_default_install_rpath "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}")
endif("${isSystemDir}" STREQUAL "-1")
-# Default rpath settings: Use rpath for build tree as well as a full path for the installed binaries.
-# For origin builds, one needs to override CMAKE_INSTALL_RPATH for example with $ORIGIN/../lib
-# Example: -DCMAKE_INSTALL_RPATH=\$ORIGIN/../lib (backslash to escape the $ in the shell)
-# Implementation note: the cache var must be STRING and not PATH or FILEPATH, otherwise CMake will
-# transform the value into an absolute path, getting rid of '$ORIGIN'.
-set(CMAKE_INSTALL_RPATH "${_default_install_rpath}" CACHE STRING "RPATH for installed binaries")
-message(STATUS "Install RPATH set to: ${CMAKE_INSTALL_RPATH}")
+# 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")
# add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
@@ -1976,6 +1975,8 @@ set(QT_CMAKE_EXPORT_NAMESPACE ${QT_CMAKE_EXPORT_NAMESPACE})")
PRIVATE_HEADER DESTINATION ${INSTALL_INCLUDEDIR}/${module}/${PROJECT_VERSION}/${module}/private
)
+ qt_apply_rpaths(TARGET "${target}" INSTALL_PATH "${INSTALL_LIBDIR}" RELATIVE_RPATH)
+
if (ANDROID AND NOT arg_HEADER_MODULE)
# Record install library location so it can be accessed by
# qt_android_dependencies without having to specify it again.
@@ -2478,6 +2479,7 @@ function(qt_add_plugin target)
NAMESPACE ${QT_CMAKE_EXPORT_NAMESPACE}::
DESTINATION "${config_install_dir}"
)
+ qt_apply_rpaths(TARGET "${target}" INSTALL_PATH "${install_directory}" RELATIVE_RPATH)
endif()
# Store the plug-in type in the target property
@@ -3378,6 +3380,8 @@ function(qt_add_tool name)
qt_install(TARGETS "${name}"
EXPORT "${INSTALL_CMAKE_NAMESPACE}${arg_TOOLS_TARGET}ToolsTargets"
DESTINATION ${INSTALL_TARGETS_DEFAULT_ARGS})
+ qt_apply_rpaths(TARGET "${name}" INSTALL_PATH "${INSTALL_BINDIR}" RELATIVE_RPATH)
+
endif()
if(QT_FEATURE_separate_debug_info AND (UNIX OR MINGW))
@@ -4150,6 +4154,140 @@ function(qt_exclude_tool_directories_from_default_target)
endif()
endfunction()
+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}")
+
+ if(NOT IS_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.
+ if(APPLE)
+ set(rpath_rel_base "@loader_path")
+ elseif(LINUX)
+ set(rpath_rel_base "$ORIGIN")
+ else()
+ message(WARNING "No known RPATH_REL_BASE for target platform.")
+ set(rpath_rel_base "NO_KNOWN_RPATH_REL_BASE")
+ 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, or non-prefix builds.
+#
+# If no RELATIVE_RPATH option is given, embeds an absolute path rpath to ${INSTALL_LIBDIR}.
+# If RELATIVE_RPATH is given, the INSTALL_PATH value is to compute the relative path from
+# ${INSTALL_LIBDIR} to wherever the target will be installed (the value of INSTALL_PATH).
+# It's the equivalent of qmake's relative_qt_rpath.
+# INSTALL_PATH is used to implement the equivalent of qmake's $$qtRelativeRPathBase().
+#
+# A cache variable QT_DISABLE_RPATH can be set to disable embedding any rpaths when installing.
+function(qt_apply_rpaths)
+ # No rpath support for win32 and android. Also no need to apply rpaths when doing a non-prefix
+ # build.
+ if(NOT QT_WILL_INSTALL OR WIN32 OR ANDROID)
+ return()
+ endif()
+
+ # Rpaths xplicitly disabled (like for uikit), equivalent to qmake's no_qt_rpath.
+ if(QT_DISABLE_RPATH)
+ return()
+ endif()
+
+ qt_parse_all_arguments(arg "qt_apply_rpaths" "RELATIVE_RPATH" "TARGET;INSTALL_PATH" "" ${ARGN})
+ if(NOT arg_TARGET)
+ message(FATAL_ERRO "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)
+ if(UIKIT)
+ # Shallow framework
+ string(APPEND arg_INSTALL_PATH "/Qt${target}.framework")
+ else()
+ # Full framework
+ string(APPEND arg_INSTALL_PATH "/Qt${target}.framework/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()
+
+ # Somewhat similar to mkspecs/features/qt.prf
+ if(arg_RELATIVE_RPATH)
+ 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)
+ list(APPEND rpaths "${rpath}")
+ else()
+ qt_compute_relative_rpath_base("${rpath}" "${arg_INSTALL_PATH}" relative_rpath)
+ list(APPEND rpaths "${relative_rpath}")
+ endif()
+ endforeach()
+
+ if(rpaths)
+ list(REMOVE_DUPLICATES rpaths)
+ set_property(TARGET "${target}" APPEND PROPERTY INSTALL_RPATH ${rpaths})
+ endif()
+endfunction()
+
# Compatibility macros that should be removed once all their usages are removed.
function(extend_target)
qt_extend_target(${ARGV})
diff --git a/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake b/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake
index 5648326ca2..a8c2cc6c65 100644
--- a/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake
+++ b/cmake/QtBuildInternals/QtBuildInternalsConfig.cmake
@@ -259,6 +259,13 @@ macro(qt_examples_build_begin)
set(QT_NO_CREATE_TARGETS TRUE)
set(BACKUP_CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ${CMAKE_FIND_ROOT_PATH_MODE_PACKAGE})
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE "BOTH")
+
+ # Because CMAKE_INSTALL_RPATH is empty by default in the repo project, examples need to have
+ # it set here, so they can run when installed.
+ # This means that installed examples are not relocatable at the moment. We would need to
+ # annotate where each example is installed to, to be able to derive a relative rpath, and it
+ # seems there's no way to query such information from CMake itself.
+ set(CMAKE_INSTALL_RPATH "${_default_install_rpath}")
endmacro()
macro(qt_examples_build_end)
diff --git a/cmake/QtPostProcess.cmake b/cmake/QtPostProcess.cmake
index 9b1769455d..798fd947f1 100644
--- a/cmake/QtPostProcess.cmake
+++ b/cmake/QtPostProcess.cmake
@@ -366,6 +366,18 @@ function(qt_generate_build_internals_extra_cmake_code)
"set(BUILD_WITH_PCH \"${BUILD_WITH_PCH}\" CACHE STRING \"\")\n")
endif()
+ # Rpath related things that need to be re-used when building other repos.
+ string(APPEND QT_EXTRA_BUILD_INTERNALS_VARS
+ "set(CMAKE_INSTALL_RPATH \"${CMAKE_INSTALL_RPATH}\" CACHE STRING \"\")\n")
+ if(DEFINED QT_DISABLE_RPATH)
+ string(APPEND QT_EXTRA_BUILD_INTERNALS_VARS
+ "set(QT_DISABLE_RPATH \"${QT_DISABLE_RPATH}\" CACHE STRING \"\")\n")
+ endif()
+ if(DEFINED QT_EXTRA_RPATHS)
+ string(APPEND QT_EXTRA_BUILD_INTERNALS_VARS
+ "set(QT_EXTRA_RPATHS \"${QT_EXTRA_RPATHS}\" CACHE STRING \"\")\n")
+ endif()
+
qt_generate_install_prefixes(install_prefix_content)
string(APPEND QT_EXTRA_BUILD_INTERNALS_VARS "${install_prefix_content}")
diff --git a/configure.cmake b/configure.cmake
index 3976362d6e..f773e28f52 100644
--- a/configure.cmake
+++ b/configure.cmake
@@ -397,7 +397,7 @@ qt_feature_config("simulator_and_device" QMAKE_PUBLIC_QT_CONFIG)
qt_feature("rpath" PUBLIC
LABEL "Build with RPATH"
AUTODETECT 1
- CONDITION BUILD_SHARED_LIBS AND UNIX AND NOT WIN32 AND NOT ANDROID AND NOT APPLE
+ CONDITION BUILD_SHARED_LIBS AND UNIX AND NOT WIN32 AND NOT ANDROID
)
qt_feature_config("rpath" QMAKE_PUBLIC_QT_CONFIG)
qt_feature("force_asserts" PUBLIC
@@ -921,6 +921,11 @@ qt_configure_add_report_entry(
)
qt_configure_add_report_entry(
TYPE ERROR
+ MESSAGE "Static builds don't support RPATH"
+ CONDITION ( QT_FEATURE_rpath OR QT_EXTRA_RPATHS ) AND NOT QT_FEATURE_shared
+)
+qt_configure_add_report_entry(
+ TYPE ERROR
MESSAGE "Command line option -coverage is only supported with clang compilers."
CONDITION QT_FEATURE_coverage AND NOT CLANG
)
diff --git a/util/cmake/configurejson2cmake.py b/util/cmake/configurejson2cmake.py
index 85ac26ec2b..f004e732f6 100755
--- a/util/cmake/configurejson2cmake.py
+++ b/util/cmake/configurejson2cmake.py
@@ -876,10 +876,9 @@ def get_feature_mapping():
"qpa_default_platform": None, # Not a bool!
"release": None,
"release_tools": None,
- "rpath_dir": None, # merely used to fill the qmake variable EXTRA_RPATHS
"rpath": {
"autoDetect": "1",
- "condition": "BUILD_SHARED_LIBS AND UNIX AND NOT WIN32 AND NOT ANDROID AND NOT APPLE",
+ "condition": "BUILD_SHARED_LIBS AND UNIX AND NOT WIN32 AND NOT ANDROID",
},
"sanitize_address": None, # sanitizer
"sanitize_memory": None,
@@ -1222,6 +1221,12 @@ def processSummaryHelper(ctx, entries, cm_fh):
print(f" XXXX UNHANDLED SUMMARY TYPE {entry}.")
+report_condition_mapping = {
+ "(features.rpath || features.rpath_dir) && !features.shared": "(features.rpath || QT_EXTRA_RPATHS) && !features.shared",
+ "(features.rpath || features.rpath_dir) && var.QMAKE_LFLAGS_RPATH == ''": None
+}
+
+
def processReportHelper(ctx, entries, cm_fh):
feature_mapping = get_feature_mapping()
@@ -1265,6 +1270,13 @@ def processReportHelper(ctx, entries, cm_fh):
if unhandled_condition:
print(f" XXXX UNHANDLED CONDITION in REPORT TYPE {entry}.")
continue
+
+ if isinstance(condition, str) and condition in report_condition_mapping:
+ new_condition = report_condition_mapping[condition]
+ if new_condition is None:
+ continue
+ else:
+ condition = new_condition
condition = map_condition(condition)
entry_args.append(lineify("CONDITION", condition, quote=False))
entry_args_string = "".join(entry_args)