From 7cb05b76c5e971cf9877572a02102b4a5e93c3bc Mon Sep 17 00:00:00 2001 From: Zhuo Zhang Date: Sun, 24 Nov 2024 00:30:31 +0800 Subject: [PATCH] [feat] Compatibility: CMake < 3.21 can also copy dlls --- rocbuild.cmake | 122 +++++++++++++++++++++++++++++++-- test.py | 24 ++++++- tests/copy_dlls/CMakeLists.txt | 28 ++++++++ tests/copy_dlls/test.c | 7 ++ tests/src/baz.c | 7 ++ tests/src/baz.h | 25 +++++++ tests/src/foo.h | 16 ++++- 7 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 tests/copy_dlls/CMakeLists.txt create mode 100644 tests/copy_dlls/test.c create mode 100644 tests/src/baz.c create mode 100644 tests/src/baz.h diff --git a/rocbuild.cmake b/rocbuild.cmake index 33aeaaf..0c4f581 100644 --- a/rocbuild.cmake +++ b/rocbuild.cmake @@ -1,7 +1,7 @@ # Author: Zhuo Zhang # 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: $ @@ -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 $ $ - 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 $ $ + 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}_$.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 " $ # ${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}\" \"$/\")\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() @@ -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}_$.cmake") + set(COPY_SCRIPT "${CMAKE_BINARY_DIR}/rocbuild_copy_opencv_videoio_plugin_dlls_${target}_$.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 "") diff --git a/test.py b/test.py index 3f296b8..14653a9 100644 --- a/test.py +++ b/test.py @@ -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') @@ -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') @@ -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': @@ -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() \ No newline at end of file diff --git a/tests/copy_dlls/CMakeLists.txt b/tests/copy_dlls/CMakeLists.txt new file mode 100644 index 0000000..1076dbc --- /dev/null +++ b/tests/copy_dlls/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/tests/copy_dlls/test.c b/tests/copy_dlls/test.c new file mode 100644 index 0000000..cedc25b --- /dev/null +++ b/tests/copy_dlls/test.c @@ -0,0 +1,7 @@ +#include "bar.h" +#include "baz.h" + +int main() +{ + return bar() + baz(); +} \ No newline at end of file diff --git a/tests/src/baz.c b/tests/src/baz.c new file mode 100644 index 0000000..a22196c --- /dev/null +++ b/tests/src/baz.c @@ -0,0 +1,7 @@ +#include "baz.h" +#include "foo.h" + +int baz() +{ + return 2 + foo(); +} \ No newline at end of file diff --git a/tests/src/baz.h b/tests/src/baz.h new file mode 100644 index 0000000..20e66e8 --- /dev/null +++ b/tests/src/baz.h @@ -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 \ No newline at end of file diff --git a/tests/src/foo.h b/tests/src/foo.h index 36099c4..95fe120 100644 --- a/tests/src/foo.h +++ b/tests/src/foo.h @@ -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 }