What is the recommended/correct value of `IMPORTED_LOCATION` for IMPORTED library targets?

This topic is related to this issue: https://gitlab.kitware.com/cmake/cmake/-/issues/23696

Description

I want to verify what is the recommended/correct value of IMPORETED_LOCATION when there exist MULTIPLE configurations (e.g. Debug and Release)?

  1. One value of a specific configuration.
  2. A list of multiple values corresponding to each configuration with $<CONFIG:cfg>.

In my opinion, I prefer the 2nd way. Take Qt6::Core IMPORTED target for example. The value of it would be like:

$<$<CONFIG:Debug>:C:/Qt/6.2.0/msvc2019_64/bin/Qt6Cored.dll>;
$<$<CONFIG:Release>:C:/Qt/6.2.0/msvc2019_64/bin/Qt6Core.dll>

However, Qt official currently takes the 1st way. And other libraries don’t even provide this property, for example OpenCV.

  • Qt6::Core: C:/Qt/6.2.0/msvc2019_64/bin/Qt6Core.dll (Release version)
  • opencv_core: <variable>-NOTFOUND (Empty)

Reason

CMake provides this genex TARGET_RUNTIME_DLLS to let us copy DLLs with add_custom_command() more conveniently. However, which DLL should be copied depends on which configuration we are using (Debug or Release). Take Qt6::Core IMPORTED target for example:

  • If we choose Debug configuration, then we need to copy Qt6Cored.dll.
  • If we choose Release configuration, then we need to copy Qt6Core.dll.

Therefore, the 2nd way would be more suitable.

Further More

If lots of people and CMake official are identified with the 2nd way, I think we should suggest those C/C++ libraries to follow it. That way, we can use TARGET_RUNTIME_DLLS genex for every libraries.

If you have multiple configs, use IMPORTED_LOCATION_<CONFIG> properties.

Along with IMPORTED_CONFIGURATIONS to list the available configs whose locations are populated.

In order to reference runtime .dll files, CMake needs to know that the imported targets are shared libraries, so type SHARED should be used. In that case, populate IMPORTED_IMPLIB_CONFIG> with the import libraries (.lib files) so they can be linked.

See the add_library documentation on imported targets.

According to the documentation, it seems that the genex TARGET_RUNTIME_DLLS is evaluated by IMPORTED_LOCATION, not IMPORTED_LOCATION_<CONFIG>.

Note: Imported Targets are supported only if they know the location of their .dll files. An imported SHARED library must have IMPORTED_LOCATION set to its .dll file.

Besides, if the target property IMPORTED_LOCATION is EMPTY or has only ONE value of a specific configuration, how do we use the genex TARGET_RUNTIME_DLLS to copy the correct *.dll to the bin directory on build stage?

find_package(Qt6 CONFIG REQUIRED
  COMPONENTS Core Widgets Gui)

add_executable(exe main.cpp)

target_link_libraries(exe 
  PRIVATE 
    Qt6::Core 
    Qt6::Widgets
    Qt6::Gui)

add_custom_command(TARGET exe POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy 
          $<TARGET_RUNTIME_DLLS:exe> 
          $<TARGET_FILE_DIR:exe>
  COMMAND_EXPAND_LISTS)

After some experiments, I found that it is FINE with the abovementioned situations. Therefore, I have some conclusions about this topic.

Conclusions

  • $<TARGET_RUNTIME_DLLS:tgt> will be evaluated by IMPORTED_LOCATION_<CONFIG>, as well.

  • If there exist IMPORTED_LOCATION and IMPORTED_LOCATION_<CONFIG> simultaneously, then $<TARGET_RUNTIME_DLLS:tgt> will be evaluated by IMPORTED_LOCATION_<CONFIG> corresponding to the current configuration, priorly.

Experiments with Qt

The following is the example CMakeLists.txt code using Qt6.

Click to expand the example CMakeLists.txt code
cmake_minimum_required(VERSION 3.21)
get_filename_component(folder_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
project(${folder_name} LANGUAGES C CXX)

include(CMakePrintHelpers)

list(APPEND CMAKE_MODULE_PATH "C:/Qt/6.3.1/msvc2019_64")
list(APPEND CMAKE_PREFIX_PATH "C:/Qt/6.3.1/msvc2019_64")

find_package(Qt6 CONFIG REQUIRED
  COMPONENTS Core Widgets)

cmake_print_properties(
  TARGETS     Qt6::Core
              Qt6::Widgets
  PROPERTIES  IMPORTED_LOCATION
              IMPORTED_LOCATION_DEBUG
              IMPORTED_LOCATION_RELEASE)

add_executable(main "main.cpp")

target_link_libraries(main 
  PRIVATE 
    Qt6::Core 
    Qt6::Widgets)

add_custom_command(
  TARGET  main POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy 
          "$<TARGET_RUNTIME_DLLS:main>"
          "$<TARGET_FILE_DIR:main>"
  COMMAND_EXPAND_LISTS)

add_custom_target(echo-TARGET_RUNTIME_DLLS
  COMMAND ${CMAKE_COMMAND} -E echo
          "$<TARGET_RUNTIME_DLLS:main>")

After configuring the projects, as we can see, BOTH IMPORTED_LOCATION and IMPORTED_LOCATION_<CONFIG> of IMPORTED targets are populated.

Click to expand output results of configuring projects
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=d:/cmake-find_package-qt/install -Sd:/cmake-find_package-qt -Bd:/cmake-find_package-qt/build/win32-MSVC-x64-Debug -G Ninja
[cmake] Not searching for unused variables given on the command line.
[cmake] -- Could NOT find WrapVulkanHeaders (missing: Vulkan_INCLUDE_DIR) 
[cmake] -- 
[cmake]  Properties for TARGET Qt6::Core:
[cmake]    Qt6::Core.IMPORTED_LOCATION = "C:/Qt/6.3.1/msvc2019_64/bin/Qt6Core.dll"
[cmake]    Qt6::Core.IMPORTED_LOCATION_DEBUG = "C:/Qt/6.3.1/msvc2019_64/bin/Qt6Cored.dll"
[cmake]    Qt6::Core.IMPORTED_LOCATION_RELEASE = "C:/Qt/6.3.1/msvc2019_64/bin/Qt6Core.dll"
[cmake]  Properties for TARGET Qt6::Widgets:
[cmake]    Qt6::Widgets.IMPORTED_LOCATION = "C:/Qt/6.3.1/msvc2019_64/bin/Qt6Widgets.dll"
[cmake]    Qt6::Widgets.IMPORTED_LOCATION_DEBUG = "C:/Qt/6.3.1/msvc2019_64/bin/Qt6Widgetsd.dll"
[cmake]    Qt6::Widgets.IMPORTED_LOCATION_RELEASE = "C:/Qt/6.3.1/msvc2019_64/bin/Qt6Widgets.dll"
[cmake] 
[cmake] -- Configuring done
[cmake] -- Generating done
[cmake] -- Build files have been written to: D:/cmake-find_package-qt/build/win32-MSVC-x64-Debug

After building the custom target echo-TARGET_RUNTIME_DLLS with Debug and Release configurations respectively, I found that $<TARGET_RUNTIME_DLLS:tgt> would be populated with its corresponding IMPORTED_LOCATION_<CONFIG>.

Click to expand output results with Debug config
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --build d:/cmake-find_package-qt/build/win32-MSVC-x64-Debug --config Debug --target echo-TARGET_RUNTIME_DLLS --
[build] [1/1 100% :: 0.038] cmd.exe /C "cd /D D:\cmake-find_package-qt\build\win32-MSVC-x64-Debug && "C:\Program Files\CMake\bin\cmake.exe" -E echo C:/Qt/6.3.1/msvc2019_64/bin/Qt6Widgetsd.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Guid.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Cored.dll"
[build] C:/Qt/6.3.1/msvc2019_64/bin/Qt6Widgetsd.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Guid.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Cored.dll
Click to expand output results with Release config
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --build d:/cmake-find_package-qt/build/win32-MSVC-x64-Release --config Release --target echo-TARGET_RUNTIME_DLLS --
[build] [1/1 100% :: 0.041] cmd.exe /C "cd /D D:\cmake-find_package-qt\build\win32-MSVC-x64-Release && "C:\Program Files\CMake\bin\cmake.exe" -E echo C:/Qt/6.3.1/msvc2019_64/bin/Qt6Widgets.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Gui.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Core.dll"
[build] C:/Qt/6.3.1/msvc2019_64/bin/Qt6Widgets.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Gui.dll;C:/Qt/6.3.1/msvc2019_64/bin/Qt6Core.dll

Experiments with OpenCV

The following is the example CMakeLists.txt code using OpenCV.

Click to expand the example CMakeLists.txt code
cmake_minimum_required(VERSION 3.21)
get_filename_component(folder_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
project(${folder_name} LANGUAGES C CXX)

include(CMakePrintHelpers)

list(APPEND CMAKE_MODULE_PATH "C:/ccxxpkgs/install")
list(APPEND CMAKE_PREFIX_PATH "C:/ccxxpkgs/install")

find_package(OpenCV CONFIG REQUIRED)

cmake_print_properties(
  TARGETS     opencv_core
              opencv_imgproc
  PROPERTIES  IMPORTED_LOCATION
              IMPORTED_LOCATION_DEBUG
              IMPORTED_LOCATION_RELEASE)

add_executable(main "main.cpp")

target_link_libraries(main 
  PRIVATE 
    opencv_core 
    opencv_imgproc)

add_custom_command(
  TARGET  main POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy 
          "$<TARGET_RUNTIME_DLLS:main>"
          "$<TARGET_FILE_DIR:main>"
  COMMAND_EXPAND_LISTS)

add_custom_target(echo-TARGET_RUNTIME_DLLS
  COMMAND ${CMAKE_COMMAND} -E echo
          "$<TARGET_RUNTIME_DLLS:main>")

After configuring the projects, as we can see, ONLY IMPORTED_LOCATION_<CONFIG> of IMPORTED targets are populated. While IMPORTED_LOCATION is empty.

Click to expand output results of configuring projects
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=d:/cmake-find_package-opencv/install -Sd:/cmake-find_package-opencv -Bd:/cmake-find_package-opencv/build/win32-MSVC-x64-Debug -G Ninja
[cmake] Not searching for unused variables given on the command line.
[cmake] -- OpenCV ARCH: x64
[cmake] -- OpenCV RUNTIME: vc16
[cmake] -- OpenCV STATIC: OFF
[cmake] -- Found OpenCV 4.5.5 in C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/lib
[cmake] -- You might need to add C:\ccxxpkgs\install\opencv-4.5.5\x64\vc16\bin to your PATH to be able to run your applications.
[cmake] -- 
[cmake]  Properties for TARGET opencv_core:
[cmake]    opencv_core.IMPORTED_LOCATION = <NOTFOUND>
[cmake]    opencv_core.IMPORTED_LOCATION_DEBUG = "C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_core455d.dll"
[cmake]    opencv_core.IMPORTED_LOCATION_RELEASE = "C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_core455.dll"
[cmake]  Properties for TARGET opencv_imgproc:
[cmake]    opencv_imgproc.IMPORTED_LOCATION = <NOTFOUND>
[cmake]    opencv_imgproc.IMPORTED_LOCATION_DEBUG = "C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_imgproc455d.dll"
[cmake]    opencv_imgproc.IMPORTED_LOCATION_RELEASE = "C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_imgproc455.dll"
[cmake] 
[cmake] -- Configuring done
[cmake] -- Generating done
[cmake] -- Build files have been written to: D:/cmake-find_package-opencv/build/win32-MSVC-x64-Debug

After building the custom target echo-TARGET_RUNTIME_DLLS with Debug and Release configurations respectively, I found that $<TARGET_RUNTIME_DLLS:tgt> would be populated with its corresponding IMPORTED_LOCATION_<CONFIG>.

Click to expand output results with Debug config
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --build d:/cmake-find_package-opencv/build/win32-MSVC-x64-Debug --config Debug --target echo-TARGET_RUNTIME_DLLS --
[build] [1/1 100% :: 0.039] cmd.exe /C "cd /D D:\cmake-find_package-opencv\build\win32-MSVC-x64-Debug && "C:\Program Files\CMake\bin\cmake.exe" -E echo C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_imgproc455d.dll;C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_core455d.dll"
[build] C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_imgproc455d.dll;C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_core455d.dll
Click to expand output results with Release config
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --build d:/cmake-find_package-opencv/build/win32-MSVC-x64-Release --config Release --target echo-TARGET_RUNTIME_DLLS --
[build] [1/1 100% :: 0.042] cmd.exe /C "cd /D D:\cmake-find_package-opencv\build\win32-MSVC-x64-Release && "C:\Program Files\CMake\bin\cmake.exe" -E echo C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_imgproc455.dll;C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_core455.dll"
[build] C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_imgproc455.dll;C:/ccxxpkgs/install/opencv-4.5.5/x64/vc16/bin/opencv_core455.dll

Suggestions

Because $<TARGET_RUNTIME_DLLS:tgt> doesn’t mention IMPORTED_LOCATION_<CONFIG> in its documentation, I suggest that CMake add some NOTES to describe this mechanism.