1 前言
为了实现如标题所述的将多个静态库合并为一个动态库,内置的 Bazel 规则是没有这个功能的,Bazel C/C++
相关的内置规则有:
- cc_binary :生成可执行文件
- cc_import :允许用户导入预编译的
C/C++
库,包括动态库、静态库 - cc_library :生成动/静态库
- cc_proto_library :从
.proto
文件生成C++
代码 - fdo_prefetch_hints :表示位于工作区中或位于指定绝对路径的 FDO 预取提示配置文件
- fdo_profile :表示工作区中或位于指定绝对路径的 FDO 配置文件
- cc_test :测试
C/C++
样例 - cc_toolchain :表示一个
C++
工具链 - cc_toolchain_suite :表示
C++
工具链的集合
而我们知道规则(Rule)定义了 Bazel 对输入执行的一系列操作,以生成一组输出。例如 cc_binary
规则可能:
- 输入(Inputs):获取一组
.cpp
文件 - 动作(Action):基于输入运行
g++
- 输出(Output):返回一个可执行文件
从 Bazel 的角度来看,g++
和标准 C++
库也是这个规则的输入。作为规则编写人员,你不仅必须考虑用户提供的规则输入,还必须考虑执行操作(Actions)所需的所有工具和库。比如我们手动的将多个静态库(libA.a、libB.a、libC.a
)合并为一个动态库(libcombined.so
):
$ gcc -shared -fPIC -Wl,--whole-archive libA.a libB.a libC.a -Wl,--no-whole-archive -Wl,-soname -o libcombined.so
注:
-Wl,option
后面接的选项最终会作为链接器 ld 的参数,即上面的命令最终还调用了 ld 命令。而-Wl,--whole-archive {xxx} -Wl,--no-whole-archive
所包围的库表示将{xxx}
库列表中所有.o
中的符号都链接进来,这样会导致链接不必要的代码进来,从而导致生成的库会相对很大。目前还没有找到相关办法是否可以做到只链接进上层模块库所调用到的函数。
在编写规则中我们就需要获取当前的编译器,我们不能直接使用固定的路径,比如 Linux 下 /usr/bin/gcc
,因为可能是交叉编译器,路径就不一样了。另外我们还需要传入 gcc
将多个静态库合并成一个动态库的相关参数、待合成的静态库列表、最后要生成的动态库名称和路径。这样就是一个比较完善的自定义规则了。
2 自定义规则实现
2.1 规则功能
- 将多个静态库合并成一个动态库
- 将多个静态库合并成一个静态库
- 可以设置生成库的名称和生成路径
- 静态库作为规则依赖
2.2 实现规则的理论基础
将多个静态库合并成一个动态库:
$ gcc -shared -fPIC -Wl,--whole-archive libA.a libB.a libC.a -Wl,--no-whole-archive -Wl,-soname -o libcombined.so
将多个静态库合并成一个静态库:
方式一:
$ cd temp
$ ar x libA.a
$ ar x libB.a
$ ar x libC.a
$ ar rc libcombined.a *.o
用这种方式无法指定库的输出目录。笨方法就是,将每个待合并的静态库都拷贝到目标目录里去,然后一一 ar -x
操作,然后再到目标目录里操作 ar rc
。这就涉及到了中间文件的产生,有一个很重要的点就是中间文件的产生只能在当前 Bazel 包中创建。中间文件的创建我们可以使用 File actions.declare_file(filename, *, sibling=None)
声明然后结合 Action 去真实创建。
方式二(需安装libtool):
# MacOS系统
$ libtool -static -o libcombined.a libA.a libB.a libC.a
在 Unix-like 系统上:
$ sudo apt-get install libtool-bin
# 生成的libcombined.a ar -x 解压出来是 libA.a libB.a libC.a ,而不是 *.o 文件。
$ libtool --mode=link gcc -o libcombined.a libA.a libB.a libC.a
# 这样可以指定生成路径,但是 *.o 的生成还是需要 ar -x 来生成
$ libtool --mode=link gcc -o libcombined.a *.o
另外我们需要规则具有参数输入功能,参数输入类型定义可以详见:https://docs.bazel.build/versions/3.4.0/skylark/lib/attr.html ,比如定义一个决定是否合成动态库或静态库的布尔参数(genstatic),以及带依赖项配置(deps):
my_cc_combine = rule(
implementation = _combine_impl,
attrs = {
"genstatic" : attr.bool(default = False),
"deps": attr.label_list(),
}
)
Action
描述了如何从一组输入生成一组输出,例如 “在 hello.c 上运行 gcc 并获取 hello.o”。创建操作(Action)时,Bazel 不会立即运行命令。它将其注册在依赖关系图中,因为一个 Action 可以依赖于另一个 Action 的输出(例如,在 C 语言中,必须在编译后调用链接器)。在执行阶段,Bazel 会决定必须以何种顺序运行哪些操作。所有创建 Action 的函数都定义在 ctx.actions
中:
ctx.actions.run
:运行一个可执行文件