4. 开发环境搭建

Cyber RT 是 Apollo 自动驾驶平台的核心中间件,负责节点通信、调度、服务发现与数据融合。要真正进入 Cyber RT 的工程实践,第一步不是写代码,而是把一套可复现、可调试、可编译的开发环境搭起来。本章围绕“源码获取 → 容器化环境 → Bazel 构建 → 编译运行 → 调试准备”这条主线,给出可落地的操作路径与常见问题排查方法。

一、核心原理:为什么 Apollo 选择 Docker + Bazel

Apollo 的依赖体系非常庞大:Protobuf、gRPC、Fast RTPS、OpenCV、PCL、CUDA、Eigen、Boost 等版本互相耦合,直接安装在宿主机上极易出现“版本地狱”。Docker 提供隔离的根文件系统,把 Ubuntu 版本、编译器版本、第三方库版本全部锁定在镜像里;Bazel 则负责增量编译、依赖图管理和跨语言构建。两者结合,使“同一份源码在不同机器上编译出相同结果”成为可能。

Cyber RT 的构建目标通常以 //cyber/... 形式组织,Bazel 会解析 BUILD 文件中的 cc_library、cc_binary、proto_library 等规则,生成依赖 DAG,再按拓扑顺序编译。理解这一点,就能明白为什么“改一个头文件会触发大量重编译”,也能理解缓存目录 ~/.cache/bazel 的重要性。

Apollo Cyber RT 开发环境搭建链路 ① 源码获取 git clone / 分支切换 ② Docker 环境 镜像拉取 / 容器启动 ③ Bazel 配置 WORKSPACE / .bazelrc ④ 编译 Cyber RT bazel build //cyber/... ⑤ 运行调试 GDB / 日志 关键依赖与工具 Ubuntu 20.04 Bazel 5.x Protobuf 3.x Fast RTPS GDB / Valgrind CUDA(可选) 常见风险点 • 宿主机与容器用户 UID 不一致导致权限错误 • Bazel 缓存污染导致编译结果异常,需 bazel clean --expunge • 网络受限时镜像拉取失败,需配置代理或离线镜像 • 编译内存不足,需限制 --jobs 并发数

二、方法流程:从零到可编译的五步操作

  1. 获取源码:从 Apollo 官方仓库克隆,并切换到与 Cyber RT 匹配的分支。建议使用 --depth=1 减少下载量。
  2. 启动 Docker 容器:使用 Apollo 提供的 apolloauto/apollo 镜像,挂载源码目录,并映射显示与调试端口。
  3. 配置 Bazel:确认 WORKSPACE 存在,检查 .bazelrc 中的编译选项与缓存路径。
  4. 编译 Cyber RT:执行 bazel build //cyber/...,观察依赖解析与编译输出。
  5. 运行与调试:启动示例节点,使用 cyber_monitor、cyber_visualizer 和 GDB 进行验证。
# 1. 获取源码
git clone --depth=1 -b master https://github.com/ApolloAuto/apollo.git
cd apollo

# 2. 启动 Docker 容器
docker run -it --name apollo_dev \
  -v $(pwd):/apollo \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -e DISPLAY=$DISPLAY \
  apolloauto/apollo:dev-latest bash

# 3. 进入容器后配置 Bazel
cd /apollo
bazel version

# 4. 编译 Cyber RT
bazel build //cyber/... --jobs=4

# 5. 运行示例
bazel run //cyber/examples:talker
bazel run //cyber/examples:listener

三、案例场景:编译一个 Cyber RT 最小节点

假设我们要在 cyber/examples 下新增一个最小发布者节点。首先编写 BUILD 文件,声明 cc_binary 并依赖 //cyber;然后编写 talker.cc,使用 cyber::Init、CreateWriter 和 Node 完成消息发布。编译命令如下:

bazel build //cyber/examples:talker
bazel run //cyber/examples:talker

如果编译成功但运行时报“channel not found”,通常是因为 CYBER_IP 或 CYBER_DOMAIN_ID 未设置,导致节点无法加入同一通信域。此时可在容器内执行:

export CYBER_IP=127.0.0.1
export CYBER_DOMAIN_ID=80
bazel run //cyber/examples:talker
建议在容器启动时就把 CYBER_IP、CYBER_DOMAIN_ID 写入 ~/.bashrc,避免每次手动设置。调试阶段还可以打开 GLOG_v=4 查看详细日志。

四、误区与复盘:环境搭建中最容易踩的坑

误区 现象 正确做法
直接在宿主机安装所有依赖 版本冲突、编译失败、难以复现 使用 Apollo 官方 Docker 镜像,锁定环境
忽略 Bazel 缓存目录权限 Permission denied,编译中断 确保容器内 UID 与宿主机一致,或使用 --output_user_root
不设置 CYBER_DOMAIN_ID 节点之间无法通信 统一设置 CYBER_DOMAIN_ID 与 CYBER_IP
编译并发过高导致 OOM Bazel 进程被 kill 使用 --jobs=2 或 --local_ram_resources=2048
调试时未保留符号信息 GDB 无法定位源码行 编译时使用 -c dbg,并保留 .dwp 文件
如果执行 bazel clean --expunge 后仍然编译失败,优先检查 WORKSPACE 中的外部依赖 URL 是否可访问。国内网络环境下,建议配置镜像源或使用离线依赖包,否则会在拉取 com_github_grpc_grpc 等外部仓库时长时间卡住。