Files
ROS2_learn/doc/80-package-build.md
T

420 lines
11 KiB
Markdown

# 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) |
---
---
## 📖 阅读路径导航
> 💡 这是仓库 `doc/` 下所有文档的推荐阅读顺序。[返回 README 总导航](../README.md#-23-篇文档怎么读)
>
> ⏱ **本文预计阅读时间**: 40 分钟
> 📍 **当前位置**: 第 19 / 24 篇
-**上一篇**: [launch 文件系统](70-launch.md)
-**下一篇**: [Docker 容器化开发](85-docker.md)