Skip to content

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.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information