How to copy DLL files into the same folder as the executable using CMake?
Asked Answered
D

13

134

We use CMake for generating the Visual Studio files of our sources in our SVN. Now my tool requires some DLL files to be in the same folder as the executable. The DLL files are in a folder alongside the source.

How can I change my CMakeLists.txt such that the generated Visual Studio project will either have already the particular DLL files in the release/debug folders or will copy them upon compilation?

Drillmaster answered 20/5, 2012 at 8:24 Comment(1)
W
160

I'd use add_custom_command to achieve this along with cmake -E copy_if_different.... For full info run

cmake --help-command add_custom_command
cmake -E


So in your case, if you have the following directory structure:

/CMakeLists.txt
/src
/libs/test.dll

and your CMake target to which the command applies is MyTest, then you could add the following to your CMakeLists.txt:

add_custom_command(TARGET MyTest POST_BUILD        # Adds a post-build event to MyTest
    COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake - E copy_if_different..."
        "${PROJECT_SOURCE_DIR}/libs/test.dll"      # <--this is in-file
        $<TARGET_FILE_DIR:MyTest>)                 # <--this is out-file path


If you just want the entire contents of the /libs/ directory copied, use cmake -E copy_directory:

add_custom_command(TARGET MyTest POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${PROJECT_SOURCE_DIR}/libs"
        $<TARGET_FILE_DIR:MyTest>)


If you need to copy different dlls depending upon the configuration (Release, Debug, eg) then you could have these in subdirectories named with the corresponding configuration: /libs/Release, and /libs/Debug. You then need to inject the configuration type into the path to the dll in the add_custom_command call, like this:

add_custom_command(TARGET MyTest POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${PROJECT_SOURCE_DIR}/libs/$<CONFIGURATION>"
        $<TARGET_FILE_DIR:MyTest>)
Wallaby answered 20/5, 2012 at 10:44 Comment(7)
Quick note for what worked in my case in case it helps someone else in the future: I have a static library project which the main executable optionally links against, and that library requires a DLL to be copied if added. So in that library's CMakeLists.txt file I used ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG> for the target destination. Otherwise it would copy it to the library build path, which was useless.Lorin
isn't it the goal of the cmake install() directives to assemble the binaries? Or maybe cmake LIBRARY things? I don't really know the toolchain.Reynaldoreynard
$<TARGET_FILE_DIR:MyTest> - what is it? How to print info, what exactly meanAlek
Unless I missed something when I was integrating the command this method doesn't work for libraries added with IMPORTED_LIBRARIES. It complains about not being able to run a post-build command when nothing has been built.Ganof
Upon testing and prodding my cmake: If you are using IMPORTED libraries, and need to relocate the DLLs, you need to use a variant command. If you have added your IMPORTED library as MyImportedLib you would use COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:MyImportedLib> $<TARGET_FILE_DIR:MyTest> Note that to run multiple post build commands you need them all bound into one custom command e.g. add_custom_command(TARGET MyTest POST_BUILD COMMAND #your first command# COMMAND #Your second command#)Ganof
Thanks, however do you have to specify a TARGET?Cubage
@Cubage You want to specify a target, because you're appending these commands to that target's build "script". IOW, the commands depend on MyTest, and can't be run until it's completed building (hence the "POST_BUILD"). So setting the commands as TARGET MyTest POST_BUILD tells CMake to wait until it's done building MyTest to run them.Diverting
D
33

I put these lines in my top-level CMakeLists.txt file. All the libraries and executables compiled by CMake will be placed in the top level of the build directory so that the executables can find the libraries and it is easy to run everything.

set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

Note that this doesn't solve the OP's problem of copying precompiled binaries from the project's source directory.

Drugi answered 23/12, 2015 at 23:21 Comment(0)
K
30

For Windows users, there is a new generator expression $<TARGET_RUNTIME_DLLS:tgt> in CMake 3.21+ and you could use this official snippet for copying all of the DLLs that a target depends on.

find_package(foo REQUIRED)

add_executable(exe main.c)
target_link_libraries(exe PRIVATE foo::foo foo::bar)
add_custom_command(TARGET exe POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:exe> $<TARGET_FILE_DIR:exe>
  COMMAND_EXPAND_LISTS
  )
Komara answered 27/10, 2021 at 9:36 Comment(5)
An issue with this is that it fails if the exe does not require any DLLs. Do you know if there's a way to work around that, given that generator expressions are evaluated late?Suffocate
not sure I understood your question. are you looking for a way to copy those dlls that the executable doesn't depend on?Komara
@LukeZhou This answer is very close, but not the way you're supposed to do it. See my answer below which handles your no-DLL situation.Roborant
Seems in cmake 3.26 the new copy -t might handle this betterMethoxychlor
In the meantime, here's my workaround: COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:exe> $<TARGET_RUNTIME_DLLS:exe> $<TARGET_FILE_DIR:exe>. This also copies the target to its own directory. This is wasteful and silly but makes sure that if the *DLLS is an empty string it's still a valid command.Methoxychlor
S
29

I've had this problem today when tried to make a Windows build of my program. And I ended up doing some research myself since all these answers didn't satisfy me. There were three main issues:

  • I wanted debug builds to be linked with debug versions of libraries and release builds to be linked with release builds of libraries, respectively.

  • In addition to that, I wanted correct versions of DLL files (Debug/Release) to be copied to output directories.

  • And I wanted to achieve all this without writing complex and fragile scripts.

After browsing some CMake manuals and some multiplatform projects at github I've found this solution:

Declare your library as a target with "IMPORTED" attribute, reference its debug and release .lib and .dll files.

add_library(sdl2 SHARED IMPORTED GLOBAL)
set_property(TARGET sdl2 PROPERTY IMPORTED_IMPLIB_RELEASE "${SDL_ROOT_PATH}/lib/SDL2.lib")
set_property(TARGET sdl2 PROPERTY IMPORTED_LOCATION_RELEASE "${SDL_ROOT_PATH}/bin/SDL2.dll")
set_property(TARGET sdl2 PROPERTY IMPORTED_IMPLIB_DEBUG "${SDL_ROOT_PATH}/lib/SDL2d.lib")
set_property(TARGET sdl2 PROPERTY IMPORTED_LOCATION_DEBUG "${SDL_ROOT_PATH}/bin/SDL2d.dll")

Link this target with your project as usual

target_link_libraries(YourProg sdl2 ...)

Make custom build step to copy dll file to its destination if it has been altered somehow since previous build

add_custom_command ( TARGET YourProg POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
    $<TARGET_FILE:sdl2> $<TARGET_FILE_DIR:YourProg>
)
Scapolite answered 4/1, 2020 at 19:27 Comment(3)
I've been struggling to understand the usefulness of generator expressions for a long time, as well as trying to figure out a way to do this reliably. This solved both problems for me at once, and should probably be the accepted answer.Verlaverlee
What if I have the same lib and dll for all the builds? Can I do the same command, something like set_property(TARGET sdl2 PROPERTY IMPORTED_IMPLIB "${SDL_ROOT_PATH}/lib/SDL2.lib")?Ribbon
I've tried it, but it copies only .lib files and not also .dll...Torin
R
24

1. The most correct way: install(TARGET_RUNTIME_DLLS) (CMake >= 3.21)

install(FILES $<TARGET_RUNTIME_DLLS:your_exe_here> TYPE BIN)

For this to work, your dependencies' CMake modules have to be well-written. In other words, they use CMake 3 targets with all their target properties set up correctly. If they set up everything right, all your DLLs will be automagically gathered up and installed alongside your exe. CMake will automatically match what type of DLL (e.g. release vs debug) to match your target exe.

This is where CMake will be headed more in the future, and the way you should prefer if you have a choice.

You should prefer install() to just straight-up copying the files into $CMAKE_RUNTIME_OUTPUT_DIRECTORY because install() is the official CMake-sanctioned way to put things in $CMAKE_RUNTIME_OUTPUT_DIRECTORY. install() is named so strangely because CMake is more than a build tool. It's also an installer-generating tool. This installer generating functionality is called CPack. In CMake's eyes, $CMAKE_RUNTIME_OUTPUT_DIRECTORY is just the holding area for CPack. When you install() a file, you tell CMake that it should be considered a program file, to be copied around wherever the exe goes. If you don't go through install(), CMake will see it as a random unrecognized file.

2. The second most correct way: install(RUNTIME_DEPENDENCIES) (CMake >= 3.21)

install(TARGETS your_exe_here
    RUNTIME ARCHIVE LIBRARY RUNTIME FRAMEWORK BUNDLE PUBLIC_HEADER RESOURCE)
install(TARGETS your_exe_here
    COMPONENT your_exe_here
    RUNTIME_DEPENDENCIES
    PRE_EXCLUDE_REGEXES "api-ms-" "ext-ms-"
    POST_EXCLUDE_REGEXES ".*system32/.*\\.dll"
    DIRECTORIES $<TARGET_FILE_DIR:your_exe_here>)

The key here is RUNTIME_DEPENDENCIES.

Internally, RUNTIME_DEPENDENCIES calls file(GET_RUNTIME_DEPENDENCIES), which scans your executable binary, tries very hard to exactly replicate what actual dependency resolution would look like, and write down all the DLLs mentioned along the way. These are passed back up to install().

What this means is that this doesn't depend on your dependencies' CMake modules having their target properties set up correctly. Your actual executable binary is scanned. Everything will get picked up.

3. The third most correct way: file(GET_RUNTIME_DEPENDENCIES) (CMake >= 3.16)

file(GET_RUNTIME_DEPENDENCIES) is what install(RUNTIME_DEPENDENCIES) calls under the hood, but file(GET_RUNTIME_DEPENDENCIES) is available in an earlier CMake version than install(RUNTIME_DEPENDENCIES). We can still do the same thing in the older CMake version, just with more boilerplate.

The tricky part is that file(GET_RUNTIME_DEPENDENCIES) can only be called at install time. This means we need to use install(CODE) to run a script that in turn calls file(GET_RUNTIME_DEPENDENCIES). For an implementation, see here.

4. Last resort: install(DIRECTORY)

install(
  DIRECTORY "${DIR_CONTAINING_YOUR_DLLS}"
  TYPE BIN
  FILES_MATCHING REGEX "[^\\\\/.]\\.[dD][lL][lL]$"
)

To use, put the DLLs appropriate for your build in $DIR_CONTAINING_YOUR_DLLS.

The trick here is that, unlike install(FILES), install(DIRECTORY) doesn't care what specific files are in the directory until install time. That means now we have all of configure time and compile time to get a list of your DLLs and stuff them in $DIR_CONTAINING_YOUR_DLLS. As long as the DLL files are in $DIR_CONTAINING_YOUR_DLLS by install time, install(DIRECTORY) will pick them up.

If you choose this method, it becomes your responsibility to match DLLs to your build config. (Consider: static vs dynamic, debug vs release, import lib version vs DLL version, libs with optional multithreading, forgetting to remove DLLs you don't need anymore.)

If you choose this method, you might want to automate DLL finding and matching using something like what vcpkg's applocal.ps1 does. (Hypothetically, it should be possible to reimplement what vcpkg's applocal.ps1 does in pure CMake using install(CODE), but I don't have an implementation ready to post.)


Hint for vcpkg

If you use vpckg with VCPKG_APPLOCAL_DEPS enabled, vcpkg will locate and copy your DLLs into your $CMAKE_RUNTIME_OUTPUT_DIRECTORY for you, but without going through install(). You need to use the install(DIRECTORY) trick to get CMake to pick them up.

(Internally, vcpkg uses dumpbin, llvm-objdump, and objdump to scan your executable binary to get these filenames.)

Roborant answered 10/1, 2023 at 3:35 Comment(9)
Thank you for the target-centric solution; just was I was looking for! I think this answer should have more votes than the accepted solution since this is where CMake is going nowadays. Still not super happy about having to pull a file from an external installation into my installation because a package manager like vcpkg would want to manage the dependencies themselves (that's what they are there for right?), but I don't really see an alternative at the moment. And aside from that, this is just perfect.Crumple
Don't forget the .lib file ($<TARGET_LINKER_FILE:tgt>)Tripterous
Trying to accomplish option 1, but this is my first time using cmake and can't seem to get it to work. I have a prebuilt library with a .dll and a header file and .lib. I used add_library(extLib SHARED IMPORTED) and set IMPORTED_LOCATION and IMPORTED_IMPLIB as well. Used target_link_libraries and target_include_directories to include the header. Then tried the install command as you wrote in option 1. It builds but the dll doesn't end up with the exe and the TARGET_RUNTIME_DLLS:myExe variable is empty. I am doing all of this in VS2022. on windows. Any tips?Magma
@Magma Did you print $<TARGET_RUNTIME_DLLS:myExe> including the <> and with file(GENERATE)?Roborant
@Roborant for printing I used include(CMakePrintHelpers) cmake_print_variables($<TARGET_RUNTIME_DLLS:myExe>), which prints 1> [CMake] -- $<TARGET_RUNTIME_DLLS:myExe>="" in the CMake output pane. Not sure what you mean by file(GENERATE)Magma
@Magma You need file(GENERATE OUTPUT <filename> CONTENT <string-with-generator-expression>) to "print" a generator expression i.e. a variable with$<..>. Generator expressions are only known at build time, so it's always blank if you use cmake_print_variables().Roborant
@Roborant Ah that makes sense. I was just writing an update to say that I created a custom build command as described in the cmake documentation for TARGET_RUNTIME_DLLS (link) and that worked, but install(FILES $<TARGET_RUNTIME_DLLS:myExe> TYPE BIN) doesn't seem to do anythingMagma
@Magma That's strange. I'm not sure why you're getting that behavior, but I'm glad you got a working solution.Roborant
@Roborant another update, I think I figured it out. It seems that when running/debugging from visual studio, it uses exe from the out/build/... directory. The install command works but puts the files in out/install/ so when you run from VS it can't find the dll and it seems like nothing happened at first. And thanks for the help troubleshooting this!Magma
S
8

Moving files during build using install

I had this issue trying to follow the CMake official tutorial on Step 9. This was the location of the file I wanted to move:

src
 |_build
    |_Debug
       - `MathFunctions.dll`

This was the location I wanted the file to be in:

src
 |_build
    |_install
         |_bin
             - `MathFunctions.dll`

Since this DLL was generated as a shared library, all I did was to include this line in the CMakeLists.txt in the subdirectory that contained the source code for the library src/Mathfunctions/CMakeLists.txt

install(FILES ${PROJECT_BINARY_DIR}/$<CONFIG>/MathFunctions.dll
DESTINATION bin)

Thanks to your answers I could think on this one. Is just one line, so I think is ok. The $<CONFIG> can have two values Debug or Release Depending on how the project is built, as the original question required.

Spectroscope answered 29/7, 2020 at 17:12 Comment(1)
Honestly that's the simplest working answer ever.Ilonailonka
R
5

An addendum to the accepted answer, added as a separate answer so I get code formatting:

If you are building your dlls in the same project, they will usually be in Release, Debug, etc. directories. You'll have to use the Visual Studio environment variables to correctly copy them. e.g.:

"${PROJECT_BINARY_DIR}/your_library/\$\(Configuration\)/your_library.dll"

for the source and

"${CMAKE_CURRENT_BINARY_DIR}/\$\(Configuration\)/your_library.dll"

for the destination. Note the escaping!

You can't use the CMake CMAKE_BUILD_TYPE variable for the configuration since it's resolved at VS project generation time and will always be whatever the default is.

Rag answered 17/11, 2014 at 22:45 Comment(1)
The last part of the accepted answer addresses this in the cleaner CMake way using $<CONFIGURATION>Eruptive
C
5

You can also use the command find_library:

find_library(<some_var> NAMES <name_of_lib> PATHS "<path/to/lib>")

With a defined EXECUTABLE_PATH, for instance:

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

you could move the .dll files that your executable need, with

file(COPY ${<some_var>}
    DESTINATION ${EXECUTABLE_OUTPUT_PATH})
Crapulent answered 2/2, 2018 at 18:23 Comment(0)
C
2

You probably need to add custom target and make it depend on one of your executable targets.

To copy file using above function use:

COMMAND ${CMAKE_PROGRAM} -E copy_if_different ${CMAKE_BINARY_DIR}/path/to/file.dll ${CMAKE_BINARY_DIR}/where/to/put/file.dll`
Cardio answered 20/5, 2012 at 8:31 Comment(0)
L
2

This is useful for one of them

SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
    ${PROJECT_SOURCE_DIR}/lib CACHE
    PATH "Directory where all the .lib files are dumped." FORCE)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY
    ${PROJECT_SOURCE_DIR}/bin CACHE
    PATH "Directory where .exe and .dll files are dumped." FORCE)
Labors answered 19/6, 2015 at 10:40 Comment(0)
F
1

The following command from the currently top rated answer depends on the output being put in /libs/.

add_custom_command(TARGET MyTest POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${PROJECT_SOURCE_DIR}/libs/$<CONFIGURATION>"
        $<TARGET_FILE_DIR:MyTest>)

I use the below command to do the copy, which works for me everywhere. Note that I'm only copying the output dll here, not the entire directory. Also note that I'm hard coding a destination /bin/ directory, which is specific to this project. But I wanted to share the $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/$<TARGET_FILE_NAME:${CMAKE_PROJECT_NAME}> syntax, which I think is neat:

add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        $<TARGET_FILE_DIR:${CMAKE_PROJECT_NAME}>/$<TARGET_FILE_NAME:${CMAKE_PROJECT_NAME}>
        ${CMAKE_CURRENT_SOURCE_DIR}/bin/$<TARGET_FILE_NAME:${CMAKE_PROJECT_NAME}>)
Frication answered 2/7, 2021 at 12:23 Comment(0)
K
0

I'm a CMake beginner, but still I wanted to shared my experience. In my case I needed a post-install copy so that all my binaries are in. In the case of third-party binary that can be imported within CMake, the following works for me:

find_package( dependency REQUIRED )
if( MSVC ) 
    # If done properly and if the dependency has a correct config file, IMPORTED_LOCATION_RELEASE should be defined
    get_target_property( DEP_SHARED_LIB_PATH dependency IMPORTED_LOCATION_RELEASE )
    # Create a bin directory in the install folder
    add_custom_command(TARGET BGS POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory  ${CMAKE_INSTALL_PREFIX}/bin/)
    # Copy the shared lib file
    add_custom_command(TARGET BGS POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${DEP_SHARED_LIB_PATH} ${CMAKE_INSTALL_PREFIX}/bin/)
endif()

Obviously IMPORTED_LOCATION_RELEASE can have variants depending on how the shared library was built / installed. Could be IMPORTED_LOCATION_DEBUG.

Maybe there's a better way to get that property name, I don't know.

Kizzee answered 9/6, 2020 at 13:23 Comment(0)
F
0

Since the CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES variable usually contains a complete list of library directories you can use this code:

install(TARGETS <your target>
    RUNTIME_DEPENDENCIES
    DIRECTORIES ${CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES}
    PRE_EXCLUDE_REGEXES "^[^lib]"
)

This code also excludes all libraries that do not start with the "lib" prefix. For example, on Windows and MinGW's g++ only libgcc_s_seh-1.dll, libstdc++-6.dll and libwinpthread-1.dll are left, which is exactly what is needed.

Flagg answered 1/3 at 20:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.