# Apollo Release Build 简介

## 背景

基于 CyberRT 的 Apollo 在相当长的时间内一直没有二进制发布版本。用户需要先在
Apollo 开发容器内自行完成对整个项目的编译构建才能运行 Apollo 中的模块和工具。这
种部署上的不足,在若干情形下对用户相当不便。SVL 模拟器的开发者发现,将 Apollo
中 Docker 镜像、Docker 卷及 Bazel 缓存和构建查出加起来,足有 40 多 GB!

## Release Build 的实现原理

这种部署上的不足的根本原因,在于 Bazel 缺少其他构建系统通常具备的开箱即用的「安
装」支持,如`make install`.

为解决这一问题,我们借鉴了[Drake](https://github.com/RobotLocomotion/drake) 项目
中的「安装」实现,,利用 Starlark 语言,实现了 适用于 Apollo 的 Bazel「安装」扩
展,支持 Apollo 中二进制程序、共享库、资源文件(配置、数据、DAG 文件等)以及文档
的安装。

单独完备的二进制程序的安装是简单的。然而,CyberRT 框架的核心概念即为将每个模块(
如感知、预测、规划)作为组件,以共享库的形式(`libX_component.so`)动态加载。在
目前的 Bazel 构建下,`mainboard`二进制程序和`libX_component.so`链接了成千上百各
其他共享库对象。如,对规划模块运行如下`ldd`命令:

```bash
ldd bazel-bin/modules/planning/libplanning_component.so
```

会输出如下消息:

```text
	linux-vdso.so.1 (0x00007ffc8a77c000)
	libmodules_Splanning_Slibplanning_Ucomponent_Ulib.so => /apollo/bazel-bin/modules/planning/../../_solib_local/libmodules_Splanning_Slibplanning_Ucomponent_Ulib.so (0x00007fe8a7f9f000)
	libmodules_Splanning_Slibnavi_Uplanning.so => /apollo/bazel-bin/modules/planning/../../_solib_local/libmodules_Splanning_Slibnavi_Uplanning.so (0x00007fe8a7d81000)
	libmodules_Splanning_Slibon_Ulane_Uplanning.so => /apollo/bazel-bin/modules/planning/../../_solib_local/libmodules_Splanning_Slibon_Ulane_Uplanning.so (0x00007fe8a7b53000)
	libmodules_Splanning_Slibplanning_Ubase.so => /apollo/bazel-bin/modules/planning/../../_solib_local/libmodules_Splanning_Slibplanning_Ubase.so (0x00007fe8a7945000)
	libmodules_Splanning_Scommon_Ssmoothers_Slibsmoother.so => /apollo/bazel-bin/modules/planning/../../_solib_local/libmodules_Splanning_Scommon_Ssmoothers_Slibsmoother.so (0x00007fe8a7739000)
	libmodules_Splanning_Splanner_Slibplanner_Udispatcher.so => /apollo/bazel-bin/modules/planning/../../_solib_local/libmodules_Splanning_Splanner_Slibplanner_Udispatcher.so (0x00007fe8a752e000)
    ...
```

如何实现对`libplanning_component.so` 及其链接的所有共享库(后缀为".so")文件的「
安装」,成为实现`install`规则中最难的部分。

幸好有`patchelf`。利用 Bazel 中`runfiles_data`的概念来确定出链接的所有共享库文件
,再利用`patchelf --force-rpath --set-rpath` 来修改其 RPATH 设置。

欲要更深入了解,请参考:
[tools/install/install.bzl](../../tools/install/install.bzl)。

## 如何执行 Release Build 构建

可运行如下命令以生成二进制发布构建产物:

```bash
./apollo.sh release -c
```

其中,`-c`为可选参数,用于清理先前构建的残留。产物位于`/apollo/output`目录。

上述命令略等价于如下 Bazel 命令:

```bash
bazel run --config=opt --config=gpu //:install \
        -- --pre_clean /apollo/output
```

可输入`./apollo.sh release -h` 查看`apollo.sh release`子命令的更多用法。

## 通过二进制发布构建产物运行 Apollo

在二进制发布产物根目录下,运行如下命令以启动 Apollo Runtime Docker 镜像:

```bash
bash docker/scripts/runtime_start.sh
```

国内用户可使用`-g cn`选项来加速 Docker 镜像的拉取。

```bash
bash docker/scripts/runtime_start.sh -g cn
```

运行如下命令以进入 Apollo Runtime Docker 环境:

```bash
bash docker/scripts/runtime_into.sh
```

启动 Dreaview:

```bash
./scripts/bootstrap.sh
```

## 如何将`install`规则应用到任一自定义模块

欲实现自定义模块的*安装*,可参考 Apollo 代码中其他模块的示例,还是以规划模块为例
:

这是最上层的[BUILD](../../BUILD) 文件的一部分:

```python
install(
    name = "install",
    deps = [
        "//cyber:install",
        # ...
        "//modules/planning:install",
        # ...
    ],
)
```

这是规划模块自身的 BUILD 文件
[modules/planning/BUILD](../../modules/planning/BUILD):

```python
filegroup(
    name = "planning_conf",
    srcs = glob([
        "conf/**",
    ]),
)

filegroup(
    name = "runtime_data",
    srcs = glob([
        "dag/*.dag",
        "launch/*.launch",
    ]) + [":planning_conf"],
)

install(
    name = "install",
    data = [
        ":runtime_data",
    ],
    targets = [
        ":libplanning_component.so",
    ],
    deps = [
        "//cyber:install",
    ],
)
```

## `install`规则的参数列表

`install`规则定义在
[tools/install/install.bzl](../../tools/install/install.bzl):

```python
install = rule(
    attrs = {
        "deps": attr.label_list(providers = [InstallInfo]),
        "data": attr.label_list(allow_files = True),
        "data_dest": attr.string(default = "@PACKAGE@"),
        "data_strip_prefix": attr.string_list(),
        "targets": attr.label_list(),
        "library_dest": attr.string(default = "@PACKAGE@"),
        "library_strip_prefix": attr.string_list(),
        "mangled_library_dest": attr.string(default = "lib"),
        "mangled_library_strip_prefix": attr.string_list(),
        "runtime_dest": attr.string(default = "bin"),
        "runtime_strip_prefix": attr.string_list(),
        "rename": attr.string_dict(),
        "install_script_template": attr.label(
            allow_files = True,
            executable = True,
            cfg = "target",
            default = Label("//tools/install:install.py.in"),
        ),
    },
    executable = True,
    implementation = _install_impl,
)
```

其具体参数列举如下

| 参数                 | 含义                                      |
| -------------------- | ----------------------------------------- |
| deps                 | 本规则依赖的其它安装规则                  |
| data                 | 待安装的资源文件(平台无关)列表          |
| data_dest            | 资源文件目标安装地址                      |
| data_strip_prefix    | 需去掉的资源文件路径前缀列表              |
| targets              | 待安装目标                                |
| runtime_dest         | 可执行目标的目标安装地址,默认为 bin 目录 |
| runtime_strip_prefix | 需去掉可执行目标路径的前缀                |
| rename               | 安装时的文件重命名                        |

## 局限性

当前的 Release Build 实现

- 只支持 C++,不支持 Python。
- 只支持 x86_64 架构,Aarch64 支持尚待完善。