From 6114d85a7db0571bfade96a710f34a01bb2037c8 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sun, 7 Oct 2018 21:58:28 +0200 Subject: RPATH: Add option for using $ORIGIN in build tree This makes binaries independent of the build directory by not embedding the build directory via RPATH. The tests are partially based on the existing RuntimePath test, but with the check moved into a POST_BUILD command such that it can be skipped when the platform lacks support. Fixes: #18413 --- Help/manual/cmake-properties.7.rst | 1 + Help/manual/cmake-variables.7.rst | 1 + Help/prop_tgt/BUILD_RPATH_USE_ORIGIN.rst | 24 +++++++++ Help/release/dev/relative-rpath.rst | 8 +++ Help/variable/CMAKE_BUILD_RPATH_USE_ORIGIN.rst | 7 +++ Modules/Platform/Linux.cmake | 1 + Source/cmComputeLinkInformation.cxx | 29 +++++++++-- Source/cmTarget.cxx | 1 + Tests/RunCMake/RuntimePath/Relative.cmake | 69 ++++++++++++++++++++++++++ Tests/RunCMake/RuntimePath/RelativeCheck.cmake | 4 ++ Tests/RunCMake/RuntimePath/RunCMakeTest.cmake | 14 ++++++ Tests/RunCMake/RuntimePath/main.c | 4 +- 12 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 Help/prop_tgt/BUILD_RPATH_USE_ORIGIN.rst create mode 100644 Help/release/dev/relative-rpath.rst create mode 100644 Help/variable/CMAKE_BUILD_RPATH_USE_ORIGIN.rst create mode 100644 Tests/RunCMake/RuntimePath/Relative.cmake create mode 100644 Tests/RunCMake/RuntimePath/RelativeCheck.cmake diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 5c3eb81..1651114 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -138,6 +138,7 @@ Properties on Targets /prop_tgt/AUTORCC_OPTIONS /prop_tgt/BINARY_DIR /prop_tgt/BUILD_RPATH + /prop_tgt/BUILD_RPATH_USE_ORIGIN /prop_tgt/BUILD_WITH_INSTALL_NAME_DIR /prop_tgt/BUILD_WITH_INSTALL_RPATH /prop_tgt/BUNDLE_EXTENSION diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 9dd36ed..b88c661 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -322,6 +322,7 @@ Variables that Control the Build /variable/CMAKE_AUTOUIC_OPTIONS /variable/CMAKE_AUTOUIC_SEARCH_PATHS /variable/CMAKE_BUILD_RPATH + /variable/CMAKE_BUILD_RPATH_USE_ORIGIN /variable/CMAKE_BUILD_WITH_INSTALL_NAME_DIR /variable/CMAKE_BUILD_WITH_INSTALL_RPATH /variable/CMAKE_COMPILE_PDB_OUTPUT_DIRECTORY diff --git a/Help/prop_tgt/BUILD_RPATH_USE_ORIGIN.rst b/Help/prop_tgt/BUILD_RPATH_USE_ORIGIN.rst new file mode 100644 index 0000000..511de7a --- /dev/null +++ b/Help/prop_tgt/BUILD_RPATH_USE_ORIGIN.rst @@ -0,0 +1,24 @@ +BUILD_RPATH_USE_ORIGIN +---------------------- + +Whether to use relative paths for the build ``RPATH``. + +This property is initialized by the value of the variable +:variable:`CMAKE_BUILD_RPATH_USE_ORIGIN`. + +On platforms that support runtime paths (``RPATH``) with the +``$ORIGIN`` token, setting this property to ``TRUE`` enables relative +paths in the build ``RPATH`` for executables that point to shared +libraries in the same build tree. + +Normally the build ``RPATH`` of an executable contains absolute paths +to the directory of shared libraries. Directories contained within the +build tree can be made relative to enable relocatable builds and to +help achieving reproducible builds by omitting the build directory +from the build environment. + +This property has no effect on platforms that do not support the +``$ORIGIN`` token in ``RPATH``, or when the :variable:`CMAKE_SKIP_RPATH` +variable is set. The runtime path set through the +:prop_tgt:`BUILD_RPATH` target property is also unaffected by this +property. diff --git a/Help/release/dev/relative-rpath.rst b/Help/release/dev/relative-rpath.rst new file mode 100644 index 0000000..5c62b10 --- /dev/null +++ b/Help/release/dev/relative-rpath.rst @@ -0,0 +1,8 @@ +relative-rpath +-------------- + +* A :variable:`CMAKE_BUILD_RPATH_USE_ORIGIN` variable and corresponding + :prop_tgt:`BUILD_RPATH_USE_ORIGIN` target property were added to + enable use of relative runtime paths (RPATHs). This helps achieving + relocatable and reproducible builds that are invariant of the build + directory. diff --git a/Help/variable/CMAKE_BUILD_RPATH_USE_ORIGIN.rst b/Help/variable/CMAKE_BUILD_RPATH_USE_ORIGIN.rst new file mode 100644 index 0000000..e34ede6 --- /dev/null +++ b/Help/variable/CMAKE_BUILD_RPATH_USE_ORIGIN.rst @@ -0,0 +1,7 @@ +CMAKE_BUILD_RPATH_USE_ORIGIN +---------------------------- + +Whether to use relative paths for the build ``RPATH``. + +This is used to initialize the :prop_tgt:`BUILD_RPATH_USE_ORIGIN` target +property for all targets, see that property for more details. diff --git a/Modules/Platform/Linux.cmake b/Modules/Platform/Linux.cmake index 1f8c1b4..b5d5464 100644 --- a/Modules/Platform/Linux.cmake +++ b/Modules/Platform/Linux.cmake @@ -1,6 +1,7 @@ set(CMAKE_DL_LIBS "dl") set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "-Wl,-rpath,") set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG_SEP ":") +set(CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN "\$ORIGIN") set(CMAKE_SHARED_LIBRARY_RPATH_LINK_C_FLAG "-Wl,-rpath-link,") set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-Wl,-soname,") set(CMAKE_EXE_EXPORTS_C_FLAG "-Wl,--export-dynamic") diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx index 0e48ca8..27b8599 100644 --- a/Source/cmComputeLinkInformation.cxx +++ b/Source/cmComputeLinkInformation.cxx @@ -421,7 +421,8 @@ std::string cmComputeLinkInformation::GetRPathLinkString() const return ""; } - // Construct the linker runtime search path. + // Construct the linker runtime search path. These MUST NOT contain tokens + // such as $ORIGIN, see https://sourceware.org/bugzilla/show_bug.cgi?id=16936 return cmJoin(this->OrderDependentRPath->GetOrderedDirectories(), ":"); } @@ -1702,6 +1703,14 @@ void cmComputeLinkInformation::GetRPath(std::vector& runtimeDirs, !this->Makefile->IsOn("CMAKE_SKIP_INSTALL_RPATH") && this->Target->GetPropertyAsBool("INSTALL_RPATH_USE_LINK_PATH"); + // Select whether to use $ORIGIN in RPATHs for artifacts in the build tree. + std::string const& originToken = this->Makefile->GetSafeDefinition( + "CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN"); + std::string targetOutputDir = this->Target->GetDirectory(this->Config); + bool use_relative_build_rpath = + this->Target->GetPropertyAsBool("BUILD_RPATH_USE_ORIGIN") && + !originToken.empty() && !targetOutputDir.empty(); + // Construct the RPATH. std::set emitted; if (use_install_rpath) { @@ -1711,6 +1720,8 @@ void cmComputeLinkInformation::GetRPath(std::vector& runtimeDirs, if (use_build_rpath) { // Add directories explicitly specified by user if (const char* build_rpath = this->Target->GetProperty("BUILD_RPATH")) { + // This will not resolve entries to use $ORIGIN, the user is expected to + // do that if necessary. cmCLI_ExpandListUnique(build_rpath, runtimeDirs, emitted); } } @@ -1728,6 +1739,8 @@ void cmComputeLinkInformation::GetRPath(std::vector& runtimeDirs, this->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX"); cmSystemTools::ConvertToUnixSlashes(rootPath); std::vector const& rdirs = this->GetRuntimeSearchPath(); + std::string const& topBinaryDir = + this->CMakeInstance->GetHomeOutputDirectory(); for (std::string const& ri : rdirs) { // Put this directory in the rpath if using build-tree rpath // support or if using the link path as an rpath. @@ -1741,6 +1754,18 @@ void cmComputeLinkInformation::GetRPath(std::vector& runtimeDirs, d += "/"; d += suffix; cmSystemTools::ConvertToUnixSlashes(d); + } else if (use_relative_build_rpath) { + // If expansion of the $ORIGIN token is supported and permitted per + // policy, use relative paths in the RPATH. + if (cmSystemTools::ComparePath(d, topBinaryDir) || + cmSystemTools::IsSubDirectory(d, topBinaryDir)) { + d = cmSystemTools::RelativePath(targetOutputDir, d); + if (!d.empty()) { + d = originToken + "/" + d; + } else { + d = originToken; + } + } } if (emitted.insert(d).second) { runtimeDirs.push_back(std::move(d)); @@ -1749,8 +1774,6 @@ void cmComputeLinkInformation::GetRPath(std::vector& runtimeDirs, // Do not add any path inside the source or build tree. std::string const& topSourceDir = this->CMakeInstance->GetHomeDirectory(); - std::string const& topBinaryDir = - this->CMakeInstance->GetHomeOutputDirectory(); if (!cmSystemTools::ComparePath(ri, topSourceDir) && !cmSystemTools::ComparePath(ri, topBinaryDir) && !cmSystemTools::IsSubDirectory(ri, topSourceDir) && diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 1458f01..987bdb3 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -218,6 +218,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type, this->SetPropertyDefault("ANDROID_ASSETS_DIRECTORIES", nullptr); this->SetPropertyDefault("ANDROID_ANT_ADDITIONAL_OPTIONS", nullptr); this->SetPropertyDefault("BUILD_RPATH", nullptr); + this->SetPropertyDefault("BUILD_RPATH_USE_ORIGIN", nullptr); this->SetPropertyDefault("INSTALL_NAME_DIR", nullptr); this->SetPropertyDefault("INSTALL_RPATH", ""); this->SetPropertyDefault("INSTALL_RPATH_USE_LINK_PATH", "OFF"); diff --git a/Tests/RunCMake/RuntimePath/Relative.cmake b/Tests/RunCMake/RuntimePath/Relative.cmake new file mode 100644 index 0000000..203241f --- /dev/null +++ b/Tests/RunCMake/RuntimePath/Relative.cmake @@ -0,0 +1,69 @@ +enable_language(C) + +if(NOT CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN) + if(CMAKE_C_PLATFORM_ID STREQUAL "Linux") + # Sanity check for platform that is definitely known to support $ORIGIN. + message(FATAL_ERROR "Platform fails to report relative RPATH support") + else() + message(STATUS "Platform does not support relative RPATHs, skipping") + endif() + return() +endif() +set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) + +function(CheckRpath target rpath) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -Dfile=$ -Drpath=${rpath} + -P "${CMAKE_CURRENT_SOURCE_DIR}/RelativeCheck.cmake" + VERBATIM + ) +endfunction() + +if(CMAKE_C_COMPILER_ID STREQUAL "XL" AND CMAKE_BINARY_DIR MATCHES " ") + # XL 16.1.0.0 fails building the library if the output path contains a space. + set(externDir) + message(STATUS "Skipping external library test because of a toolchain bug") +else() + get_filename_component(externDir "${CMAKE_BINARY_DIR}" DIRECTORY) + set(externDir "${externDir}/Relative-extern") +endif() + +add_library(utils SHARED A.c) +add_library(utils-sub SHARED A.c) +set_property(TARGET utils-sub PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libs) +if(externDir) + add_library(utils-extern SHARED A.c) + set_property(TARGET utils-extern PROPERTY LIBRARY_OUTPUT_DIRECTORY ${externDir}) +endif() + +add_executable(main main.c) +target_link_libraries(main utils) +CheckRpath(main "\$ORIGIN") + +add_executable(main-norel main.c) +target_link_libraries(main-norel utils) +set_property(TARGET main-norel PROPERTY BUILD_RPATH_USE_ORIGIN OFF) +CheckRpath(main-norel "${CMAKE_CURRENT_BINARY_DIR}") + +add_executable(mainsub main.c) +target_link_libraries(mainsub utils) +set_property(TARGET mainsub PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +CheckRpath(mainsub "\$ORIGIN/../") + +add_executable(main-sub main.c) +target_link_libraries(main-sub utils-sub) +CheckRpath(main-sub "\$ORIGIN/libs") + +add_executable(mainsub-sub main.c) +target_link_libraries(mainsub-sub utils-sub) +set_property(TARGET mainsub-sub PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +CheckRpath(mainsub-sub "\$ORIGIN/../libs") + +if(externDir) + # Binaries linking to libraries outside the build tree should have an absolute RPATH. + add_executable(main-extern main.c) + target_link_libraries(main-extern utils-extern) + CheckRpath(main-extern "${externDir}") +endif() diff --git a/Tests/RunCMake/RuntimePath/RelativeCheck.cmake b/Tests/RunCMake/RuntimePath/RelativeCheck.cmake new file mode 100644 index 0000000..9ee403f --- /dev/null +++ b/Tests/RunCMake/RuntimePath/RelativeCheck.cmake @@ -0,0 +1,4 @@ +file(RPATH_CHECK FILE "${file}" RPATH "${rpath}") +if(NOT EXISTS "${file}") + message(FATAL_ERROR "RPATH for ${file} did not contain the expected value") +endif() diff --git a/Tests/RunCMake/RuntimePath/RunCMakeTest.cmake b/Tests/RunCMake/RuntimePath/RunCMakeTest.cmake index 3f238f2..6f1baa1 100644 --- a/Tests/RunCMake/RuntimePath/RunCMakeTest.cmake +++ b/Tests/RunCMake/RuntimePath/RunCMakeTest.cmake @@ -16,3 +16,17 @@ function(run_SymlinkImplicit) ${CMAKE_COMMAND} -Ddir=${RunCMake_TEST_BINARY_DIR} -P ${RunCMake_SOURCE_DIR}/SymlinkImplicitCheck.cmake) endfunction() run_SymlinkImplicit() + +function(run_Relative) + # Use a single build tree for a few tests without cleaning. + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Relative-build) + set(RunCMake_TEST_NO_CLEAN 1) + if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG) + set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug) + endif() + file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}") + file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}") + run_cmake(Relative) + run_cmake_command(Relative-build ${CMAKE_COMMAND} --build . --config Debug) +endfunction() +run_Relative() diff --git a/Tests/RunCMake/RuntimePath/main.c b/Tests/RunCMake/RuntimePath/main.c index 8488f4e..181cd09 100644 --- a/Tests/RunCMake/RuntimePath/main.c +++ b/Tests/RunCMake/RuntimePath/main.c @@ -1,4 +1,6 @@ +extern int libA(void); + int main(void) { - return 0; + return libA(); } -- cgit v0.12