3、工作空间与功能包:从零搭建你的ROS2项目骨架

好,咱们正式开始动手了。

前两章我们把ROS2的哲学和工具链聊了个遍,但说实话,光说不练假把式。这一章,咱们要亲手创建ROS2项目最基础的两个单元——工作空间(Workspace)功能包(Package)

我见过不少新手,一上来就急着写代码,结果编译报错找半天,发现是工作空间没建对。嗯,磨刀不误砍柴工,咱们先把地基打牢。

3.1 工作空间:你的代码大本营

工作空间,说白了就是一个顶层目录。ROS2的所有代码、依赖、编译产物都放在这里面。我个人习惯把它叫做 ros2_ws,当然你也可以叫 my_robot_ws,随你高兴。

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

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

你只需要手动创建 src 目录,其他三个是编译时自动生成的。

我的习惯:我会在 src 目录下再建一个 third_party 文件夹,专门放第三方的功能包。这样自己的代码和别人的代码分得清清楚楚,找问题的时候不头疼。

3.1.1 创建工作空间的命令

操作很简单,就两行命令:

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

等等,先别急着敲 colcon build。第一次建空工作空间时,我建议你先在 src 里放一个功能包再编译。否则编译完你会发现 install 目录里空空如也,容易让人心里发虚。

注意:每次打开新终端,都要先 source 一下工作空间的环境变量:
source ~/ros2_ws/install/setup.bash
或者把它加到 ~/.bashrc 里,省得每次手动敲。

3.2 功能包:ROS2的最小组织单元

功能包(Package)是ROS2里代码组织的基本单位。一个功能包可以包含节点、消息定义、配置文件、启动文件等等。

ROS2支持两种功能包:

  • CMake 包:用CMakeLists.txt管理,适合C++项目
  • Python 包:用setup.py管理,适合Python项目

我个人更推荐C++项目用CMake包,Python项目用Python包。混着用也不是不行,但维护起来有点别扭。

3.2.1 创建功能包的命令

创建功能包用 ros2 pkg create 命令。举个例子:

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

这条命令会创建一个名为 my_robot_pkg 的CMake功能包,并自动添加 rclcppstd_msgs 作为依赖。

如果你用Python:

ros2 pkg create my_python_pkg \
  --build-type ament_python \
  --dependencies rclpy std_msgs
避坑指南:我曾经在创建功能包时忘了加 --dependencies 参数,结果后面手动改CMakeLists.txt改得手忙脚乱。建议创建时就指定好依赖,省得后面补。

3.3 理解package.xml:功能包的身份证

每个功能包根目录下都有一个 package.xml 文件。它记录了功能包的元信息,比如名字、版本、作者、许可证、依赖等。

一个典型的 package.xml 长这样:

<?xml version="1.0"?>
<package format="3">
  <name>my_robot_pkg</name>
  <version>0.0.1</version>
  <description>我的第一个ROS2功能包</description>
  <maintainer email="your_email@example.com">your_name</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

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

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

这里有几个关键点:

  • <name>:必须和文件夹名字一致,否则编译会报错
  • <version>:建议遵循语义化版本号,比如0.1.0
  • <depend>:运行时和编译时都需要依赖的包
  • <build_depend>:仅编译时需要
  • <exec_depend>:仅运行时需要
我的经验:大多数情况下用 <depend> 就够了。它同时覆盖了编译和运行依赖,省心。只有当你明确知道某个包只在编译时需要(比如测试框架),才用 <test_depend>

3.4 理解CMakeLists.txt:编译的指挥中心

对于CMake功能包,CMakeLists.txt 是编译的核心。它告诉编译器:源文件在哪、依赖在哪、要生成什么目标。

一个基础的 CMakeLists.txt 结构如下:

cmake_minimum_required(VERSION 3.8)
project(my_robot_pkg)

# 查找依赖
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

# 添加可执行文件
add_executable(my_node src/my_node.cpp)

# 链接库
ament_target_dependencies(my_node rclcpp std_msgs)

# 安装规则
install(TARGETS my_node DESTINATION lib/${PROJECT_NAME})

# 编译后处理
ament_package()

咱们一行行拆开看:

命令 作用
cmake_minimum_required 指定最低CMake版本,ROS2 Humble要求3.8以上
project() 定义项目名,必须和package.xml一致
find_package() 查找并加载依赖包
add_executable() 从源文件生成可执行文件
ament_target_dependencies() 链接依赖库,ROS2特有的方式
install() 指定安装路径
ament_package() 必须放在最后,完成编译后处理
注意:千万别忘了 ament_package()!我刚开始学ROS2时,有一次写CMakeLists.txt漏了这一行,编译死活不通过,查了半天才发现。这个函数会生成一些必要的安装文件,少了它你的功能包就没办法被其他包引用。

3.5 编译与测试

功能包写好了,怎么编译?回到工作空间根目录:

cd ~/ros2_ws
colcon build --packages-select my_robot_pkg

--packages-select 参数只编译指定的包,省时间。如果你改了很多包,可以不加这个参数,全部重新编译。

编译完成后,记得source环境:

source install/setup.bash

然后运行你的节点:

ros2 run my_robot_pkg my_node
调试小技巧:如果编译报错,先检查 package.xmlCMakeLists.txt 里的包名是否一致。我遇到过好几次,就是因为名字拼写不一致,编译报找不到包。另外,colcon build 加上 --symlink-install 参数,可以让Python脚本的修改即时生效,不用每次重新编译。

3.6 本章小结

咱们这一章干了三件事:

  1. 创建了工作空间,理解了它的目录结构
  2. 创建了功能包,区分了CMake包和Python包
  3. 读懂了 package.xmlCMakeLists.txt 的核心结构

说白了,工作空间是你的项目文件夹,功能包是你的代码模块。package.xml 是身份证,CMakeLists.txt 是施工图。这三样东西搞明白了,后面写节点、发消息、搭系统,就顺理成章了。

下一章,咱们要写第一个真正的ROS2节点。到时候你会看到,今天建的这个骨架,是怎么长出肌肉来的。