Преглед изворни кода

Link explicitly to private transitive dependencies on stub libraries

We represent stub libraries, e.g., for CUDA, using imported `SHARED`
library targets with only `IMPORTED_IMPLIB`, and no `IMPORTED_LOCATION`,
to indicate that the stub file is meant only for linkers and not dynamic
loaders.  See commit 7351d590ee (cmTarget: Add a way to represent
imported shared library stubs, 2023-07-17, v3.28.0-rc1~344^2) and commit
fc6508921c (cmComputeLinkInformation: Restore soname lookup for
non-imported targets, 2023-12-05, v3.28.0~4^2).

If a shared library is linked to a stub, it has a `NEEDED` field
populated with the `SONAME` found in the stub.  When a dependent target
links to such a shared library, some linkers want to find a library file
on disk and load it to see what symbols it provides.  This is necessary
for linkers that enforce `--no-allow-shlib-undefined`.  On hosts with
only the stub library installed, e.g., with only the CUDA toolkit
development package, the real runtime library corresponding to the
stub's `SONAME` may not even exist, so no `-rpath-link` flag can help
linkers find it.  Pass the stub library to linkers explicitly so they
can find it without searching.
Brad King пре 1 година
родитељ
комит
dd4a6dff92

+ 4 - 1
Modules/FindCUDAToolkit.cmake

@@ -1100,7 +1100,10 @@ if(CUDAToolkit_FOUND)
     if(CUDA_${lib_name}_LIBRARY MATCHES "/stubs/" AND NOT WIN32)
       # Use a SHARED library with IMPORTED_IMPLIB, but not IMPORTED_LOCATION,
       # to indicate that the stub is for linkers but not dynamic loaders.
-      # It will not contribute any RPATH entry.
+      # It will not contribute any RPATH entry.  When encountered as
+      # a private transitive dependency of another shared library,
+      # it will be passed explicitly to linkers so they can find it
+      # even when the runtime library file does not exist on disk.
       set(CUDA_IMPORT_PROPERTY IMPORTED_IMPLIB)
       set(CUDA_IMPORT_TYPE     SHARED)
     endif()

+ 10 - 1
Source/cmComputeLinkInformation.cxx

@@ -1331,7 +1331,16 @@ void cmComputeLinkInformation::AddSharedDepItem(LinkEntry const& entry)
   }
 
   // If in linking mode, just link to the shared library.
-  if (this->SharedDependencyMode == SharedDepModeLink) {
+  if (this->SharedDependencyMode == SharedDepModeLink ||
+      // For an imported shared library without a known runtime artifact,
+      // such as a CUDA stub, a library file named with the real soname
+      // may not be available at all, so '-rpath-link' cannot help linkers
+      // find it to satisfy '--no-allow-shlib-undefined' recursively.
+      // Pass this dependency to the linker explicitly just in case.
+      // If the linker also uses '--as-needed' behavior, this will not
+      // add an unnecessary direct dependency.
+      (tgt && tgt->IsImported() &&
+       !tgt->HasKnownRuntimeArtifactLocation(this->Config))) {
     this->AddItem(entry);
     return;
   }

+ 4 - 1
Tests/RunCMake/CMakeLists.txt

@@ -435,7 +435,10 @@ add_RunCMake_test(ObjectLibrary)
 add_RunCMake_test(ParseImplicitIncludeInfo)
 add_RunCMake_test(ParseImplicitLinkInfo)
 if(UNIX AND CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG)
-  add_RunCMake_test(RuntimePath -DCMAKE_EXECUTABLE_FORMAT=${CMAKE_EXECUTABLE_FORMAT})
+  add_RunCMake_test(RuntimePath
+    -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+    -DCMAKE_EXECUTABLE_FORMAT=${CMAKE_EXECUTABLE_FORMAT}
+    )
 endif()
 add_RunCMake_test(ScriptMode)
 add_RunCMake_test(Swift -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}

+ 25 - 0
Tests/RunCMake/RuntimePath/RunCMakeTest.cmake

@@ -32,3 +32,28 @@ if(CMAKE_EXECUTABLE_FORMAT STREQUAL "ELF")
   run_cmake_command(GenexCheck
     ${CMAKE_COMMAND} -Ddir=${RunCMake_BINARY_DIR}/Genex-build -P ${RunCMake_SOURCE_DIR}/GenexCheck.cmake)
 endif()
+
+block()
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Stub-build)
+  if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(bin_dir "${RunCMake_TEST_BINARY_DIR}/Debug")
+    set(lib_dir "${RunCMake_TEST_BINARY_DIR}/lib/Debug")
+  else()
+    set(bin_dir "${RunCMake_TEST_BINARY_DIR}")
+    set(lib_dir "${RunCMake_TEST_BINARY_DIR}/lib")
+  endif()
+  run_cmake(Stub)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(Stub-build ${CMAKE_COMMAND} --build . --config Debug)
+  if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|SunOS)$|BSD")
+    set(ldpath LD_LIBRARY_PATH)
+  elseif(CMAKE_SYSTEM_NAME MATCHES "^(Darwin)$")
+    set(ldpath DYLD_LIBRARY_PATH)
+  elseif(CMAKE_SYSTEM_NAME MATCHES "^(AIX)$")
+    set(ldpath LIBPATH)
+  endif()
+  if(ldpath)
+    run_cmake_command(Stub-fail ${CMAKE_COMMAND} -E env LANG=C ${bin_dir}/StubExe)
+    run_cmake_command(Stub-pass ${CMAKE_COMMAND} -E env --modify ${ldpath}=path_list_prepend:${lib_dir} ${bin_dir}/StubExe)
+  endif()
+endblock()

+ 1 - 0
Tests/RunCMake/RuntimePath/Stub-fail-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/RuntimePath/Stub-fail-stderr.txt

@@ -0,0 +1 @@
+(error while loading shared libraries: libStub\.so\.1|Library not loaded: '?@rpath/libStub\.1\.dylib'?|(Cannot|Could not) load module libStub\.so|fatal: libStub\.so\.1: open failed|Shared object "libStub\.so\.1" not found)

+ 4 - 0
Tests/RunCMake/RuntimePath/Stub.c

@@ -0,0 +1,4 @@
+int Stub(void)
+{
+  return 0;
+}

+ 25 - 0
Tests/RunCMake/RuntimePath/Stub.cmake

@@ -0,0 +1,25 @@
+enable_language(C)
+
+add_library(Stub SHARED Stub.c)
+set_target_properties(Stub PROPERTIES
+  SOVERSION 1
+  LIBRARY_OUTPUT_DIRECTORY lib
+  )
+
+set(StubDir ${CMAKE_CURRENT_BINARY_DIR}/lib/stubs)
+set(Stub "${StubDir}/${CMAKE_SHARED_LIBRARY_PREFIX}Stub${CMAKE_SHARED_LIBRARY_SUFFIX}")
+add_custom_target(StubCopy
+  COMMAND ${CMAKE_COMMAND} -E make_directory "${StubDir}"
+  COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_SONAME_FILE:Stub>" "${Stub}"
+  BYPRODUCTS ${Stub}
+  )
+add_dependencies(StubCopy Stub)
+add_library(Imp::Stub SHARED IMPORTED)
+set_property(TARGET Imp::Stub PROPERTY IMPORTED_IMPLIB "${Stub}")
+add_dependencies(Imp::Stub StubCopy)
+
+add_library(StubUse SHARED StubUse.c)
+target_link_libraries(StubUse PRIVATE Imp::Stub)
+
+add_executable(StubExe StubExe.c)
+target_link_libraries(StubExe PRIVATE StubUse)

+ 5 - 0
Tests/RunCMake/RuntimePath/StubExe.c

@@ -0,0 +1,5 @@
+extern int StubUse(void);
+int main(void)
+{
+  return StubUse();
+}

+ 5 - 0
Tests/RunCMake/RuntimePath/StubUse.c

@@ -0,0 +1,5 @@
+extern int Stub(void);
+int StubUse(void)
+{
+  return Stub();
+}