CMake应用:合并静态库的最佳实践

在实际项目中,往往需要将一些基础库或者算法库发布出去,但是不同项目可能需要用到不同的子模块,此时为了保持简洁,可能需要合并多个静态库为一个。

在笔者的实际工作中,合并静态库的需求还是有的,而且大多数时候都是基于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

参数解释:

  1. x:拆解静态库文件为其包含的内容
  2. c:封装.o文件为静态库文件
  3. r:覆盖同名库文件或者新创建目标库文件
  4. 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/mathsrc/nn,分别编译得到两个静态库目标libmath.alibnn.a

现在在项目根目录下的CMakeLists.txt中:通过add_custom_command命令配合add_custom_target命令,将libmath.alibnn.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)

代码解释:

  1. OUTPUT:指定输出文件名称(会被标记为GENERATED
  2. COMMAND:后面跟的就是要执行的命令,这里就和在shell中执行命令差不多
  3. DEPENDS:表明依赖的目标
  4. add_custom_target指明的目标依赖于合并操作的输出(libmerge.a),而合并操作需要依赖目标mathnn,所以在这两个目标文件生成以后,就会去执行合并操作

需要注意的是,合并静态库的时候需要知道每个静态库的路径,在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,而mergeIMPORTED_LOCATION指定的这个文件是自动生成的,所以CMake就知道需要等libmerge.a生成之后才能开始链接demo

如果没有add_custom_target那一行,执行编译构建会报错,由此也可见一斑:

make[2]: *** No rule to make target `libmerge.a', needed by `demo'

以上便是CMake下合并静态库的实践,欢迎交流指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章