Runtime Resource Copying with CMake as of 3.24

(Runtime resources: dlls, dylibs, sos, etc, that need to be placed alongside executables to run)

I’ve been trying to answer my own question since early Monday, so at this juncture I’m explicitly ignoring the plethora of existing answers on this topic because - tip of the that - KitWare has moved the goalposts beautifully in the last few releases. No snark - I’m liking this “oh, it does that now!” experience.

I’ll use a concrete example. Our Project uses FMOD to provide sound to our own Sound abstraction library. We then have both executables and additional libraries that use OurSound. As a result, there are targets that unknowingly depend on FMOD.

include (vendored/fmod/fmod.cmake)  # defines ExternalFMOD target, POST_BUILD_FMOD
add_library (OurSound OurSound.cpp OurSound.h)
target_link_libraries (OurSound INTERFACE ExternalFMOD)

add_executable (Beep Beep.cpp)
target_link_libraries (Beep OurSound)
POST_BUILD_FMOD(Beep)  # copy dynamics for the appropriate platform

add_library (Engine Engine.cpp)
target_link_libraries (Engine PUBLIC OurSound)

add_executable (Game Game.cpp)
target_link_libraries (Game PUBLIC Engine)
# Well, it works on *my* machine (uncomment below to fix elsewhere)
# POST_BUILD_FMOD(Game)

The questions

What are the best practices for this as of 3.24, now?

Is there now a “target_runtime_dependencies” builtin for this, or something like that? Is the russian-nesting-doll post-build idiom still the best approach? Is there a way to attach a post-build dependency to an interface/imported library that works for a copy_if_different?

The russian-nesting doll approach was what seemed to be the multi-platform best strategy:

# in win32 .cmake
add_library (
    ExternalFMOD_Native
    SHARED
    IMPORTED
    GLOBAL
)
set_target_properties (ExternalFMOD_Native PROPERTIES IMPORTED_IMPLIB "${_fmod_libs_dir}/x64/fmod_vc.lib")
set_target_properties (ExternalFMOD_Native PROPERTIES IMPORTED_LOCATION "${_fmod_libs_dir}/x64/fmod.dll")
set_target_properties (ExternalFMOD_Native PROPERTIES IMPORTED_IMPLIB_DEBUG "${_fmod_libs_dir}/x64/fmodL_vc.lib")
set_target_properties (ExternalFMOD_Native PROPERTIES IMPORTED_LOCATION_DEBUG "${_fmod_libs_dir}/x64/fmodL.dll")

set_property (TARGET ExternalFMOD APPEND PROPERTY RUNTIME_REQUIREMENTS ${_fmod_libs_dir}/x64/fmod${_library_suffix}.dll)

target_link_libraries (ExternalFMOD INTERFACE ExternalFMOD_Native)

function (POST_BUILD_FMOD_WIN32 APP_NAME)
      add_custom_command (TARGET ${APP_NAME} POST_BUILD
                        COMMENT "Copying FMOD DLLs for ${APP_NAME}"
                        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        $<TARGET_PROPERTY:ExternalFMOD,RUNTIME_REQUIREMENTS>
                        $<TARGET_FILE_DIR:${APP_NAME}>
    )
endfunction ()

in the common cmake:

function (POST_BUILD_FMOD APP_NAME)
  if (WIN32)
    POST_BUILD_FMOD_WIN32(APP_NAME)
  ...
endfunction ()

Obviously I could collapse these to just the top-level POST_BUILD, but before I do that work across our libraries, I want to ensure there isn’t a different approach I can now leverage entirely

function (POST_BUILD_FMOD APP_NAME)
  get_target_property (requirements ExternalFMOD RUNTIME_REQUIREMENTS)
  # uh, I always forget how to test this so that it checks the property wasn't empty or unset...
  if (requirements)
    add_custom_command (TARGET ${APP_NAME} POST_BUILD
                        COMMENT "Copying FMOD dynamic resources for ${APP_NAME}"
                        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        ${requirements}
                        $<TARGET_FILE_DIR:${APP_NAME}>
    )
  endif ()
endfunction ()

Cc: @kyle.edwards