3、工作空间与功能包:工作空间概念、创建与编译,功能包结构、创建命令与依赖管理

好,咱们进入第三章。这一章讲的是ROS2开发的基础设施——工作空间和功能包。说白了,这就是你写代码的“工地”和“工程单元”。我见过不少新手,代码写了一大堆,结果编译报错找半天,最后发现是工作空间没建对。嗯,咱们先把地基打牢。

3.1 工作空间:你的代码“工地”

工作空间(Workspace)在ROS2里,就是一个存放所有功能包的顶层目录。你所有的节点、消息定义、配置文件,都放在这个“工地”里。ROS2通过工作空间来管理编译和依赖。

我个人习惯,在~/ros2_ws下干活。当然,名字随你取,但路径里最好别有中文和空格,否则编译时你会哭的。

3.1.1 工作空间的结构

一个标准的ROS2工作空间长这样:

ros2_ws/
├── src/          # 源码目录,放你的功能包
├── build/        # 编译中间文件(自动生成)
├── install/      # 安装后的文件(自动生成)
└── log/          # 编译日志(自动生成)

你只需要手动创建src目录,其他三个是编译工具自动生成的。别手贱去改build和install里的东西,我刚开始时干过这事,结果编译全乱了。

3.1.2 创建工作空间

步骤很简单,就三条命令:

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build

等等,第一次建空工作空间时,colcon build会报个警告,说没找到功能包。别慌,这是正常的。等你在src里放了功能包再编译就行。

小技巧: 我习惯在 ~/.bashrc 里加一行 source ~/ros2_ws/install/setup.bash,这样每次打开终端,工作空间的环境变量就自动加载了。省得每次都要手动 source。

3.2 功能包:ROS2的“工程单元”

功能包(Package)是ROS2里最小的可编译单元。一个功能包可以包含节点、消息定义、服务定义、配置文件等。你可以把它理解成一个“模块”或“组件”。

为什么要有功能包?你想想看,如果所有代码都堆在一个目录里,那得多乱。功能包帮你把不同功能的代码隔离开,方便复用和分发。

3.2.1 功能包的结构

一个典型的C++功能包结构如下:

my_package/
├── CMakeLists.txt      # 编译规则(C++包用)
├── package.xml         # 包描述文件(所有包都有)
├── src/                # 源码目录
│   └── my_node.cpp
├── include/            # 头文件目录
│   └── my_package/
│       └── my_node.hpp
├── msg/                # 自定义消息(可选)
├── srv/                # 自定义服务(可选)
├── launch/             # 启动文件(可选)
└── config/             # 配置文件(可选)

Python包的结构稍有不同,没有CMakeLists.txt,而是setup.py和setup.cfg。但package.xml是通用的,每个包都必须有。

核心文件: package.xml 是功能包的“身份证”,里面记录了包名、版本、作者、许可证,以及最重要的——依赖关系。编译工具靠它来解析依赖。

3.2.2 创建功能包

ROS2提供了专门的命令来创建功能包,不用你手动去搭目录结构。我个人觉得这个设计很贴心,省了不少事。

创建C++功能包:

cd ~/ros2_ws/src
ros2 pkg create my_cpp_pkg --build-type ament_cmake --dependencies rclcpp std_msgs

创建Python功能包:

cd ~/ros2_ws/src
ros2 pkg create my_py_pkg --build-type ament_python --dependencies rclpy std_msgs

参数说明:

参数 说明
--build-type 编译类型:ament_cmake(C++)或 ament_python(Python)
--dependencies 依赖的包名,多个用空格隔开
--node-name (可选)自动生成一个节点模板
注意: 包名只能用字母、数字和下划线,而且不能以数字开头。我曾经用了一个带连字符的包名,结果编译时各种报错,查了半天才发现是命名问题。

3.3 依赖管理:别让你的包“断粮”

一个功能包很少是孤立的,它通常会依赖其他包。比如你的节点要用到rclcpp(ROS2客户端库)和std_msgs(标准消息类型)。依赖管理就是告诉编译工具:我的包需要哪些“粮食”。

3.3.1 声明依赖

依赖声明在两个地方:

  1. package.xml:用 <depend> 标签声明运行时和编译时依赖。
  2. CMakeLists.txt(C++包)或 setup.py(Python包):用 find_package() 或 install_requires 声明编译依赖。

举个例子,package.xml里的依赖声明:

<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>geometry_msgs</depend>

对应的CMakeLists.txt里:

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)

这两个地方必须保持一致,否则编译会报错。我见过有人只改了package.xml,忘了改CMakeLists.txt,结果折腾了半天。

3.3.2 自动安装依赖

当你从别处拉了一个功能包,它可能依赖一些你没装的系统库。这时候可以用rosdep来自动安装:

cd ~/ros2_ws
rosdep install -i --from-path src --rosdistro humble -y

这个命令会扫描src下所有功能包的package.xml,自动安装缺失的依赖。嗯,省心不少。

避坑指南: 我曾经在Ubuntu 20.04上装ROS2 Humble,结果rosdep报错说找不到某些包。后来发现是系统源没配好。建议先执行 sudo apt update,再跑rosdep。

3.4 编译工作空间

把功能包放进src后,就可以编译了。ROS2的编译工具是colcon,它比ROS1的catkin快不少。

3.4.1 常用编译命令

命令 作用
colcon build 编译整个工作空间
colcon build --packages-select my_pkg 只编译指定包(省时间)
colcon build --symlink-install 使用符号链接安装(Python包修改后不用重新编译)
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug Debug模式编译(方便调试)

我个人最常用的是--symlink-install,尤其是开发Python节点时。改了代码直接运行,不用重新编译,爽得很。

3.4.2 编译后的环境配置

编译完成后,需要source一下才能使用新编译的包:

source ~/ros2_ws/install/setup.bash

如果你用的是zsh,就source setup.zsh。我建议把这个命令加到~/.bashrc里,一劳永逸。

注意: 如果你同时source了多个工作空间,后source的会覆盖前面的。所以最好只source一个工作空间,或者用 --merge-install 选项合并安装。

3.5 知识体系总览

下面这张图把本章的核心逻辑串起来了,你可以对照着理解:

工作空间与功能包知识体系 工作空间与功能包知识体系 工作空间 (Workspace) src/ 源码目录 build/ 编译中间 install/ 安装产物 log/ 编译日志 功能包 (Package) package.xml CMakeLists.txt src/ 源码 msg/srv/launch 依赖管理 package.xml 声明 CMakeLists.txt 声明 rosdep 自动安装 工作空间是容器 功能包是单元 依赖是纽带

从这张图可以看得很清楚:工作空间是顶层容器,里面放功能包;功能包有自己的结构(package.xml、源码、消息定义等);依赖管理则把功能包和外部库连接起来。三者缺一不可。

好了,这一章的内容就到这。工作空间和功能包是ROS2开发的基石,你后面写的每个节点、每个通信逻辑,都跑不出这个框架。多建几个包练练手,很快就熟了。