¿Cómo copiar archivos DLL en la misma carpeta que el ejecutable usando CMake?
Usamos CMake para generar los archivos de Visual Studio de nuestras fuentes en nuestro SVN. Ahora mi herramienta requiere que algunos archivos DLL estén en la misma carpeta que el ejecutable. Los archivos DLL están en una carpeta junto a la fuente.
¿Cómo puedo cambiar mi CMakeLists.txt
modo de que el proyecto de Visual Studio generado ya tenga los archivos DLL particulares en las carpetas de lanzamiento/depuración o los copie durante la compilación?
Lo usaría add_custom_command
para lograr esto junto con cmake -E copy_if_different...
. Para obtener información completa, ejecute
cmake --help-command add_custom_command
cmake -E
Entonces, en su caso, si tiene la siguiente estructura de directorios:
/CMakeLists.txt
/src
/libs/test.dll
y su objetivo de CMake al que se aplica el comando es MyTest
, entonces puede agregar lo siguiente a su 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
Si solo desea copiar todo el contenido del /libs/
directorio, utilice 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>)
Si necesita copiar diferentes archivos DLL dependiendo de la configuración (Versión, Depuración, por ejemplo), puede tenerlos en subdirectorios nombrados con la configuración correspondiente: /libs/Release
y /libs/Debug
. Luego debe inyectar el tipo de configuración en la ruta al dll en la add_custom_command
llamada, así:
add_custom_command(TARGET MyTest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${PROJECT_SOURCE_DIR}/libs/$<CONFIGURATION>"
$<TARGET_FILE_DIR:MyTest>)
Puse estas líneas en mi archivo CMakeLists.txt de nivel superior. Todas las bibliotecas y ejecutables compilados por CMake se colocarán en el nivel superior del directorio de compilación para que los ejecutables puedan encontrar las bibliotecas y sea fácil ejecutar todo.
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
Tenga en cuenta que esto no resuelve el problema del OP de copiar archivos binarios precompilados desde el directorio fuente del proyecto.
Tuve este problema hoy cuando intenté crear una compilación de mi programa para Windows. Y terminé investigando un poco yo mismo ya que todas estas respuestas no me satisfacían. Había tres cuestiones principales:
Quería que las compilaciones de depuración estuvieran vinculadas con las versiones de depuración de las bibliotecas y que las compilaciones de lanzamiento estuvieran vinculadas con las compilaciones de lanzamiento de las bibliotecas, respectivamente.
Además de eso, quería copiar las versiones correctas de los archivos DLL (depuración/liberación) en los directorios de salida.
Y quería lograr todo esto sin escribir guiones complejos y frágiles.
Después de explorar algunos manuales de CMake y algunos proyectos multiplataforma en github, encontré esta solución:
Declare su biblioteca como destino con el atributo "IMPORTADO", haga referencia a su depuración y publique archivos .lib y .dll.
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")
Vincula este objetivo con tu proyecto como siempre
target_link_libraries(YourProg sdl2 ...)
Realice un paso de compilación personalizado para copiar el archivo dll a su destino si se ha modificado de alguna manera desde la compilación anterior
add_custom_command ( TARGET YourProg POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:sdl2> $<TARGET_FILE_DIR:YourProg>
)
Para los usuarios de Windows, hay una nueva expresión generadora $<TARGET_RUNTIME_DLLS:tgt>
en CMake 3.21+ y pueden usar este fragmento oficial para copiar todas las DLL de las que depende un objetivo.
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
)
1. La forma más correcta: install(TARGET_RUNTIME_DLLS)
(CMake >= 3.21)
install(FILES $<TARGET_RUNTIME_DLLS:your_exe_here> TYPE BIN)
Para que esto funcione, los módulos CMake de sus dependencias deben estar bien escritos. En otras palabras, utilizan objetivos CMake 3 con todas sus propiedades de destino configuradas correctamente. Si configuraron todo correctamente, todas sus DLL se recopilarán e instalarán automáticamente junto con su archivo ejecutable. CMake coincidirá automáticamente con el tipo de DLL (por ejemplo, lanzamiento o depuración) que coincida con su archivo ejecutable de destino.
Aquí es hacia donde se dirigirá más CMake en el futuro, y la forma que usted debería preferir si tiene la opción.
Debería preferir install()
simplemente copiar los archivos directamente $CMAKE_RUNTIME_OUTPUT_DIRECTORY
porque install()
es la forma oficial autorizada por CMake de colocar las cosas en $CMAKE_RUNTIME_OUTPUT_DIRECTORY
. install()
Lleva un nombre tan extraño porque CMake es más que una herramienta de compilación. También es una herramienta generadora de instaladores. Esta funcionalidad de generación de instalador se llama CPack. A los ojos de CMake, $CMAKE_RUNTIME_OUTPUT_DIRECTORY
es solo el área de retención de CPack. Cuando crea install()
un archivo, le dice a CMake que debe considerarse un archivo de programa y que debe copiarse dondequiera que vaya el exe. Si no lo revisa install()
, CMake lo verá como un archivo aleatorio no reconocido.
2. La segunda forma más correcta: 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>)
La clave aquí es RUNTIME_DEPENDENCIES
.
Internamente, RUNTIME_DEPENDENCIES
Calls file(GET_RUNTIME_DEPENDENCIES)
, que escanea su binario ejecutable, se esfuerza por replicar exactamente cómo se vería la resolución de dependencia real y escribe todas las DLL mencionadas en el camino. Estos se devuelven a install()
.
Lo que esto significa es que esto no depende de que los módulos CMake de sus dependencias tengan sus propiedades de destino configuradas correctamente. Se escanea su binario ejecutable real. Todo será recogido.
3. La tercera forma más correcta: file(GET_RUNTIME_DEPENDENCIES)
(CMake >= 3.16)
file(GET_RUNTIME_DEPENDENCIES)
es lo que install(RUNTIME_DEPENDENCIES)
se llama bajo el capó, pero file(GET_RUNTIME_DEPENDENCIES)
está disponible en una versión de CMake anterior a install(RUNTIME_DEPENDENCIES)
. Todavía podemos hacer lo mismo en la versión anterior de CMake, solo que con más texto repetitivo.
La parte complicada es que file(GET_RUNTIME_DEPENDENCIES)
sólo se puede llamar en el momento de la instalación. Esto significa que necesitamos usar install(CODE)
para ejecutar un script que a su vez llame file(GET_RUNTIME_DEPENDENCIES)
. Para una implementación, consulte aquí .
4. Último recurso:install(DIRECTORY)
install(
DIRECTORY "${DIR_CONTAINING_YOUR_DLLS}"
TYPE BIN
FILES_MATCHING REGEX "[^\\\\/.]\\.[dD][lL][lL]$"
)
Para usarlo, coloque las DLL apropiadas para su compilación en formato $DIR_CONTAINING_YOUR_DLLS
.
El truco aquí es que, a diferencia de install(FILES)
, install(DIRECTORY)
no le importa qué archivos específicos haya en el directorio hasta el momento de la instalación. Eso significa que ahora tenemos todo el tiempo de configuración y compilación para obtener una lista de sus DLL y guardarlas $DIR_CONTAINING_YOUR_DLLS
. Siempre que los archivos DLL estén disponibles en $DIR_CONTAINING_YOUR_DLLS
el momento de la instalación, install(DIRECTORY)
los recogerá.
Si elige este método, será su responsabilidad hacer coincidir las DLL con su configuración de compilación. (Considere: estático versus dinámico, depuración versus lanzamiento, importación de versión de biblioteca versus versión de DLL, bibliotecas con subprocesos múltiples opcionales, olvidándose de eliminar las DLL que ya no necesita).
Si elige este método, es posible que desee automatizar la búsqueda y comparación de DLL utilizando algo como what vcpkg
's applocal.ps1 does
. (Hipotéticamente, debería ser posible volver a implementar lo que vcpkg
hace applocal.ps1
en CMake puro usando install(CODE)
, pero no tengo una implementación lista para publicar).
Consejo paravcpkg
Si usa vpckg
la VCPKG_APPLOCAL_DEPS
opción habilitada, vcpkg
localizará y copiará sus archivos DLL en su $CMAKE_RUNTIME_OUTPUT_DIRECTORY
archivo, pero sin pasar por install()
. Debes usar el install(DIRECTORY)
truco para que CMake los recoja.
(Internamente, vcpkg
utiliza dumpbin
, llvm-objdump
y objdump
para escanear su binario ejecutable para obtener estos nombres de archivo).