云计算百科
云计算领域专业知识百科平台

《ROS2 构建系统 dissect:colcon 与 CMakeLists.txt 是如何协作的》

《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()

    编译时发生了什么:

  • cmake .. 阶段处理 (1)-(9),生成 build.ninja。
  • ninja talker 阶段根据 (5) 的 add_executable 和源文件路径,调用 g++ 生成对象文件。
  • 链接阶段根据 (6) 解析出的 -lrclcpp 等参数,生成最终二进制。
  • ninja install 阶段执行 (8),使 ros2 run cpp01_topic talker 能找到该二进制。

  • 6. 总结

    • colcon 解决多包协作问题:自动排序依赖、并行编译、环境隔离。
    • CMakeLists.txt 解决单包构建问题:源文件组织、依赖解析、编译选项、安装规则。
    • CMake 是两者之间的桥梁:把声明式的构建描述(CMakeLists.txt)转化为命令式的编译指令(Makefile)。

    记忆口诀:

    colcon 管排队,CMake 管配方,make 管炒菜。

    理解了这个流程,你就能看懂编译错误(如 “target does not exist” 是 CMake 配置阶段问题,“undefined reference” 是链接阶段问题),也能正确配置跨包依赖和自定义消息接口。不再为“为什么要写这么多 CMake 代码”而困惑——因为 C++ 的编译,本质上就是一场精确的工程控制。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 《ROS2 构建系统 dissect:colcon 与 CMakeLists.txt 是如何协作的》
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!