CMake transitive dependency linked by name only

I did ask this on StackOverflow already without much luck, so apologies for the redundancy if you’ve already seen this.

It seems that dylibs linked directly in target_link_libraries get linked by absolute path, but those coming in transitively through CMake target dependencies are dropping the absolute path and linking by name only. This breaks the MacOS fixup bundle functionality that normally post-processes an .app directory to include the libraries in relocatable fashion because it can’t find the dylib(s).

For example, I have a dependency on DCMTK, which itself has a dependency on libicu. DCMTK is built by for static linking by CMake, so it has a targets config file and it lists the dependency on the libicu dylib by absolute path:

$ grep libicu DCMTKTargets-release.cmake
  IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE "/usr/local/app-libs/icu-67.1/lib/libicuuc.dylib;/usr/local/app-libs/icu-67.1/lib/libicudata.dylib;pthread"

Here is a minimal CMake to reproduce, although you of course need to build libicu for dynamic linking and DCMTK for static linking in the paths shown.

CMakeLists.txt

cmake_minimum_required(VERSION 3.11)
project(CatalinaTest)

set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "" FORCE)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_OSX_SYSROOT})


list(APPEND CMAKE_PREFIX_PATH
  /usr/local/app-libs/vscdcmtk-3.6.3/lib/cmake/dcmtk
)

find_package(LibXml2 REQUIRED)
find_package(DCMTK REQUIRED CONFIG)

add_executable(CatalinaTest main.cpp)

target_link_libraries(CatalinaTest
  ${LIBXML2_LIBRARIES}
  ofstd
)

The linker command that runs is:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -target x86_64-apple-macos10.14 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -L/Users/jake/devel/cmake-test.build/Release -F/Users/jake/devel/cmake-test.build/Release -filelist /Users/jake/devel/cmake-test.build/CatalinaTest.build/Release/CatalinaTest.build/Objects-normal/x86_64/CatalinaTest.LinkFileList -Wl,-search_paths_first -Wl,-headerpad_max_install_names /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/libxml2.tbd /usr/local/app-libs/vscdcmtk-3.6.3/lib/libofstd.a /usr/local/app-libs/icu-67.1/lib/libicuuc.dylib /usr/local/app-libs/icu-67.1/lib/libicudata.dylib -lpthread -Xlinker -dependency_info -Xlinker /Users/jake/devel/cmake-test.build/CatalinaTest.build/Release/CatalinaTest.build/Objects-normal/x86_64/CatalinaTest_dependency_info.dat -o /Users/jake/devel/cmake-test.build/Release/CatalinaTest

So it passes both direct and transitive dependencies by absolute path in the same section without any other linker modification flags between. Now, in the linked executable:

$ otool -L Release/CatalinaTest 
Release/CatalinaTest:
	/usr/lib/libxml2.2.dylib (compatibility version 10.0.0, current version 10.9.0)
	libicuuc.67.dylib (compatibility version 67.0.0, current version 67.1.0)
	libicudata.67.dylib (compatibility version 67.0.0, current version 67.1.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0)

We can see libxml2's dylib has the absolute path but libicu's two dylibs do not. Although this example has a tbd file for libxml2, the behavior is the same if we use another library that passes a dylib directly. The difference seems to be all indirect/transitive dylibs are name-only, and all direct dylibs get the absolute path.

What is causing this difference and how can I get the transitive dylibs to link by full path so that bundle post processing can proceed as expected?

These differences are caused by the library IDs of the libraries in question. It doesn’t matter at all how the library is referenced on the command line. The first line for non-executables from otool -L is the library ID and this gets copied into the list for anything linking to it.

The referenced libicu* libraries seem to have naive IDs that indicate that they are expected to be loaded via the default paths or DYLD_LIBRARY_PATH. I would suggest fixing their IDs (if possible) or fixing things up afterwards with install_name_tool upon copying them into the final package.