使用 CMake 将多个静态库合并为一个

问题描述 投票:0回答:8

我有一个与 cmake 邮件列表上的问题非常相似的问题,其中我们有一个依赖于许多静态库的项目(所有静态库都是从各个子模块中的源代码构建的,每个子模块都有自己的 CMakeLists.txt 描述每个库的构建过程) )我想将其合并到一个静态库中以发布给消费者。我的库的依赖项可能会发生变化,我不想让这些变化给开发人员带来进一步的负担。巧妙的解决方案是将所有库捆绑到一个库中。 有趣的是,当将目标设置为

target_link_libraries

并像这样使用它时,

mylib
命令不会组合所有静态数据。 。
target_link_libraries(mylib a b c d)

但是,奇怪的是,如果我将 
mylib

项目作为可执行项目的子模块,并且仅链接到顶级可执行文件 CMAkeLists.txt 中的

mylib
,则该库似乎确实被合并了。 IE。 mylib 是 27 MB,而不是当我将目标设置为仅构建时的 3MB
mylib
有一些解决方案描述了将库解包到目标文件中并重新组合(

here

here),但是当 CMake 似乎完全能够自动合并库(如上面示例中所述)时,这似乎非常笨拙。是否有我缺少的神奇命令,或者推荐的制作发布库的优雅方法?

c++ c cmake static-libraries
8个回答
20
投票
a

b
,其中
a
取决于
b
。 .
啊啊

#ifndef A_H #define A_H class aclass { public: int method(int x, int y); }; #endif

a.cpp

#include "a.h" #include "b.h" int aclass::method(int x, int y) { bclass b; return x * b.method(x,y); }

b.h

#ifndef B_H #define B_H class bclass { public: int method(int x, int y); }; #endif

b.cpp

#include "b.h" int bclass::method(int x, int y) { return x+y; }

主.cpp

#include "a.h" #include <iostream> int main() { aclass a; std::cout << a.method(3,4) << std::endl; return 0; }

可以将它们编译成单独的静态库,然后使用自定义目标组合静态库。

cmake_minimum_required(VERSION 2.8.7) add_library(b b.cpp b.h) add_library(a a.cpp a.h) add_executable(main main.cpp) set(C_LIB ${CMAKE_BINARY_DIR}/libcombi.a) add_custom_target(combined COMMAND ar -x $<TARGET_FILE:a> COMMAND ar -x $<TARGET_FILE:b> COMMAND ar -qcs ${C_LIB} *.o WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS a b ) add_library(c STATIC IMPORTED GLOBAL) add_dependencies(c combined) set_target_properties(c PROPERTIES IMPORTED_LOCATION ${C_LIB} ) target_link_libraries(main c)

使用 Apple 的 
libtool

版本的自定义目标也可以正常工作。 。 .

add_custom_target(combined
        COMMAND libtool -static -o ${C_LIB} $<TARGET_FILE:a> $<TARGET_FILE:b>
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        DEPENDS a b
        )

仍然有接缝,好像应该有一个更简洁的方法。 .


12
投票

function(combine_archives output_archive list_of_input_archives) set(mri_file ${TEMP_DIR}/${output_archive}.mri) set(FULL_OUTPUT_PATH ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/lib${output_archive}.a) file(WRITE ${mri_file} "create ${FULL_OUTPUT_PATH}\n") FOREACH(in_archive ${list_of_input_archives}) file(APPEND ${mri_file} "addlib ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/lib${in_archive}.a\n") ENDFOREACH() file(APPEND ${mri_file} "save\n") file(APPEND ${mri_file} "end\n") set(output_archive_dummy_file ${TEMP_DIR}/${output_archive}.dummy.cpp) add_custom_command(OUTPUT ${output_archive_dummy_file} COMMAND touch ${output_archive_dummy_file} DEPENDS ${list_of_input_archives}) add_library(${output_archive} STATIC ${output_archive_dummy_file}) add_custom_command(TARGET ${output_archive} POST_BUILD COMMAND ar -M < ${mri_file}) endfunction(combine_archives)

它具有使用 add_custom_command 而不是 add_custom_target 的好处。这样,库(及其依赖项)仅在需要时构建,而不是每次都构建。
缺点是打印虚拟文件的生成。


9
投票

https://cristianadam.eu/20190501/bundling-together-static-libraries-with-cmake/

基本,定义一个 CMake 函数,该函数将收集目标所需的所有静态库并将它们组合成一个静态库:

add_library(awesome_lib STATIC ...); bundle_static_library(awesome_lib awesome_lib_bundled)

这是实际功能的复制和粘贴:

function(bundle_static_library tgt_name bundled_tgt_name) list(APPEND static_libs ${tgt_name}) function(_recursively_collect_dependencies input_target) set(_input_link_libraries LINK_LIBRARIES) get_target_property(_input_type ${input_target} TYPE) if (${_input_type} STREQUAL "INTERFACE_LIBRARY") set(_input_link_libraries INTERFACE_LINK_LIBRARIES) endif() get_target_property(public_dependencies ${input_target} ${_input_link_libraries}) foreach(dependency IN LISTS public_dependencies) if(TARGET ${dependency}) get_target_property(alias ${dependency} ALIASED_TARGET) if (TARGET ${alias}) set(dependency ${alias}) endif() get_target_property(_type ${dependency} TYPE) if (${_type} STREQUAL "STATIC_LIBRARY") list(APPEND static_libs ${dependency}) endif() get_property(library_already_added GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency}) if (NOT library_already_added) set_property(GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency} ON) _recursively_collect_dependencies(${dependency}) endif() endif() endforeach() set(static_libs ${static_libs} PARENT_SCOPE) endfunction() _recursively_collect_dependencies(${tgt_name}) list(REMOVE_DUPLICATES static_libs) set(bundled_tgt_full_name ${CMAKE_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${bundled_tgt_name}${CMAKE_STATIC_LIBRARY_SUFFIX}) if (CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|GNU)$") file(WRITE ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "CREATE ${bundled_tgt_full_name}\n" ) foreach(tgt IN LISTS static_libs) file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "ADDLIB $<TARGET_FILE:${tgt}>\n") endforeach() file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "SAVE\n") file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "END\n") file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar INPUT ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in) set(ar_tool ${CMAKE_AR}) if (CMAKE_INTERPROCEDURAL_OPTIMIZATION) set(ar_tool ${CMAKE_CXX_COMPILER_AR}) endif() add_custom_command( COMMAND ${ar_tool} -M < ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar DEPENDS ${static_libs} OUTPUT ${bundled_tgt_full_name} COMMENT "Bundling ${bundled_tgt_name}" VERBATIM) elseif(MSVC) find_program(lib_tool lib) foreach(tgt IN LISTS static_libs) list(APPEND static_libs_full_names $<TARGET_FILE:${tgt}>) endforeach() add_custom_command( COMMAND ${lib_tool} /NOLOGO /OUT:${bundled_tgt_full_name} ${static_libs_full_names} DEPENDS ${static_libs} OUTPUT ${bundled_tgt_full_name} COMMENT "Bundling ${bundled_tgt_name}" VERBATIM) else() message(FATAL_ERROR "Unknown bundle scenario!") endif() add_custom_target(bundling_target ALL DEPENDS ${bundled_tgt_full_name}) add_dependencies(bundling_target ${tgt_name}) add_library(${bundled_tgt_name} STATIC IMPORTED) set_target_properties(${bundled_tgt_name} PROPERTIES IMPORTED_LOCATION ${bundled_tgt_full_name} INTERFACE_INCLUDE_DIRECTORIES $<TARGET_PROPERTY:${tgt_name},INTERFACE_INCLUDE_DIRECTORIES>) add_dependencies(${bundled_tgt_name} bundling_target) endfunction()



4
投票

## Create static library (by joining the new objects and the dependencies) ADD_LIBRARY("${PROJECT_NAME}-static" STATIC ${SOURCES}) add_custom_command(OUTPUT lib${PROJECT_NAME}.a COMMAND rm ARGS -f *.o COMMAND ar ARGS -x ${CMAKE_BINARY_DIR}/lib${PROJECT_NAME}-static.a COMMAND rename ARGS 's/^/lib${PROJECT_NAME}-static./g' *.o COMMAND rename ARGS 's/\\.o/.otmp/g' *.o COMMAND ar ARGS -x ${CMAKE_SOURCE_DIR}/lib/a/liba.a COMMAND rename ARGS 's/^/liba./g' *.o COMMAND rename ARGS 's/\\.o/.otmp/g' *.o COMMAND ar ARGS -x ${CMAKE_SOURCE_DIR}/lib/b/libb.a COMMAND rename ARGS 's/^/libb./g' *.o COMMAND rename ARGS 's/\\.o/.otmp/g' *.o COMMAND rename ARGS 's/\\.otmp/.o/g' *.otmp COMMAND ar ARGS -r lib${PROJECT_NAME}.a *.o COMMAND rm ARGS -f *.o DEPENDS "${PROJECT_NAME}-static") add_custom_target(${PROJECT_NAME} ALL DEPENDS lib${PROJECT_NAME}.a)

否则,如果这些库是您的,您应该使用 CMake OBJECT 库,这是一种很好的合并它们的机制。


1
投票
https://cmake.org/pipermail/cmake/2018-September/068263.html

CMake 似乎不支持这一点。


1
投票
CMake 配置文件

,其中包含以应有的方式链接所有内容的所有必要位。 CMake 可以用来生成这些文件,或者生成 pkg-config 文件,可能还有其他格式的“告诉我如何链接这个和那个库”工具。 一些用户可能会对您链接到的库感兴趣,他们甚至可能在链接您的库时使用相同库的自己的副本/版本。正是在这种情况下,您的解决方案很糟糕,并且阻止用户集成多段代码,因为您决定他们必须绝对使用该依赖项的副本(这就是将静态库依赖项合并到一个静态库中时所做的事情) .


1
投票
answer

的解决方案,但它支持从给定目标检索输入库路径,并且还支持多配置生成器,如 Ninja Multi-Config: # Combine a list of library targets into a single output archive # Usage: # combine_archives(output_archive_name input_target1 input_target2...) function(combine_archives output_archive) # Generate the MRI file for ar to consume. # Note that a separate file must be generated for each build configuration. set(mri_file ${CMAKE_BINARY_DIR}/$<CONFIG>/${output_archive}.mri) set(mri_file_content "create ${CMAKE_BINARY_DIR}/$<CONFIG>/lib${output_archive}.a\n") FOREACH(in_target ${ARGN}) string(APPEND mri_file_content "addlib $<TARGET_FILE:${in_target}>\n") ENDFOREACH() string(APPEND mri_file_content "save\n") string(APPEND mri_file_content "end\n") file(GENERATE OUTPUT ${mri_file} CONTENT ${mri_file_content} ) # Create a dummy file for the combined library # This dummy file depends on all the input targets so that the combined library is regenerated if any of them changes. set(output_archive_dummy_file ${CMAKE_BINARY_DIR}/${output_archive}.dummy.cpp) add_custom_command(OUTPUT ${output_archive_dummy_file} COMMAND touch ${output_archive_dummy_file} DEPENDS ${ARGN}) add_library(${output_archive} STATIC ${output_archive_dummy_file}) # Add a custom command to combine the archives after the static library is "built". add_custom_command(TARGET ${output_archive} POST_BUILD COMMAND ar -M < ${mri_file} COMMENT "Combining static libraries for ${output_archive}" ) endfunction(combine_archives)

libTargetC.a

libTargetA.a
生成
libTargetB.a
的用法类似于:
add_library(TargetA STATIC ...)
add_library(TargetB STATIC ...)
combine_archives(TargetC TargetA TargetB)



0
投票
${CMAKE_AR}

而不是直接使用 ar 命令(并假设它可用。)。即使在交叉编译场景中,CMake 的变量也已配置为指向正确的存档工具。

    

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.