ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ROS2编译入门:从colcon build到工作空间维护

ROS2编译入门:从colcon build到工作空间维护 上手ROS2很多人第一个拦路虎不是概念是编译。我在群里见过太多人卡在colcon build这一步报错刷屏心态直接崩掉。其实编译这件事搞懂底层逻辑之后就是一套固定流程。这篇文章就围绕“ROS2编译入门”展开把我自己从纯小白到能独立编译维护工作空间的经验包括踩过的坑、总结的套路一次性讲清楚。不管你是刚装完ROS2还没跑通小乌龟还是已经能跑demo但一编译就心虚这篇都适合你。1. 先搞明白ROS2为什么要编译编译到底在干什么1.1 从源码到可执行文件中间发生了什么很多人一听到“编译”两个字就头大潜意识里觉得这是大佬才干的事。其实编译就是一个“翻译”过程你写的是给人看的代码C、Python机器只认二进制指令编译器就是那个翻译官。在ROS2的世界里编译这件事比普通C工程多了一层复杂性。原因在于ROS2是分布式架构一个机器人系统往往由几十个甚至上百个功能包组成每个包都有自己的依赖关系。你不可能手动一个包一个包地去g编译所以社区搞出了一套自动化编译工具链。这套工具链的核心是colcon。它负责做三件事解析你工作空间里所有包的依赖关系按照依赖顺序逐个编译每个包把编译产物安装到统一目录供运行时调用有个类比特别贴切colcon就像是装修公司的项目经理它不亲自砌墙刷漆不直接编译代码但它知道哪个工种先进场依赖顺序知道材料堆在哪安装目录还能在某个环节出问题时快速定位是哪个工人的责任报错定位到具体包。1.2 编译方式选择的门道二进制安装 vs 源码编译新手最容易绕晕的是明明apt install ros-humble-desktop一条命令就能装好ROS2为什么还要折腾源码编译这里要分清楚两个概念系统级安装和工作空间级编译。系统级安装就是二进制包相当于买一台装好系统的电脑开机即用。但问题很现实你不能随便改系统文件装新包要等官方发布想调试某个功能包的源码更是寸步难行。工作空间级编译就是源码编译相当于你自己从零件开始组装电脑想换哪个零件、想怎么优化全由你自己说了算。具体到开发场景当你需要修改ROS2某个核心包的源码并测试自己的改动使用官方还未发布的开发版功能为特定硬件平台做适配优化学习源码看看消息传递机制到底怎么实现的这些场景下源码编译是绕不开的。还有个中间状态很多人没意识到你在自己工作空间里写一个自定义功能包也要编译。这就是入门者最常见的“编译”场景——不是编译整个ROS2而是编译自己的包。所以别被“源码编译ROS2”这种大词吓到日常开发中95%的编译都是编译你自己那几行代码。2. 编译前的环境准备工具链配齐成功一半2.1 Ubuntu版本和ROS2发行版的对应关系我见过太多人编译报错查了半天最后发现是Ubuntu版本和ROS2版本不匹配。这不是玄学是版本之间有硬性依赖关系。ROS2的发行版跟Ubuntu版本是绑定的每个发行版都针对特定Ubuntu版本做了测试和优化ROS2发行版对应Ubuntu版本状态FoxyUbuntu 20.04已停止维护GalacticUbuntu 20.04已停止维护HumbleUbuntu 22.04LTS长期支持目前最主流JazzyUbuntu 24.04较新正在快速普及Rolling滚动更新面向开发者我的建议很直接如果是新手上路无脑选Humble Ubuntu 22.04。原因很简单Humble是LTS版本社区资源最多你踩到的坑绝大多数别人都踩过搜解决方案一搜一个准。Jazzy虽然新但很多第三方库的适配还没跟上新手遇坑排查成本太高。2.2 必备工具链安装一个都不能少确定了系统版本之后编译工具链要装全。很多人只装了ROS2本体没装编译相关的开发工具colcon build一执行就报command not found。基础工具链分三块每块都有固定安装方式。第一块是编译工具本身。ROS2底层用的编译系统是ament它是在CMake基础上封装的所以CMake必须装。此外还需要colcon作为编译调度工具sudo apt install python3-colcon-common-extensions sudo apt install cmake第二块是依赖管理工具rosdep。这个工具的作用是自动解析你的包依赖哪些系统库然后帮你装好。很多人编译自己下载的源码包时报错信息里经常出现fatal error: xxx.h: No such file or directory十有八九是rosdep没跑依赖的系统库没装。sudo apt install python3-rosdep sudo rosdep init rosdep update第三块是Python开发组件。ROS2的很多工具链是Python写的colcon本身也是Python包。建议安装sudo apt install python3-pip python3-venv python3-dev这里插一句我踩过最深的一个坑系统里同时有多个Python版本时colcon和ament工具链可能被装到了不同版本的Python目录下结果执行任何命令都提示找不到模块。解决办法是统一使用系统默认Python版本安装所有Python工具不要手动换Python版本再装一遍。2.3 环境变量配置说了一万遍还是有人忘编译前还有一个关键动作source环境变量。这里的原理并不复杂ROS2系统安装完成后它的可执行文件和库文件都不在系统默认搜索路径里需要手动告诉Shell去哪找。source /opt/ros/humble/setup.bash这句命令只在当前终端生效所以每次打开新终端都要执行一次。嫌麻烦就在~/.bashrc里加上一劳永逸。echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc另外ROS2工作空间本身也有环境变量。当你编译完自己的工作空间后也要source一次source install/setup.bash这里面的逻辑是ROS2系统是基础环境你的工作空间是在这个基础上叠加的。先source系统再source工作空间系统才知道去哪里找你自己编译的包。顺序反了就会发生“系统包能找到自己编译的包找不到”的怪现象。3. 实操核心手把手完成你的第一次colcon编译3.1 创建工作空间目录结构决定一切ROS2的编译有一个强制性的目录规范新手最容易忽略。工作空间Workspace的标准结构是ros2_ws/ ├── src/ # 存放所有功能包的源码 ├── build/ # 编译中间文件自动生成 ├── install/ # 编译产物安装目录自动生成 └── log/ # 编译日志自动生成src目录是你唯一的“输入”其他目录都是colcon自动生成的“输出”。创建命令很简单mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build注意一个小细节colcon build必须在ros2_ws根目录下执行不能在src里执行。因为colcon需要扫描整个工作空间的所有包它根据当前目录下的src文件夹来定位源码。3.2 colcon build的核心参数不只是敲个回车colcon build最基础的用法就是无参数直接敲但这在实际开发中不太够用。我梳理了几个出场率最高的参数每个都是特定场景下的好帮手。# 只编译指定包日常开发最常用 colcon build --packages-select my_package # 跳过指定包 colcon build --packages-skip my_package # 符号链接安装模式Python开发强烈推荐 colcon build --symlink-install # 编译全部包并启动并行编译 colcon build --parallel-workers 4 # 编译时忽略测试大幅提速 colcon build --cmake-args -DBUILD_TESTINGOFF--symlink-install这个参数值得单独讲一下。默认情况下Python代码会被复制到install目录这意味着你每次改代码都要重新colcon build不然跑的还是旧版本。加上这个参数后install目录里放的是指向你源码的符号链接改完代码立即生效不用重新编译。凡是做Python包开发我建议无脑加这个参数。C包则恰恰相反源码修改后必须重新编译不像Python那样能“热更新”。这是两种语言的编译机制决定的C需要把源码转换成机器码Python只是解释执行。3.3 一个完整的编译流程实录我拿一个实际项目演示一下完整的编译流程。假设我们要写一个机器人状态发布节点工作空间里放一个自定义包# 1. 进入工作空间 cd ~/ros2_ws # 2. 创建功能包这里示范C版本 ros2 pkg create robot_status_publisher \ --build-type ament_cmake \ --dependencies rclcpp std_msgs # 3. 编写节点源码这里省略具体代码逻辑 # 4. 编译这个包 colcon build --packages-select robot_status_publisher \ --symlink-install \ --cmake-args -DBUILD_TESTINGOFF # 5. 加载编译产物 source install/setup.bash # 6. 运行节点验证 ros2 run robot_status_publisher robot_status_node这里有个很重要的细节ros2 pkg create创建包时--dependencies rclcpp std_msgs参数会把依赖写入package.xml和CMakeLists.txt。如果你忘了写这个参数后面编译大概率会报Could not find a package configuration file provided by rclcpp之类的错误。编译输出的信息也很关键。当看到Summary: 1 package finished时说明编译成功看到Summary: 1 package failed时说明编译失败。失败时要学会看日志log目录下每个包都有独立的日志文件colcon默认在终端只打印错误摘要完整的警告和错误细节要看日志文件里的stdout_stderr.log。3.4 C包和Python包在编译时的本质差异很多人会有疑问Python不是解释执行的语言吗为什么还要编译这个理解其实有个盲区。ROS2的Python包确实不需要把.py文件转成机器码但需要做一系列“元数据编译”。ament_python构建类型会帮你生成install目录下的完整包结构包括package.xml的信息解析、setup.py的安装流程、resource索引文件等。这些工作本质上是让ROS2的工具链比如ros2 run、ros2 pkg list能找到你的包。C包的编译则复杂得多。ament_cmake构建类型会启动CMake依次执行配置、编译、安装三个阶段最终生成可执行文件、动态链接库、头文件和CMake配置。整个过程涉及CMakeLists.txt里的各种宏比如ament_target_dependencies( robot_status_node rclcpp std_msgs )这句宏的作用是自动帮你设置头文件搜索路径、链接库路径和依赖库的链接顺序。新手千万别自己手写target_link_libraries直接用ament_target_dependencies可以让工具链自动处理那些“依赖的依赖”省掉无穷无尽的undefined reference错误。4. 编译错误排查记住这几个套路少掉一半头发4.1 高频报错和解决方案速查表我在各大技术社区刷帖和亲身实践基础上整理了下面这张高频报错速查表基本覆盖了入门阶段90%的编译问题报错现象根本原因解决方案Package xxx not found依赖的系统库未安装运行rosdep install --from-paths src -y -i自动安装fatal error: rclcpp/rclcpp.hpp: No such file or directory头文件路径没配置好检查CMakeLists.txt里ament_target_dependencies是否正确声明依赖Could not find a package configuration file provided by xxxCMake找不到依赖包确认依赖包是否已安装如果包在另一个工作空间需要同时加载两个工作空间的环境变量undefined reference to链接阶段找不到函数实现检查是否声明了链接库依赖C类有没有正确的头文件声明ModuleNotFoundError: No module named xxxPython依赖缺失或环境混乱在src目录执行rosdep install或者用pip install补齐依赖colcon: command not foundcolcon未安装sudo apt install python3-colcon-common-extensionsFailed to load entry point xxx包的索引文件损坏删掉build和install目录重新完整编译4.2 launch文件修改后需要重新编译吗这个问题的答案取决于你改的是什么类型的包。如果是Python包的launch文件不需要重新编译。前提是你编译时加了--symlink-install参数install目录下是指向你源码的链接。没有这个参数的话launch文件是复制过去的副本改了源码里的文件自然不生效。如果是C包需要重新编译。因为C的launch文件本质上是可执行文件链接的任何C源码改动都要重新编译链接后才能生效。一个省心的做法写launch文件时尽量把参数、话题名这些“软配置”都设计成可在launch文件里通过参数传入不要硬编码在C源码里。这样90%的调整只需要改launch文件完全不用碰编译流程。4.3 编译过慢告别每次都全量编译随着工作空间里的包越来越多全量编译的时间会从几十秒变成好几分钟甚至十几分钟。等编译的过程真的太磨人了。我这里分享几个提速经验用--packages-select精确编译只编译修改过的包以及依赖它的下游包。别一个colcon build横扫千军。关闭测试--cmake-args -DBUILD_TESTINGOFF这个参数能省掉编译测试代码的时间实际提速非常明显。调整并行度--parallel-workers参数可以同时编译多个包。数值一般设为CPU物理核心数不要盲目设大。并行度过高会导致内存吃紧反而拖慢速度。我实测下来8核16线程的机器设4到6比较合适。创建独立开发空间把经常改的包放在一个独立的工作空间其余稳定的包放在另一个工作空间。利用ROS2的COLCON_IGNORE文件把不需要编译的包目录标记为忽略就像给那个包挂上一个“请不要动我”的牌子。4.4 排查思路我的标准操作流程遇到编译报错时我有一套固定的排查流程效率比瞎试高得多第一步看报错位置。终端里colcon会标出哪个包的编译失败直接定位到那个包。如果报错不在当前编译的包里先去查它依赖的包是否编译成功。第二步看完整日志。colcon build默认只显示最后的错误摘要完整日志在log/latest_build/包名/stdout_stderr.log。用tail -n 100看日志最后面错误通常都在末尾。第三步找到第一个error。编译器报错会列出一大堆但通常情况下只有第一个是真正的错误后面的都是连锁反应。往上翻日志找到error:开头的那一行这才是问题的根源。第四步搜索这个错误。把报错信息里最关键的几行复制到搜索引擎不要用太长太具体的路径否则搜不到结果。必须把错误信息处理一下再搜保留error: xxx核心部分和涉及的文件名对排查最有帮助。5. 一个容易忽略的话题工作空间的依赖管理与多空间协同5.1 rosdep的正确打开方式rosdep这个工具我前面提到过但值得展开讲。它解决的问题是你的代码依赖了别人的代码别人的代码又依赖了系统库这串依赖关系靠人肉一个个装迟早会爆炸。rosdep的工作原理是解析你src目录下所有package.xml里声明的依赖然后对照系统仓库自动安装。标准用法是cd ~/ros2_ws rosdep install --from-paths src --ignore-src -r -y这里参数的含义分别是--from-paths src从src目录开始扫描所有包的依赖--ignore-src忽略工作空间里已有的包这些包会通过编译解决-r跳过无法解析的依赖别让一条错误阻断整个安装-y安装过程中自动确认刚克隆一个别人的项目时第一件事永远是跑这条命令。跑完之后再编译能省掉一大半的报错。5.2 多个工作空间的加载顺序实际工作中你可能会同时维护多个工作空间。比如一个存放第三方库的、一个存放自己团队公共代码的、一个存放当前项目代码的。ROS2的多空间协作逻辑是“叠加”后面的覆盖前面的。加载顺序很重要source /opt/ros/humble/setup.bash # 系统基础 source ~/third_party_ws/install/setup.bash # 第三方库 source ~/team_ws/install/setup.bash # 团队公共包 source ~/project_ws/install/setup.bash # 当前项目越靠后加载的工作空间优先级越高。当两个工作空间存在同名包时后加载的会覆盖先加载的。这个特性在有定制需求时特别好用比如你想修改某个第三方库的行为可以把自己修改过的包放在后面的工作空间里覆盖掉原来的版本。但这些叠加命令全部塞进~/.bashrc之后终端启动会变慢而且容易忘记当前终端加载的是哪套环境。我自己的做法是把这些source命令写成一个独立的shell脚本需要时手动执行避免所有终端都背着一堆环境变量。6. 写完代码之后的那些小事也是编译的一部分6.1 package.xml和CMakeLists.txt的关系很多新手写包时只关注源码忽略了两个配置文件。这两个文件是编译系统的“输入指令”缺一不可而且必须保持一致。package.xml是“包的信息卡”记录包名、版本、作者、许可证和所有依赖。rosdep解析依赖就是靠它。CMakeLists.txt或setup.py是“构建蓝图”告诉编译系统源码在哪、依赖哪些库、可执行文件叫什么。一个特别容易犯的错在package.xml里声明了依赖但忘了在CMakeLists.txt里写ament_target_dependencies。这样编译照样报错因为CMake不知道要去哪里找头文件和库。反过来CMakeLists里写了依赖但package.xml漏了机器上没装依赖库时rosdep也不会帮你装。我的习惯是两个文件同时修改改完交叉检查一遍再编译能省下不必要的试错时间。6.2 自定义消息、服务和动作需要特殊处理ROS2里消息Message、服务Service、动作Action的定义文件.msg、.srv、.action也需要编译。它们的编译过程比较特殊系统会先把.msg文件生成对应的C和Python代码然后再编译这些生成的代码。举个例子你定义了一个robot_status.msgstring robot_name float32 battery_level bool is_moving编译时ament会先生成robot_status.hpp头文件C和robot_status.py模块Python然后再链接到你的节点代码里。这就是为什么你修改了.msg文件后依赖它的所有包都必须重新编译。倒是有一个简单的小技巧修改消息定义后在终端先只编译消息所在的包然后再编译依赖它的包。顺序不对就会遇到“类型未定义”的报错。C代码里要使用自定义消息时头文件引入的路径也有讲究#include your_package/msg/robot_status.hpp这个路径由ament_target_dependencies自动配置好不要自己在include_directories里瞎指。6.3 修改了环境变量或launch文件参数需要编译吗这个问题在热词搜索里出现频率很高。简单直白地给个结论环境变量setup.bash、local_setup.bash相关不需要编译。改的是安装后的环境配置直接source一下或重启终端就生效。launch文件里的参数值不需要编译。launch文件是运行时解析的改完直接重新运行launch命令就行。launch文件本身的逻辑比如新增了Node实例、修改了IncludeLaunchFile如果没有以源码形式打包即没有使用--symlink-install需要重新编译。.msg、.srv、.action定义文件必须重新编译而且所有依赖它的包都要跟着重新编译。这个判断方法很简单你改的是“配置”还是“代码”配置改动通常不需要编译代码改动基本上都需要。掌握了这个原则面对各种“要不要编译”的问题就不会纠结了。7. 经验之谈从编译入门到能独立维护工作空间的几个建议编译入门这件事说难也难说简单也简单。最重要的一步是转变心态不要怕报错。每一次报错都是一次学习机会排查报错的过程就是你理解构建系统底层逻辑的过程。我记得自己刚开始学的那个阶段连着三天都在跟CMake报错较劲。一度怀疑自己是不是不适合写机器人代码。后来踩的坑多了慢慢发现编译系统就像一个有脾气但讲逻辑的工具你摸清了它的脾气后来越用越顺手。也没必要死记硬背那些命令行参数真正用多了自然就记住了。我的建议是初期可以准备一个Markdown文档把自己遇到的每一个坑都记录下来格式很简单报错信息摘录关键几行、原因分析、解决方案。用不了两个月这个文档就会成为你最宝贵的排查手册。更要主动去阅读别人的构建脚本尤其是那些Star多的ROS2项目。看别人的CMakeLists.txt怎么组织package.xml怎么声明依赖各种编译选项的用法是什么。看得多了自己的构建功底也就上来了。如果真的遇到解决不了的问题优先在官方文档或技术社区搜索英文关键词尽可能带着完整日志去提问。一条好问题值得被好好回答一条带完整日志和详细描述的问题完全可以让别人一眼帮你定位到问题所在。
返回列表