ROS 2 节点从源码到运行的完整依赖流程
1、HelloWorld 节点
bash
# 1、创建功能包(工作空间的:src目录下)
# com_yyy 是(自定义)的(功能包)的名词
ros2 pkg create com_yyy --build-type ament_cmake --dependencies rclcpp --node-name helloworld
# 2、编辑源文件
.cpp文件
# 3、编辑配置文件
package.xml、CMakeLists.txt
# 4、编译(工作空间下)
colcon build
# 5、执行(工作空间下)
# helloworld 是node-name 名称
. install/setup.bash
ros2 run com_yyy helloworld
2、执行流程
深入到 文件级、命令级、变量级,覆盖了从代码编写 → 编译 → 链接 → 安装 → 环境激活 → 运行的全链路细节。
bash
1. 编写用户源码
└── 文件: ./src/helloworld.cpp
└── 内容包含:
#include <rclcpp/rclcpp.hpp> ← 尖括号 → 系统/第三方头文件
int main() { ... }
2. 编写构建描述文件
└── 文件: ./CMakeLists.txt
├── cmake_minimum_required(VERSION 3.8)
├── project(com_yyy)
├── find_package(ament_cmake REQUIRED)
├── find_package(rclcpp REQUIRED) ← 关键:声明对 rclcpp 的依赖
├── add_executable(helloworld src/helloworld.cpp)
├── ament_target_dependencies(helloworld rclcpp)
└── install(TARGETS helloworld DESTINATION lib/${PROJECT_NAME})
3. 构建前:激活 ROS 2 系统环境(提供构建依赖) -> 注意执行source命令:我写在了 ~/.bashrc 文件中, 所以打开任意一个终端,都能用ros2命令
这个source 就是为了设置环境变量这些:能使用ros2 命令
└── 执行命令:
source /opt/ros/humble/setup.bash
│
└── 该脚本内部:
├── 设置 COLCON_CURRENT_PREFIX="/opt/ros/humble"
└── source /opt/ros/humble/local_setup.bash
│
└── /opt/ros/humble/local_setup.bash 内容(节选):
export AMENT_PREFIX_PATH="/opt/ros/humble"
export CMAKE_PREFIX_PATH="/opt/ros/humble${CMAKE_PREFIX_PATH:+:${CMAKE_PREFIX_PATH}}"
export PATH="/opt/ros/humble/bin:$PATH"
export LD_LIBRARY_PATH="/opt/ros/humble/lib:$LD_LIBRARY_PATH"
export PYTHONPATH="/opt/ros/humble/lib/python3.10/site-packages:$PYTHONPATH"
export PKG_CONFIG_PATH="/opt/ros/humble/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
→ 此时 shell 环境中:
$ echo $CMAKE_PREFIX_PATH
/opt/ros/humble
4. 执行构建命令
└── 命令: colcon build --packages-select com_yyy
│
└── colcon 内部行为:
├── 创建 build/com_yyy/, install/, log/ 目录
├── 在 build/com_yyy/ 中调用:
cmake -DCMAKE_INSTALL_PREFIX=../install \
-DCMAKE_PREFIX_PATH="/opt/ros/humble" \ ← 显式传入(也读环境)
/path/to/ws/src/com_yyy
│
└── CMake 执行过程:
├── 读取 CMakeLists.txt
├── 执行 find_package(rclcpp REQUIRED)
│ └── 搜索策略(按顺序):
│ 1. CMAKE_PREFIX_PATH 中每个路径的 share/rclcpp/cmake/
│ 2. 找到: /opt/ros/humble/share/rclcpp/cmake/rclcppConfig.cmake
│
├── 加载 rclcppConfig.cmake
│ └── 该文件定义(通过 set 或 import):
│ rclcpp_INCLUDE_DIRS = /opt/ros/humble/include
│ rclcpp_LIBRARIES = rclcpp::rclcpp (CMake target)
│ (实际是 IMPORTED target,指向 /opt/ros/humble/lib/librclcpp.so)
│
├── 执行 ament_target_dependencies(helloworld rclcpp)
│ └── 等价于:
│ target_link_libraries(helloworld rclcpp::rclcpp)
│ (自动附加 include_directories、compile_definitions 等)
│
└── 生成 build.ninja 或 Makefile
└── 编译规则示例(ninja -t commands | grep helloworld):
g++ -I/opt/ros/humble/include \
-I/usr/include/eigen3 \
-isystem /opt/ros/humble/include/opencv4 \
-std=c++17 \
-o CMakeFiles/helloworld.dir/src/helloworld.cpp.o \
-c /path/to/ws/src/com_yyy/src/helloworld.cpp
5. 编译与链接
└── 执行: ninja (或 make)
│
└── 链接命令示例:
g++ CMakeFiles/helloworld.dir/src/helloworld.cpp.o \
-o helloworld \
-L/opt/ros/humble/lib \
-lrclcpp -lrcutils -lrmw_implementation ... \
-Wl,-rpath,/opt/ros/humble/lib ← 关键:嵌入运行时库搜索路径
→ 生成: build/com_yyy/helloworld (未安装)
6. 安装(部署)
└── 执行: cmake --install . --prefix ../install
│
└── 复制文件到 install/ 目录:
├── install/com_yyy/lib/com_yyy/helloworld
├── install/com_yyy/share/com_yyy/package.xml
└── install/com_yyy/share/ament_index/resource_index/packages/com_yyy (注册包)
→ 同时,colcon 自动生成:
├── install/_local_setup_util.sh
├── install/local_setup.bash
└── install/setup.bash
其中 install/local_setup.bash 内容(节选):
export AMENT_PREFIX_PATH="/path/to/ws/install${AMENT_PREFIX_PATH:+:${AMENT_PREFIX_PATH}}"
export PATH="/path/to/ws/install/com_yyy/lib/com_yyy:$PATH"
export LD_LIBRARY_PATH="/path/to/ws/install/com_yyy/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
export CMAKE_PREFIX_PATH="/path/to/ws/install${CMAKE_PREFIX_PATH:+:${CMAKE_PREFIX_PATH}}"
7. 运行前:激活当前工作空间环境
└── 执行: source install/setup.bash
│
└── 该脚本:
├── 设置 COLCON_CURRENT_PREFIX="/path/to/ws/install"
├── source /opt/ros/humble/local_setup.bash ← 继承上游(chaining)
└── source /path/to/ws/install/local_setup.bash ← 加载本工作空间
→ 最终环境变量(优先级从高到低):
PATH = /path/to/ws/install/com_yyy/lib/com_yyy:/opt/ros/humble/bin:...
LD_LIBRARY_PATH = /path/to/ws/install/com_yyy/lib:/opt/ros/humble/lib:...
AMENT_PREFIX_PATH = /path/to/ws/install:/opt/ros/humble
8. 启动节点
└── 执行: ros2 run com_yyy helloworld
│
└── ros2 CLI 工作流程:
├── 读取 AMENT_PREFIX_PATH,分割为前缀列表
├── 对每个前缀检查 share/<pkg>/package.xml 是否存在
│ → 找到 /path/to/ws/install/share/com_yyy/package.xml
├── 在该前缀下查找可执行文件:
│ → install/com_yyy/lib/com_yyy/helloworld
└── execvp("helloworld", ...) 启动进程
→ 进程启动后:
├── 动态链接器 (ld-linux.so) 加载依赖库
├── 根据 ELF 中的 RPATH(来自 -Wl,-rpath,...)和 LD_LIBRARY_PATH 查找 .so
│ → 成功加载 /opt/ros/humble/lib/librclcpp.so
└── 程序正常运行,打印日志
3、补充关键细节说明
3-1、关于 RPATH(运行时库路径嵌入)
-
即使你没设
LD_LIBRARY_PATH,程序也能找到librclcpp.so,因为 CMake 默认启用了:cmakeset(CMAKE_BUILD_RPATH_USE_ORIGIN ON) set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") # 或类似 -
你可以用
readelf -d helloworld | grep RPATH查看。
3-2、 ament_index 是什么?
install/share/ament_index/resource_index/packages/com_yyy是一个空文件- 它的作用是让
ros2 pkg list或get_package_share_directory()能快速发现你的包
3-3、为什么需要两次 source?
| 场景 | 必须 source? | 原因 |
|---|---|---|
colcon build |
是(至少 source 系统 ROS) | 否则 CMake 找不到 rclcpp |
ros2 run 自己的节点 |
是(必须 source install/) | 否则 ros2 不知道你的包存在 |
ros2 run demo_nodes_cpp talker |
只需 source 系统 ROS | 因为它在 /opt/ros/... 里 |
3-4、 验证命令(供你实操)
bash
# 1. 查看构建时 CMake 用了哪些 include
cat build/com_yyy/compile_commands.json | jq '.[].command' | grep -o '\-I[^ ]*'
# 2. 查看可执行文件的 RPATH
readelf -d install/com_yyy/lib/com_yyy/helloworld | grep RPATH
# 3. 查看运行时库依赖
ldd install/com_yyy/lib/com_yyy/helloworld | grep rclcpp
# 4. 查看环境变量
source install/setup.bash
echo $AMENT_PREFIX_PATH