Skip to content

Instantly share code, notes, and snippets.

@micahsnyder
Last active March 25, 2024 12:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save micahsnyder/5d98ac8548b429309ec5a35bca9366da to your computer and use it in GitHub Desktop.
Save micahsnyder/5d98ac8548b429309ec5a35bca9366da to your computer and use it in GitHub Desktop.
An example using CMake/CTest to collect runtime DLL dependencies prior to testing
lib/lib.h:
lib/lib_private.h:
lib/lib.c:
#
# Assume some C library that makes use of OpenSSL
#
lib/CMakeLists.txt:
#
# Test executables
#
add_library(library_obj OBJECT)
target_sources(library_obj
PRIVATE
lib.c
lib_private.h
PUBLIC
lib.h)
target_include_directories(library_obj
PRIVATE ${CMAKE_BINARY_DIR} # For access to the generated config.h
)
add_library(library SHARED)
target_sources(library PUBLIC lib.h)
target_link_libraries(library
PUBLIC
library_obj
OpenSSL::SSL
OpenSSL::Crypto)
if(WIN32)
install( TARGETS library DESTINATION ${CMAKE_INSTALL_PREFIX} )
# Also install shared library (DLL) dependencies
install( CODE [[
file(GET_RUNTIME_DEPENDENCIES
LIBRARIES
$<TARGET_FILE:library>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:OpenSSL::SSL>
$<TARGET_FILE_DIR:OpenSSL::Crypto>
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
file(INSTALL
DESTINATION "${CMAKE_INSTALL_PREFIX}"
TYPE SHARED_LIBRARY
FOLLOW_SYMLINK_CHAIN
FILES "${_file}"
)
endif()
endforeach()
#message("UNRESOLVED_DEPENDENCIES_VAR: ${_u_deps}")
]] )
else()
install( TARGETS library DESTINATION ${CMAKE_INSTALL_LIBDIR} )
endif()
test/CMakeLists.txt:
#
# Test executables
#
add_executable(check_program)
target_sources(check_program
PRIVATE
check_program.h
check_program.c)
target_link_libraries(check_program
PRIVATE
libcheck::check
OpenSSL::SSL
OpenSSL::Crypto)
target_include_directories(check_program PRIVATE
${PROJECT_SOURCE_DIR}/lib # For access to private lib headers (these are unit tests, after all)
${CMAKE_BINARY_DIR} # For access to the generated config.h
)
target_compile_definitions(check_program PUBLIC OBJDIR="${CMAKE_CURRENT_BINARY_DIR}" BUILDDIR="${CMAKE_CURRENT_BINARY_DIR}" SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
if(WIN32)
#
# Generate GetLibs-$<CONFIG>.ctest which will collect check_program DLL dependencies
#
set(GEN_SCRIPT [[
file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES
$<TARGET_FILE:check_program>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:libcheck::check>
$<TARGET_FILE_DIR:OpenSSL::SSL>
$<TARGET_FILE_DIR:OpenSSL::Crypto>
CONFLICTING_DEPENDENCIES_PREFIX CTEST_CONFLICTING_DEPENDENCIES
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
message("DEPENDENCY: ${_file}")
file(COPY ${_file} DESTINATION $<TARGET_FILE_DIR:check_program>)
endif()
endforeach()
#message("UNRESOLVED_DEPENDENCIES_VAR: ${_u_deps}")
# Collect our libs
file(COPY $<TARGET_FILE:library> DESTINATION $<TARGET_FILE_DIR:check_program>)
# Collect our apps
# No apps in this example
#file(COPY $<TARGET_FILE:myapp> DESTINATION $<TARGET_FILE_DIR:check_program>)
]])
file(GENERATE OUTPUT GetLibs-$<CONFIG>.ctest CONTENT ${GEN_SCRIPT})
endif()
#
# The Tests, run with `ctest`
#
add_test(NAME library_tests COMMAND check_program)
set_property(TEST library_tests PROPERTY ENVIRONMENT CK_FORK=no)
if(WIN32)
set_directory_properties(PROPERTIES TEST_INCLUDE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Run-GetLibs.ctest)
endif()
tests/Run-GetLibs.ctest:
include(copy_runtime_dependencies-${CTEST_CONFIGURATION_TYPE}.ctest)
@vladd12
Copy link

vladd12 commented Mar 22, 2024

What about using tests_properties instead copying dlls? Something like that:

set_tests_properties(
  some_tests PROPERTIES ENVIRONMENT
  "PATH=$<TARGET_FILE_DIR:lib_a>\;$<TARGET_FILE_DIR:lib_b>\;"
)

@micahsnyder
Copy link
Author

That could be better.

I use the same GET_RUNTIME_DEPENDENCIES technique to copy dependency DLL's at install time to build install packages that have everything you need.

It's been a while since I wrote this. I suspect I didn't consider setting PATH as you suggest, and just copy-pasted something that I knew worked.

@micahsnyder
Copy link
Author

Here is that mean for installing the external DLL dependencies: https://github.com/Cisco-Talos/clamav/blob/main/libclamav/CMakeLists.txt#L472-L504

@vladd12
Copy link

vladd12 commented Mar 25, 2024

Well, it's just a long time ago, I understand 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment