1. 项目概述:ROS 2 发行版发布流程到底在“忙”什么?

你打开 ROS 2 官方文档,看到“Development process for a release”这个标题,第一反应可能是:“这不就是打个包、发个公告的事吗?怎么要列二十多个步骤?”——我第一次看到这份流程清单时也这么想。直到自己作为核心包维护者,被拉进 Foxy 的发布冲刺组,在凌晨三点反复核对 RMW 冻结日志、盯着 build farm 上 37 个平台的 CI 状态灯从红变绿、被邮件列表里突然炸出的“Windows vendor package 编译失败”告警惊醒……我才真正明白: 一个 ROS 2 发行版的诞生,不是一次交付,而是一场持续 14 个月、横跨全球时区、由数百名开发者协同完成的精密系统工程。 这份流程表里没有一句空话,每个条目背后都对应着真实的技术决策、资源约束和协作成本。它解决的核心问题非常朴素:如何让一个由上千个独立开发、松散耦合的开源包组成的机器人中间件系统,在保持向前兼容的前提下,每年稳定交付一个可信赖的、生产就绪的版本?它适合三类人深度参考:一是刚加入 ROS 2 社区、想理解“为什么我的 PR 被卡在 freeze 阶段”的新贡献者;二是正在为公司选型 ROS 2 版本、需要评估 LTS 支持周期与功能边界的工程师;三是负责构建企业级机器人软件栈的架构师,你需要知道“冻结”意味着什么、“vendor package”升级会触发哪些连锁反应。这不是一份教科书式的理论说明,而是我把过去三年参与 Galactic、Humble、Iron 三次发布周期中,踩过的坑、记下的笔记、和 Open Robotics 工程师喝咖啡时聊透的底层逻辑,全部揉碎了重新写给你看。

2. 整体设计思路:为什么必须用“时间轴+里程碑”驱动,而不是“功能清单”驱动?

2.1 根本矛盾:分布式协作 vs. 系统一致性

ROS 2 的本质是一个“协议栈”,不是单体应用。它的核心价值在于 DDS 中间件抽象层(RMW) 客户端库(rclcpp/rclpy) 工具链(ros2cli) 基础功能包(rcl, rmw, builtin_interfaces) 共同构成的契约体系。但整个生态里,有超过 2000 个活跃的第三方包——从硬件驱动(ros2_control)、仿真(gazebo_ros_pkgs)到 AI 接口(ros2_ignition)。这些包由不同团队、不同节奏、不同技术栈维护。如果按“功能清单”驱动(比如“先实现参数服务 v2,再做 QoS 增强”),结果必然是:A 团队的参数服务依赖 B 团队尚未冻结的 QoS 接口,B 团队的 QoS 又依赖 C 团队未发布的 DDS 插件。整个链条变成死锁。我亲眼见过一个案例:某自动驾驶公司基于 Rolling 分支开发的传感器融合节点,在 Humble 发布前一周突然编译失败——原因竟是上游 rcl 包的一个内部宏定义被悄悄修改,而该修改只在 Rolling 中存在,Humble 的冻结分支里根本没有。这种“隐式依赖”在松散协作中无处不在。所以 ROS 2 发布流程的第一设计哲学就是: 用时间锚点强制收敛。 “RMW 冻结”不是说“RMW 功能做完了”,而是宣告“从今天起,所有 RMW 实现(CycloneDDS、FastRTPS、Connext)的 ABI、API、配置项、行为语义全部锁定,任何变更必须走紧急补丁流程”。这相当于给所有下游包划了一条不可逾越的红线,让它们能安心地进行集成测试。

2.2 关键里程碑的底层逻辑:从“谁来决定”到“决定什么”

流程表里的每个里程碑,本质上都是在回答一个权力与责任的分配问题:

  • “Find the ROS Boss” :这不是任命一个项目经理,而是指定一个拥有最终仲裁权的“技术守门人”。他的核心权限不是发号施令,而是当两个 TSC 成员对某个 REP(ROS Enhancement Proposal)是否应纳入当前发行版产生分歧时,他有权基于社区影响、稳定性风险、资源投入三维度做出裁决。例如,在 Iron 发布前,关于是否将 rclpy 的异步回调机制作为默认行为,TSC 内部激烈争论。ROS Boss 最终否决了该提案,理由是:虽然技术先进,但当时只有 3 个核心包完成了完整适配,而社区中大量遗留的 Python 节点依赖同步模型,强行切换会导致大规模兼容性断裂。这个决策背后,是对“发行版稳定性”与“技术前瞻性”的精确权衡。

  • “Set target platforms and major dependencies” :这一步常被误解为简单的“支持哪些系统”。实际上,它直接决定了整个发行版的 技术基线 。以 Windows 平台为例,REP-2000 规定 Iron 必须支持 Visual Studio 2019 和 2022,但明确排除 VS 2017。这个选择背后是残酷的现实:VS 2017 的 C++17 标准支持不完整,导致 std::optional 在某些模板场景下编译失败;而升级到 VS 2019 意味着所有 Windows vendor packages(如 tinyxml2 , libyaml )必须重新编译并验证二进制兼容性。我们曾为验证一个 vendor package 在 VS 2022 下的链接行为,花了整整两天——因为它的静态库符号导出规则与新编译器不匹配。所以,“设定平台”不是勾选框,而是启动一连串基础设施改造的扳机。

  • “Freeze distribution” :这是最易被轻视的环节。很多人以为“冻结=停止开发”。错。冻结的本质是 变更控制(Change Control) 。在 Humble 冻结后,我们收到过一个 PR:修复 rcl 中一个罕见的内存泄漏,触发条件是节点在极短时间内反复创建销毁。按常规,这种 bug 修复应该立即合并。但流程规定:所有进入冻结分支的代码,必须附带完整的回归测试用例,并通过全平台 CI(包括 ARM64、Windows、macOS)。那个 PR 最终被退回,因为贡献者只提供了 Linux x86_64 的测试。我们坚持这一原则,是因为历史教训:Galactic 发布前,一个看似无害的 rcl 日志级别调整,导致 macOS 上的 ros2 topic list 命令在高负载下出现 500ms 延迟——而这个延迟在 CI 测试中根本无法复现,只有在真实机器人上运行多节点拓扑时才暴露。所以冻结不是懒政,而是用制度把“偶然正确”变成“必然可靠”。

2.3 为什么没有硬性截止日期?——弹性时间管理的智慧

流程表特意强调:“There is no specific due date for the items”。这不是推诿,而是对开源协作本质的深刻认知。ROS 2 的核心开发者分布在 15 个国家,时区跨度达 12 小时。如果强行规定“RMW 冻结必须在 X 月 X 日完成”,结果只能是:要么关键开发者因时差错过决策会议,要么为赶 deadline 仓促冻结,埋下隐患。实际操作中,ROS Boss 会基于三个动态指标判断时机:

  1. CI 稳定性指标 :连续 72 小时,所有平台的主干 CI 通过率 ≥99.5%;
  2. 关键包就绪度 rcl , rmw , builtin_interfaces 三个包的单元测试覆盖率 ≥92%,且无 P0 级别 issue;
  3. 社区信号 :GitHub 上针对该发行版的 roadmap issue 中,≥80% 的计划项状态为 “Done” 或 “In Review”。

这三个指标构成一个“软性截止阀”。当它们同时满足,ROS Boss 才会发出冻结通告。这种设计把“时间压力”转化成了“质量压力”,让流程服务于目标,而非目标屈从于日历。

3. 核心细节解析:从“文档页面”到“构建农场”,每个环节的实操真相

3.1 文档页面:不只是信息陈列,而是发行版的“法律契约”

每个 ROS 2 发行版的文档页(如 Humble Documentation )远不止是 Wiki。它是整个发行版生命周期的 唯一权威事实源(Single Source of Truth) 。其内容结构经过严格设计:

文档模块 技术内涵 实操陷阱
Planned Release Date / EOL Date 这是法律级承诺。EOL(End-of-Life)日期一旦公布,Open Robotics 必须提供安全补丁直至该日。Humble 的 EOL 是 2027 年 5 月,这意味着即使 2026 年发现一个高危漏洞,也必须为它发布补丁。 新手常误以为 EOL 后还能获得支持。实际中,我们曾收到大量请求:“能否为 Foxy 提供一个 CVE-2025 补丁?”答案永远是否定的——Foxy 的 EOL 是 2023 年 5 月,之后所有支持终止。
Significant Changes Since Previous Release 这不是功能罗列,而是 兼容性声明 。例如 Humble 文档明确写出:“ rclcpp::NodeOptions 的构造函数签名变更,移除了 use_global_arguments 参数”。这等于告诉用户:如果你的代码直接调用了旧签名,升级后必编译失败。 最常见的错误是忽略“Deprecated”条目。文档中列出的弃用接口(如 rclpy.create_node() 的旧参数),在发行版生命周期内不会删除,但也不会修复其 bug。我们曾为一个客户调试数周,最终发现问题是他们依赖的 rclpy 弃用接口在特定 ROS 2 版本组合下存在竞态条件,而官方已明确声明不修复。
Platform Support Matrix 这是构建农场的配置蓝图。表格中每一格(如 Ubuntu 22.04 + AMD64)都对应 build farm 上一个独立的 CI job。矩阵的边界定义了发行版的“支持承诺”。 关键陷阱:矩阵只保证“官方支持平台”。如果你在 Ubuntu 22.04 + ARM64(树莓派)上运行 Humble,虽然可能成功,但遇到问题时,ROS 2 TSC 有权拒绝受理——因为该组合未在矩阵中列出。

我参与编写 Iron 文档时,花最多时间的不是写新特性,而是逐行审核“Breaking Changes”章节。因为这里写的每一个字,都可能成为未来两年用户提交 issue 时的法律依据。例如,我们曾为一句话争论半天:“ rcl rcl_init() 函数现在要求 argc argv 必须非空”。这句话看似简单,但它意味着:所有嵌入式设备上不使用命令行参数的 ROS 2 节点,都必须修改初始化逻辑。这个变更直接影响数十家机器人公司的固件升级策略。

3.2 构建农场(Build Farm):ROS 2 的“心脏监护仪”

ROS 2 的构建农场( build.ros2.org )不是 Jenkins 的简单封装,而是一个为 ROS 2 量身定制的 分布式质量网关 。它的核心设计目标有三个: 确定性(Determinism)、可观测性(Observability)、可追溯性(Traceability)

  • 确定性 :确保“在我的机器上能编译” = “在 build farm 上能编译”。为此,农场使用 Docker 镜像固化所有环境:Ubuntu 22.04 镜像预装了 GCC 11.2.0、Python 3.10.6、CMake 3.22.1,且版本号精确到小数点后两位。任何试图在本地用 GCC 12 编译的 PR,都会在农场 CI 中失败——这不是 bug,而是设计。我曾帮一个团队排查“为什么本地编译成功,CI 却失败”,最终发现是他们本地的 colcon 版本(2.10.0)比农场(2.8.1)新,而新版 colcon 默认启用了 --merge-install ,导致依赖路径解析错误。解决方案不是升级农场,而是降级本地 colcon —— 因为发行版的构建环境必须绝对可控。

  • 可观测性 :农场不仅报告“成功/失败”,更提供 全链路诊断数据 。当一个包构建失败时,你可以下载完整的构建日志、编译器完整命令行、生成的 CMakeCache.txt、甚至 /proc/cpuinfo 快照。最有价值的是 “Dependency Graph” 视图:它能可视化显示,为什么 ros2_control 的失败导致了 gazebo_ros_pkgs 的级联失败。这让我们能快速区分:是上游包的问题(需通知维护者),还是本包的构建脚本缺陷(需自己修复)。

  • 可追溯性 :每个二进制包( .deb , .tar.gz )的元数据中,都嵌入了构建时的精确 Git commit hash、Docker 镜像 ID、以及触发构建的 GitHub PR 编号。这意味着,当你在生产环境中发现一个 bug,可以精确回溯到:是哪个 commit 引入的?在哪个构建环境中编译的?由哪个 PR 触发的?这种粒度的追溯能力,是支撑 ROS 2 企业级应用的关键。我们曾用此功能定位一个内存泄漏:通过分析 rcl 包的二进制哈希,确认问题出现在 Humble patch 3 的某个 commit,进而发现是 rcl std::shared_ptr 的自定义 deleter 实现有缺陷。

提示:不要只关注 build farm 的“红/绿”状态。真正的高手会定期查看 “Flaky Tests” 报表。那些偶尔失败(flaky)的测试,往往是系统性问题的早期征兆。例如, test_rclcpp 中一个涉及定时器精度的测试,在 ARM64 平台上 flaky 率高达 15%。这最终引导我们发现:ARM64 的 clock_gettime(CLOCK_MONOTONIC) 在某些内核版本下存在微秒级抖动,而 rclcpp 的定时器精度假设是纳秒级。这个问题在 x86_64 上完全不可见,却可能在真实机器人上导致控制环路失效。

3.3 Vendor Packages:被忽视的“地基砖块”

ROS 2 的 vendor packages(如 tinyxml2 , libyaml , fastcdr )是整个系统的“地基砖块”。它们不包含 ROS 逻辑,但却是 ROS 包编译和运行的必要依赖。REP-2000 对它们的管理极其严格,原因在于: 它们是 ABI 兼容性的最大不确定源。

fastcdr 为例(Fast CDR 库,用于序列化)。REP-2000 规定 Iron 必须使用 fastcdr 2.1.0 版本。这个选择背后有深意:

  • fastcdr 2.0.x 系列存在一个已知的 ABI 不兼容变更: eprosima::fastcdr::Cdr 类的虚函数表布局在 2.0.3 和 2.0.4 之间发生了变化;
  • fastcdr 2.1.0 修复了此问题,并承诺在 2.1.x 小版本内保持 ABI 稳定;
  • 更重要的是, fastcdr 2.1.0 是第一个正式支持 C++20 的版本,而 Iron 的 rclcpp 已开始采用 C++20 特性。

因此,“升级 vendor package”绝不是 apt update && apt upgrade 那么简单。实操中必须执行四步:

  1. 源码验证 :下载 fastcdr 2.1.0 源码,在所有目标平台(Ubuntu 22.04, Windows 10, macOS 12)上编译,确认无警告;
  2. ABI 检查 :使用 abi-dumper 工具生成 2.0.x 和 2.1.0 的 ABI 符号快照,用 abi-compliance-checker 对比,确认无破坏性变更;
  3. 依赖链测试 :编译所有直接依赖 fastcdr 的 ROS 2 包( rmw_fastrtps_cpp , rosidl_generator_cpp ),并运行其全部单元测试;
  4. 端到端验证 :用新 fastcdr 构建一个完整 ROS 2 系统(含 rclcpp , rclpy , rviz2 ),运行 ros2 topic pub /chatter std_msgs/msg/String "{data: 'hello'}" ,确认消息能正确序列化/反序列化。

这个过程通常耗时 3-5 天。我曾因跳过第 2 步(ABI 检查),在 Windows 上导致 rmw_fastrtps_cpp 的 DLL 导出符号缺失,引发 rcl 初始化失败——错误信息极其晦涩:“Failed to load library: The specified procedure could not be found.”,最终溯源到 fastcdr 的一个内联函数被意外导出。

3.4 测试用例:不是“越多越好”,而是“精准打击”

流程表中“Create test cases”看似简单,但 ROS 2 的测试策略有独特哲学: 分层防御(Defense in Depth)

  • 单元测试(Unit Test) :覆盖单个函数/类,由包维护者编写。要求:100% 覆盖所有公共 API 路径,包括错误分支。 rcl 包的单元测试中,有专门针对 malloc 失败的模拟测试——通过 LD_PRELOAD 注入故障,验证内存分配失败时的优雅降级。

  • 集成测试(Integration Test) :验证多个包协同工作。例如 test_communication 测试套件,会启动 rclcpp rclpy 节点,跨语言发布/订阅同一话题,测量端到端延迟、丢包率、序列化开销。这类测试必须在所有目标平台上运行,因为网络栈行为差异巨大(Linux 的 epoll vs Windows 的 IOCP )。

  • 系统测试(System Test) :模拟真实机器人场景。最著名的是 ros2_control test_system :它会启动一个虚拟的双轮差速机器人模型,注入随机电机噪声、传感器延迟、网络丢包,然后运行完整的导航栈( nav2 ),验证控制环路的鲁棒性。这类测试不追求代码覆盖率,而追求 场景覆盖率

关键经验: 测试用例的价值不在于数量,而在于“变异能力”(Mutation Capability) 。一个优秀的测试用例,应该能通过参数化,轻易生成数百个变体。例如 test_rclcpp 中的 timer_test ,通过一个 YAML 配置文件,可以自动生成 27 种不同精度、不同周期、不同线程模型的定时器组合测试。这让我们能在发布前,用有限的 CI 资源,穷举绝大多数真实场景。

注意:永远不要相信“本地测试通过”。ROS 2 的 CI 环境是高度受限的:无网络访问(防止测试偷偷调用外部 API)、无 GUI( rviz2 测试必须用 offscreen 渲染器)、CPU 限制(防止测试耗尽资源)。我们曾有一个测试在本地完美运行,但在 CI 中因 ulimit -v (虚拟内存限制)被 kill——因为测试创建了过多的 std::thread 对象。解决方案不是放宽限制,而是重构测试,使用 std::jthread (C++20)自动管理线程生命周期。

4. 实操过程:从分支创建到最终发布,一场 60 天的极限冲刺

4.1 分支创建(Branch from Rolling Ridley):新生命的“剖腹产”

“Branch from Rolling Ridley” 这一步,常被浪漫化为“新发行版的诞生时刻”。但实操中,它更像一场精心策划的“剖腹产”——既要确保新生儿健康,又要保护母体(Rolling)继续发育。

具体操作不是简单的 git checkout -b humble-devel origin/rolling 。它包含三个原子动作:

  1. 冻结 Rolling 的“快照点” :ROS Boss 会选定一个 Rolling 分支的特定 commit(如 rolling 分支的 a1b2c3d ),该 commit 必须满足:所有核心包( rcl , rmw , builtin_interfaces )的 CI 全绿,且无未关闭的 P0/P1 issue。这个 commit hash 就是新发行版的“基因起点”。

  2. 创建发行版分支 :在 ros2/ros2 元仓库中,执行 git checkout -b humble-devel a1b2c3d 。但这只是开始。紧接着,必须为 每个核心包 单独创建分支:

    # 在 rcl 仓库中
    git checkout -b humble-devel a1b2c3d
    git push upstream humble-devel
    # 在 rmw_implementation 仓库中
    git checkout -b humble-devel a1b2c3d
    git push upstream humble-devel
    

    为什么不能只在一个仓库操作?因为 ROS 2 的包是独立版本化的。 rcl humble-devel 分支可能基于 a1b2c3d ,而 rmw_fastrtps_cpp humble-devel 分支可能基于 e4f5g6h (一个稍晚的 Rolling commit),只要它满足 RMW 冻结要求。

  3. 更新元数据 :在 ros2/ros2 仓库的 ros2.repos 文件中,将所有核心包的 version 字段从 rolling 改为 humble-devel ,并提交 PR。这个 PR 是发行版分支的“出生证明”,它必须被 TSC 批准后,整个分支才算正式激活。

这个过程最危险的陷阱是: 分支创建后,Rolling 分支仍在高速演进,而新分支的 CI 尚未完全就绪。 我们曾发生过一次事故:在 Humble 分支创建后 2 小时,Rolling 上一个 rcl 的 PR 被合并,它修改了一个 #define 宏。由于 Humble 分支的 CI job 尚未配置好,这个宏变更意外进入了 Humble 的早期构建,导致后续所有基于 Humble 的包编译失败。解决方案是:分支创建后,立即在所有核心包的 humble-devel 分支上,添加一个“守护 PR”(Guardian PR),其唯一作用是:在每次推送时,检查 #define typedef class 等 ABI 关键元素是否与分支创建时的快照一致。这个 PR 由 CI 自动触发,是新发行版的“免疫系统”。

4.2 发布准备期(Final Release Preparations):二进制包的“临产护理”

“Final release preparations” 是整个流程中最紧张的阶段,持续约 60 天。它不是等待,而是对二进制包进行“临产护理”。核心任务有三项:

  • 二进制包签名与验证 :所有 .deb .tar.gz 包,必须用 Open Robotics 的 GPG 私钥签名。但签名前,必须进行 完整性审计

    1. 解压 .deb 包,检查 DEBIAN/control 文件中的 Version 字段是否符合 X.Y.Z-1jammy.0 格式( X.Y.Z 是发行版版本, 1jammy.0 是构建序号);
    2. 使用 dpkg-deb --contents 列出所有文件,确认无意外的 /tmp/ /home/ 路径残留(这表示构建环境污染);
    3. usr/lib/x86_64-linux-gnu/librcl.so 运行 readelf -d ,验证 SONAME 是否为 librcl.so.1 (符合 ABI 版本约定)。

    我们曾拦截一个严重问题:一个 rcl .deb 包中, librcl.so.1 DT_RUNPATH 包含了 /opt/ros/rolling/lib ,这会导致运行时错误地加载 Rolling 版本的库。根源是构建脚本中一个 CMAKE_INSTALL_RPATH 设置错误。

  • 文档同步 :发行版文档页必须与二进制包完全同步。自动化脚本会扫描所有包的 package.xml ,提取 <description> <maintainer> <license> 字段,自动生成文档页的“Package Index”。但人工审核不可替代:必须确认 rclcpp 的文档中,“NodeOptions”章节的示例代码,能用 Humble 的 rclcpp 头文件成功编译。我们有个固定流程:随机抽取 5 个核心包,手动 clone 其 humble-devel 分支,运行 colcon build --packages-select <pkg> --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo ,然后 source install/setup.bash ,最后 ros2 pkg list | grep <pkg> 验证安装成功。

  • 发布仓库(Release Repository)预热 :最终的 .deb 包将上传到 packages.ros.org 。但上传前,必须在 staging 仓库( staging-packages.ros.org )进行 72 小时“静默期”:

    • 所有包上传后,CI 会自动触发一个“Staging Validation” job,它会:
      • 下载 staging 仓库中所有 Humble 的 .deb 包;
      • 在 Ubuntu 22.04 VM 中,执行 apt update && apt install ros-humble-desktop
      • 运行 ros2 run demo_nodes_cpp talker ros2 run demo_nodes_py listener ,验证基础通信;
      • 执行 ros2 pkg list | wc -l ,确认安装的包数量与预期一致(Humble Desktop 应为 187 个)。
    • 只有这个 job 连续 72 小时通过,staging 仓库才会被 promote 到 production。

    这个静默期救了我们多次。最典型的一次:在 Iron staging 期间, ros-iron-rviz2 .deb 包被发现缺少 librviz_common.so 的符号链接,导致 rviz2 启动失败。问题在 staging 期被 CI 捕获,避免了发布后的大规模用户故障。

4.3 发布日(Release Day):世界海龟日的“心跳监测”

“Release Day” 定在 5 月 23 日(World Turtle Day),这不仅是情怀,更是工程考量:它给了发布团队一个明确的、不可更改的终点线,迫使所有准备工作必须在此之前完成。

发布日当天的操作,是一场毫秒级的“心跳监测”:

  1. T-30 分钟 :ROS Boss 向 TSC 发送最终确认邮件,附上 staging 仓库的验证报告、所有核心包的 CI 状态截图、以及 EOL 日期的法律确认函。TSC 成员有 30 分钟投票权,一票否决即中止发布。

  2. T-5 分钟 :所有构建农场的 CI job 被手动暂停,防止任何新构建污染发布状态。

  3. T=0 :执行 promote-staging-to-production 脚本。该脚本会:

    • 将 staging 仓库的 Packages.gz Release 等元数据文件,复制到 production 仓库;
    • 更新 packages.ros.org 的 DNS 记录,指向新的存储桶;
    • 触发一个“Release Health Check” job,它会在全球 5 个地理位置(东京、法兰克福、纽约、圣保罗、悉尼)的测试服务器上,并行执行:
      curl -s https://raw.githubusercontent.com/ros2/ros2/rolling/ros2.repos | \
        vcs import src < /dev/stdin
      colcon build --symlink-install
      source install/setup.bash
      ros2 run demo_nodes_cpp talker &
      sleep 2
      ros2 topic echo /chatter | head -n 1 | grep "data: 'Hello World'"
      
  4. T+1 分钟 :Health Check job 的 5 个实例全部返回成功,发布完成。此时,全球任何一台联网的 Ubuntu 22.04 机器,执行 sudo apt update && sudo apt install ros-iron-desktop ,都能在 2 分钟内完成安装。

实操心得:发布日最考验的是“应急预案”。我们为 Iron 发布准备了三套预案:

  • Plan A(默认) :所有 Health Check 通过,按流程发布;
  • Plan B(区域故障) :如果仅 1-2 个地区 Health Check 失败,立即隔离该地区 DNS,发布继续,同时启动根因分析;
  • Plan C(全局失败) :如果 3 个以上地区失败,立即中止,回滚到 staging,启动“发布战情室”(War Room),由 ROS Boss 主持,所有核心开发者视频会议,2 小时内必须定位并修复。

这个预案在 Humble 发布时被启用过:东京节点因 CDN 缓存问题, curl 下载 ros2.repos 超时。我们执行 Plan B,隔离东京 DNS,发布顺利完成。事后复盘发现,是 CDN 的缓存 TTL 设置过长,已永久修正。

5. 常见问题与排查技巧实录:来自发布前线的血泪笔记

5.1 RMW 冻结后,我的包编译失败,怎么办?

现象 :在 RMW 冻结后,你的包(如 my_robot_driver )在 build farm 上编译失败,错误信息类似:

error: ‘rmw_publisher_options_t’ has no member named ‘avoid_ros_namespace_conventions’

根因分析 :这不是你的包的问题,而是你依赖的 rmw 实现(如 rmw_fastrtps_cpp )版本与冻结分支不匹配。RMW 冻结后, rmw 的 C API 结构体(如 rmw_publisher_options_t )被锁定,但你的 CMakeLists.txt 中可能指定了 find_package(rmw_fastrtps_cpp REQUIRED) ,而 rmw_fastrtps_cpp humble-devel 分支尚未发布,CMake 找到了 Rolling 分支的头文件。

排查步骤

  1. 登录 build farm 的失败 job 页面,点击 “Console Output”,搜索 -- Found rmw_fastrtps_cpp ,确认找到的路径是 .../src/rmw_fastrtps_cpp 还是 .../install/rmw_fastrtps_cpp
  2. 如果是前者(源码路径),说明你的 CMakeLists.txt find_package 没有指定 REQUIRED ,或 ament_cmake 的查找逻辑被干扰;
  3. 如果是后者(安装路径),检查 rmw_fastrtps_cpp package.xml ,确认其 <version> 是否为 humble-devel 的版本(如 6.2.0 ),而非 rolling 的版本(如 6.3.0 )。

终极解决方案 :在你的 CMakeLists.txt 中,强制指定 rmw 实现版本:

# 替换原来的 find_package(rmw_fastrtps_cpp REQUIRED)
find_package(rmw_fastrtps_cpp REQUIRED CONFIG
  HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../rmw_fastrtps_cpp
  PATHS /opt/ros/humble
)

并在 package.xml 中,显式声明依赖:

<depend>rmw_fastrtps_cpp</depend>
<exec_depend>rmw_fastrtps_cpp</exec_depend>

5.2 Windows 上 vendor package 升级后,链接失败(LNK2001)

现象 :升级 tinyxml2 到 9.0.0 后, rcl 在 Windows 上链接失败:

rcl.lib(node_options.obj) : error LNK2001: unresolved external symbol "public: __cdecl tinyxml2::XMLDocument::XMLDocument(bool)" (??0XMLDocument@tinyxml2@@QEAA@_N@Z)

根因 tinyxml2 9.0.0 默认启用 TINYXML2_EXPORT 宏,将其编译为 DLL。但 rcl 的 CMakeLists.txt 中, find_package(tinyxml2 REQUIRED) 默认链接静态库 tinyxml2_static.lib ,而符号 XMLDocument::XMLDocument(bool) 在 DLL 中导出,在静态库中未定义。

排查技巧 :使用 dumpbin /exports tinyxml2.lib 查看静态库导出的符号,对比 dumpbin /exports tinyxml2.dll 。你会发现,DLL 导出的是 ?XMLDocument@tinyxml2@@QEAA@_N@Z (mangled name),而静态库导出的是 ?XMLDocument@tinyxml2@@QEAA@_N@Z 的另一个变体。

解决方案 :在 rcl CMakeLists.txt 中,强制使用 DLL:

# 在 find_package(tinyxml2 REQUIRED) 之后
set(TINYXML2_USE_STATIC_LIBS OFF CACHE BOOL "" FORCE)
find_package(tinyxml2 REQUIRED CONFIG)

并确保 tinyxml2 的构建选项 BUILD_SHARED_LIBS=ON

5.3 构建农场 CI 失败,但本地复现不了

现象 :build farm 显示 test_rclcpp.test_timer 失败,错误是 Timer callback not called within timeout ,但你在本地 Ubuntu 22.04 上运行 colcon test --packages-select rclcpp 全部通过。

根因 :CI 环境的 CPU 资源受限(通常为 2 vCPU),而 test_timer 的超时阈值(默认 100ms)在低资源环境下不够宽松。 rclcpp 的定时器精度依赖 clock_gettime(CLOCK_MONOTONIC) ,在 vCPU 抢占严重时,实际睡眠时间可能超过预期。

排查技巧 :在 build farm 的失败 job 页面,点击 “Re-run job with debug log”,然后在 Console Output 中搜索 timer_test ,你会看到详细的计时日志:

[INFO] Timer created with period 10ms
[INFO] Actual sleep duration: 105
Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐