
1. 为什么要把自己的包推进 ROS2 官方源很多人第一次听到发布 ROS2 官方包这件事第一反应是我把代码丢到 GitHub 上别人 clone 下来编译不就行了吗为什么还要费劲走 Bloom、rosdistro 这一整套流程这个问题我当年也问过自己直到有一次团队里一个新人为了用我写的一个小工具包光是配环境、拉依赖、解决编译报错就折腾了一下午我才真正意识到官方源的价值。官方源的本质是把编译这件事从用户端搬到了构建农场端。用户只需要一条apt install命令二进制包就直接装好了不用管你的包依赖了哪些库、用了哪个版本的 CMake、编译时要不要加什么特殊 flag。对于机器人项目来说这一点尤其重要——ROS2 的依赖树往往很深一个包可能间接依赖几十个其他包让每个用户自己去解决这些依赖体验是灾难性的。从更实际的角度看进官方源还意味着几件事。第一是版本可追溯你的包会和特定的 ROS2 发行版比如 Humble、Jazzy绑定用户装哪个版本的系统就对应哪个版本的包不会出现我这边能编译你那边不行的扯皮。第二是持续集成构建农场会在多个平台amd64、arm64上自动构建等于免费帮你做了跨平台验证。第三是可信度背书能进官方源的包至少说明它的构建配置是规范的、依赖声明是完整的这对开源项目的传播帮助很大。不过我得先把丑话说在前面这条路并不轻松。从零到一个包真正出现在packages.ros.org上中间要过好几道关卡任何一道卡住都可能让你卡壳好几天。我自己第一个包从开始折腾到真正apt装上前后花了差不多两周其中大部分时间不是在写代码而是在和各种配置、CI 报错、review 意见搏斗。所以这篇文章我不会只给你一条理想路径而是把每一步的坑、每个决策背后的原因都讲清楚让你少走弯路。这篇文章适合两类人一类是已经写好了 ROS2 功能包、想把它分享给更多人的开发者另一类是纯粹想搞明白 ROS2 生态是怎么运转的、想深入理解这套发布机制的学习者。不管你是哪一类只要跟着走一遍你对 ROS2 整个工具链的理解都会上一个台阶。2. 发布前的硬性门槛你的包得先合格在动手走发布流程之前有一件事必须先做确认你的包本身是可发布的。我见过太多人兴冲冲地开始搞 Bloom结果卡在第一步——包的结构根本不符合规范。这不是 Bloom 的问题是包本身的问题。所以这一节我们先把自己的包体检一遍。2.1 包结构必须符合 ament 规范ROS2 用的是 ament 构建系统底层还是 CMake但包管理和依赖声明换了一套。一个标准的 ROS2 包目录结构大概长这样my_awesome_pkg/ ├── package.xml ├── CMakeLists.txt # C 包 ├── setup.py / setup.cfg # Python 包 ├── include/my_awesome_pkg/ ├── src/ ├── launch/ └── README.md这里最容易出问题的是package.xml。它是整个包的身份证Bloom 和构建农场都靠它来识别你的包。几个必须检查的点name必须和目录名一致。目录叫my_awesome_pkgpackage.xml里的 name 也必须是my_awesome_pkg大小写、下划线都不能差。version必须是合法的版本号格式是MAJOR.MINOR.PATCH比如0.1.0。Bloom 会读这个字段格式不对直接报错。maintainer必须填真实有效的邮箱。这个邮箱会公开显示在包的元数据里构建农场出问题时会往这个邮箱发通知。我建议用一个你经常看的邮箱别填个废弃的。license不能空着。开源包一般用 Apache-2.0 或 BSD这个字段会影响到能不能进官方源。依赖声明要完整。depend、build_depend、exec_depend、test_depend这些标签要分清楚。漏声明依赖是新手最常见的错误本地能编译是因为你机器上恰好装了那个库但构建农场是干净环境一编译就挂。我踩过的一个坑是本地开发时随手apt install了一个库代码里用了它但package.xml里忘了写。本地编译一路绿灯提交到构建农场直接失败报错信息还特别隐晦找了大半天才发现是依赖没声明。所以我的建议是在提交前用一个干净的容器环境比如官方提供的ros:humble镜像跑一遍编译能过再往下走。2.2 版本号和 tag 的对应关系ROS2 官方源的发布机制是基于 git tag 的。也就是说Bloom 不是直接读你主分支的代码而是读你打的 tag。这里有个约定tag 的名字必须是包名/版本号的格式比如my_awesome_pkg/0.1.0。为什么是这个格式因为一个仓库里可能放多个包monorepo用包名做前缀才能区分是哪个包的哪个版本。如果你一个仓库只有一个包也照样要遵守这个格式别偷懒只写0.1.0Bloom 认不出来。版本号的选择也有讲究。第一次发布建议从0.1.0开始别一上来就1.0.0。因为1.0.0在语义化版本里意味着 API 稳定而你刚发布的包大概率还会改接口。用0.x.y给自己留点余地等接口稳定了再升到1.0.0。提示每次发布新版本都要更新package.xml里的version然后打一个新的 tag。版本号只能往上走不能回退也不能重复。如果你打错了 tag 想删掉重打要小心——如果这个 tag 已经被 Bloom 处理过重新用同一个版本号会出问题稳妥的做法是升一个 patch 版本号。2.3 用catkin_pkg和rosdep做本地自检在正式走流程前有两个工具能帮你提前发现问题。第一个是catkin_pkg它能解析你的package.xml检查格式是否合法pip install catkin_pkg python3 -c from catkin_pkg.package import parse_package; parse_package(package.xml)如果package.xml有问题这条命令会直接报错比等到 Bloom 阶段再发现要省事得多。第二个是rosdep用来检查依赖声明是否完整。在干净环境里跑rosdep check --from-paths . --ignore-src它会列出所有没被满足的依赖。如果某个依赖你没在package.xml里声明但代码里用了rosdep不一定能直接发现它只能检查你声明了的但至少能帮你确认声明了的依赖都能被正确解析。3. Bloom 到底帮你做了什么很多人对 Bloom 的理解停留在一个发布工具但具体它做了什么、为什么需要它其实说不清楚。搞清楚这一点后面遇到报错你才知道该往哪个方向查。3.1 Bloom 的核心职责生成 release 仓库Bloom 做的事情用一句话概括它把你的源码仓库转换成一个专门用于发布的 release 仓库。这个 release 仓库里放的不是你的源码而是一堆发布元数据——包括debian/目录下的打包配置、tracks.yaml发布轨道配置等等。为什么要多这一层因为源码仓库和发布仓库的职责是不一样的。源码仓库你天天改分支乱七八糟发布仓库是稳定的、只记录哪个版本对应哪个 tag的映射关系。构建农场只认发布仓库这样你源码仓库怎么折腾都不影响已经发布的版本。Bloom 的工作流程大致是读取你源码仓库的package.xml确认包名和版本。在本地生成 release 仓库的元数据。把这些元数据推送到一个独立的 release 仓库通常叫repo.git对应的repo-release。后续每次发新版都是往这个 release 仓库里追加一条记录。我第一次跑 Bloom 的时候看到它生成了那么多文件心里直犯嘀咕这些文件我能改吗答案是——大部分不要手动改。Bloom 生成的配置是模板化的你手动改了下次 Bloom 再跑可能就覆盖了。真正需要你介入的是少数几个配置文件后面会讲。3.2 安装 Bloom 与前置依赖Bloom 本身是个 Python 工具装起来不复杂但有几个前置条件sudo apt install python3-pip python3-venv pip3 install bloom装完之后验证一下bloom --version如果报错说找不到命令多半是 pip 装的脚本没在 PATH 里检查一下~/.local/bin是否在 PATH 中。除了 Bloom你还需要一个 GitHub 账号并且配置好 SSH key 或者 personal access token。因为 Bloom 要往你的 release 仓库推代码没有认证是推不上去的。我建议用 SSH key配置一次一劳永逸比每次输 token 省事。还有一个容易被忽略的点你的源码仓库必须有一个合法的package.xml在仓库根目录或者能被 Bloom 找到的位置。如果包在子目录里Bloom 需要你告诉它包在哪这个在bloom-release的交互流程里会问到。3.3bloom-release交互流程逐项拆解真正开始发布是跑这条命令bloom-release --rosdistro humble --track humble my_awesome_pkg这里的--rosdistro humble指定目标发行版--track humble指定发布轨道track。track 的概念后面细说第一次发布一般就用发行版名字当 track 名。命令跑起来后Bloom 会问你一连串问题我逐个解释Release repository url你的 release 仓库地址。第一次发布时这个仓库还不存在Bloom 会问你要不要创建一般填gitgithub.com:你的用户名/repo-release.git。Upstream repository url你的源码仓库地址。Upstream repository version源码仓库的版本一般填master或main。Package listBloom 会自动扫描出仓库里的包确认一下包名对不对。Version当前要发布的版本号Bloom 会从package.xml里读出来确认即可。这些问题里最容易出错的是仓库地址。SSH 地址和 HTTPS 地址格式不一样填错了 Bloom 推不上去。我建议统一用 SSH 格式并且提前在本地测试一下git ls-remote url能不能通。跑完这一轮Bloom 会在本地生成 release 仓库的内容并尝试推送到远程。如果推送成功你会看到 release 仓库里多了一堆文件。这时候先别急着高兴真正的考验还在后面。4. 构建农场与 rosdistro包是怎么上线的Bloom 推送完 release 仓库只是完成了登记。包真正变成apt能装的东西还要经过构建农场编译、rosdistro 索引这两步。这一节讲清楚这中间的链路。4.1 构建农场是怎么被触发的ROS2 官方用的是 ROS Build Farm它监听的是 rosdistro 仓库里的配置。当你的包被登记到 rosdistro 后构建农场会定期扫描发现有新版本就自动拉取 release 仓库的代码在多个平台上编译。这里有个关键概念叫release track。track 决定了你的包在哪个发行版、哪个平台上构建。比如humbletrack 对应 Humble 发行版构建农场会为这个 track 生成 amd64 和 arm64 的二进制包。构建农场编译失败是家常便饭尤其是第一次。失败原因五花八门依赖没装、CMake 版本不对、代码里有平台相关的写法等等。每次失败构建农场会往你package.xml里填的 maintainer 邮箱发通知邮件里会有构建日志的链接。一定要认真看日志别只看最后一行报错往往真正的错误在前面几百行。我印象最深的一次失败是代码里用了一个 C17 的特性但构建农场默认用的是 C14。本地编译没问题是因为我的 CMakeLists 里手动指定了 C17但那个指定写在了if(CMAKE_COMPILER_IS_GNUCXX)分支里构建农场用的编译器不满足这个条件就跳过了。这种问题不看完整日志根本发现不了。4.2 rosdistro 索引让 apt 能找到你的包构建农场编译成功后包会被上传到packages.ros.org的仓库里。但这时候apt还不一定能找到它因为还需要更新 rosdistro 的索引。rosdistro 是一个独立的仓库里面有一堆 YAML 文件记录了每个发行版下有哪些包、每个包的版本、release 仓库地址等信息。你的包要出现在索引里需要往 rosdistro 提一个 PR把包的信息加进去。这个 PR 的内容大概是往humble/distribution.yaml里加一段my_awesome_pkg: source: type: git url: https://github.com/你/repo.git version: master release: packages: - my_awesome_pkg tags: release: release/humble/{package}/{version} url: https://github.com/你/repo-release.git version: 0.1.0-1 status: developed这段配置看起来简单但字段一个都不能错。url要指向 release 仓库version要和 release 仓库里的 tag 对应。我第一次提 PR 的时候url填成了源码仓库被 reviewer 指出来才发现。rosdistro 的 PR 会有人 reviewreview 通过后合并索引就更新了。这个过程可能需要几天取决于 reviewer 的响应速度。合并之后apt update一下你的包就能被搜到了。4.3 从提交到apt install的完整时间线把整个链路串起来一个包从你决定发布到用户能apt install大致经历这些阶段阶段操作大致耗时准备检查包结构、打 tag1-2 天Bloom 发布跑 bloom-release推 release 仓库半天构建农场编译自动触发多平台编译几小时到 1 天rosdistro PR提交索引 PR等待 review1-3 天索引生效合并后 apt 可搜到几小时加起来顺利的话一周左右。不顺利的话卡在构建农场编译失败上可能要反复折腾更久。所以心态要放平别指望一天搞定。5. 那些让我卡了半天的报错和它们的根因这一节是这篇文章最有价值的部分。我把发布过程中遇到过的、以及帮别人排查过的典型报错整理出来每个都讲清楚根因和排查思路。这些报错在官方文档里往往只有一句话但实际排查起来要费不少功夫。5.1 Bloom 报 Could not determine release version这个报错通常出现在跑bloom-release的时候。根因一般是package.xml里的version字段格式不对或者 Bloom 找不到package.xml。排查步骤确认package.xml在仓库根目录或者在 Bloom 能扫描到的位置。检查version字段必须是x.y.z格式不能有v前缀不能有-后缀。如果包在子目录跑 Bloom 时要用--package-path指定路径。我遇到过一次是因为package.xml里版本写成了0.1少了一个 patch 位。Bloom 要求必须是三段式改成0.1.0就好了。5.2 构建农场报 Unable to locate package这个报错在构建日志里很常见意思是构建农场在装依赖时找不到某个包。根因通常是你在package.xml里声明了一个依赖但这个依赖在目标发行版里不存在或者名字写错了。排查思路去packages.ros.org上搜一下这个依赖包名确认它在目标发行版里存在。检查依赖名的大小写和下划线ROS2 的包名是大小写敏感的。如果依赖是系统库不是 ROS 包确认它在rosdep的数据库里有对应的 key。有一次我声明了一个依赖叫opencv4但 rosdep 里对应的 key 是libopencv-dev构建农场就找不到。这种问题要去 rosdep 的 base.yaml 里查正确的 key。5.3 rosdistro PR 被 reviewer 打回rosdistro 的 PR 被打回原因通常有几类格式不对YAML 缩进错了或者字段顺序不对。YAML 对缩进极其敏感多一个空格少一个空格都会出问题。信息不一致PR 里写的版本号和 release 仓库里的 tag 对不上。包名冲突你想起的包名已经被别人用了。这种情况只能改名没有别的办法。被打回不要气馁reviewer 的每条意见都是帮你把包做规范。我第一个 PR 被打回了三次每次都是格式问题改完就过了。5.4 版本号回退引发的连锁问题这是个比较隐蔽的坑。假设你发布了0.1.0发现有问题想回退到0.0.9重新发。千万不要这么做。因为构建农场和 rosdistro 都记录了0.1.0这个版本你回退版本号会导致索引混乱用户apt的时候可能装到错误的版本。正确的做法是发布0.1.1在 changelog 里说明修复了什么。版本号只能往前走这是发布机制的硬性约束。注意如果你打错了 tag比如把0.1.0打成了0.1.1而0.1.1还没被 Bloom 处理过可以删掉 tag 重打。但如果已经被处理过就只能升版本号了。判断方法是看 release 仓库里有没有对应的记录。6. 让发布流程可持续的几个习惯第一次发布成功后很多人就松懈了。但发布不是一锤子买卖后续每次更新都要走一遍流程。养成几个好习惯能让后续发布轻松很多。6.1 用 changelog 管理版本变更ROS2 生态里有个工具叫catkin_generate_changelog能根据 git 提交记录自动生成 changelog。虽然它主要是给 catkin 用的但 ament 包也能用类似思路。我的做法是维护一个CHANGELOG.rst每次发版前手动更新写清楚这个版本改了什么、修了哪些 bug、有没有破坏性变更。这个文件会跟着包一起发布用户升级时能看到变更内容体验会好很多。changelog 的格式建议遵循 Keep a Changelog 规范分Added、Changed、Fixed、Removed几个类别。别小看这个习惯等你的包有几十个用户的时候一份清晰的 changelog 能省掉大量这个版本改了啥的询问。6.2 把发布流程脚本化每次发布都要跑一堆命令手动敲容易出错。我后来把整个流程写成了一个 shell 脚本#!/bin/bash set -e PKG_NAMEmy_awesome_pkg NEW_VERSION$1 ROS_DISTROhumble # 更新 package.xml 版本号 sed -i s|version.*/version|version${NEW_VERSION}/version| package.xml # 提交并打 tag git add package.xml CHANGELOG.rst git commit -m Release ${NEW_VERSION} git tag ${PKG_NAME}/${NEW_VERSION} git push origin master --tags # 跑 bloom-release bloom-release --rosdistro ${ROS_DISTRO} --track ${ROS_DISTRO} ${PKG_NAME} --edit这个脚本把版本号更新、提交、打 tag、跑 Bloom 串起来了每次发版只需要./release.sh 0.2.0就行。注意--edit参数它会让 Bloom 打开编辑器让你确认配置避免自动跑出错。6.3 关注构建农场的状态页面构建农场有个状态页面能看到你的包在各个平台上的构建情况。养成定期看一眼的习惯能及早发现平台相关的问题。比如你的包在 amd64 上编译通过但在 arm64 上失败状态页面会直接标红比等用户反馈要快得多。状态页面的地址在 ROS 官网的 build farm 板块能找到这里不贴具体链接你搜 ROS build farm status 就能找到。进去之后按包名搜索能看到历史构建记录和每次的日志。6.4 处理用户反馈的依赖问题包发布出去之后用户可能会反馈各种依赖问题。最常见的是我装了你的包但运行时报找不到某个库。这种情况往往是exec_depend声明不全导致的。处理这类问题的思路是让用户提供rosdep check的输出看看哪个依赖没被满足。如果是你漏声明的补上package.xml发个 patch 版本。如果是用户环境的问题引导他们跑rosdep install补全依赖。我建议在 README 里写清楚安装步骤包括rosdep install --from-paths src --ignore-src -y这一步。很多用户不知道 rosdep 能自动装依赖手动一个个装很容易漏。7. 关于这套机制我的一些真实体会折腾完第一个包之后我对 ROS2 这套发布机制的看法变了不少。一开始觉得它繁琐、门槛高但用久了会发现这套设计的每一环都有它的道理。Bloom 多出来的那一层 release 仓库看似冗余实则是把开发和发布解耦了让源码仓库可以自由折腾而不影响已发布的版本。构建农场的多平台编译虽然经常报错让人抓狂但它确实帮你覆盖了你自己根本测不到的平台。如果你正准备发布自己的第一个包我的建议是别追求一次成功。第一次发布大概率会卡在某个环节这很正常。把每次报错当成学习这套机制的机会搞清楚它为什么这么设计比单纯把包发出去更有价值。等你的包真正出现在packages.ros.org上用户一条apt install就能用上你写的东西时那种成就感是值得的。最后分享一个小心得发布前多花点时间把package.xml和 README 写好。这两个文件是用户接触你包的第一入口package.xml决定了依赖能不能正确解析README 决定了用户会不会用。我见过太多功能很棒但文档稀烂的包用户装上了却不知道怎么用最后只能卸载。把这两样东西做扎实你的包才能真正被人用起来。