1. 为什么我要折腾这件事先交代一下背景。我在一家做移动机器人底盘的公司干了快六年日常跟ROS2打交道从Foxy一路用到Humble。团队里积累了不少内部工具包比如一个专门做轮式里程计标定的节点、一个把雷达点云转成代价地图的辅助库还有一个封装了常见底盘CAN协议驱动的接口层。这些东西平时就在公司内部的GitLab上放着谁要用就clone下来自己编译时间一长问题就来了新人入职光配环境就得折腾两天版本对不上、依赖缺失、编译报错各种鸡毛蒜皮的事反复消耗精力。后来我就想能不能把这些内部包整理一下挑几个通用性强的发布到ROS2官方索引里去这样不管是内部同事还是外部用户直接一句rosdep install加colcon build就能跑起来省掉大量沟通成本。而且说实话把自己写的包挂到官方索引上对个人履历也是一种背书。但真动手之后才发现从本地能编译到官方能索引之间隔着一整套流程规范、元数据要求、CI校验和社区约定。我前后踩了差不多三周的坑提交了四次PR才最终合并。这篇文章就把整个流程从头到尾拆一遍包括每一步为什么要这么做、哪些地方容易翻车、以及我实际踩过的那些坑。如果你手里也有一个自认为还不错的ROS2包想把它推到官方索引里这篇应该能帮你省掉不少来回折腾的时间。提示本文基于ROS2 Humble Hawksbill版本和rosdistro的当前流程撰写不同发行版在细节上可能有差异但核心逻辑一致。2. 先搞清楚发布成官方包到底意味着什么2.1 官方索引的运作机制很多人以为发布ROS2官方包就是把代码传到某个官方仓库然后别人就能apt install了。这个理解只对了一半。ROS2的包分发体系其实分两层第一层是源码索引也就是rosdistro仓库里的.distribution.yaml文件。这个文件记录了每个包的名字、仓库地址、版本分支、依赖关系等元数据。当你在rosdep里执行安装命令时系统就是查这个文件来找到对应的源码仓库。第二层是二进制分发也就是通过ROS build farm构建农场自动编译出deb包推送到APT源里。这一层是自动化的你不需要手动编译deb但前提是你的包已经进入了源码索引并且通过了build farm的编译验证。所以整个发布流程的核心其实是把你的包信息注册到rosdistro的索引文件里然后让build farm能成功编译通过。听起来简单但实际操作中涉及的东西相当多。2.2 什么样的包适合发布不是所有包都适合往官方索引里塞。我总结了几条判断标准通用性包的功能是否只对特定公司或特定硬件有意义如果强依赖某个私有CAN协议或内部SDK那发上去别人也用不了反而增加维护负担。依赖可控所有依赖是否都能通过rosdep解析如果依赖某个不在ROS生态里的第三方库你得先确保它有对应的rosdep key或者你自己去贡献一个。许可证合规代码的license必须是OSI认可的开源许可证Apache 2.0、MIT、BSD这些都没问题。公司内部代码要发布的话得先过法务。维护意愿发布之后你得持续维护至少保证在新发行版出来时能及时适配。如果只是发完就不管了build farm挂了也没人修那还不如不发。我最终选了三个包出来一个是纯头文件的坐标变换工具库一个是基于话题的里程计标定节点还有一个是底盘驱动的抽象接口层。这三个包的共同特点是依赖干净、功能通用、不涉及公司核心业务逻辑。2.3 整体流程概览整个流程可以拆成这么几个阶段代码整理确保包结构规范、package.xml完整、CMakeLists.txt正确仓库准备代码托管在公开仓库分支策略清晰rosdep key处理确保所有依赖都有对应的key提交rosdistro PR修改distribution.yaml添加包信息CI校验与build farm编译等待自动检查通过后续维护版本更新、新发行版适配下面我逐个阶段展开讲。3. 代码整理阶段把包收拾干净再出门3.1 package.xml的规范化package.xml是ROS2包的身份证官方索引和build farm都靠它来识别包的元信息。很多人本地写的时候比较随意但要发布到官方这个文件必须严格规范。一个合格的package.xml至少包含这些字段?xml version1.0? ?xml-model hrefhttp://download.ros.org/schema/package_format3.xsd schematypenshttp://www.w3.org/2001/XMLSchema? package format3 nameodom_calibrator/name version0.1.0/version descriptionA ROS2 node for wheel odometry calibration based on topic messages./description maintainer emailyour.emailexample.comYour Name/maintainer licenseApache-2.0/license buildtool_dependament_cmake/buildtool_depend dependrclcpp/depend dependnav_msgs/depend dependgeometry_msgs/depend dependtf2_ros/depend test_dependament_lint_auto/test_depend test_dependament_lint_common/test_depend export build_typeament_cmake/build_type /export /package几个容易出问题的地方我单独说一下description字段不能太短也不能包含特殊字符。我第一次提交时写的是calibration tool结果CI直接报错说描述信息不够充分。后来改成了完整的一句话描述才通过。这个字段会显示在官方索引页面上所以要写得清楚明白。maintainer邮箱必须是真实可用的。build farm在编译失败时会往这个邮箱发通知如果邮箱无效出了问题你根本不知道。license必须和仓库里的LICENSE文件一致。我见过有人package.xml写Apache-2.0但仓库里放的是GPL的LICENSE文件这种不一致会被CI直接拦下来。version字段建议从0.1.0开始遵循语义化版本规范。不要一上来就写1.0.0除非你的API已经稳定到可以承诺兼容性。3.2 CMakeLists.txt的常见坑CMakeLists.txt是另一个高频出错点。ROS2的ament构建系统对CMakeLists.txt有一些约定俗成的要求不满足的话build farm编译会失败。首先find_package的顺序和依赖声明要匹配。比如你package.xml里声明了tf2_ros依赖那CMakeLists.txt里就必须有对应的find_package(tf2_ros REQUIRED)。我遇到过一次本地编译能过是因为工作空间里恰好有其他包提供了tf2_ros但build farm是干净环境直接就找不到。其次安装规则要写全。头文件、可执行文件、launch文件、配置文件、甚至README该install的都要install。build farm编译完之后会检查安装产物如果某个文件没被install运行时就会找不到。install(TARGETS odom_calibrator_node DESTINATION lib/${PROJECT_NAME} ) install(DIRECTORY launch config DESTINATION share/${PROJECT_NAME} ) install(DIRECTORY include/ DESTINATION include )还有一个细节是ament_export_dependencies。如果你的包是给别人用的库需要导出依赖否则下游包链接时会找不到头文件路径。3.3 代码风格与lint检查ROS2官方对代码风格有明确要求主要是基于ament_lint系列工具。你需要在CMakeLists.txt里加上lint测试if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() endif()然后在package.xml里加上对应的test_depend。这样在build farm编译时会自动跑cpplint、cppcheck、uncrustify、lint_cmake等检查。任何一个不过整个编译就失败。我在这上面栽过跟头。我的代码里有一些行超过了120个字符本地编译时没注意因为lint测试默认不跑。但build farm会跑结果直接报了一堆格式错误。后来我本地先跑一遍colcon test把所有lint问题修完才提交。实操心得提交之前务必在本地干净工作空间里跑一次完整的colcon build加colcon test确保所有lint测试通过。这一步能帮你省掉至少一轮PR来回。4. 仓库准备与rosdep key处理4.1 仓库结构的最佳实践官方索引对仓库结构没有强制要求但有一些约定俗成的做法能让你的包更容易被接受。最常见的是单仓库单包结构也就是一个GitHub仓库里只放一个ROS2包。这种结构最简单rosdistro配置也最直接。如果你的仓库里有多个包需要在distribution.yaml里用packages字段逐个列出稍微麻烦一点但也能做。仓库的根目录下建议放这些文件README.md说明包的功能、安装方法、使用示例LICENSE开源许可证全文.gitignore排除build、install、log等目录CHANGELOG.rst版本变更记录可选但推荐分支策略上我建议至少维护一个main分支作为开发分支然后为每个ROS2发行版开一个对应的分支比如humble、iron、jazzy。rosdistro的distribution.yaml里会指定每个发行版对应哪个分支。这样做的好处是不同发行版的API差异可以隔离不会因为适配新版本而破坏老版本的编译。4.2 rosdep key的坑这是整个流程里最容易被低估的环节。rosdep是ROS的依赖管理工具它维护了一个从依赖名到系统包名的映射表。你的package.xml里声明的每个depend都必须能在rosdep里找到对应的key否则build farm编译时会报cannot resolve dependency。ROS2核心包和常见第三方库的key都已经有了比如rclcpp、nav_msgs、eigen、boost这些。但如果你依赖了某个比较冷门的库就可能没有对应的key。我遇到的情况是我的标定节点依赖了一个做非线性优化的库这个库不在ROS生态里。解决办法有两个一是自己贡献一个rosdep key。这需要往rosdistro仓库的rosdep/base.yaml或python.yaml里提交PR添加一条映射规则。这个PR的审核周期可能比较长而且需要你提供该库在各个平台上的包名信息。二是把依赖打包进你的仓库。如果那个库不大可以直接把源码放到你的仓库里作为子目录然后在CMakeLists.txt里用add_subdirectory引入。这样就不需要rosdep key了但会增加仓库体积和维护成本。我最终选了第一种方案因为那个优化库在Ubuntu的apt源里有现成的包只是rosdep还没收录。提交key的PR大概等了一周多才合并。注意在提交rosdistro PR之前一定要先确认所有依赖的rosdep key都已经存在。否则你的PR会被打回让你先解决依赖问题。4.3 版本号与tag管理rosdistro的distribution.yaml里需要指定一个版本号这个版本号会跟你的仓库tag关联。建议每次发布新版本时在仓库里打一个tag格式如0.1.0或v0.1.0然后在distribution.yaml里引用这个版本。版本号的更新不需要每次都改distribution.yaml。build farm会定期检查你的仓库是否有新tag如果有就会自动触发编译。但distribution.yaml里的版本号需要手动更新否则索引页面上显示的版本会过时。5. 提交rosdistro PR的完整实操5.1 Fork与克隆rosdistro仓库rosdistro仓库在GitHub上地址是ros/rosdistro。你需要先fork到自己账号下然后clone到本地git clone https://github.com/your-username/rosdistro.git cd rosdistro git remote add upstream https://github.com/ros/rosdistro.git仓库里跟ROS2相关的主要是humble/distribution.yaml、iron/distribution.yaml这些文件。你要修改的是你目标发行版对应的那个文件。5.2 编辑distribution.yaml在distribution.yaml里每个包对应一个条目。你需要添加的内容大概长这样odom_calibrator: source: type: git url: https://github.com/your-username/odom_calibrator.git version: humble status: maintained几个字段的含义source.type源码类型一般是gitsource.url仓库地址必须是公开可访问的source.version分支名或tag名建议用分支名方便后续更新status维护状态maintained表示活跃维护developed表示开发中end-of-life表示停止维护添加的位置有讲究。distribution.yaml里的包是按字母序排列的你得插到正确的位置否则CI会报格式错误。我第一次提交时随手加在文件末尾结果CI直接说排序不对。5.3 提交PR与CI校验提交PR之后会自动触发一系列CI检查。这些检查包括YAML格式校验确保文件语法正确排序校验确保包名按字母序排列仓库可访问性校验确保url能正常clonerosdep key校验确保所有依赖都能解析如果任何一项不过PR页面上会显示红色的叉你需要修复后重新push。我前三次PR分别因为排序错误、rosdep key缺失、仓库权限问题被打回第四次才全部通过。CI通过之后会有维护者来review你的PR。Review的内容主要是看包的功能是否通用、命名是否规范、描述是否清晰。如果没问题就会合并然后build farm会在下一次构建周期里尝试编译你的包。5.4 build farm编译与结果查看build farm的编译状态可以在build.ros.org上查看。搜索你的包名能看到各个平台的编译结果。绿色表示成功黄色表示警告红色表示失败。如果编译失败点击进去能看到详细的日志。常见的失败原因包括失败原因典型日志关键词解决方法依赖缺失cannot find package检查package.xml和rosdep key编译错误error:本地复现并修复代码lint不通过cpplint/cppcheck本地跑colcon test修复安装规则缺失file not found补全install规则许可证问题license mismatch统一package.xml和LICENSE我遇到过一次编译失败日志显示某个头文件找不到。排查后发现是CMakeLists.txt里漏了ament_export_include_directories导致下游包链接时找不到头文件路径。补上之后重新触发编译就过了。6. 常见问题与排查技巧实录6.1 依赖解析失败怎么办这是最高频的问题。表现是build farm编译时报cannot resolve dependency或者rosdep key not found。排查步骤在本地干净环境里跑rosdep check --from-paths src --ignore-src看哪些依赖解析不了对于解析不了的依赖去rosdistro/rosdep/base.yaml里搜索是否有对应的key如果没有考虑自己贡献key或者把依赖打包进仓库如果有但版本不匹配检查package.xml里的依赖名是否拼写正确我踩过的一个坑是package.xml里写的是dependopencv/depend但rosdep里的key其实是opencv2。这种命名不一致的问题很隐蔽本地编译时因为系统里装了opencv所以能过但build farm解析依赖时就挂了。6.2 编译通过但运行时报错有时候build farm编译能过但用户安装后运行时报错。这种情况通常是运行时依赖没声明清楚。比如你的代码在运行时需要读取某个配置文件但CMakeLists.txt里没有把这个文件install到share目录。编译时不会报错但运行时找不到文件。解决办法是在CMakeLists.txt里补全所有运行时需要的资源文件install(DIRECTORY config launch rviz DESTINATION share/${PROJECT_NAME} )另外如果代码里用了pluginlib或者class_loader需要在package.xml里用export标签导出插件描述文件否则运行时加载插件会失败。6.3 PR被打回的常见原因根据我的经验和观察其他PR的情况被打回的原因主要有这几类命名不规范包名包含大写字母、下划线开头、或者跟已有包重名。ROS2包名要求全小写单词间用下划线分隔。描述太简略description字段只有一两个词维护者会要求补充。仓库没有LICENSE或者LICENSE跟package.xml里的license字段不一致。分支策略混乱distribution.yaml里指定的分支不存在或者分支里没有对应的package.xml。依赖了私有库依赖了某个不公开的仓库或SDK这种直接会被拒。6.4 版本更新的正确姿势包发布之后后续更新版本时不需要重新提交rosdistro PR除非你要改仓库地址或分支名。你只需要在仓库里打新tagbuild farm会自动检测并触发编译。但distribution.yaml里的版本号需要手动更新。这个更新可以通过提交PR来完成也可以等维护者定期批量更新。我一般是在打tag之后顺手提一个PR更新版本号保持索引信息准确。实操心得建议在仓库的README里加一个build farm的状态徽章这样用户一眼就能看到当前编译状态。徽章可以从build.ros.org获取。7. 我踩过的那些坑与最终建议回过头看整个流程最难的不是技术本身而是对规范的理解和细节的把控。我前后花了三周多其中大部分时间不是在写代码而是在修各种格式问题、依赖问题和CI报错。如果让我给后来者几条建议我会说第一先在本地模拟build farm的环境。用一个干净的Docker容器只装ROS2基础环境然后从零开始rosdep install加colcon build加colcon test。这一步能提前暴露90%的问题。第二package.xml和CMakeLists.txt要反复检查。这两个文件是build farm的主要检查对象任何不一致都会导致失败。建议对照官方文档的模板逐字段核对。第三不要怕PR被打回。维护者打回你的PR是在帮你发现问题每次打回都是一次学习机会。我第四次提交才通过但通过之后对整个体系的理解深刻了很多。第四发布只是开始维护才是长期工作。新发行版出来时要及时适配依赖库升级时要跟进用户提issue时要响应。如果没做好长期维护的准备不如先在公司内部用着。最后分享一个实用技巧rosdistro仓库里有一个rosdistro/rosdep目录里面维护了所有rosdep key的映射。在写package.xml之前先去这个目录里搜一下你要用的依赖有没有对应的key能省掉很多来回。另外ros/rosdistro的PR页面里有很多历史PR可以参考看看别人是怎么写的照着改就行。
