0

I have a project which is basically a library DLL and some test code for the DLL. It is structured with a top level directory a subdirectory for the library code and a subdirectory for the test code.

When I run the tests in my IDE they fail because the DLL is not in the same subdirectory as the test executable. To get around this I tried to use some CMake magic which doesn't work, contrary to the documentation.

My magic works as follows:

CMakelists.txt in library subdirectory to copy DLL up to the top level so that the test code (and in future, applications that use the DLL) can always find it in a consistent place:

add_custom_command(
    OUTPUT "${CMAKE_BINARY_DIR}/my.dll"
    COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/my.dll" "${CMAKE_BINARY_DIR}/my.dll"
    DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/my.dll"
)

add_custom_target(export_my_dll
    DEPENDS "${CMAKE_BINARY_DIR}/my.dll"
)

This all works and I see in the build output that the targets are built, and the output file is up to date.

CMakelists.txt in tests subdirectory to copy DLL down from the top level:

add_custom_command(
    OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/my.dll"
    COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/my.dll" "${CMAKE_CURRENT_BINARY_DIR}/my.dll"
    DEPENDS export_my_dll
)

add_custom_target(dlls_for_tests
    DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/my.dll"
)

add_dependencies(tests dlls_for_tests)

"tests" is the target for my test executable. The build output says it builds the "dlls_for_tests" target, but the DLL is not up to date in the binary directory for the tests executable - the tests executable is using an old outdated DLL.

The build output (QtCreator) looks like:

...
[ 75%] Generating ../my.dll
[ 79%] Built target export_my_dll
[ 82%] Built target dlls_for_tests
Scanning dependencies of target SpectreCtrl_Tests
[ 89%] Building CXX object project/tests/CMakeFiles/tests.dir/test_func.cpp.obj
[ 94%] Linking CXX executable tests.exe
[ 100%] Built target tests

I have two questions:

1) Why doesn't this work?

2) Is there a better way to do this that avoids copying the DLl when it hasn't changed or just that is easier to read?

===============

I have found two ways that work, but I don't much like either:

add_custom_command(
    OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/my.dll"
    COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:my_dll>" "${CMAKE_CURRENT_BINARY_DIR}"
    DEPENDS "$<TARGET_FILE:my_dll>"
)

add_custom_target(dlls_for_test
    DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/my.dll"
)

add_dependencies(tests dlls_for_test)

This explicitly uses the file name - you need to get it right and if you change the target name of the library (and hence the library name) this will break. Hopefully it will skip the copy if the DLL didn't change.

add_custom_target(dlls_for_tests
    COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:my_dll>" "${CMAKE_CURRENT_BINARY_DIR}"
)

add_dependencies(tests dlls_for_tests)

This is much shorter and sweeter, but it will always copy the DLL even if it didn't change.

AlastairG
  • 4,119
  • 5
  • 26
  • 41
  • You can add the custom comand as postbuild step to the target, `add_custom_command(TARGET tests POST_BUILD ...)`. So it is updated every time the tests are rebuilt. – vre Jan 11 '19 at 09:37
  • @vre I tried it as a PRE_LINK rather than POST_BUILD and it kept saying "TARGET PRE_LINK not defined" or something like that. In any case that's the same effect as my second option but implemented as a single custom command rather than two entries. Just in case I am getting the syntax wrong, could you type it up in full as an answer for me to try, please. – AlastairG Jan 11 '19 at 10:15
  • Does this answer your question? [How to copy DLL files into the same folder as the executable using CMake?](https://stackoverflow.com/questions/10671916/how-to-copy-dll-files-into-the-same-folder-as-the-executable-using-cmake) – 273K Mar 26 '23 at 19:13

2 Answers2

3

I would add an POST_BUILD step to your target as follows:

add_library(my_dll SHARED ${DLL_SOURCES} ${DLL_HEADERS} ${DLL_RESOURCES} )

add_executable(tests ${SOURCES} ${HEADERS} ${RESOURCES} )

target_link_libraries(tests
    my_dll
)

add_custom_command(TARGET tests
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:my_dll> "${CMAKE_CURRENT_BINARY_DIR}"
    COMMENT "Copy dll file to ${CMAKE_CURRENT_BINARY_DIR} directory" VERBATIM
)

The order of the commands matter.

Another possibility is to define a common output directory in the top level CMakeLists.txt file:

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
vre
  • 6,041
  • 1
  • 25
  • 39
  • I've been put on something else but will try this later. One thing; your example has the add_library and add_executable in the same CMakelists.txt whereas I have them in two different such files with a parent CMakelists.txt (as stated in the question). I really do like the idea of outputting everything to the same direction. That's an idea that really appeals. – AlastairG Jan 11 '19 at 14:20
  • Approach with setting `CMAKE_*_OUTPUT_DIRECTORY` variables is described in [that answer](https://stackoverflow.com/a/34445220/3440745) to the duplicate(?) question. – Tsyvarev Jan 11 '19 at 15:27
  • In the end I didn't use setting CMAKE_XXXX_OUTPUT_DIRECTORY variables, but thinking about it, it would save me a lot of complexity in my scripts to build installers as finding dependencies built as part of the project would be much simpler. If I get time I may go back and retrofit this to all existing projects. – AlastairG Mar 15 '19 at 10:33
0

but the DLL is not up to date in the binary directory for the tests executable

This is because your custom command in the tests/ subdirectory has no dependency from the library file. You have only dependency from your custom target:

DEPENDS export_my_dll

Just add dependency from the file, and everything will work:

DEPENDS "${CMAKE_BINARY_DIR}/my.dll"
# Dependency from the target is also needed
DEPENDS export_my_dll
Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • Doesn't work. I tried that. CMake complains that it has no rule to make "${CMAKE_BINARY_DIR}/my.dll" (except it expands the path fully). This agrees with the CMake documentation which states that the dependencies need to either be an output of an add_custom_command in the same directory (same CMakelists.txt) or be a proper target. – AlastairG Jan 11 '19 at 14:16
  • Weird, it works for me. File-level dependency "${CMAKE_BINARY_DIR}/my.dll" should be checked only after all dependee targets have been built. Because target `export_my_dll` builds this file, it should be no problems. Are you sure that script `test/CMakeLists.txt` is processed **after** `lib/CMakeLists.txt` one? So the target `export_my_dll` is already declared when it used in *DEPENDS* clause? – Tsyvarev Jan 11 '19 at 15:17