在实际项目中,往往需要将一些基础库或者算法库发布出去,但是不同项目可能需要用到不同的子模块,此时为了保持简洁,可能需要合并多个静态库为一个。
在笔者的实际工作中,合并静态库的需求还是有的,而且大多数时候都是基于CMake的项目,所以希望能够基于不同配置,自动合并多个模块的静态库为一个,方便发布版本和管理。本文介绍的就是如何在CMake工程中,优雅地完成多个静态库目标的合并。
本文仍以本系列的开源项目https://gitee.com/RealCoolEngineer/cmake-template为例(当前commit id: 9015193
)。
一 合并静态库的方法
静态库其实就是一些源文件被编译成对应机器代码文件(.o
文件)的集合。
在Linux系统中,通过ar
命令可以对静态库进行各种操作,在MacOS
下可以使用libtool
工具。有以下几种不同的合并静态库的方法。
1 方法1
先使用ar
把静态库拆解为多个.o
文件:
ar x liba.a
ar x libb.a
再把所有的.o
文件打包为一个静态库:
ar crs libmerge.a *.o
参数解释:
-
x
:拆解静态库文件为其包含的内容 -
c
:封装.o
文件为静态库文件 -
r
:覆盖同名库文件或者新创建目标库文件 -
s
:相当于对结果执行一次ranlib
,为静态库的内容添加索引,提高访问效率
2 方法2
当然,还有更加简洁的命令:
ar crsT libmerge.a liba.a libb.a
参数T
表示将后续所有静态库中的.o
文件打包到第一个参数指定的静态库文件中,如果不加该参数,得到的将会是后面几个.a
文件的集合。可以使用命令ar -t
查看打包的内容,诸君手动一试便知。
MacOS下的
ar
命令和Linux的有所不同,此法不适用于MacOS。
3 方法3
使用MRI
脚本。
首先编写一个MRI
脚本,比如merge.mri
:
create libmerge.a
addlib liba.a
addlib libb.a
save
end
然后使用命令:
ar -M < merge.mri
MacOS下面的的
ar
命令并没有-M
参数,所以此法也不适用于MacOS系统。
4 方法4
此方法针对MacOS系统,在MacOS系统下可以使用libtool
命令:
libtool -static -o libmerge.a liba.a libb.a
在Linux下也有
libtool
工具,但是用法和MacOS也是不一致的,所以此法不适用于Linux。
二 基于CMake合并静态库
在示例项目cmake-template
中,源码目录下有两个子目录:src/math
和src/nn
,分别编译得到两个静态库目标libmath.a
和libnn.a
。
现在在项目根目录下的CMakeLists.txt
中:通过add_custom_command
命令配合add_custom_target
命令,将libmath.a
和libnn.a
合并为libmerge.a
;并将合并的静态库文件导入使用。
1 合并静态库
这里使用了CMake的内置变量APPLE
,如果是MacOS
系统,APPLE
会被设置为true
,以此来确定要使用libtool
还是ar
。
合并静态库实现代码如下:
# Merge library
if (APPLE)
add_custom_command(OUTPUT libmerge.a
COMMAND libtool -static -o libmerge.a $<TARGET_FILE:math> $<TARGET_FILE:nn>
DEPENDS math nn)
else()
add_custom_command(OUTPUT libmerge.a
COMMAND ar crsT libmerge.a $<TARGET_FILE:math> $<TARGET_FILE:nn>
DEPENDS math nn)
endif()
add_custom_target(_merge ALL DEPENDS libmerge.a)
代码解释:
-
OUTPUT
:指定输出文件名称(会被标记为GENERATED) -
COMMAND
:后面跟的就是要执行的命令,这里就和在shell中执行命令差不多 -
DEPENDS
:表明依赖的目标 -
add_custom_target
指明的目标依赖于合并操作的输出(libmerge.a
),而合并操作需要依赖目标math
和nn
,所以在这两个目标文件生成以后,就会去执行合并操作
需要注意的是,合并静态库的时候需要知道每个静态库的路径,在CMake中,目标静态库math
的路径可以使用生成器表达式$<TARGET_FILE:math>
获取。
但是还有另外一种情况,如果是使用find_library
查找到的静态库,比如:
find_library(LIB_C c HINTS ${SEARCH_PATH})
这时DEPENDS
中就不用加上c
,而${LIB_C}
就是该库的路径了。
2 导入合并的静态库并使用
现在把合并的静态库导入,并在链接demo
可执行程序时使用。
代码实现如下:
add_library(merge STATIC IMPORTED GLOBAL)
set_target_properties(merge PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/libmerge.a
)
# Build demo executable
add_executable(demo src/c/main.c)
target_link_libraries(demo PRIVATE merge)
因为libmerge.a
是命令add_custom_command
指定的输出,所以它会标记为是自动生成的文件(GENERATED)。
链接demo
的时候依赖导入的静态库merge
,而merge
的IMPORTED_LOCATION
指定的这个文件是自动生成的,所以CMake就知道需要等libmerge.a
生成之后才能开始链接demo
。
如果没有add_custom_target
那一行,执行编译构建会报错,由此也可见一斑:
make[2]: *** No rule to make target `libmerge.a', needed by `demo'
以上便是CMake下合并静态库的实践,欢迎交流指正。