Skip to content

Commit

Permalink
[feat] Compatibility: CMake < 3.21 can also copy dlls
Browse files Browse the repository at this point in the history
  • Loading branch information
zchrissirhcz committed Nov 23, 2024
1 parent 3671f9d commit 7cb05b7
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 10 deletions.
122 changes: 115 additions & 7 deletions rocbuild.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Author: Zhuo Zhang <[email protected]>
# Homepage: https://github.com/zchrissirhcz/rocbuild

cmake_minimum_required(VERSION 3.21)
cmake_minimum_required(VERSION 3.10)

# CMake 3.10: include_guard()
# CMake 3.21: $<TARGET_RUNTIME_DLLS:tgt>
Expand Down Expand Up @@ -47,10 +47,118 @@ macro(rocbuild_enable_ninja_colorful_output)
endmacro()


function(rocbuild_copy_dll target)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:${target}> $<TARGET_FILE_DIR:${target}>
COMMAND_EXPAND_LISTS
# Transitively list all link libraries of a target (recursive call)
# Modified from https://github.com/libigl/libigl/blob/main/cmake/igl/igl_copy_dll.cmake, GPL-3.0 / MPL-2.0
function(rocbuild_get_dependencies_recursive OUTPUT_VARIABLE TARGET)
get_target_property(_aliased ${TARGET} ALIASED_TARGET)
if(_aliased)
set(TARGET ${_aliased})
endif()

get_target_property(_IMPORTED ${TARGET} IMPORTED)
get_target_property(_TYPE ${TARGET} TYPE)
if(_IMPORTED OR (${_TYPE} STREQUAL "INTERFACE_LIBRARY"))
get_target_property(TARGET_DEPENDENCIES ${TARGET} INTERFACE_LINK_LIBRARIES)
else()
get_target_property(TARGET_DEPENDENCIES ${TARGET} LINK_LIBRARIES)
endif()

set(VISITED_TARGETS ${${OUTPUT_VARIABLE}})
foreach(DEPENDENCY IN ITEMS ${TARGET_DEPENDENCIES})
if(TARGET ${DEPENDENCY})
get_target_property(_aliased ${DEPENDENCY} ALIASED_TARGET)
if(_aliased)
set(DEPENDENCY ${_aliased})
endif()

if(NOT (DEPENDENCY IN_LIST VISITED_TARGETS))
list(APPEND VISITED_TARGETS ${DEPENDENCY})
rocbuild_get_dependencies_recursive(VISITED_TARGETS ${DEPENDENCY})
endif()
endif()
endforeach()
set(${OUTPUT_VARIABLE} ${VISITED_TARGETS} PARENT_SCOPE)
endfunction()

# Transitively list all link libraries of a target
function(rocbuild_get_dependencies OUTPUT_VARIABLE TARGET)
set(DISCOVERED_TARGETS "")
rocbuild_get_dependencies_recursive(DISCOVERED_TARGETS ${TARGET})
set(${OUTPUT_VARIABLE} ${DISCOVERED_TARGETS} PARENT_SCOPE)
endfunction()

# Copy .dll dependencies to a target executable's folder. This function must be called *after* all the CMake
# dependencies of the executable target have been defined, otherwise some .dlls might not be copied to the target
# folder.
function(rocbuild_copy_dlls target)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.21")
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:${target}> $<TARGET_FILE_DIR:${target}>
COMMAND_EXPAND_LISTS
)
return()
endif()

# Sanity checks
if(CMAKE_CROSSCOMPILING OR (NOT WIN32))
return()
endif()

if(NOT TARGET ${target})
message(STATUS "rocbuild_copy_dlls() was called with a non-target: ${target}")
return()
endif()

# Sanity checks
get_target_property(TYPE ${target} TYPE)
if(NOT ${TYPE} STREQUAL "EXECUTABLE")
message(FATAL_ERROR "rocbuild_copy_dlls() was called on a non-executable target: ${target}")
endif()

# set the name of file to be written
if(DEFINED CMAKE_CONFIGURATION_TYPES)
set(COPY_SCRIPT "${CMAKE_BINARY_DIR}/rocbuild_copy_dlls_${target}_$<CONFIG>.cmake")
else()
set(COPY_SCRIPT "${CMAKE_BINARY_DIR}/rocbuild_copy_dlls_${target}.cmake")
endif()

add_custom_command(
TARGET ${target}
PRE_LINK
COMMAND ${CMAKE_COMMAND} -E touch "${COPY_SCRIPT}"
COMMAND ${CMAKE_COMMAND} -P "${COPY_SCRIPT}"
COMMENT "Copying dlls for target ${target}"
)

# Retrieve all target dependencies
rocbuild_get_dependencies(TARGET_DEPENDENCIES ${target})

set(DEPENDENCY_FILES "")
foreach(DEPENDENCY IN LISTS TARGET_DEPENDENCIES)
get_target_property(TYPE ${DEPENDENCY} TYPE)
if(NOT (${TYPE} STREQUAL "SHARED_LIBRARY" OR ${TYPE} STREQUAL "MODULE_LIBRARY"))
continue()
endif()
string(APPEND DEPENDENCY_FILES " $<TARGET_FILE:${DEPENDENCY}> # ${DEPENDENCY}\n")
endforeach()

set(COPY_SCRIPT_CONTENT "")
string(APPEND COPY_SCRIPT_CONTENT
"set(dependency_files \n${DEPENDENCY_FILES})\n\n"
"list(REMOVE_DUPLICATES dependency_files)\n\n"
"foreach(file IN ITEMS \${dependency_files})\n"
" if(EXISTS \"\${file}\")\n "
"execute_process(COMMAND \${CMAKE_COMMAND} -E copy_if_different "
"\"\${file}\" \"$<TARGET_FILE_DIR:${target}>/\")\n"
" endif()\n"
)
string(APPEND COPY_SCRIPT_CONTENT "endforeach()\n")

# Finally generate one script for each configuration supported by this generator
message(STATUS "Populating copy rules for target: ${target}")
file(GENERATE
OUTPUT "${COPY_SCRIPT}"
CONTENT "${COPY_SCRIPT_CONTENT}"
)
endfunction()

Expand Down Expand Up @@ -80,9 +188,9 @@ function(rocbuild_copy_opencv_videoio_plugin_dlls target)
endif()

if(DEFINED CMAKE_CONFIGURATION_TYPES)
set(COPY_SCRIPT "${CMAKE_BINARY_DIR}/rocbuild_copy_opencv_videoio_plugin_dlls_for_${target}_$<CONFIG>.cmake")
set(COPY_SCRIPT "${CMAKE_BINARY_DIR}/rocbuild_copy_opencv_videoio_plugin_dlls_${target}_$<CONFIG>.cmake")
else()
set(COPY_SCRIPT "${CMAKE_BINARY_DIR}/rocbuild_copy_opencv_videoio_plugin_dlls_for_${target}.cmake")
set(COPY_SCRIPT "${CMAKE_BINARY_DIR}/rocbuild_copy_opencv_videoio_plugin_dlls_${target}.cmake")
endif()
set(COPY_SCRIPT_CONTENT "")

Expand Down
24 changes: 22 additions & 2 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def test_artifact_path(self):
self.assertTrue(os.path.exists('build/artifacts_path/Release/subfoo_static.lib'))
self.assertTrue(os.path.exists('build/artifacts_path/Release/subfoo_shared.dll'))
self.assertTrue(os.path.exists('build/artifacts_path/Release/subhello.exe'))
shutil.rmtree('build/artifacts_path')
elif os_name == 'linux':
self.check_generate('artifacts_path')
self.check_build('artifacts_path')
Expand All @@ -91,6 +92,7 @@ def test_artifact_path(self):
self.assertTrue(os.path.exists('build/artifacts_path/libsubfoo_static.a'))
self.assertTrue(os.path.exists('build/artifacts_path/libsubfoo_shared.so'))
self.assertTrue(os.path.exists('build/artifacts_path/subhello'))
shutil.rmtree('build/artifacts_path')
elif os_name == 'mac':
self.check_generate('artifacts_path')
self.check_build('artifacts_path')
Expand All @@ -100,8 +102,7 @@ def test_artifact_path(self):
self.assertTrue(os.path.exists('build/artifacts_path/libsubfoo_static.a'))
self.assertTrue(os.path.exists('build/artifacts_path/libsubfoo_shared.dylib'))
self.assertTrue(os.path.exists('build/artifacts_path/subhello'))

shutil.rmtree('build/artifacts_path')
shutil.rmtree('build/artifacts_path')

def test_debug_postfix(self):
if os_name == 'windows':
Expand Down Expand Up @@ -202,5 +203,24 @@ def test_hide_symbols(self):
self.assertTrue(lines[1].endswith(' T _bar_internal'))
shutil.rmtree('build/hide_symbols')

def test_copy_dlls(self):
if os_name == 'windows':
self.check_generate('copy_dlls', args='-DCOPY_DLLS=0')
self.check_build('copy_dlls', args='--config Release')
items = os.listdir('build/copy_dlls/test/Release')
self.assertEqual(len(items), 1, items)
self.assertTrue(items[0] == 'test.exe')
shutil.rmtree('build/copy_dlls')

self.check_generate('copy_dlls', args='-DCOPY_DLLS=1')
self.check_build('copy_dlls', args='--config Release')
items = os.listdir('build/copy_dlls/test/Release')
self.assertEqual(len(items), 4)
self.assertTrue(items[0] == 'bar.dll')
self.assertTrue(items[1] == 'baz.dll')
self.assertTrue(items[2] == 'foo.dll')
self.assertTrue(items[3] == 'test.exe')
shutil.rmtree('build/copy_dlls')

if __name__ == "__main__":
unittest.main()
28 changes: 28 additions & 0 deletions tests/copy_dlls/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.10)
project(test_copy_dlls)

include(../../rocbuild.cmake)

add_library(foo SHARED ../src/foo.c)
target_compile_definitions(foo PRIVATE FOO_EXPORTS)
target_include_directories(foo PUBLIC ../src)
set_target_properties(foo PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/foo)

add_library(bar SHARED ../src/bar.c ../src/bar_internal.c)
target_compile_definitions(bar PRIVATE BAR_EXPORTS)
target_include_directories(bar PUBLIC ../src)
set_target_properties(bar PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bar)

add_library(baz SHARED ../src/baz.c)
target_compile_definitions(baz PRIVATE BAZ_EXPORTS)
target_include_directories(baz PUBLIC ../src)
set_target_properties(baz PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/baz)
target_link_libraries(baz PRIVATE foo)

add_executable(test test.c)
target_link_libraries(test PRIVATE bar baz)
set_target_properties(test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test)

if(COPY_DLLS)
rocbuild_copy_dlls(test)
endif()
7 changes: 7 additions & 0 deletions tests/copy_dlls/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "bar.h"
#include "baz.h"

int main()
{
return bar() + baz();
}
7 changes: 7 additions & 0 deletions tests/src/baz.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "baz.h"
#include "foo.h"

int baz()
{
return 2 + foo();
}
25 changes: 25 additions & 0 deletions tests/src/baz.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#ifdef BAZ_EXPORTS
# if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)
# define BAZ_API __declspec(dllexport)
# elif defined(__GNUC__) && __GNUC__ >= 4
# define BAZ_API __attribute__ ((visibility ("default")))
# endif
#else
# if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)
# define BAZ_API __declspec(dllimport)
# else
# define BAZ_API
# endif
#endif

#ifdef __cplusplus
extern "C" {
#endif

BAZ_API int baz();

#ifdef __cplusplus
}
#endif
16 changes: 15 additions & 1 deletion tests/src/foo.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
#pragma once

#ifdef FOO_EXPORTS
# if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)
# define FOO_API __declspec(dllexport)
# elif defined(__GNUC__) && __GNUC__ >= 4
# define FOO_API __attribute__ ((visibility ("default")))
# endif
#else
# if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)
# define FOO_API __declspec(dllimport)
# else
# define FOO_API
# endif
#endif

#ifdef __cplusplus
extern "C" {
#endif

int foo();
FOO_API int foo();

#ifdef __cplusplus
}
Expand Down

0 comments on commit 7cb05b7

Please sign in to comment.