一个典型的基于ament_cmake构建方式的CMakeLists.txt文件
bash
cmake_minimum_required(VERSION 3.8)
project(ch2_node_cpp)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++17
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
set(executable_node2go node2go)
set(executable_node2go2 node2go2)
set(executable_multinode multinode)
set(executable_multilifecyclenode multilifecyclenode)
set(executable_global_arg globalarg)
set(library_node2go nodelib)
set(library_getid getid)
set(library_lifecyclenode lifecyclenode2go)
set(dependencies
rclcpp
rclcpp_lifecycle
)
include_directories(include)
add_library(${library_node2go} SHARED
src/node2go.cpp
)
add_library(${library_getid} SHARED
src/getid.cpp
)
add_library(${library_lifecyclenode} SHARED
src/lifecyclenode2go.cpp
)
add_executable(${executable_node2go}
src/main.cpp
)
add_executable(${executable_node2go2}
src/main2.cpp
)
add_executable(${executable_multinode}
src/multinode.cpp
)
add_executable(${executable_multilifecyclenode}
src/multilifecyclenode.cpp
)
add_executable(${executable_global_arg}
src/global_arg.cpp
)
ament_target_dependencies(${library_node2go}
${dependencies}
)
ament_target_dependencies(${library_getid}
${dependencies}
)
ament_target_dependencies(${library_lifecyclenode}
${dependencies}
)
ament_target_dependencies(${executable_node2go}
${dependencies}
)
ament_target_dependencies(${executable_node2go2}
${dependencies}
)
ament_target_dependencies(${executable_multinode}
${dependencies}
)
ament_target_dependencies(${executable_multilifecyclenode}
${dependencies}
)
ament_target_dependencies(${executable_global_arg}
${dependencies}
)
target_link_libraries(${executable_node2go}
${library_node2go}
)
target_link_libraries(${executable_node2go2}
${library_node2go}
)
target_link_libraries(${executable_multinode}
${library_getid}
)
target_link_libraries(${executable_multilifecyclenode}
${library_lifecyclenode}
)
install(TARGETS
${library_node2go}
${library_getid}
${library_lifecyclenode}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(
TARGETS ${executable_node2go}
TARGETS ${executable_node2go2}
TARGETS ${executable_multinode}
TARGETS ${executable_multilifecyclenode}
TARGETS ${executable_global_arg}
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
install(DIRECTORY include/
DESTINATION include/
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()
ament_export_include_directories(include)
ament_export_libraries(${library_node2go} ${library_getid} ${library_lifecyclenode})
ament_export_dependencies(${dependencies})
ament_package()
上面贴的这个 CMakeLists.txt 是一个典型的 ROS 2 包的构建脚本,它使用 ament_cmake 作为构建系统。
这几个 ament_* 系列命令其实都是 ROS 2 在标准 CMake 之上封装的一套宏,作用是让包能:
- 正确地声明依赖和编译选项(本包如何编译、链接哪些库)
- 正确地向其它 ROS 2 包导出你的接口(头文件、库、依赖)
- 注册本包 到 ROS 2 的包索引,以便被
colcon、rosdep等工具找到并使用
下面将逐个说明它们的作用和为什么需要它们👇
1. ament_target_dependencies()
作用
- 在 ROS 2 的 CMake 宏里,它会:
- 在目标(可执行文件或库)上添加正确的
include_directories和link_libraries。 - 从
find_package()得到依赖包的编译选项和链路选项,并应用到该目标。 - 自动添加依赖包的
ament_export_*信息(这样你的构建知道依赖包在哪里)。
- 在目标(可执行文件或库)上添加正确的
为什么需要
- 如果你只用原生 CMake 的
target_link_libraries()+target_include_directories(),你需要手工处理依赖的路径和编译选项。 - ROS 2 为了跨平台和标准化,把这些配置打包在
ament_target_dependencies()里,更简单可靠。 - 保证你的 target 能找到 ROS 2 API 头文件(比如
rclcpp)以及正确链接它的库。
2. ament_export_include_directories()
作用
- 向其它包声明你的头文件路径。
- 当其它包用
find_package(ch2_node_cpp)时,这个路径会自动加入它们的include_directories()。
为什么需要
- 如果你的包提供公共头文件(比如
include/里的.hpp),直接放在include/并不会自动让别的包找到。 - 通过
ament_export_include_directories(include),你告诉构建系统: "如果有人依赖我,就把我的include/加到他们的编译器搜索路径里。"
3. ament_export_libraries()
作用
- 向其它包声明你的库目标。
- 让依赖你的包知道需要链接哪些库文件,以及它们的名字。
为什么需要
- 你的包可能编译了比如
nodelib、getid这样的库,并且这些库在别的包的代码中要调用。 - 如果不 export,这些库不会在依赖包的 CMake 中自动链接;依赖包必须手动写路径,这不方便也不安全。
4. ament_export_dependencies()
作用
- 告诉其它包: "如果你依赖我,你也会隐式依赖这些第三方包(比如 rclcpp、rclcpp_lifecycle)"。
为什么需要
- 依赖链传递:
如果 A 依赖你的包 B,而 B 又依赖rclcpp,那么 A 在构建时也必须有rclcpp的编译信息和链接信息。 ament_export_dependencies()自动帮你传递这些依赖,避免 A 写重复的find_package(rclcpp REQUIRED)。
5. ament_package()
作用
- 这是 ament_cmake 的核心收尾命令,必须在 CMakeLists.txt 末尾调用。
- 主要功能:
- 生成 ROS 2 包的 package.xml 对应的 CMake 导出文件。
- 收集所有之前
ament_export_*的信息并打包。 - 让其它 ROS 2 包可以用
find_package()来找到你的包。 - 向
colcon、ament_index注册包信息。
为什么需要
- 如果不调用
ament_package(),你的包就没有被 ROS 2 的包系统正确注册,别的包用find_package()会失败。 - 这是 ROS 2 中声明"我的包构建完成,可以被别人用"的关键一步。
小结(简单的依赖关系图)
ament_target_dependencies() # 设置目标的编译 & 链接信息(内部使用 find_package 数据)
|
+-- 提供依赖包的 include & library 信息,给当前包用
ament_export_include_directories() # 导出头文件路径
ament_export_libraries() # 导出库路径
ament_export_dependencies() # 导出依赖包
ament_package() # 收集以上所有导出信息并注册到 ROS 2 包索引
💡 简单理解:
ament_target_dependencies()➡️ 我怎么编译我的东西ament_export_*➡️ 别人怎么用我的东西ament_package()➡️ 把这些信息打包,并让别人能找到我