1
0
Эх сурвалжийг харах

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
Peter Wu 7 жил өмнө
parent
commit
6114d85a7d

+ 1 - 0
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

+ 1 - 0
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

+ 24 - 0
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.

+ 8 - 0
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.

+ 7 - 0
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.

+ 1 - 0
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")

+ 26 - 3
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<std::string>& 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<std::string> emitted;
   if (use_install_rpath) {
@@ -1711,6 +1720,8 @@ void cmComputeLinkInformation::GetRPath(std::vector<std::string>& 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<std::string>& runtimeDirs,
       this->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX");
     cmSystemTools::ConvertToUnixSlashes(rootPath);
     std::vector<std::string> 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<std::string>& 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<std::string>& 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) &&

+ 1 - 0
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");

+ 69 - 0
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=$<TARGET_FILE:${target}> -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()

+ 4 - 0
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()

+ 14 - 0
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()

+ 3 - 1
Tests/RunCMake/RuntimePath/main.c

@@ -1,4 +1,6 @@
+extern int libA(void);
+
 int main(void)
 {
-  return 0;
+  return libA();
 }