你第一次在ROS2里敲colcon build的时候,大概率跟我当初一样懵:明明在ROS1里被catkin_make惯坏了,到了ROS2怎么连编译工具都换了个名字?更别说网上教程东一句西一句,今天让你--symlink-install,明天让你--parallel-workers 4,参数一堆,就是没人讲清楚每个到底干嘛用的。这篇文章我直接把我自己从ROS1迁到ROS2、从踩坑到顺手的过程写出来,不说废话,全是怎么用colcon把ROS2工程编译明白的干货,适合刚装完ROS2 Humble、正在为编译发愁的新手,也适合想搞清楚colcon和catkin到底差在哪的进阶用户。
1. 从catkin到colcon:ROS2为什么非要换一套编译工具
1.1 不是换名字那么简单
很多人以为colcon只是catkin_make的替代品,换个命令就行,其实背后是整个构建逻辑变了。ROS1时代,catkin_make把工作空间分成src、build、devel三个目录,编译产物统一放在devel里面,运行时靠source devel/setup.bash把路径加进环境变量。
ROS2这一代把中间产物和安装产物拆得更彻底:build目录是纯中间文件,install目录才对应ROS1里的devel。这样做的直接好处是结构更接近标准CMake的安装流程,交叉编译、多架构部署、甚至流水线自动化都更好做。同时ROS2的包类型不再只有catkin一种,还有ament_cmake、ament_python,甚至纯CMake包或Python包都要混在一个工作空间里编译。colcon就是为这种“多类型混合、依赖自动拓扑排序、增量编译”的场景设计的。
1.2 colcon的工作空间长什么样
一个标准ROS2工作空间,用colcon build编译之后会生成这几个目录:
src:你存放各功能包的源码目录。build:每个包的中间编译产物,比如CMake的缓存、Makefile、.o文件。install:每个包最终安装后的目录,里面有lib、share、include等。运行时你source的setup.bash就在这里。log:编译日志目录,比如某个包失败时的详细日志文件。这个目录在排查问题时很重要,后面我在常见问题部分会细说。
把它们分这么清楚,不只是洁癖,而是增量编译的关键。我改了src里一个包的一行代码,colcon只需要重新编这一个包,其他包直接跳过,比ROS1时代动不动全量编译快很多。
2. colcon build基础命令:先把最常用的几个弄明白
2.1 安装colcon:不是装完ROS2就有
很多人装完ROS2后直接敲colcon,结果提示command not found,先不要慌,因为colcon在部分ROS2发行版里不是默认安装的。Ubuntu 22.04 + ROS2 Humble的情况下,我自己用的是:
sudo apt install python3-colcon-common-extensions这个包会把colcon、colcon-common-extensions等常用插件都装上,后续用到的--packages-select、--symlink-install这些参数都已经包含在内。
装好之后,建议先确认一下版本:
colcon version-check如果提示有更新,或者你发现某些参数不支持,可以升级一下:
pip3 install -U colcon-common-extensions2.2 最基础的编译流程:build + source
进入工作空间根目录(也就是包含src的目录),执行:
colcon build如果你的包代码没有语法错误,依赖也都正常,编译完成后会生成前面说的build、install、log三个目录。但这里有一个绕不开的坑:编译完不等于能用。你必须先加载环境变量:
source install/setup.bash我见过很多新手在这里栽跟头:编译成功了,运行ros2 run my_package node,结果报错Package 'my_package' not found。原因就是没有sourceinstall/setup.bash,ROS2根本不知道你的包装到了哪里。如果你是第一次接触ROS2,请把这个流程刻进DNA:先colcon build,再source install/setup.bash,然后才能ros2 run。
如果你用的是zsh,source文件要换成setup.zsh;如果是bash,则用setup.bash。不同shell对应不同的文件,别搞混了。
2.3 只编译一个包:--packages-select
工作空间里包多了之后,每次colcon build都会全量扫描,即使只是微调一个包也要走一遍全流程,浪费时间不说,编译日志还很啰嗦。
我的习惯是:只编译我关心的包,用--packages-select指定包名:
colcon build --packages-select my_package这条命令的意思是,只编译my_package这个包,以及它依赖的其他包(如果检测到依赖没编译过,会先编译依赖)。所以我一般这样组合使用:
colcon build --packages-select my_package --symlink-install这样改完代码之后,能让编译更快,调试循环更短。
如果你只想跳过某个包,用--packages-ignore:
colcon build --packages-ignore my_bad_package这个在仓库里有一个包损坏、你又急需验证其他包时非常有用,不用删掉那个包,直接忽略掉编译就行。
3. 编译线程数与性能调优:别再傻傻等全量编译
3.1 --parallel-workers到底怎么设置
colcon build默认会利用当前机器的所有CPU核心数来并行编译。机器配置好没感觉,但如果你在虚拟机、Docker容器或者配置一般的笔记本上编译大项目,经常会出现内存爆掉、卡死甚至直接OOM的情况。
这时候用--parallel-workers限制并行任务数:
colcon build --parallel-workers 2这里需要解释一下背后的机制:colcon在并行编译时,本质上会同时启动多个子任务,每个子任务内部又可能调用make -j继续并行。所以这个参数不是简单地等于“用几个线程”,而是控制同时有几个编译任务在跑,每个任务内部还会再开线程。因此如果你想要严格限制CPU占用,光设这个还不够,还需要配合限制make的并行度,稍后我会讲到--cmake-args时再说。
我的实际经验是:在四核八线程的机器上,同时开2个并行任务比较稳,既能保证编译速度,又不会让系统卡到鼠标都动不了。如果你只是编译单个包,内存不超过8GB,直接用默认参数也行。
3.2 编译大项目时的内存与时长控制
ROS2里最让人头疼的往往是编译消息包,比如geometry_msgs、sensor_msgs这些基础消息包依赖链长,生成的头文件多,内存占用高。我编译一个包含十几二十个包的机器人工程时,遇到过内存飙到16GB的情况。
如果内存吃紧,建议这样:
colcon build --parallel-workers 1 --cmake-args -DCMAKE_BUILD_TYPE=Release--parallel-workers 1相当于串行编译,速度慢但稳定,几乎不会因为内存暴增而失败。而-DCMAKE_BUILD_TYPE=Release会去掉调试符号、开启优化,编译产物更小、运行更快,但编译时间可能稍微变长。如果你平时需要在GDB里调试,就别用Release,保持默认的Debug或RelWithDebInfo更好。
这里还有一个小技巧:如果你只想释放编译时的CPU压力,可以额外给make传-j参数:
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --make-args -j4这样colcon在执行每个包的make步骤时会限制为4个进程,整体多任务并行的资源消耗会大幅降低,比单独用--parallel-workers更细腻。
3.3 编译日志过长?用log-level控制输出
编译时刷屏最多的是各包的CMake输出和编译进度,如果你只关心错误,可以用:
colcon build --event-handlers console_direct+这个参数会把编译输出直接实时打印到终端上,便于观察进度。但输出真的很多,我一般只在调试具体包的编译问题时用。日常编译我习惯用默认模式:console_cohesion,它会把每一条日志都归档到log目录里,终端上只显示总体摘要,看起来清爽得多。
关于log目录,我后面还会再提一次,因为它对于排查编译失败真的太重要了。
4. 高级用法:覆盖安装、符号链接与混合编译
4.1 --symlink-install:Python开发的“救命稻草”
ROS2里有一类大量使用Python写的包,比如用ament_python构建的节点。默认情况下,colcon build会把Python源码复制到install目录里。这意味着你改一行Python代码,不想办法重新编译的话,运行时的ros2 run是不会感知到变化的。
--symlink-install参数就是为了解决这个问题:
colcon build --symlink-install它会将src里的Python文件以符号链接的方式安装到install目录。这样你改源码之后,无需重新编译即可生效,对于开发调试来说非常高效。我后期在调机器人节点的时候,基本每次都带--symlink-install,改完代码直接ros2 run,几分钟一次迭代,不要太爽。
不过要注意,C++代码不会因为符号链接而变化,因为C++编译后的二进制是独立生成的,这里符号链接只对Python脚本、配置文件、launch文件这类解释型资源有效。但即使如此,这个参数对于提高开发效率也已经足够了。
4.2 混合编译:一个工作空间里同时有C++和Python包
一个实际项目里,C++包负责核心算法,Python包负责逻辑控制或调试工具,这种混合很常见。colcon的强项就是能统一处理多种构建类型,并且自动解析包之间的依赖顺序。
不用你做任何额外配置,只要每个包都遵循它的构建系统规范(ament_cmake用CMakeLists.txt,ament_python用setup.py),colcon会自动识别并按照依赖顺序依次编译。我见过不少人一开始担心“Python包要不要单独处理”,其实完全不用。
比如我的工作空间里有一个C++写的消息定义包my_msgs,一个Python写的控制节点my_control,my_control的package.xml里声明了:
<depend>my_msgs</depend>我直接全量编译即可:
colcon build --symlink-installcolcon会先编译my_msgs,再编译my_control,无需我手动干预。这就是colcon比catkin_make更智能的地方。
4.3 依赖管理与安装prefix的关系
colcon build安装的默认prefix是当前工作空间下的install目录,通常不需要改动。但如果你要“覆盖安装”某个ROS2自带包(比如自己改了turtlesim的源码),你需要把install目录作为优先加载路径。默认情况下source install/setup.bash后,当前工作空间的包会优先于系统安装的/opt/ros/humble路径。
如果你看到“明明编译了,ros2 node list里却没有”的情况,先确认是不是有多个工作空间的setup.bash重复source了,以及当前环境变量里AMENT_PREFIX_PATH的顺序是否被其他环境覆盖了。
可以用这个命令检查某个包最终从哪里加载:
ros2 pkg prefix my_package如果输出的路径不对,就去调整环境变量加载顺序。
5. 常见错误与排查记录:这些坑我都替你踩过
5.1 Setuptools DeprecationWarning:能编译但看着心惊
Python包编译时,经常看到一长串黄色警告:
Setuptools DeprecationWarning: setup.py install is deprecated.这不是致命错误,也不影响编译,但出现时我总会去排查一下。大多数情况下是因为系统装了新版本setuptools,而ament_python还在用老旧的setup.py install模式。
解决方案很简单:暂时降级setuptools,或者干脆无视它。我实际采用的是无视它,因为编译产物和安装行为都正常,强行动系统包反而可能引发更多问题。
5.2 找不到包、找不到消息:环境变量不生效的排查法
如果ros2 run报找不到包,或者ros2 topic找不到自定义消息类型,可以按顺序做这几步:
# 查看当前环境包含了哪些工作空间 echo $AMENT_PREFIX_PATH # 查看某个包是否注册 ros2 pkg list | grep my_package # 查看自定消息是否被识别 ros2 interface list | grep my_msgs如果ros2 pkg list里没有你刚编译的包,大概率是没source对setup.bash,或者编译时该包就已经失败。这时候去log目录翻日志比在终端里大海捞针强得多。我遇到过一次,编译时报错信息被其他包输出淹没,终端的摘要信息只写了“失败”,具体原因却在log/latest_build/my_bad_package/stdout_stderr.log文件里。打开这个文件后,问题一目了然:是CMake版本不兼容导致的。
5.3 编译到一半卡死:内存不足还是死锁?
我遇到过几次编译卡死的状况,表现是终端毫无输出,系统变得很卡。这种一般是并行编译进程过多、内存耗尽导致的。即使你没有用--parallel-workers,有些大型C++包内部也会自己并行,比如OpenCV、PCL这些重依赖包在编译时,make -j默认会把所有核心都用满。
处理方式很简单:直接Ctrl+C停掉,然后降低并行度重新编译:
colcon build --parallel-workers 1如果这个包本身内部还有make -j$(nproc)的逻辑,你再额外加:
colcon build --parallel-workers 1 --make-args -j2这样基本能保证编译过程稳定不崩。
5.4 编译“成功”了但程序运行行为不对:覆盖安装与缓存的坑
colcon build默认是增量编译,只重编改动的部分。如果你改了自定义消息定义(比如.msg文件),却没有把依赖这个消息的包都重新编译,运行时会因为接口不匹配而出现奇怪的问题。
举个例子:你改了my_msgs里的消息结构,直接colcon build --packages-select my_msgs,编译很快完成,但你运行用这个消息的my_node时,可能还在用旧的二进制。解决办法是手动把依赖链上的包全部重编一次:
colcon build --packages-select my_msgs my_node更省事的方案是直接用--packages-up-to,它会把你指定的包及其依赖链都编译一遍:
colcon build --packages-up-to my_node这个是我强烈推荐的办法,几乎不会漏掉需要重新编译的包。
6. 编译过程排查工具与技巧:把日志变成你的调试利器
6.1 善用log目录定位失败的真正原因
colcon build失败时,终端往往只显示一句“Summary: 1 package failed”。对于只有一个包的工程无所谓,但如果是几十个包的工程,你根本不知道失败的是哪个,更别说失败原因了。
colcon会在log目录下按照时间戳命名子目录,比如log/latest_build。这个latest_build是一个软链接,指向最近一次构建的所有日志。出错时,我最常用的命令是:
grep -r "error:" log/latest_build/这样可以快速找出所有包含编译错误的文件,然后直接定位到具体的包和报错行,效率比在终端翻输出高得多。
如果你在用IDE或者VS Code,直接把log/latest_build/文件夹拖进去全局搜索,体验更爽。
6.2 使用--event-handlers获取完整现场输出
默认模式下,colcon只会显示最后几行输出,如果你想要实时看到完整的编译过程,可以用:
colcon build --event-handlers console_direct+这个参数会像在ROS1里用catkin_make那样,把所有编译输出直接打印到终端。注意是实时的、完整的,不会像默认模式那样只保留摘要。对于观察当前卡在哪个包、哪个编译阶段,非常直观。
但输出刷屏太快,我通常只在以下两种场景使用:
- 排查某个包编译失败但不知道卡在哪一步;
- 确认某个特定依赖包是否被正确识别并编译。
日常大量编译时,还是默认的console_cohesion更舒服。
6.3 复用编译缓存:让重复编译快到飞起
colcon的增量编译机制本身就很快,但如果你频繁在多个分支之间切换,或者偶尔会清空build目录,可以用ccache来加速C++编译:
sudo apt install ccache然后在编译时启用:
colcon build --cmake-args -DCMAKE_CXX_COMPILER_LAUNCHER=ccache启用之后,即使你删掉了build目录,只要源码没变,C++编译也会直接命中ccache缓存,速度提升非常明显。我在一个包含PCL、OpenCV等重依赖的项目里,启用ccache后清理重编的时间从十几分钟降到了两分钟左右,体感完全不一样。
6.4 环境变量隔离:多工作空间切换的踩坑记录
如果你同时维护多个ROS2工作空间,最容易遇到的情况是:两个工作空间里有同名包,或者一个工作空间source之后又source了另一个,导致环境变量AMENT_PREFIX_PATH叠加得很混乱。
我最开始就是在这上面栽跟头的:同一个包名在两个工作空间都存在,运行时怎么都加载不到我新编的那个,后来发现是之前某个终端里旧工作空间的setup.bash还在环境变量里。
解决办法是每次新开终端后,只source你当前需要的工作空间,不要重复source其他空间:
# 新终端,先加载系统ROS2 source /opt/ros/humble/setup.bash # 再加载你当前工作空间 source ~/ros2_ws/install/setup.bash如果已经混入了其他空间的路径,直接新开一个终端,比试图清理AMENT_PREFIX_PATH要省事得多。
7. 实操总结与工作流建议:我现在是怎么用colcon的
7.1 一套适合日常开发的高效编译命令
经过大量的实际项目验证,我现在日常开发中基本固定在用这样的编译组合:
# 日常开发,改了多个包,需要快速验证 colcon build --symlink-install --packages-up-to my_node # 只改了一个包且没有影响其他包时 colcon build --packages-select my_package --symlink-install # 大型工程全量编译时,为了稳定性适当限并行 colcon build --parallel-workers 2 --cmake-args -DCMAKE_BUILD_TYPE=Release这套组合我用了大半年,简单来说就是:日常改哪个编哪个,涉及依赖变更就--packages-up-to确保依赖链完整,全量重编时控制并行度防止资源吃紧。
7.2 新开终端必备的source流程
如果你和我一样多开终端,建议把下面这段写进~/.bashrc文件里,省得每次手动source:
source /opt/ros/humble/setup.bash source ~/ros2_ws/install/setup.bash注意,不要同时在~/.bashrc里source多个ROS2工作空间,否则会出现我前面提到的环境变量污染问题。如果你开了多个工作空间,更好的方案是写一个小的shell函数,按需切换。
7.3 最后分享一个我常用的“三连”排查法
当碰到“编译成功但运行不对”这类怪问题时,我会统一执行以下三步:
# 第一步:彻底清理,排除增量编译的缓存干扰 rm -rf build install log # 第二步:全量重编,确保所有包都是基于当前代码生成 colcon build --symlink-install # 第三步:确认环境加载的是当前工作空间 source install/setup.bash ros2 pkg prefix my_package这个方法虽然暴力,但能解决九成以上“莫名其妙”的问题。colcon的增量编译在绝大多数情况下都很聪明,但也正因为聪明,偶尔会出现缓存不一致导致的诡异行为。回到最朴素的“删掉重来”,往往比在细节上猜来猜去更高效。