TARGET_SONAME_FILE on mac misfunctions when IMPORTED_SONAME is not set
SUMMARY: Setting a path for a SHARED IMPORTED library with IMPORTED_LOCATION, then trying to read it back with $<TARGET_SONAME_FILE>, will on MacOS mangle the filepath unless you also set IMPORTED_SONAME.
CONTEXT: We have a CMake project for a game engine which interfaces to many other libraries both open source and closed source. We have a scheme where we create a target for each library. If it is an open source library we build it, if it is closed source we add_library(... SHARED IMPORTED)
. We then copy the dynamic library into place using an -E copy
custom command and either TARGET_FILE_NAME (on Windows) or TARGET_SONAME_FILE (on MacOS and Linux). This scheme ensures consistency between closed and open libraries, between different platforms, and (on Mac) between building for debugging and building a .app bundle.* On Windows and Linux the scheme has not given us problems.
On mac, we hit a weird issue.
EXAMPLE: Check out this repository, branch mac-spatializer-test-2, git submodule update --init
, download Steam Audio aka Phonon, and run (paths adjusted for your own system)
mkdir build
(cd build && cmake -DLOVR_ENABLE_PHYSICS=NO -DCMAKE_BUILD_TYPE=Debug -DLOVR_USE_STEAM_AUDIO=1 -DLOVR_STEAM_AUDIO_PATH=/Users/mcc/Downloads/spatializers/steamaudio_api ..)
(cd build && cmake --build .)
You will want to test two commits, 189bfe8
and a8851f0
. The difference is a8851f0
adds IMPORTED_SONAME. You will probably need to do a full-clean build after changing commits. After running each test, look at the CMake output and look in build/bin
.
OBSERVED BEHAVIOR:
Early in our CMake file, if LOVR_USE_STEAM_AUDIO and APPLE are both set, we run:
add_library(Phonon SHARED IMPORTED)
message("Phonon IMPORTED_LOCATION: ${LOVR_STEAM_AUDIO_PATH}/lib/OSX/libphonon.dylib")
set_target_properties(Phonon PROPERTIES IMPORTED_LOCATION "${LOVR_STEAM_AUDIO_PATH}/lib/OSX/libphonon.dylib")
set(LOVR_PHONON Phonon)
Which is supposed to create a target and store an IMPORTED_LOCATION for it. Then later in the file, we run:
function(move_lib)
if(TARGET ${ARGV0})
add_custom_command(TARGET lovr POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "TARGET ${ARGV0} COPY $<TARGET_SONAME_FILE:${ARGV0}>"
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_SONAME_FILE:${ARGV0}>
${EXE_DIR}/$<TARGET_SONAME_FILE_NAME:${ARGV0}>
)
endif()
endfunction()
move_lib(${LOVR_PHONON})
This is supposed to extract the previously set IMPORTED_LOCATION, and use that path to copy the file into a designated spot. Notice the message()
and -E echo
, which give us visibility into what CMake is doing.
When we execute the CMake file, this prints out:
Phonon IMPORTED_LOCATION: /Users/mcc/Downloads/spatializers/steamaudio_api/lib/OSX/libphonon.dylib
...
TARGET Phonon COPY /Users/mcc/Downloads/spatializers/steamaudio_api/lib/OSX/
Somehow, the libphonon.dylib
has been dropped. The -E copy
tries to copy the directory, which is wrong, and COPY refuses to do it anyway, so this is a noop. libphonon.dylib
is not found in build/bin
, and the program fails to find libphonon.dylib at runtime.
If (as Alex Reinking suggested to me on Stack Overflow) I add IMPORTED_SONAME "@rpath/libphonon.dylib"
to the Phonon set_target_properties
, the problem is resolved. If I do this the echo prints:
TARGET Phonon COPY /Users/mcc/Downloads/spatializers/steamaudio_api/lib/OSX//libphonon.dylib
There is an extra /
there, but the -E copy works fine, the DLL is copied, and the program works as intended.
EXPECTED BEHAVIOR: Because I wrote a specific path for IMPORTED_LOCATION, I would expect that it would be read back out verbatim by TARGET_SONAME_FILE even if I do not set IMPORTED_SONAME.
IS THIS A BUG?: I think this is a functional bug in CMake. The behavior seems wrong. Setting the rpath may be important to many CMake activities, but just reading the path back is not one of them. If the rpath is important for some reason I don't understand, then CMake should have been able to automatically derive the rpath because running otool -L
on the official libphonon.dylib
it has @rpath/libphonon.dylib
already set. And if the behavior is correct for some reason I don't understand, you still have a documentation bug, since the documentation for TARGET_SONAME_FILE does not warn there is magic behavior related to Macintoshes or IMPORTED_SONAME.
Also I think that extra /
is a bug, I also see the bonus /
in the -E echo
for another target in the same file (OculusAudio which I can give you build instructions for if you want them). The /
appears to be harmless but maybe in some circumstances it is not harmless, for example hypothetically it could confuse a regex.
CONFIGURATION: All above tests were performed on cmake 3.20.0 on MacOS 10.13.6. I used SteamAudio version 2.0-beta.19.
* When I asked about the above issue on Stack Overflow, Alex Reinking from your project questioned our file-copying scheme and pointed out we could leave the libraries where they are and resolve them with RPATH. That sounds reasonable to me (in fact it is how we did it on mac until a couple weeks ago) but the decision is not mine, I am not the maintainer, I am just submitting a patch to add support for some libraries on mac. If I have a compelling argument the CMake file is doing something substantially wrong (for example, if we are using TARGET_SONAME_FILE
but we ought to be using TARGET_FILE_NAME
?) I can make that argument to the maintainer but otherwise I have to work with the architecture that has been chosen.