¿Cómo copiar archivos DLL en la misma carpeta que el ejecutable usando CMake?

Resuelto Mat asked hace 12 años • 12 respuestas

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.txtmodo 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?

Mat avatar May 20 '12 15:05 Mat
Aceptado

Lo usaría add_custom_commandpara 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/Releasey /libs/Debug. Luego debe inyectar el tipo de configuración en la ruta al dll en la add_custom_commandllamada, así:

add_custom_command(TARGET MyTest POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${PROJECT_SOURCE_DIR}/libs/$<CONFIGURATION>"
        $<TARGET_FILE_DIR:MyTest>)
Fraser avatar May 20 '2012 10:05 Fraser

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.

David Grayson avatar Dec 23 '2015 23:12 David Grayson

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>
)
Minor Threat avatar Jan 04 '2020 19:01 Minor Threat

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
  )
Gnimuc avatar Oct 27 '2021 09:10 Gnimuc

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_DIRECTORYporque 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_DIRECTORYes 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_DEPENDENCIESCalls 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_DLLSel 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 vcpkghace applocal.ps1en CMake puro usando install(CODE), pero no tengo una implementación lista para publicar).


Consejo paravcpkg

Si usa vpckgla VCPKG_APPLOCAL_DEPSopción habilitada, vcpkglocalizará y copiará sus archivos DLL en su $CMAKE_RUNTIME_OUTPUT_DIRECTORYarchivo, pero sin pasar por install(). Debes usar el install(DIRECTORY)truco para que CMake los recoja.

(Internamente, vcpkgutiliza dumpbin, llvm-objdumpy objdumppara escanear su binario ejecutable para obtener estos nombres de archivo).

midrare avatar Jan 10 '2023 03:01 midrare