《ROS2 构建系统 :colcon 与CMakeLists.txt 是如何协作的》
文章目录
- 《ROS2 构建系统 :colcon 与CMakeLists.txt 是如何协作的》
-
-
- 1. 三层架构:谁在干活?
- 2. 实战流程:一条指令的背后
-
- Step 1: 包发现与拓扑排序
- Step 2: 创建隔离的构建环境
- Step 3: CMake 配置阶段(Configuration)
- Step 4: 编译阶段(Build)
- Step 5: 安装阶段(Install)
- 3. 为什么不用 colcon “全自动”生成 CMakeLists?
- 4. 常见误区澄清
-
- 误区 1:“我把 cpp 放在 src/ 下,colcon 应该能自动找到”
- 误区 2:“CMakeLists.txt 只是用来找 ROS 包的”
- 误区 3:“改了 CMakeLists.txt 不需要重新 colcon build”
- 5. 实战:最小可运行的 CMakeLists.txt 解剖
- 6. 总结
-
前言
很多从单片机或纯 Python 转过来的 ROS2 初学者都会有这个困惑:“我明明只需要跑个 colcon build,为什么还要费劲写那一堆 CMakeLists.txt?colcon 自己不能把 cpp 文件编译了吗?”
答案是:colcon 是包工头,CMakeLists.txt 是施工图纸。没有图纸,包工头不知道如何把钢筋水泥(源代码)变成楼房(可执行文件)。本文将拆解 ROS2 Humble 下的完整构建链路,讲清楚 CMake 到底在编译时做了什么,以及为什么你的 CMakeLists.txt 不能省。
1. 三层架构:谁在干活?
在 ROS2(ament_cmake 体系)中,一次完整的编译涉及三个层级:
| 构建编排器 | colcon | package.xml | 并行调度多个包的编译顺序 |
| 构建系统生成器 | cmake | CMakeLists.txt | 生成 Makefile/Ninja 脚本 |
| 构建执行器 | make/ninja | Makefile | 调用 g++ 生成二进制 |
关键洞察:colcon 不直接编译代码。它只负责找到包 → 解决依赖顺序 → 调用 cmake → 调用 make。
2. 实战流程:一条指令的背后
当你在 ws01_plumbing 目录执行:
colcon build –packages-select cpp01_topic –cmake-args -DCMAKE_BUILD_TYPE=Release
系统内部经历了以下流水线:
Step 1: 包发现与拓扑排序
colcon 读取 src/ 下所有 package.xml,解析出 <build_type>ament_cmake</build_type> 和 <depend> 依赖,构建出编译顺序图(确保 base_interfaces_demo 先于 cpp01_topic 编译)。
Step 2: 创建隔离的构建环境
colcon 为 cpp01_topic 创建独立工作目录:
- 源码目录:src/cpp01_topic/
- 构建目录:build/cpp01_topic/(cmake 和 make 在此干活)
- 安装目录:install/cpp01_topic/(最终产物存放地)
这一步隔离性是 colcon 相比旧版 catkin_make 的最大优势,避免多个包在相同目录下互相污染中间文件。
Step 3: CMake 配置阶段(Configuration)
colcon 调用 CMake,传入关键参数:
cmake /path/to/src/cpp01_topic \\
-DCMAKE_INSTALL_PREFIX=/path/to/install \\
-DAMENT_PREFIX_PATH=/path/to/install
此时 CMake 逐行解析你的 CMakeLists.txt,执行以下动作:
查找依赖:find_package(rclcpp REQUIRED) 通过 rclcppConfig.cmake 找到头文件路径(/opt/ros/humble/include/rclcpp)和库文件。
声明构建目标:add_executable(talker src/demo01_talker_str.cpp) 告诉 CMake:我要把一个 cpp 编译成名叫 talker 的可执行文件。
链接依赖:ament_target_dependencies(talker rclcpp std_msgs) 自动填充 -I(头文件)、-L(库路径)、-l(链接库名)编译参数,无需手动写 target_link_libraries。
安装规则:install(TARGETS talker DESTINATION lib/${PROJECT_NAME}) 标记这个可执行文件在 make install 时要被复制到 install/lib/cpp01_topic/。
注意:此时没有生成任何二进制,CMake 只是在 build/ 目录下生成了 Makefile(或 build.ninja),里面记录了“如何编译”的完整指令集。
Step 4: 编译阶段(Build)
colcon 调用 make(或 ninja):
make -j8 # 8 线程并行编译
make 读取 Makefile,执行:
- 编译:g++ -c src/demo01_talker_str.cpp -I/opt/ros/humble/include … -o CMakeFiles/talker.dir/demo01_talker_str.cpp.o
- 链接:g++ CMakeFiles/talker.dir/demo01_talker_str.cpp.o -lrclcpp … -o talker
生成的 talker 二进制文件位于 build/cpp01_topic/talker。
Step 5: 安装阶段(Install)
colcon 调用 make install,CMake 根据 Step 3 的 install() 指令,把二进制拷贝到 install/cpp01_topic/lib/cpp01_topic/talker,并处理 package.xml 的导出。
3. 为什么不用 colcon “全自动”生成 CMakeLists?
因为 C++ 是静态编译语言,编译规则高度依赖项目结构,无法像 Python 那样直接解释执行。CMakeLists.txt 必须显式声明:
- 源文件列表:哪些 cpp 参与编译?
- 头文件路径:你自己的 include/ 目录在哪?
- 依赖传递:用了自定义消息 base_interfaces_demo,需要链接生成的 .so 文件。
- 编译选项:开没开 -std=c++17?有没有 -Wall -Wextra?
- 安装目标:哪些文件要打包进 install 目录供其他包调用?
举例:如果你的包提供了库(.so)供其他包链接,你需要写:
add_library(my_utils SHARED src/utils.cpp)
ament_target_dependencies(my_utils rclcpp)
ament_export_targets(my_utils_targets HAS_LIBRARY_TARGET) # 关键:导出给别的包用
install(TARGETS my_utils EXPORT my_utils_targets …)
这类语义无法由 colcon 自动推断,必须由开发者精确描述。
4. 常见误区澄清
误区 1:“我把 cpp 放在 src/ 下,colcon 应该能自动找到”
正解:C++ 没有“自动发现源文件”机制(不像 Python)。你必须在 CMakeLists.txt 里显式 add_executable(xx src/xx.cpp)。遗漏会导致 undefined reference 或你截图里那种 target does not exist 错误。
误区 2:“CMakeLists.txt 只是用来找 ROS 包的”
正解:find_package 只是 10% 的工作。更重要的是定义构建图(Build Graph):谁依赖谁、编译顺序、链接顺序。如果你用了自定义消息(.msg),CMake 还要驱动代码生成器(rosidl_generate_interfaces),把 .msg 变成 .hpp,这些都在 CMakeLists.txt 里编排。
误区 3:“改了 CMakeLists.txt 不需要重新 colcon build”
正解:修改 CMakeLists.txt 属于元构建系统变更,必须重新走 Step 3(cmake 配置)。colcon 会自动检测文件修改并重新运行 cmake,但如果你在构建目录手动改,会导致构建状态不一致,建议 rm -rf build/ 后重新 colcon build。
5. 实战:最小可运行的 CMakeLists.txt 解剖
以下是一个 ROS2 C++ 节点的标准模板,逐行注释说明 CMake 的介入时机:
cmake_minimum_required(VERSION 3.8) # (1) 声明最低 CMake 版本,必须第一行
project(cpp01_topic) # (2) 定义项目名,影响 ${PROJECT_NAME}
# (3) 编译标准声明:C++17(影响 g++ 的 -std 参数)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# (4) 依赖发现阶段:CMake 会去 /opt/ros/humble/share/rclcpp/cmake 找配置文件
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(base_interfaces_demo REQUIRED)
# (5) 构建目标声明:告诉 CMake 生成一个叫 talker 的可执行文件
add_executable(talker src/demo01_talker_str.cpp)
# (6) 依赖关联:自动填充 -I 和 -L 路径
ament_target_dependencies(talker
rclcpp
std_msgs
base_interfaces_demo
)
# (7) 编译特性:确保使用 C++14/17(ROS2 Humble 要求 C++17)
target_compile_features(talker PUBLIC c_std_99 cxx_std_17)
# (8) 安装阶段:make install 时把 talker 拷到 install/lib/cpp01_topic/
install(TARGETS talker
DESTINATION lib/${PROJECT_NAME}
)
# (9) ament 包标记:声明这是一个 ROS2 包,必须最后
ament_package()
编译时发生了什么:
6. 总结
- colcon 解决多包协作问题:自动排序依赖、并行编译、环境隔离。
- CMakeLists.txt 解决单包构建问题:源文件组织、依赖解析、编译选项、安装规则。
- CMake 是两者之间的桥梁:把声明式的构建描述(CMakeLists.txt)转化为命令式的编译指令(Makefile)。
记忆口诀:
colcon 管排队,CMake 管配方,make 管炒菜。
理解了这个流程,你就能看懂编译错误(如 “target does not exist” 是 CMake 配置阶段问题,“undefined reference” 是链接阶段问题),也能正确配置跨包依赖和自定义消息接口。不再为“为什么要写这么多 CMake 代码”而困惑——因为 C++ 的编译,本质上就是一场精确的工程控制。
网硕互联帮助中心




评论前必须登录!
注册