使用 CMake 将几个静态库合并为一个
Posted
技术标签:
【中文标题】使用 CMake 将几个静态库合并为一个【英文标题】:Combining several static libraries into one using CMake 【发布时间】:2016-06-20 13:55:26 【问题描述】:我有一个与 cmake 邮件列表中的 described 非常相似的问题,我们有一个依赖于许多静态库的项目(所有这些库都是从各个子模块中的源代码构建的,每个子模块都有自己的 CMakeLists.txt 描述构建过程每个库)我想组合成一个静态库以发布给消费者。我的库的依赖项可能会发生变化,我不想让开发人员进一步负担这些变化。简洁的解决方案是将所有库捆绑到一个库中。
有趣的是,target_link_libraries
命令在将目标设置为mylib
并像这样使用它时并没有结合所有的静态数据。 .
target_link_libraries(mylib a b c d)
但是,奇怪的是,如果我将 mylib
项目设为可执行项目的子模块,并且仅在***可执行文件 CMAkeLists.txt 中链接到 mylib
,则该库似乎确实被合并了。 IE。 mylib 是 27 MB,而不是当我将目标设置为仅构建 mylib
时的 3MB。
有一些解决方案描述了将库解包到目标文件中并重新组合(here 和 here),但是当 CMake 似乎完全能够如上例中所述自动合并库时,这似乎非常笨拙。是否有我缺少的魔术命令,或者推荐的制作发布库的优雅方式?
【问题讨论】:
但是您将如何处理所有这些包含文件和目录? @Drop 它们都被剔除或隐藏在mylib
的公共界面后面。 deps 应该对消费者不可见
如果您使用 gcc,并且不要求您的 makefile 独立于编译器,您可以尝试 --whole-archive
选项。
@KarstenKoop 需要同时是 Apple Clang 和 GCC
嗯@n.m。我希望让 CMake 以独立于平台的方式执行此操作,因为这是该工具的重点。我下面的答案有效,但由于其平台依赖性而很糟糕
【参考方案1】:
给出我能想到的最简单的工作示例:2 个类,a
和 b
,其中 a
取决于 b
。 .
啊.h
#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;
main.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
)
仍然接缝,好像应该有一个更整洁的方式。 .
【讨论】:
这不能回答问题。这个回复是关于组合几个目标文件,而问题是关于组合几个库。 @ctcchen:嗯,看起来CMakeLists.txt
将生成一个组合库libcombi.a
。但是我要学习的问题是:这真的需要自定义目标吗?没有更标准的机制吗?
@ctcchen 不,他从 a.cpp 和 b.cpp 创建了 2 个库“a”和“b”,然后将它们组合到库“c”中。
谢谢!在Windows上对应的方法是添加自定义命令“lib.exe /OUT:combi.lib a.lib b.lib
”(很高兴知道CMake没有提供任何帮助方法,我们必须单独手动支持每个平台)【参考方案2】:
您可以使用此功能加入任意数量的库。
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 的好处。这样,库(及其依赖项)仅在需要时而不是每次都构建。 缺点是打印了虚拟文件的生成。
【讨论】:
虽然这对于组合目标文件很有用,但我认为可以通过添加target_link_libraries
调用来获取传递依赖项来改进它。
您能详细说明一下吗?缺少哪些依赖项?
不适用于 macos。 -M
在 macos 上的 ar
命令中不存在 :(
这应该是公认的答案,谢谢!可以轻松修改上述内容以在 MacOS 上使用libtool
,使其适用于所有类 Unix 操作系统。【参考方案3】:
这并不能直接回答问题,但我发现它很有用:
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
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
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】:如果您尝试合并的库来自第三方,那么(按照 learnvst 示例)此代码会处理可能的 .o 文件替换(例如,如果 liba 和 libb 都具有文件名 zzz.o)
## 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 库,这是一个很好的合并它们的机制。
【讨论】:
您几乎肯定应该使用PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
而不是CMAKE_...
变体。将其他 CMake 项目导入“超级构建”变得越来越流行,而上面的答案在这种情况下会中断。【参考方案5】:
https://cmake.org/pipermail/cmake/2018-September/068263.html
CMake 似乎不支持。
【讨论】:
【参考方案6】:做到这一点的正确方法不是通过组合静态库来捏造,而是向用户提供CMake Config files,其中包含所有必要的位,这些位以应该链接的方式链接所有内容。 CMake 可用于生成这些文件,或生成 pkg-config 文件,可能还有其他格式的“告诉我如何链接这个和那个库”工具。
有些用户可能会对您链接到的库感兴趣,他们甚至可能在链接您的库时使用自己的相同库的副本/版本。正是在这种情况下,您的解决方案很糟糕,并阻止用户集成多段代码,因为您决定他们必须绝对使用您的该依赖项的副本(当您将静态库依赖项组合到一个静态库中时,您会这样做) .
【讨论】:
以上是关于使用 CMake 将几个静态库合并为一个的主要内容,如果未能解决你的问题,请参考以下文章