CMake 静态库合并问题
一般我们使用 cmake 生成静态库或者动态库时,它们生成的库都仅包含工程内部的文件,当引用了外部的静态库时,均是以链接形式存在在 cmake 文件中。当我们发布(也就是 install)一个静态库时,这个静态库并不是独立的,如果你想使用它,仍需要查找齐全它所依赖的库。
简单一点说就是,我们编写的静态库 A,依赖了静态库 B、C,直接发布的话,在使用该库时会提示没有 B、C 库的符号。为了方便用户使用我们的库,当有必要时,就需要直接将 ABC 库合并起来,并发布成一个无依赖的库 D,这个过程称为合并静态库。
原理
合并静态库需要使用特殊的工具,msvc 提供 lib.exe
,gcc 提供 ar
,Macos 提供 libtool
。下面以 ar
的使用过程为例。
首先一个我们需要关于静态库的基本知识, 静态库是源文件编译后生成的 .obj/.o
文件的集合。
使用如下命令可以将静态库拆分开,可以看到,静态库包含一个 obj 文件
> ar x ./libbirl4th_basic_lib.a
> ls
controller.cpp.obj libbirl4th_basic_lib.a
将 obj 文件合并为一个静态库
λ ar rcs merge.a *.obj
λ ls
controller.cpp.obj libbirl4th_basic_lib.a merge.a
命令含义:
x
:拆解静态库文件为其包含的内容r
:替换或添加文件到归档文件中。c
:创建归档文件,如果它不存在。s
:相当于对结果执行一次ranlib
,为静态库的内容添加索引,提高访问效率T or --thin
:对归档文件进行瘦身、压缩。
例如我们有一些静态库文件,那么可以采用如下命令行编译,即可将 ABC 静态库打包到 D 中。
ar crs D.a A.a B.a C.a
使用 T or --thin
可对生成的库文档进一步瘦身:
ar crs --thin D.a A.a B.a C.a
为了验证瘦身带来的效果,我这里打包一些静态库,并分别生成 merge_s.a merge_sT.a
两个库文件,可以看到,使能了 T 之后的文件小了 10 倍还多。
ar crsT ./merge_sT.a `
C:/MyWorkSoft/CPlusPlusLib/lib_thirdparty/FMT/lib/libfmt.a `
D:/BIRL_LAB/ForthModularRobot/Project/birl4th_basic/build/Host-GCC-12.2.0-Release/libbirl4th_basic_lib.a `
D:/BIRL_LAB/ForthModularRobot/Project/birl4th_basic/build/Host-GCC-12.2.0-Release/_birl4th/lib/libbirl4th_lib_s.a `
C:/MyWorkSoft/CPlusPlusLib/lib_thirdparty/spdlog/lib/libspdlog.a `
C:/MyWorkSoft/CPlusPlusLib/lib_thirdparty/orocos_kdl/lib/liborocos-kdl.a
ar crs ./merge_s.a `
C:/MyWorkSoft/CPlusPlusLib/lib_thirdparty/FMT/lib/libfmt.a `
D:/BIRL_LAB/ForthModularRobot/Project/birl4th_basic/build/Host-GCC-12.2.0-Release/libbirl4th_basic_lib.a `
D:/BIRL_LAB/ForthModularRobot/Project/birl4th_basic/build/Host-GCC-12.2.0-Release/_birl4th/lib/libbirl4th_lib_s.a `
C:/MyWorkSoft/CPlusPlusLib/lib_thirdparty/spdlog/lib/libspdlog.a `
C:/MyWorkSoft/CPlusPlusLib/lib_thirdparty/orocos_kdl/lib/liborocos-kdl.a
ls -alh merge_s.a merge_sT.a
-rw-r--r-- 1 91225 197609 6.6M Dec 21 21:50 merge_s.a
-rw-r--r-- 1 91225 197609 401K Dec 21 21:50 merge_sT.a
需要注意的是,T 选项还带来了展开文件内容的效果,如下,经过实际编译测试,静态库必须仅包含 obj 文件才能链接其它程序一起编译,存在嵌套情况的话会找不到符号。因此,为了使合并后的静态库可以链接、编译,必须使用 crsT
。
λ ar -t ./merge_s.a
libfmt.a
libbirl4th_basic_lib.a
libbirl4th_lib_s.a
libspdlog.a
liborocos-kdl.a
λ ar -t ./merge_sT.a
format.cc.obj
os.cc.obj
controller.cpp.obj
io.cpp.obj
logger.cpp.obj
times.cpp.obj
utils.cpp.obj
controller_device.cpp.obj
device_sim.cpp.obj
pid.cpp.obj
device.cpp.obj
filter.cpp.obj
filter_c.c.obj
gravity_compensate.cpp.obj
joint_force_observer.cpp.o
.......
实践
CMake 中是不支持合并静态库的,想要合并静态库只能借助于 add_custom_command
add_custom_target
用外部命令来实现。
# 待合并的 static 库
list(APPEND lib_merged
orocos-kdl
birl4th_lib::birl4th_lib
fmt::fmt
spdlog::spdlog
)
# 获得静态库位置
list(APPEND lib_locations "")
foreach(lib ${lib_merged})
set(lib_location $<TARGET_FILE:${lib}>)
list(APPEND lib_locations ${lib_location})
endforeach(lib)
# 如果rebuild,删掉之前生成的文件
add_custom_target(rm_libmerge.a
COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_CURRENT_BINARY_DIR}/libmerge.a
)
# 调用命令行重新生成
add_custom_command(OUTPUT libmerge.a
COMMAND ${CMAKE_AR} crsT ${CMAKE_CURRENT_BINARY_DIR}/libmerge.a ${lib_locations}
DEPENDS rm_libmerge.a
)
可以看到,一共分三个步骤:
- 列出所有需要合并的静态库。
- 使用
$<TARGET_FILE:${lib}>
获取静态库位置。 - 调用
ar crsT
合并静态库,并展开嵌套。需要注意的是,这里添加了一个依赖,使得每次 build 时都会重新合并静态库,保证库是最新的。
需要注意的是,使用 ${CMAKE_AR}
确保使用工具链对应的 ar
有了静态库后,就可以包装一下,然后进行调用了。
# 设置生成的静态库的输出名称
add_library(merge STATIC IMPORTED )
set_target_properties(merge PROPERTIES
OUTPUT_NAME libmerged
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/libmerge.a
DEPENDS libmerge.a
)
target_include_directories(merge INTERFACE some_include)
# 调用时直接link即可。
add_executable(${target} "${target}.cpp")
target_link_libraries(${target} merge)
安装、导出
如果你想安装或者导出该库,那么需要再包装一下才行,这样包装后的库就像一个普通的静态库一样了。
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> crsT <TARGET> <LINK_FLAGS> <OBJECTS>")
message(STATUS "ARCHIVE: ${CMAKE_CXX_ARCHIVE_CREATE}")
add_library(birl4th_basic STATIC $<TARGET_OBJECTS:merge> )
set_target_properties(birl4th_basic PROPERTIES LINKER_LANGUAGE CXX)
#
target_include_directories(birl4th_basic PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> #
$<INSTALL_INTERFACE:${birl4th_basic_install_include_dir}>)
add_library(birl4th_basic::birl4th_basic ALIAS birl4th_basic)
set_target_properties(birl4th_basic
PROPERTIES
POSITION_INDEPENDENT_CODE 1
# WINDOWS_EXPORT_ALL_SYMBOLS ON
ARCHIVE_OUTPUT_NAME "birl4th_basic"
DEBUG_POSTFIX "_rd"
RELEASE_POSTFIX "_s"
PUBLIC_HEADER "${HEADERS}"
)
需要注意一下几个问题:
- 二次包装会导致静态库发生嵌套,导致链接时找不到符号,因此需要想办法让它在链接时自动使用
ar
的--thin
选项,因此配置CMAKE_CXX_ARCHIVE_CREATE
,可惜的是这个是全局选项,如果有针对目标的配置就好了。 - 因为 merge 库已经丢失了语言信息,因此需要重新指定语言:
set_target_properties(birl4th_basic PROPERTIES LINKER_LANGUAGE CXX)