init: ROS2 learning suite
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
# 80 · 包构建机制 colcon / ament(完全指南)
|
||||
|
||||
> **目标**:理解 ROS2 一个包从源码到 `ros2 run` 能找到的完整流程,能自己写 ament_python / ament_cmake 包。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 工作空间结构](#1-工作空间结构)
|
||||
- [2. 包类型](#2-包类型)
|
||||
- [3. package.xml(包身份证)](#3-packagexml包身份证)
|
||||
- [4. ament_python 包详解](#4-ament_python-包详解)
|
||||
- [5. ament_cmake 包详解](#5-ament_cmake-包详解)
|
||||
- [6. colcon build 内部流程](#6-colcon-build-内部流程)
|
||||
- [7. ament_index 与 ros2 工具发现](#7-ament_index-与-ros2-工具发现)
|
||||
- [8. 依赖解析与 rosdep](#8-依赖解析与-rosdep)
|
||||
- [9. 自定义消息 / 服务 / Action(进阶)](#9-自定义消息--服务--action进阶)
|
||||
- [10. 常见构建错误 + 解决](#10-常见构建错误--解决)
|
||||
- [11. 在本仓库里跑](#11-在本仓库里跑)
|
||||
- [12. 进阶:多包 workspace / release 流程](#12-进阶多包-workspace--release-流程)
|
||||
|
||||
---
|
||||
|
||||
## 1. 工作空间结构
|
||||
|
||||
```
|
||||
ros2_ws/ ← 工作空间根
|
||||
├── src/ ← 包源码(你写代码的地方)
|
||||
│ ├── pkg_a/ ← 一个 ROS 包
|
||||
│ ├── pkg_b/
|
||||
│ └── ...
|
||||
├── build/ ← colcon build 中间产物
|
||||
├── install/ ← colcon install 产物(ros2 唯一识别)
|
||||
├── log/ ← build/test 日志
|
||||
└── ...
|
||||
```
|
||||
|
||||
**`install/` 是唯一能被 `ros2` 命令识别的目录**。每次进新 shell 必须:
|
||||
```bash
|
||||
source install/setup.bash
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 包类型
|
||||
|
||||
ROS2 有两种主流 build_type:
|
||||
|
||||
| build_type | 文件 | 用途 |
|
||||
|---|---|---|
|
||||
| **ament_python** | `setup.py` + `package.xml` | Python 包 |
|
||||
| **ament_cmake** | `CMakeLists.txt` + `package.xml` | C++ 包 |
|
||||
| ament_cmake_python | 两者混合 | 复杂项目 |
|
||||
|
||||
---
|
||||
|
||||
## 3. package.xml(包身份证)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd"?>
|
||||
<package format="3">
|
||||
<name>my_pkg</name>
|
||||
<version>0.1.0</version>
|
||||
<description>...</description>
|
||||
<maintainer email="x@y.com">Name</maintainer>
|
||||
<license>Apache-2.0</license>
|
||||
|
||||
<depend>rclpy</depend> <!-- 构建 + 运行都需 -->
|
||||
<build_depend>rosidl_default_generators</build_depend>
|
||||
<exec_depend>rclpy</exec_depend>
|
||||
<exec_depend>std_msgs</exec_depend>
|
||||
|
||||
<test_depend>python3-pytest</test_depend>
|
||||
|
||||
<export>
|
||||
<build_type>ament_python</build_type>
|
||||
</export>
|
||||
</package>
|
||||
```
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| `<depend>` | 构建 + 运行 |
|
||||
| `<build_depend>` | 仅构建 |
|
||||
| `<exec_depend>` | 仅运行 |
|
||||
| `<test_depend>` | 仅测试 |
|
||||
| `<build_type>` | `ament_python` / `ament_cmake` |
|
||||
|
||||
---
|
||||
|
||||
## 4. ament_python 包详解
|
||||
|
||||
### 4.1 目录结构
|
||||
|
||||
```
|
||||
py_pubsub/
|
||||
├── package.xml
|
||||
├── setup.py ← 标准 setuptools
|
||||
├── setup.cfg ← ament_python 脚本目录约定
|
||||
├── resource/
|
||||
│ └── py_pubsub ← 空文件,ament_index marker(必填!)
|
||||
├── py_pubsub/ ← Python 模块
|
||||
│ ├── __init__.py
|
||||
│ └── *.py
|
||||
├── launch/
|
||||
│ └── *.py
|
||||
└── test/
|
||||
└── test_*.py
|
||||
```
|
||||
|
||||
### 4.2 setup.py
|
||||
|
||||
```python
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
package_name = 'py_pubsub'
|
||||
|
||||
setup(
|
||||
name=package_name,
|
||||
version='0.1.0',
|
||||
packages=find_packages(exclude=['test']),
|
||||
data_files=[
|
||||
('share/ament_index/resource_index/packages',
|
||||
['resource/' + package_name]),
|
||||
('share/' + package_name, ['package.xml']),
|
||||
('share/' + package_name + '/launch', ['launch/*.py']),
|
||||
],
|
||||
install_requires=['setuptools'],
|
||||
zip_safe=True,
|
||||
maintainer='xs',
|
||||
maintainer_email='dev@example.com',
|
||||
description='Python talker/listener demo for ROS2 Humble',
|
||||
license='Apache-2.0',
|
||||
tests_require=['pytest'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'talker = py_pubsub.publisher_member_function:main',
|
||||
'listener = py_pubsub.subscriber_member_function:main',
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `data_files` 必须把 `package.xml` + `launch/` 拷到 `share/<pkg>/`
|
||||
- `resource/<pkg>` 空文件 → ament_index marker(必填!)
|
||||
- `entry_points/console_scripts` 暴露 `ros2 run` 可执行名
|
||||
|
||||
### 4.3 setup.cfg
|
||||
|
||||
```ini
|
||||
[develop]
|
||||
script_dir=$base/lib/py_pubsub
|
||||
[install]
|
||||
install_scripts=$base/lib/py_pubsub
|
||||
```
|
||||
|
||||
控制 `console_scripts` 装到 `$base/lib/<pkg>/`,ROS2 启动器能找到。
|
||||
|
||||
---
|
||||
|
||||
## 5. ament_cmake 包详解
|
||||
|
||||
### 5.1 目录结构
|
||||
|
||||
```
|
||||
cpp_pubsub/
|
||||
├── package.xml
|
||||
├── CMakeLists.txt
|
||||
├── src/
|
||||
│ └── *.cpp
|
||||
├── include/ ← 公开头文件(可选)
|
||||
├── launch/
|
||||
│ └── *.py
|
||||
├── test/
|
||||
│ └── test_*.cpp
|
||||
└── config/ ← YAML 配置(可选)
|
||||
```
|
||||
|
||||
### 5.2 CMakeLists.txt
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(cpp_pubsub VERSION 0.1.0)
|
||||
|
||||
if(NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
|
||||
# GCC/Clang 编译警告
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
find_package(ament_cmake REQUIRED)
|
||||
find_package(rclcpp REQUIRED)
|
||||
find_package(std_msgs REQUIRED)
|
||||
|
||||
include_directories(include)
|
||||
|
||||
# 节点可执行
|
||||
add_executable(talker src/publisher_member_function.cpp)
|
||||
ament_target_dependencies(talker rclcpp std_msgs)
|
||||
|
||||
add_executable(listener src/subscriber_member_function.cpp)
|
||||
ament_target_dependencies(listener rclcpp std_msgs)
|
||||
|
||||
# 装到 install/lib/<pkg>/
|
||||
install(TARGETS talker listener DESTINATION lib/${PROJECT_NAME})
|
||||
|
||||
# launch 文件装到 share/<pkg>/launch/
|
||||
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME}/)
|
||||
|
||||
# 测试
|
||||
if(BUILD_TESTING)
|
||||
find_package(ament_cmake_gtest REQUIRED)
|
||||
ament_add_gtest(test_pub_sub test/test_pub_sub.cpp)
|
||||
ament_target_dependencies(test_pub_sub rclcpp std_msgs)
|
||||
endif()
|
||||
|
||||
ament_package() ← 必填!
|
||||
```
|
||||
|
||||
**关键**:
|
||||
- `ament_target_dependencies(<target> pkg1 pkg2)` 同时设置 include path 和 link
|
||||
- `install(TARGETS ...)` 把可执行装到 install
|
||||
- `install(DIRECTORY launch ...)` 把 launch 装到 share
|
||||
- `ament_package()` 末尾必填
|
||||
|
||||
---
|
||||
|
||||
## 6. colcon build 内部流程
|
||||
|
||||
```bash
|
||||
colcon build --symlink-install --packages-select <pkg>
|
||||
```
|
||||
|
||||
按依赖顺序,每个包:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 1. 发现 src/<pkg>/ 下的 package.xml │
|
||||
│ 2. 读 <export><build_type>,决定 build type │
|
||||
│ 3. 调用对应 build_type 的 hook: │
|
||||
│ ament_python → python setup.py build + install │
|
||||
│ ament_cmake → cmake + make + ament_package │
|
||||
│ 4. 写 ament_index 标记 (share/ament_index/...) │
|
||||
│ 5. 把产物装到 install/<pkg>/ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
加 `--symlink-install`:
|
||||
- Python:源码**软链**到 install,改源码立即生效(不用重 build)
|
||||
- C++:可执行仍硬编,但 launch / config 文件软链
|
||||
|
||||
---
|
||||
|
||||
## 7. ament_index 与 ros2 工具发现
|
||||
|
||||
`ros2 run / launch / pkg executables / topic info` 等命令都靠 **ament_index**:
|
||||
- 启动时扫 `install/<pkg>/share/ament_index/resource_index/packages/<pkg>` 文件
|
||||
- 找到包路径后,扫 `install/<pkg>/lib/<pkg>/` 找可执行
|
||||
- 扫 `install/<pkg>/share/<pkg>/launch/` 找 launch 文件
|
||||
|
||||
**手动重建索引**(极少需要):
|
||||
```bash
|
||||
# 索引在 install/share/ament_index/ 里,正常情况下 colcon build 自动维护
|
||||
ros2 doctor --report # 看索引健康
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 依赖解析与 rosdep
|
||||
|
||||
`package.xml` 的 `<depend>` 让 colcon 自动排构建顺序:
|
||||
- 你的 `py_pubsub` 依赖 `rclpy`,`rclpy` 是 apt 包,colcon 跳过
|
||||
- `py_srv` 依赖 `example_interfaces`,同上
|
||||
- colcon 只 build 我们 src/ 下的包
|
||||
|
||||
**`rosdep`** 是 apt 包管理:
|
||||
```bash
|
||||
sudo rosdep init && rosdep update
|
||||
sudo rosdep install -i --from-paths src/ # 装齐所有 apt 依赖
|
||||
```
|
||||
|
||||
本仓库基础镜像 `osrf/ros:humble-desktop` 已装齐所有 ROS2 客户端,**不需要额外 rosdep**。
|
||||
|
||||
---
|
||||
|
||||
## 9. 自定义消息 / 服务 / Action(进阶)
|
||||
|
||||
需要:
|
||||
1. 在包内建 `msg/`、`srv/`、`action/` 目录
|
||||
2. 写 `.msg` / `.srv` / `.action` 文件
|
||||
3. `package.xml` 加:
|
||||
- `<build_depend>rosidl_default_generators</build_depend>`
|
||||
- `<exec_depend>rosidl_default_runtime</exec_depend>`
|
||||
4. `CMakeLists.txt` 加 `rosidl_generate_interfaces(${PROJECT_NAME} ${MSG_FILES} ...)`
|
||||
5. `setup.py` `data_files` 加 `('share/<pkg>/msg': ['msg/*.msg'], ...)`
|
||||
6. **ament_python 需要单独 `rosidl` 包**(混合构建)
|
||||
|
||||
**本仓库只用 example_interfaces 自带的 Fibonacci / AddTwoInts**(由 ROS2 系统包提供),避开这个复杂度。
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见构建错误 + 解决
|
||||
|
||||
### 10.1 `Could not find a package configuration file provided by "rclpy"`
|
||||
少装 apt 包:
|
||||
```bash
|
||||
sudo apt install ros-humble-rclpy # 镜像外
|
||||
```
|
||||
|
||||
### 10.2 `Package 'X' not found in colcon build`
|
||||
ament_index 没刷新。重启容器 + source install/setup.bash。
|
||||
|
||||
### 10.3 `ament_python` install 0 files
|
||||
`setup.py` 的 `packages=` 不对。检查 `py_pubsub/` 下有 `__init__.py`。
|
||||
|
||||
### 10.4 launch 文件未找到
|
||||
检查 `setup.py`:
|
||||
```python
|
||||
('share/' + package_name + '/launch', ['launch/*.py']),
|
||||
```
|
||||
**`launch/*.py` 是 glob**,不是 `'launch/foo.py'`。
|
||||
|
||||
### 10.5 ROS Domain 冲突
|
||||
`ROS_DOMAIN_ID=0` 默认。多项目用 `ROS_DOMAIN_ID=42` 隔离。
|
||||
|
||||
### 10.6 Cyclone DDS 缓存了 CMakeCache
|
||||
`Could not find ROS middleware implementation 'rmw_cyclonedds_cpp'` 但又设了 `RMW_IMPLEMENTATION=rmw_cyclonedds_cpp` → CMake 缓存了。
|
||||
**修法**:
|
||||
```bash
|
||||
rm -rf build install log
|
||||
colcon build ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 在本仓库里跑
|
||||
|
||||
### 11.1 全部 build
|
||||
|
||||
```bash
|
||||
cd /root/ros2_ws
|
||||
colcon build --symlink-install \
|
||||
--packages-select py_pubsub cpp_pubsub py_srv py_action_demo cpp_robot_tf2 py_vision_demo bringup
|
||||
```
|
||||
|
||||
### 11.2 增量 build(只编改的)
|
||||
```bash
|
||||
colcon build
|
||||
```
|
||||
|
||||
### 11.3 清理后重 build
|
||||
```bash
|
||||
rm -rf build install log
|
||||
colcon build --symlink-install
|
||||
```
|
||||
|
||||
### 11.4 单包 build
|
||||
```bash
|
||||
colcon build --packages-select py_pubsub
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 进阶:多包 workspace / release 流程
|
||||
|
||||
### 12.1 多 workspace 覆盖
|
||||
|
||||
```bash
|
||||
# 用 setup.bash 叠加
|
||||
source /opt/ros/humble/setup.bash
|
||||
source /workspace1/install/setup.bash
|
||||
source /workspace2/install/setup.bash
|
||||
# 后 source 的覆盖前面
|
||||
```
|
||||
|
||||
### 12.2 Release 流程
|
||||
|
||||
```bash
|
||||
# 1) bloom-generate 准备 release
|
||||
sudo apt install python3-bloom
|
||||
bloom-generate rosdebian --os-only ubuntu:jammy
|
||||
|
||||
# 2) 构建 .deb 包
|
||||
bloom-release --rosdistro humble --track humble --new 0.1.0 my_pkg
|
||||
|
||||
# 3) 推送到 ROS 仓库(rosindex / GitHub)
|
||||
```
|
||||
|
||||
**本仓库未发布**,如要 release 走标准流程。
|
||||
|
||||
---
|
||||
|
||||
## 接下来读
|
||||
|
||||
| 主题 | 文档 |
|
||||
|---|---|
|
||||
| Launch 文件 | [`70-launch.md`](70-launch.md) |
|
||||
| Docker 开发 | [`85-docker.md`](85-docker.md) |
|
||||
| 测试策略 | [`90-testing.md`](90-testing.md) |
|
||||
| 三机部署 | [`100-embedded-deployment.md`](100-embedded-deployment.md) |
|
||||
Reference in New Issue
Block a user