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.)