当前位置: 首页 > news >正文

【CMakeLists.txt的写法(基于ROS2)】

CMakeLists.txt的写法(基于ROS2)

  • 基本构成
    • 1. 配置(环境基础配置,头部声明)
      • set的用法 - -(创建变量并给它赋值)
    • 2. 查找(依赖包查找- - find_package)
      • 完整语法与参数详解
      • 常用实例
      • 自定义消息/服务/动作
      • 自定义工作空间包的查找
    • 3. 构建(库和可执行节点)
      • 构建库(Library)- - 其他包能够使用
      • 包含并使用库
      • 处理非ROS2的第三方库
      • 构建可执行节点
    • 4. 链接
    • 5. 安装- - -install
      • 完整示例:综合应用
      • 🚨 两个关键注意事项
    • 完整的代码示例

基本构成

在 ROS2 的 C++ 项目中,CMakeLists.txt 不是普通的编译脚本,而是一份“构建说明书”。它严格按照 “配置 → 查找 → 构建 → 链接 → 安装” 的生命周期组织。

1. 配置(环境基础配置,头部声明)

cmake_minimum_required(VERSION3.8)# 指定最低 CMake 版本project(your_package_name)# 定义项目名称,自动创建两个变量:(后续可直接使用) # ${PROJECT_NAME}(本CMakeLists.txt的project名称) # ${PROJECT_SOURCE_DIR}(本CMakeLists.txt所在的文件夹路径)
  • 作用:设置构建环境的门槛。ROS2 Humble 强烈建议 CMake 版本 ≥ 3.8。
  • 注意:project() 定义的名称必须与package.xml中的< name >标签完全一致,否则 colcon build 会报错。

set的用法 - -(创建变量并给它赋值)

set(变量名 值)
  • 如果只给一个值,就是普通字符串变量。
  • 如果给多个值(用空格或分号隔开),CMake 会自动将其拼接为分号分隔的列表(实际上是字符串列表)。
set(变量名 值 CACHE 类型 描述[FORCE])
  • 这种变量会写入 CMakeCache.txt,在多次 colcon build 之间保留。
  • <类型> 必须是 STRING、BOOL、PATH、FILEPATH 之一。
  • 加 FORCE 会强制覆盖已有的缓存值。

变量作用域核心规则

  • 局部变量(不带CACHE):作用域仅限于当前 CMakeLists.txt 及其通过 add_subdirectory 包含的子目录。子目录的修改不会影响父目录。
  • 缓存变量(带CACHE):全局生效,所有子目录都能读取。
  • 引用变量:使用 $ {变量名} 取值。如果变量未定义,${变量名} 会被替换为空字符串(不会报错)。

常见使用案例

1. 强制指定 C++ 标准

# 设置 C++标准为17,且必须严格执行(不允许降级)set(CMAKE_CXX_STANDARD17)set(CMAKE_CXX_STANDARD_REQUIRED ON)# 关闭编译器对非标准 GNU 扩展的支持(保持跨平台兼容性)set(CMAKE_CXX_EXTENSIONS OFF)
  • 原理:CMAKE_CXX_STANDARD 是 CMake 内置变量,不写这行,编译器默认使用 C++14,会导致 std::make_unique 等 C++17 语法报错。

2. 定义源码列表,提高可维护性(避免 add_executable 写很长)

# 将多个源文件组合成一个变量set(SOURCES src/main.cpp src/controller.cpp src/pid.cpp src/odometry.cpp)# 然后直接在 add_executable 中引用add_executable(my_node ${SOURCES})
  • 优势:如果新增/删除源文件,只需改 set 这一处,不用动 add_executable 那一长串。

3. 修改编译优化等级(Debug/Release 切换)

# 强制设为 Release 模式(默认通常是 Debug)set(CMAKE_BUILD_TYPE Release)# 或者在 Debug 模式下禁用优化,保留调试符号set(CMAKE_CXX_FLAGS_DEBUG"-g -O0")
  • 注意:如果不设 CMAKE_BUILD_TYPE,很多编译器默认不给优化,导致运行变慢。在真实机器人上测试时,强烈建议改为 Release。

4. 给编译器加特定参数(如打开所有警告)

# 追加编译选项(不会覆盖原有选项)set(CMAKE_CXX_FLAGS"${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")# 或者专门针对特定目标(更推荐这种,不影响其他节点)set(MY_NODE_FLAGS"-O3 -march=native")target_compile_options(my_node PRIVATE ${MY_NODE_FLAGS})

5. 处理列表变量(追加/删除/遍历)

# 定义初始列表set(MY_PACKAGES rclcpp std_msgs)# 追加元素(方法1:用 list 命令)list(APPEND MY_PACKAGES geometry_msgs visualization_msgs)# 追加元素(方法2:直接 set 拼接,注意引号)set(MY_PACKAGES ${MY_PACKAGES}nav_msgs)# 此时变成5个元素 # 移除某个元素list(REMOVE_ITEM MY_PACKAGES visualization_msgs)# 遍历列表(高级用法,在 ROS2 中常用于批量安装启动文件)foreach(pkg ${MY_PACKAGES})find_package(${pkg}REQUIRED)endforeach()

6. 设置缓存变量(让用户在命令行灵活修改)
在 CMakeLists.txt 中写入:

set(USE_SIM_TIMEfalseCACHE BOOL"是否使用仿真时间模式")

此时,用户在执行 colcon build 时可以传入参数覆盖:

colcon build--packages-select your_pkg--cmake-args-DUSE_SIM_TIME=ON

编译后,这个值会固化到 build/your_pkg/CMakeCache.txt 中,后续编译直接读取缓存值,无需重复输入。

- 注意:列表变量必须带引号

set(FLAGS-Wall-Wextra)# 错误!CMake 会把-Wextra 当成第二个值set(FLAGS"-Wall -Wextra")# 正确!整体作为一个字符串

- 注意:覆盖内置变量要谨慎。比如 set(CMAKE_CXX_STANDARD 17) 必须写在 project() 之后,因为 project() 会初始化编译器检测,写在前面可能导致部分检测失效。

2. 查找(依赖包查找- - find_package)

告诉 CMake,去系统里把某个“外部库”的安装位置、头文件路径、库文件路径以及编译宏全部挖出来,并准备好供后续使用。

完整语法与参数详解

find_package(<包名>[版本号][EXACT][QUIET][REQUIRED][COMPONENTS<组件列表>])
参数含义建议
REQUIRED必须找到,找不到立即报错并终止 CMake 配置。必须加。如果不加,找不到时只会警告,后续 ament_target_dependencies 会因为变量为空而报更奇怪的链接错误。
版本号如 find_package(rclcpp 5.0.0 REQUIRED)极少用,因为 ROS2 版本由发行版(Humble)整体锁定。
QUIET静默模式,不输出“找到了”的提示信息。一般不写,方便观察日志排查路径是否正确。
COMPONENTS只查找该包中的特定子组件。例如 find_package(OpenCV REQUIRED COMPONENTS core imgproc),在 ROS2 中对 nav2_msgs 等复合包有用。
EXACT默认行为(不加 EXACT) 精确行为(加 EXACT)find_package(rclcpp 5.0.0 EXACT REQUIRED)表示版本号必须是5.0.0,不加EXACT表示版本号>=5.0.0

执行成功后,CMake 会在内存中定义一系列变量(以 rclcpp 为例):

变量名含义示例值
rclcpp_FOUND是否找到(True/False)TRUE
rclcpp_INCLUDE_DIRS头文件搜索路径/opt/ros/humble/include/rclcpp
rclcpp_LIBRARIES库文件名rclcpp(对应 librclcpp.so)
rclcpp_DEFINITIONS编译宏定义(极少用到)-DRCLCPP_BUILD_DLL

注意:

  • 在 ROS2 的现代用法中,几乎不用手动写 target_include_directories 去引用这些变量。我们直接用 ament_target_dependencies,它会自动读取这些变量并附加到目标上。
  • CMakeLists.txt 中的 find_package:负责编译时找到头文件和静态/动态库。
  • package.xml 中的 < depend> 标签:负责运行时把包路径加入 AMENT_PREFIX_PATH。

如果你只在 CMake 里写了 find_package(rclcpp REQUIRED),却忘了在 package.xml 里写 < depend >rclcpp</ depend >:
👉 编译能通过(因为 find_package 去了系统路径找),但编译完成后,colcon build 生成的 install 目录下的环境脚本中,不会把 rclcpp 写入依赖链。当你运行 ros2 run 时,系统会报错 symbol lookup error 或 cannot open shared object file。

常用实例

# 查找系统或工作空间中已安装的 ROS2 基础包find_package(ament_cmake REQUIRED)# Ament 构建系统的核心(必须)find_package(rclcpp REQUIRED)# ROS2 C++客户端库(写节点必须)find_package(std_msgs REQUIRED)# 标准消息包(如果需要订阅/发布基础类型)find_package(geometry_msgs REQUIRED)# 几何消息(如 Twist,Pose) # 如果自定义了接口(.msg/.srv/.action),必须查找生成器find_package(rosidl_default_generators REQUIRED)
  • 底层逻辑:find_package 会去 /opt/ros/humble 和 install 目录下寻找对应的 xxx-config.cmake 文件,加载库路径、头文件路径和编译宏定义。
  • 黄金法则:CMakeLists.txt 中 find_package 列出的每一个包,必须同时在 package.xml 中用< depend>或<build_depend>声明,否则编译通过但运行时可能缺少动态链接库。

自定义消息/服务/动作

# 查找生成依赖find_package(rosidl_default_generators REQUIRED)# 声明要生成的接口文件(假设 msg/MyMsg.msg 和 srv/MySrv.srv 存在)rosidl_generate_interfaces(${PROJECT_NAME}"msg/MyMsg.msg""srv/MySrv.srv"DEPENDENCIES std_msgs # 生成代码时依赖的包(如 MyMsg 里引用了 std_msgs/Header))# 让当前包也可以使用自己生成的接口头文件ament_export_dependencies(rosidl_default_runtime)
  • 作用:将 .msg / .srv 文本描述,编译成 C++ 可用的 .hpp 头文件和动态库
  • 生成位置:编译后会在 install/your_package/include/your_package/msg/my_msg.hpp 下生成代码。
  • 关键点:DEPENDENCIES 必须包含消息字段中引用的所有其他 ROS2 包。

自定义工作空间包的查找

假设你的 src 下有两个包:my_interface(定义消息)和 my_node(使用消息)。
在 my_node/CMakeLists.txt 中,需要这样写才能找到隔壁包:

#1.必须用 find_package 找到自定义接口包(因为它在你的工作空间 install 里)find_package(my_interface REQUIRED)#2.然后才能把它的消息库链接过来add_executable(publisher src/publisher.cpp)ament_target_dependencies(publisher rclcpp my_interface)

3. 构建(库和可执行节点)

构建库(Library)- - 其他包能够使用

步骤:

1.构建功能包
首先,创建一个构建类型为ament_cmake的包。

cd~/ros2_ws/src ros2 pkg create my_math_lib--build-type ament_cmake--dependencies rclcpp #--dependencies rclcpp 指定了库可能依赖的ROS2核心包

2.编写库的代码

  • 头文件:在 include/my_math_lib/my_math_lib.hpp 中声明你的函数或类。
  • 源文件:在 src/my_math_lib.cpp 中实现它们。

3.配置CMakeLists.txt
这是构建库最关键的部分,需要完成以下几个任务:

  • a. 定义库目标:使用 add_library() 将源文件编译成库。通常使用 SHARED (动态库)。
add_library(${PROJECT_NAME}SHARED src/my_math_lib.cpp)

一个包通常只导出一个与包同名的核心库,可以将核心库需要的所有.cpp文件都加在括号里

  • b. 链接ROS2依赖:使用 ament_target_dependencies() 为你的库链接它所需的ROS2包
ament_target_dependencies(${PROJECT_NAME}rclcpp)
  • c. 声明头文件路径:使用 target_include_directories() 声明公共头文件目录,并利用生成器表达式区分构建和安装阶段
target_include_directories(${PROJECT_NAME}PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)
  • d. 导出目标和依赖:这是让其他包能找到你的库的关键
    ament_export_targets():将你的库目标(${PROJECT_NAME})导出,供其他包使用。
    ament_export_dependencies():声明你的库所依赖的ROS2包(如rclcpp)。这样,当其他包使用你的库时,会自动链接这些依赖。
ament_export_targets(export_${PROJECT_NAME}HAS_LIBRARY_TARGET)ament_export_dependencies(rclcpp)
  • e. 安装文件:将头文件和编译好的库文件安装到工作空间的install目录下
install(DIRECTORY include/DESTINATION include)install(TARGETS ${PROJECT_NAME}EXPORT export_${PROJECT_NAME}LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin)

EXPORT参数必须与ament_export_targets()中的名字保持一

  • f. 别忘了结尾:在所有配置的最后,必须加上 ament_package()。

4.配置 package.xml
package.xml 无需特殊修改,默认的 buildtool_depend 和 build_type 就足够了。

包含并使用库

如果另一个包 complex_test 需要使用上面构建的my_math_lib库,则需要:
1.在 package.xml 中声明依赖
在 complex_test 的 package.xml 中,添加对 my_math_lib 的依赖。

<depend>my_math_lib</depend>

2.在 CMakeLists.txt 中查找并链接

a. 查找库:使用 find_package() 来查找 my_math_lib。

find_package(my_math_lib REQUIRED)

b. 创建可执行文件:定义你的节点目标。

add_executable(complex_test src/complex_test.cpp)

c. 链接依赖:使用 ament_target_dependencies() 链接 my_math_lib 和它所需的所有依赖。

ament_target_dependencies(complex_test my_math_lib rclcpp)

这条命令会自动处理 my_math_lib 的头文件路径、库文件以及它导出的所有依赖(如 rclcpp)。

d. 安装可执行文件:为了让 ros2 run 能找到,需要安装目标。

install(TARGETS complex_test DESTINATION lib/${PROJECT_NAME})

3. 在源码中使用库
在你的C++代码中,就可以直接包含并使用库的头文件了。

#include"my_math_lib/my_math_lib.hpp"// ... 在代码中调用 my_math_lib 提供的功能

处理非ROS2的第三方库

对于 Eigen3、OpenCV 这类非ROS2的第三方库,处理方式略有不同:

  • 如果第三方库支持 find_package():这是最理想的方式。
    1.在 CMakeLists.txt 中使用 find_package() 找到它。
    2.在 ament_target_dependencies() 中直接添加包名。如果该库导出了自己的依赖,ament_target_dependencies 也能自动处理
find_package(Eigen3 REQUIRED)ament_target_dependencies(complex_test Eigen3)
  • 如果第三方库不支持 find_package():你需要手动指定路径。
    1.将库文件(.so/.a)和头文件(.h/.hpp)放在你包内的目录中,如 lib/ 和 include/。
    2.在 CMakeLists.txt 中手动添加头文件路径和链接库。
include_directories(include)add_executable(cantest src/cantest.cpp)#当前功能包的target_link_libraries(cantest ${CMAKE_CURRENT_SOURCE_DIR}/lib/libcanbus.so)

构建可执行节点

4. 链接

ROS2 / CMake 中实现链接的 3 种写法:
在 CMakeLists.txt 中,有三种手段来指挥链接器。
① ament_target_dependencies() —— ROS2 的首选(智能链接)
这是 ROS2 封装的最强工具,不仅链接库,还会自动传递头文件路径和依赖关系。

find_package(rclcpp REQUIRED)add_executable(my_node src/main.cpp)ament_target_dependencies(my_node rclcpp std_msgs)

它相当于自动帮你写了两行代码:

  • 添加头文件:target_include_directories(…)
  • 链接库:target_link_libraries(my_node ${rclcpp_LIBRARIES} ${std_msgs_LIBRARIES})
    并且,如果 rclcpp 依赖了 librmw,ament_target_dependencies 会递归把 librmw 也链接进来。

② target_link_libraries() —— CMake 原生写法(手动链接)
当你在链接非 ROS2 的纯第三方库(如 OpenCV、Eigen、PCL)或自己写的纯 C++ 库时使用。

find_package(OpenCV REQUIRED)include_directories(${EIGEN3_INCLUDE_DIR}${OpenCV_INCLUDE_DIRS}include)add_executable(my_vision src/vision.cpp)# 手动链接 OpenCV 库target_link_libraries(my_vision ${OpenCV_LIBRARIES})

5. 安装- - -install

install() 是一个 CMake 命令,它的作用就像一份 “部署清单” 。它告诉构建系统,在编译完成后,需要把哪些文件(比如编译好的节点程序、自定义的接口文件、启动脚本等)复制到 install 目录下的哪个具体位置。
install() 命令主要用于安装四种不同类型的对象:

  1. 安装可执行文件 (TARGETS)
    用途:安装编译生成的节点程序(可执行文件)和库文件(libxxx.so)。
    标准写法:
install(TARGETS<target1><target2>...DESTINATION lib/${PROJECT_NAME})
  • TARGETS:后面跟着你用 add_executable() 或 add_library() 定义的目标名称。

  • DESTINATION:指定安装的目标文件夹。

  • 对于可执行文件,必须安装在 lib/${PROJECT_NAME} 目录下。

  • 对于库文件,同样建议安装在 lib/${PROJECT_NAME} 目录下。

  1. 安装目录 (DIRECTORY)
    用途:安装一整个文件夹,例如 launch/ 启动文件夹、config/ 配置文件夹、rviz/ 可视化配置文件等。
    标准写法:
install(DIRECTORY<目录1><目录2>...DESTINATION share/${PROJECT_NAME})
  • DIRECTORY:后面跟着要安装的文件夹名称,例如 launch 或 config。

  • DESTINATION:指定安装的目标文件夹。通常安装在 share/${PROJECT_NAME} 目录下。

  1. 安装文件 (FILES)
    用途:安装单个文件,例如根目录下的 package.xml 或一些重要的配置文件。
    标准写法:
install(FILES<文件1><文件2>...DESTINATION share/${PROJECT_NAME})
  • FILES:后面跟着要安装的文件名。

  • DESTINATION:与安装目录类似,通常目标也是 share/${PROJECT_NAME}。

  1. 安装头文件 (DIRECTORY)
    用途:安装公共头文件(.h/.hpp),以便工作空间中的其他功能包能够通过 find_package 找到并调用你编写的库。
    标准写法:
install(DIRECTORY include/DESTINATION include)
  • 这与安装 launch 目录的写法类似,但目标路径是 include。

  • 注意路径 include/ 末尾的斜杠 /,这表示安装该目录下的内容,而不是整个目录本身。

完整示例:综合应用

#...(find_package,add_executable,target_link_libraries 等)# 安装编译生成的目标文件install(TARGETS talker # 节点1listener # 节点2my_math_lib # 自定义库 DESTINATION lib/${PROJECT_NAME})# 安装头文件(供其他包使用)install(DIRECTORY include/DESTINATION include)# 安装启动文件和配置文件install(DIRECTORY launch config DESTINATION share/${PROJECT_NAME})# 安装自定义接口定义文件(如果有)install(DIRECTORY msg srv action DESTINATION share/${PROJECT_NAME}/)#...ament_package()必须在最后ament_package()

🚨 两个关键注意事项

  • 位置必须在 ament_package() 之前:所有的 install() 命令都必须放在 CMakeLists.txt 文件的末尾,但在 ament_package() 这个最终命令的之前。

  • 没有 install 就无法使用:如果缺少对应的 install() 命令,编译生成的文件就不会被复制到 install 目录。这意味着 ros2 run 无法找到节点,ros2 launch 无法找到启动文件,find_package 也无法找到你的库和头文件。

完整的代码示例

参考:ROS2CMakeLists的常见内容

http://www.jsqmd.com/news/1233304/

相关文章:

  • .NET企业级开发:架构设计与性能优化实战
  • CommitRecap:用Git提交历史生成技术叙事报告的CLI工具
  • docker核心概念
  • HarmonyOS7 旋转手势:RotationGesture 实现双指旋转组件
  • 塑身衣不会卷边哪家好? - 中媒介
  • 找冷链锁鲜靠谱的烤小肠批发商 - 中媒介
  • 推荐洗衣液贴牌全流程定制服务商 - 中媒介
  • 操作系统不支持压缩包内文件搜索怎么办?
  • LangChain框架实战:大语言模型应用开发指南
  • SolidWorks建模到3D打印:解决外壳装配误差的DFM实战指南
  • HarmonyOS7 滑动手势:SwipeGesture 检测快速滑动方向
  • 沈阳哪里能买到安全的有机全脂羊乳粉? - 中媒介
  • 白酒老酒库存足哪家效果好? - 中媒介
  • 瓜子哪家干净卫生? - 中媒介
  • 湖南工地专用内墙涂料抗裂效果好不好 - 中媒介
  • 2026宠物小程序开发十大公司测评:洗护预约、用品商城与会员怎么选?含零代码SAAS、AI编程、源码定制
  • LangChain:大语言模型集成框架的核心技术与应用
  • PyTorch 2框架核心优化与实战指南
  • Windows右键菜单混乱不堪?3个步骤让它重获新生
  • AI工具如何革新企业级应用设计流程
  • 婴儿游泳洗澡服务哪家专业安全 - 中媒介
  • 从入门到高阶:2026电商美工精英班,PS、C4D、AI、PR全掌握
  • 2026年7月行业内专业的V型混合机实力厂家推荐,真空耙式干燥机/振动流化床干燥机/双锥混合机,V型混合机直销工厂选哪家 - 品牌推荐师
  • MyBatis-Plus高阶用法与Spring Boot3实战技巧
  • Java 17性能优化:ZGC、JIT与向量API实战解析
  • 深圳特色速冻食品批发 - 中媒介
  • Shiny for Python:构建交互式数据可视化应用指南
  • 运城市闻喜县2026最新黄金回收门店及联系方式指南 黄金回收白银回收铂金回收店铺TOP5排行榜 - 大熊猫898989
  • Structured Pruning of Large Language Models 解读
  • ML工程师的组织影响力:从模型交付到业务闭环