】)
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 内置变量不写这行编译器默认使用 C14会导致 std::make_unique 等 C17 语法报错。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 模式默认通常是 Debugset(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 -marchnative)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_TIMEON编译后这个值会固化到 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 精确行为加 EXACTfind_package(rclcpp 5.0.0 EXACT REQUIRED)表示版本号必须是5.0.0不加EXACT表示版本号5.0.0执行成功后CMake 会在内存中定义一系列变量以 rclcpp 为例变量名含义示例值rclcpp_FOUND是否找到True/FalseTRUErclcpp_INCLUDE_DIRS头文件搜索路径/opt/ros/humble/include/rclcpprclcpp_LIBRARIES库文件名rclcpp对应 librclcpp.sorclcpp_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.msgsrv/MySrv.srvDEPENDENCIES 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.xmlpackage.xml 无需特殊修改默认的 buildtool_depend 和 build_type 就足够了。包含并使用库如果另一个包 complex_test 需要使用上面构建的my_math_lib库则需要1.在 package.xml 中声明依赖在 complex_test 的 package.xml 中添加对 my_math_lib 的依赖。dependmy_math_lib/depend2.在 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代码中就可以直接包含并使用库的头文件了。#includemy_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 依赖了 librmwament_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. 安装- - -installinstall() 是一个 CMake 命令它的作用就像一份 “部署清单” 。它告诉构建系统在编译完成后需要把哪些文件比如编译好的节点程序、自定义的接口文件、启动脚本等复制到 install 目录下的哪个具体位置。install() 命令主要用于安装四种不同类型的对象安装可执行文件 (TARGETS)用途安装编译生成的节点程序可执行文件和库文件libxxx.so。标准写法install(TARGETStarget1target2...DESTINATION lib/${PROJECT_NAME})TARGETS后面跟着你用 add_executable() 或 add_library() 定义的目标名称。DESTINATION指定安装的目标文件夹。对于可执行文件必须安装在 lib/${PROJECT_NAME} 目录下。对于库文件同样建议安装在 lib/${PROJECT_NAME} 目录下。安装目录 (DIRECTORY)用途安装一整个文件夹例如 launch/ 启动文件夹、config/ 配置文件夹、rviz/ 可视化配置文件等。标准写法:install(DIRECTORY目录1目录2...DESTINATION share/${PROJECT_NAME})DIRECTORY后面跟着要安装的文件夹名称例如 launch 或 config。DESTINATION指定安装的目标文件夹。通常安装在 share/${PROJECT_NAME} 目录下。安装文件 (FILES)用途安装单个文件例如根目录下的 package.xml 或一些重要的配置文件。标准写法install(FILES文件1文件2...DESTINATION share/${PROJECT_NAME})FILES后面跟着要安装的文件名。DESTINATION与安装目录类似通常目标也是 share/${PROJECT_NAME}。安装头文件 (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的常见内容