跑ROS的人大概都有过这种经历:节点代码自己觉得写得没毛病,catkin_make或者colcon build一回车,终端开始疯狂刷红字,最后停在一行CMake Error上,光标一闪一闪等着你。ROS、cmake、报错这三样东西,几乎贯穿了从装环境第一天到日常维护的每一天——有人统计过,新手在ROS上花掉的时间,一半以上不是写算法,而是跟构建系统较劲。
这篇是我做ROS开发这几年攒下来的一份报错处理笔记,边踩边记,持续更新。覆盖范围主要是Ubuntu下的ROS 1(Melodic、Noetic)和ROS 2(Humble、Jazzy),会顺手带上Windows下CMake命令识别不了、嵌入式交叉编译、以及一些"CMake不只服务ROS"的跨界场景。它不是一个CMake语法教程,也不是官方文档的搬运,我写的是"看到这行红字,下一步该敲什么"。
目标读者有两类:一类是已经能写节点、但一编译就懵的新手;另一类是团队里那个专门负责"修环境"的人,别人报错截图发过来,你得在十分钟内判断是依赖没装还是缓存脏了。环境类、依赖声明类、链接类、缓存污染类的问题,排查路径完全不一样,用同一套手法去撞只会白费时间,所以下面我会把它们拆开讲。
1. 先把CMake报错分层,别一上来就改CMakeLists
1.1 ROS构建链路上CMake到底站在哪一层
很多人对CMake的误解在于,以为它是"编译器"。它其实是个构建脚本生成器:你写一份CMakeLists.txt,CMake读取它,结合系统里已有的环境变量、依赖包配置,生成Makefile或者Ninja文件,然后才由make/ninja去调用gcc/g++真正编译。所以CMake报错,绝大多数时候不是"代码写错了",而是"它没能拼出正确的编译命令"。
在ROS这套体系里,这条链路还要多绕两圈。ROS 1用catkin,本质是一层CMake宏(catkin_package()、add_message_files()这些),你在顶层调用catkin_make或catkin build,工具会先遍历src下所有包的package.xml,按依赖关系排序,再逐个进包目录跑CMake。ROS 2换成ament_cmake + colcon,find_package(catkin ...)变成find_package(ament_cmake ...),消息生成从catkin的宏换成rosidl的接口生成器,除此之外的核心逻辑没变。
理解这一点很关键:ROS构建报错的源头可能在四个完全不同的地方——package.xml(声明依赖)、CMakeLists.txt(怎么用依赖)、shell环境变量(依赖在哪)、以及系统层(编译器、库文件是否存在)。把它们混成一锅,就会陷入"改了十遍CMakeLists还是同样报错"的死循环。
1.2 catkin和colcon两代体系,报错长得不一样
新手最常见的一个坑,是把ROS 1的教程命令照搬到ROS 2上。这两代的报错文案有很明显的特征,认出特征就能立刻判断方向,我给你整理成一张对照表:
| 报错特征 | 属于哪一代 | 通常是什么问题 |
|---|---|---|
Could not find a package configuration file provided by "xxx" | 两代都有 | 依赖包没装,或CMake找不到配置文件 |
catkin_package() ... must be called before | ROS 1 | 宏调用顺序错了 |
find_package(ament_cmake REQUIRED)失败 | ROS 2 | 没source/opt/ros/<distro>/setup.bash |
rosidl_generate_interfaces() the first argument must be a valid target name | ROS 2 | 写消息包时参数写错或包名不合法 |
Project 'xxx' specifies '/include/xxx' as an include dir, which is not found | ROS 1 | catkin_package里声明的INCLUDE_DIRS路径不存在 |
Error(s) in package 'xxx': The manifest must not contain the following tags: run_depend | ROS 2 | package.xml用了ROS 1的format 1标签 |
这张表只是入口,后面每一类我都会展开。我的习惯是:报错先看关键词,判断它属于"找不到东西"还是"顺序不对"还是"链接失败",这三类的处理动作差别极大。"找不到东西"是环境问题,去翻apt list和rosdep;"顺序不对"是文件内容问题,去翻CMakeLists.txt;"链接失败"是二进制问题,去翻ldd和nm。
1.3 动手之前先确认的三条基线
我踩过最冤的一类坑,是花了两小时研究CMake语法,最后发现是环境没source。所以在动手改任何文件之前,先花三十秒确认三条基线:
- ROS环境是否已source:
echo $ROS_DISTRO应该输出你的发行版名(noetic/humble),输出为空说明没source,ROS 2更要命,colcon build找不到rclcpp十有八九就是这个原因。 - 当前工作空间是否在覆盖链上:
echo $CMAKE_PREFIX_PATH看看里面有哪些工作空间的install或devel目录。如果你之前装过一个老版本的包,它的路径排在前面,CMake就会优先找到那个老版本。 - CMake缓存是否干净:
build/目录里有个CMakeCache.txt,里面记着上次配置时的绝对路径、编译器版本。你换了路径、换了Python、换了gcc,缓存里的旧值就成了定时炸弹。这也是为什么网上那么多人说"删掉build重编就好了"——他们不是玄学,是踩过。
这三条确认完,再去读报错,效率完全不一样。我一般会建议在动手前把这条命令当习惯:
cd ~/your_ws rm -rf build devel install # ROS 1 # 或者 rm -rf build install log # ROS 2注意:删之前确认
src里有你的源码,并且所有改动已经保存。删build/devel/install是安全的,删src是不可逆的。另外,构建中的工作空间不要一边catkin build一边删,会留下半截的缓存。
2. 环境层的报错:cmake命令找不到、版本不对、多版本打架
2.1 "cmake不是内部或外部命令"与Linux下的command not found
先说Windows上的那个经典报错,我见过太多人卡在这:
cmake : 无法将"cmake"项识别为 cmdlet、函数、脚本文件或可运行程序的名称。这不是CMake坏了,是Windows的PATH里没有CMake的bin目录。用官方安装包安装时,安装向导里有一个"Add CMake to the system PATH for all users / current user"的勾选项,很多人一路Next根本没注意,勾没打上,装完就在PowerShell里报错。补救方法有两种:一是重新运行安装程序选择"Add to PATH",二是手动把C:\Program Files\CMake\bin加进系统环境变量,加完之后必须重开一个新的终端窗口,老窗口的环境变量不会刷新。我见过有人改完PATH在原窗口里反复敲cmake --version然后怀疑人生,就是这个原因。
Linux下的command not found更简单,装就完了:
sudo apt update sudo apt install -y cmake build-essential cmake --version但如果是在一个刚装好的Ubuntu上,apt update本身就失败,或者装完CMake还是用不了,那就得回头看软件源。这里顺便提一句社区里流传的那些一键安装脚本(比如鱼香ROS那套),确实能省掉配源、装rosdep、写环境变量这些琐事,对纯新手很友好。但它省事的同时也替你改了不少系统配置,比如替换APT源、批量装依赖,一旦脚本中途失败或者源不匹配,留下的半成品环境排查起来反而更麻烦。我的建议是:用可以,但用完之后记得自己确认cmake --version、echo $ROS_DISTRO、rosdep --version这三个命令的返回值,别装完就以为万事大吉。
2.2 CMake版本和ROS发行版是有对应关系的
CMake版本不是越新越好。ROS的很多CMake宏是针对特定年代的CMake写的,版本跨太大就会出现兼容性问题。我来把常见组合列一下,这张表建议存下来:
| Ubuntu版本 | ROS发行版 | 系统自带CMake版本 | 备注 |
|---|---|---|---|
| 18.04 | Melodic | 3.10.2 | 老工程多,注意cmake_minimum_required写的2.8 |
| 20.04 | Noetic / Foxy | 3.16.3 | ROS 1的最后一个长期版本 |
| 22.04 | Humble | 3.22.1 | 目前装机量最大的ROS 2版本 |
| 24.04 | Jazzy | 3.28.x | 新项目建议直接上这个组合 |
两种典型报错的区分很重要。第一种是"要求过高":
CMake Error at CMakeLists.txt:1 (cmake_minimum_required): CMake 3.20 or higher is required. You are running version 3.10.2这说明包作者写了一个比你系统更新的最低版本要求,解决方式要么升级CMake,要么把这一行改回你能满足的版本(但要确认作者没有真的用到3.20的新特性,否则改了也编不过)。第二种是"要求过低"带来的策略告警——在CMake 3.30之后,对cmake_minimum_required(VERSION 2.8)这种老写法会打出兼容性告警,继续升级后可能直接变成错误。临时绕过的办法是配置时传一个策略下限:
colcon build --cmake-args -DCMAKE_POLICY_VERSION_MINIMUM=3.5 # ROS 1 下 catkin_make -DCMAKE_POLICY_VERSION_MINIMUM=3.5这只是权宜之计,正规做法还是把老工程的cmake_minimum_required抬到3.5以上,并顺手清掉不再需要的旧策略代码。
2.3 想升级或卸载CMake,别用apt硬怼
用apt install cmake装的是发行版仓库里的版本,想升到更新的版本,用apt往往会把一堆系统包连带升级,风险不小。我比较推荐的是官方预编译包,解压到/opt然后用软链接或者PATH管理:
wget https://github.com/Kitware/CMake/releases/download/v3.28.1/cmake-3.28.1-linux-x86_64.tar.gz sudo tar -zxvf cmake-3.28.1-linux-x86_64.tar.gz -C /opt sudo ln -sf /opt/cmake-3.28.1-linux-x86_64/bin/cmake /usr/local/bin/cmake cmake --version关键点在于/usr/local/bin在PATH里的优先级高于/usr/bin,所以这样不会破坏系统自带的CMake,出问题把软链接删掉就能回退。这比apt remove cmake再装新版的粗暴做法干净得多——后者会连带卸掉依赖cmake的一批包,某些情况下连构建工具链都跟着受损。
如果你用多个版本做交叉验证,可以用update-alternatives管理,或者干脆用conda虚拟环境隔离。但要提醒一句:用conda装的cmake在ROS里很容易出问题,因为它会连带把Python解释器路径也改掉,CMake配置时找到的是conda的Python,链接出来的库和系统Python不匹配。我在这个坑里待过一下午,最后靠conda deactivate解决的。
3. 依赖声明类报错:package.xml和CMakeLists.txt的"两处一致"
3.1 依赖必须写两遍,这是设计而不是冗余
新手最想不通的一点:我在package.xml里写了<depend>roscpp</depend>,为什么还要在CMakeLists.txt里写find_package(catkin REQUIRED COMPONENTS roscpp)?这不是重复劳动吗?
不是。这两个文件服务的是两个不同的工具链。package.xml是给包管理器看的——rosdep、colcon、catkin build靠它计算依赖顺序、决定先编谁后编谁;CMakeLists.txt是给构建系统看的——CMake靠find_package去定位头文件路径和库文件路径,生成-I和-l参数。只写前者,构建顺序对了但编译器不知道头文件在哪;只写后者,单包编译能过,但整体构建时顺序可能错乱。
ROS 2里逻辑一样,只是名字变了:package.xml里写<depend>rclcpp</depend>,CMakeLists.txt里写find_package(rclcpp REQUIRED)加ament_target_dependencies(my_node rclcpp)。
ROS 2的package.xml还多了一层格式版本要求。如果你从ROS 1的包里直接复制了一份package.xml过来,很可能带着<run_depend>这种format 1的标签,构建时会直接报:
Error(s) in package 'xxx': The manifest (with format version 2) must not contain the following tags: run_depend解决就是把<run_depend>换成<exec_depend>,把<build_depend>和<run_depend>该合并的合并成<depend>。这一步看着无聊,但它是跨版本迁移时最容易忽略的一环。
3.2 CMakeLists.txt的语句顺序,顺序错了一种都编不过
CMakeLists.txt是有隐含顺序要求的,把它当成一份有执行次序的脚本而不是配置文件,理解起来会顺很多。一份标准的ROS 1包大致是这个顺序:
cmake_minimum_required(VERSION 3.0.2) project(my_pkg) find_package(catkin REQUIRED COMPONENTS roscpp std_msgs message_generation ) add_message_files(FILES MyMsg.msg) generate_messages(DEPENDENCIES std_msgs) catkin_package( INCLUDE_DIRS include LIBRARIES my_pkg CATKIN_DEPENDS roscpp std_msgs ) include_directories(include ${catkin_INCLUDE_DIRS}) add_executable(my_node src/my_node.cpp) target_link_libraries(my_node ${catkin_LIBRARIES}) add_dependencies(my_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})我逐个说下关键点的意图。find_package必须在最前面,因为它要把依赖包的变量(catkin_INCLUDE_DIRS、catkin_LIBRARIES)导入进来,后面的语句才有东西可用。catkin_package()必须放在add_executable之前,它的作用是向外部"导出"你这个包的能力,如果顺序反了,其他包依赖你的时候拿不到配置信息。generate_messages()要在catkin_package()之前,因为它决定了导出的消息类型。
ROS 2的顺序略有不同,消息包和普通包几乎是两套写法。普通节点包是find_package(ament_cmake REQUIRED)→find_package(rclcpp REQUIRED)→add_executable→ament_target_dependencies→install(TARGETS ...);消息包则要用rosidl_generate_interfaces(${PROJECT_NAME} "msg/MyMsg.msg"),而且接口生成必须在任何依赖它的target之前完成。这里有个高频错误是ROS 2新手把rosidl_generate_interfaces写在add_executable后面,结果编译时报找不到生成的.hpp——因为生成任务还没注册,target就已经开始找头文件了。
3.3 自定义消息引发的"头文件找不到"
报错长这样:
fatal error: my_pkg/MyMsg.h: No such file or directory这个报错有几种可能,排查顺序我一般是这样:
第一,看消息文件是不是真的被注册了。ROS 1要确认add_message_files里列出了这个.msg,ROS 2要确认rosidl_generate_interfaces的参数里包含它。文件名写错一个字母,生成的头文件名字就完全不一样。
第二,看依赖包有没有在package.xml里声明。消息编译需要message_generation(ROS 1的构建期)和message_runtime(运行期),ROS 2需要rosidl_default_generators和rosidl_default_runtime。少写一个,生成的头文件不会出现在include路径里。
第三,看是否加了target依赖。ROS 1里那个看起来又长又反直觉的写法是必需的:
add_dependencies(my_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})它的含义是"我的可执行文件必须在所有消息生成完成之后再编译"。${catkin_EXPORTED_TARGETS}管的是别人定义的消息,${${PROJECT_NAME}_EXPORTED_TARGETS}管的是自己定义的消息。少写这一行,并行编译时就会出现"有时能过有时不过"的诡异现象——因为编译顺序在并行环境下是不确定的,这也是为什么很多人的包在自己机器上好好的,换台机器就崩。
4. 工作空间、链接与交叉编译的深水区
4.1 overlay冲突:你引用的可能不是你写的那个包
ROS支持多工作空间叠加,底层是/opt/ros/<distro>,上面可以叠好几个自己建的overlay。这个机制很灵活,但也是"改了代码不生效"的头号元凶。
判断方法很简单:
rospack find my_pkg # ROS 1 ros2 pkg prefix my_pkg # ROS 2输出的路径如果不是你当前工作空间的devel或install目录,说明你引用的确实不是刚改的那个。原因通常是.bashrc里source了好几个工作空间,顺序靠后的会覆盖靠前的。解决方法有两种:一是把当前工作空间的source挪到最后,二是每次构建前手动source一次:
source /opt/ros/humble/setup.bash source ~/my_ws/install/setup.bash提示:如果两个工作空间里有同名包,
CMAKE_PREFIX_PATH的顺序决定用哪个。用echo $CMAKE_PREFIX_PATH | tr ':' '\n'拆开看,一眼就能看出优先级。
还有一个反直觉的现象:你明明删掉了一个包,编译却还在报它的错。这是残留的install目录在作祟。ROS 2的install目录里带着编译期生成的CMake配置文件,光删build是不够的,三个目录要一起删。
4.2 链接阶段的报错:undefined reference和cannot find -l
前面几类都是"配置"问题,这一类是真正的"链接"问题,通常发生在最后一步。两个典型文案:
/usr/bin/ld: cannot find -lmy_lib这个意思是链接器在标准库路径里找不到名为libmy_lib.so或libmy_lib.a的文件。常见原因是依赖库只装了开发包的运行时版本,或者你自己编的库没有install。查询和修复:
ldconfig -p | grep my_lib # 系统里到底有没有这个库 find / -name "libmy_lib*" 2>/dev/null # 全盘找一下undefined reference to `SomeClass::method()'这个更微妙,说明库找到了,但里面没有这个符号。原因通常是ABI不兼容或者版本不匹配——比如你链接的是一个头文件版本3.0、库文件版本2.0的组合。这种情况我会用nm直接查符号:
nm -D -C /path/to/libmy_lib.so | grep SomeClass如果符号确实不在里面,那就不是链接参数的问题,而是你链接的库本身就不是想要的那个。第三方库的这类问题,八成能通过rosdep解决:
rosdep install --from-paths src --ignore-src -r -y这条命令的作用是扫描src下所有package.xml里声明的系统依赖,自动用apt装上对应的开发包。它能治好的病比想象中多。唯一的坑是rosdep update本身依赖网络访问,网络条件不好时会失败,这种情况下社区有预置数据源的替代方案,或者手动把缺的包用apt装上,效果一样。
4.3 交叉编译场景:micro-ROS与ESP32的环境隔离
这一类报错是ROS 2时代新增的,因为越来越多人在做micro-ROS、ESP32这类嵌入式节点,主机和目标的构建环境混在一起,特别容易出问题。
一个典型报错是IDF的环境变量没设置,导致CMake里这行失败:
include($ENV{IDF_PATH}/tools/cmake/project.cmake)原因是IDF_PATH是空的。解决方案看起来很直白——export.sh一下就行了,但这里有个顺序陷阱:ESP-IDF的导出脚本和ROS的setup脚本会互相干扰,尤其是涉及到Python环境和编译器前缀的时候。所以正确做法是用两个独立的终端:一个终端只做主机侧的ROS构建,另一个终端source $IDF_PATH/export.sh后做固件构建,别在同一个shell里来回切。
如果确实需要在同一个流程里跑,用独立的构建目录和显式的工具链文件隔离:
cmake -B build-esp32 -DCMAKE_TOOLCHAIN_FILE=$IDF_PATH/tools/cmake/toolchain-esp32.cmake ..另一个高频报错是主机的colcon build把目标平台的包也一起编了,报出各种架构不匹配。这时候用--packages-select精确指定,或者在工作空间里建一个COLCON_IGNORE空文件把嵌入式包排除掉。
5. 从别的领域看CMake报错:套路其实是通用的
5.1 那些"不是ROS但同样是CMake"的案例
我这两年有个体会:CMake报错的排查方法论是跨领域的。你只要理解了"CMake负责生成构建脚本,报错一般是它找不到某个输入"这个前提,很多看起来完全无关的场景就能用同一套思路处理。
比如深度学习框架的编译。有人装Detectron2时报错,翻到最后发现是CMake找不到合适的编译器版本,或者CUDA架构参数没配对。这和ROS里CMAKE_C_COMPILER is not a full path的报错是同一类问题——工具链的某个输入没有被正确指定。再比如Flutter的Android项目报unable to find suitable Visual Studio toolchain,本质上也是构建系统找不到它需要的那套工具链,和CMake找不到依赖包是同构的。
桌面打包的链条就更典型了。用Electron打Linux的deb包时需要fpm,fpm又依赖rpmbuild,任何一环缺失,报错都是从最外层抛出来的,你得顺着链条往下挖。这个"链式依赖"的排查思路,和ROS里rosdep找不到某个包、于是CMake找不到配置文件、于是find_package失败,是一模一样的。
5.2 嵌入式和桌面端:CMake正在变成通用选择
还有一个经常被问的问题:CMake能不能替代Keil做嵌入式的构建?从能力上说,CMake从来不负责编译,它负责生成构建脚本,所以"替代Keil"的准确说法是"用CMake + arm-none-eabi-gcc + 工具链文件,替代Keil的IDE构建"。链接脚本、启动文件、烧录步骤都由你自己用命令行串起来,比如烧录用JLink的命令行工具,构建用CMake生成的makefile。
这条路的好处是构建过程可脚本化、可进版本管理、能接CI,坏处是初始配置成本高,一个toolchain.cmake要调半天。对ROS用户来说这其实是熟悉的领域——我们本来就在用命令行构建,只不过把工具链从x86换成了ARM。
5.3 为什么CMake的报错总是很"绕"
CMake报错看起来绕,根本原因在于它是个"两层翻译"系统:你写的是声明式的脚本,它翻译成Makefile,Makefile再翻译成编译器命令。报错可能是任何一层抛出来的,而CMake的输出又倾向于把调用栈也打出来——CMake Error at /opt/ros/noetic/share/catkin/cmake/catkinConfig.cmake:83 (find_package)这种,路径和行号一大堆,新手一看就晕。
我的处理习惯是:只看报错块的第一行和最后一行。第一行告诉你是什么错(Error还是Warning),最后一行通常告诉你缺什么,中间那一大堆是调用栈。抓住"缺什么"这个信息,再顺着前面说的层级往下查,效率会高很多。
6. 报错速查与三条实操定位手法
6.1 常见报错速查表
下面这张表是我自己常年备着的,遇到报错先扫一眼匹配:
| 报错原文(节选) | 大概率根因 | 第一步动作 |
|---|---|---|
cmake: command not found/无法将"cmake"项识别为... | CMake未安装或PATH未包含 | which cmake,检查PATH并重开终端 |
Could not find a package configuration file provided by "xxx" | 依赖包未安装或未source | 先source目标环境,再rosdep install |
Project 'xxx' specifies '/include/xxx' as an include dir, which is not found | catkin_package里的路径不存在 | 建目录或修正路径 |
fatal error: xxx/msg/Yyy.h: No such file or directory | 消息生成顺序问题 | 检查add_dependencies是否补齐 |
c++: fatal error: Killed signal terminated program cc1plus | 并行编译吃满内存被系统杀掉 | 降并行度,-j2或--parallel-workers 2 |
/usr/bin/ld: cannot find -lxxx | 库文件缺失或路径不对 | ldconfig -p、find定位 |
undefined reference to ... | 链接到的库版本不对 | nm -D查符号,确认库来源 |
The manifest must not contain the following tags: run_depend | package.xml格式版本混用 | 改成exec_depend |
Compatibility with CMake < 3.5 will be removed | 老工程配新CMake | 抬高cmake_minimum_required或加策略下限 |
那个cc1plus被杀掉的报错值得单独说一句,因为它的表象非常吓人——编译到一半突然中断,没有任何语法错误提示,新手会以为是代码有问题。真实原因是g++编译某个大文件时内存占用超过可用内存,被系统的OOM机制干掉了。解决办法就是别贪并行度,尤其是虚拟机只给了4G内存的时候,catkin_make -j2或者colcon build --parallel-workers 2会稳得多。
6.2 三步定位法:清缓存、读日志、最小复现
我把日常的排查流程压缩成三步,按顺序走,八成的问题能在十分钟内收敛。
第一步,清缓存重试。别觉得这是"重启大法",它是有依据的——CMakeCache.txt里存着上次配置的所有绝对路径,路径变了、依赖变了,缓存就成了错误来源。清理命令是rm -rf build devel install(ROS 1)或rm -rf build install log(ROS 2)。这一步能解决大概三成的"莫名其妙"问题。
第二步,读日志文件的最后二十行。终端输出会被截断,完整日志才是真相。catkin build 的日志在log/latest_build/<pkg>/build.make.<时间戳>.log,colcon 的在log/latest_build/<pkg>/stderr.log。看的时候用tail -n 50,并且专门找第一个error——后面跟着的一大串往往是第一个错误的连锁反应,盯着第十个错误分析纯属浪费时间。
第三步,最小复现。如果整包构建失败,把范围缩到单包:
colcon build --packages-select my_pkg --cmake-clean-cache --event-handlers console_direct+ catkin build my_pkg --no-deps--cmake-clean-cache会强制重新跑CMake配置,console_direct+让输出不经缓冲直接打到终端,方便边看边改。ROS 1的--no-deps能跳过无关包,把问题限制在一个包里。
需要看完整的编译命令时,加这两个参数:
colcon build --cmake-args -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=DebugCMAKE_VERBOSE_MAKEFILE=ON会把每条g++命令行都打出来,链接参数里少了哪个-l、头文件路径里少了哪个-I,一眼可见。这个参数是我排查链接类问题的第一选择,比猜快得多。
6.3 几条踩出来的心得,比文档管用
别在中文路径下编译。这不是危言耸听,某些工具链处理非ASCII路径时确实会出问题,报错还非常隐晦,比如某个字体相关的处理会提示非Unicode TrueType字体的错误,让你往完全错误的方向查。工作空间统一放~/ws这种纯英文路径下,能省掉一类玄学问题。
改完CMakeLists.txt一定要重新配置,不能只重新make。有人改了文件直接make,发现改动不生效,因为CMake没有重新跑配置阶段。touch一下CMakeLists.txt或者加--cmake-clean-cache都行。
注意conda和ROS的冲突。如果你装了conda并且默认激活了base环境,find_package(Python)找到的会是conda的Python,链接出来的.so和系统Python不匹配。症状通常是明明装了的Python包在ROS节点里import不到。最省事的办法是在跑ROS构建前conda deactivate,或者在.bashrc里把conda的自动激活关掉。
虚拟机内存不够的时候,别用高并行度。这个坑我踩过好几次,catkin_make -j8在4G内存的虚拟机上必崩,而且崩溃方式是编译进程被杀,报错信息看起来跟代码毫无关系。给虚拟机的内存和并行度做个匹配,2核就-j2,别贪。
保留一份能跑的环境快照。环境类问题最烦的是"改着改着原来能跑的也跑不了了"。我现在的习惯是,一个工作空间调通之后立刻打一个容器镜像或者至少把apt list --installed导出一份,出了问题能快速对比出被改动了什么。这个习惯救过我两次,尤其是在同一个系统上折腾多个ROS版本的时候。
最后分享一个我最近才养成的小习惯:每次遇到新的报错并且解决之后,在项目根目录开一个BUILD_NOTES.md,把报错原文、根因、解决命令三行记下来。一开始觉得麻烦,坚持半年之后发现,同一个坑基本不会再踩第二次——因为人对"自己写过的字"的记忆,远比对自己"看过的报错"靠谱。