1. The most correct way: 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 transitively-linked DLLs will be automagically gathered up and installed alongside your exe.
A big difference between this and the straight-up copy-the-files approach is that, because it goes through install()
, CMake will actually know about them as files to-be-installed. CPack will know about your DLLs and include them in any installer you generate with it. CMake will automatically adjust what type of DLL (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.
2. The second most correct way: 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: 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
.
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.)