3、ROS2工作空间与功能包:工作空间概念、创建与编译工作空间、功能包结构、创建功能包、package.xml与CMakeLists.txt解析
好,咱们今天聊聊ROS2里最基础、也最绕不开的两个东西——工作空间和功能包。
说实话,我刚开始学ROS1那会儿,就被工作空间这个概念搞晕过。明明就是个文件夹,为啥要叫这么高大上的名字?后来做项目做多了才明白,这玩意儿就像你的工具箱,所有工具(功能包)都得放进去,才能顺手拿起来用。
工作空间是什么?说白了就是个“项目根目录”
工作空间(Workspace),在ROS2里通常叫colcon_ws或者ros2_ws。它就是一个顶层文件夹,里面按固定结构放着你所有的功能包。
我个人习惯把工作空间建在~/ros2_ws/下。为什么?因为好记,而且很多教程都这么用,你跟着做不容易出错。
一个标准的工作空间长这样:
ros2_ws/
├── src/ # 源码目录,放你的功能包
├── build/ # 编译中间文件,别手动改
├── install/ # 安装后的文件,setup.bash就在这
└── log/ # 编译日志,出错了来这查
嗯,这里要注意:src目录是你唯一需要手动操作的地方。其他三个目录都是编译工具自动生成的,你千万别手贱去删里面的东西,除非你想重新编译。
创建与编译工作空间
创建工作空间其实就三步,我带你走一遍:
# 第一步:创建目录结构
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
# 第二步:编译(现在src还是空的,但可以编译)
colcon build
# 第三步:生效环境
source install/setup.bash
你可能会问:“src是空的,编译个啥?”
问得好。其实colcon build会在空工作空间里生成一些基础配置,比如install/setup.bash。这样你后续加功能包时,环境就已经准备好了。
source /opt/ros/humble/setup.bash,再 source ~/ros2_ws/install/setup.bash。顺序别搞反了,否则系统会找不到ROS2自带的包。
功能包结构——ROS2的最小单元
功能包(Package)是ROS2里组织代码的基本单位。一个功能包可以包含节点、消息定义、服务、参数配置等等。
一个典型的C++功能包长这样:
my_package/
├── CMakeLists.txt # 编译规则
├── package.xml # 包信息(依赖、版本、作者)
├── src/ # 源码
│ └── my_node.cpp
├── include/ # 头文件
│ └── my_package/
│ └── my_node.hpp
├── msg/ # 自定义消息(可选)
├── srv/ # 自定义服务(可选)
├── launch/ # 启动文件(可选)
└── config/ # 配置文件(可选)
Python功能包结构更简单:
my_py_package/
├── package.xml
├── setup.py # 代替CMakeLists.txt
├── setup.cfg
├── my_py_package/ # Python模块目录
│ └── my_node.py
└── resource/ # 资源文件
我记得刚开始带团队时,有个同事把package.xml写错了依赖,结果编译报错查了一下午。后来我定了个规矩:先写package.xml,再写代码。这样依赖关系一目了然,不会漏。
创建功能包——用命令一步到位
ROS2提供了专门的命令来创建功能包,别自己手搓目录结构,容易出错。
# 进入工作空间的src目录
cd ~/ros2_ws/src
# 创建C++功能包
ros2 pkg create my_cpp_pkg \
--build-type ament_cmake \
--dependencies rclcpp std_msgs
# 创建Python功能包
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 |
(可选)自动生成一个节点模板 |
-),只能用下划线(_)。比如 my-package 是错的,my_package 才对。否则编译时会报“非法字符”错误,而且错误信息很不直观,我当时查了半小时才找到原因。
package.xml解析——功能包的“身份证”
每个功能包都必须有一个package.xml。它告诉ROS2:你是谁?你依赖谁?
一个典型的package.xml长这样:
<?xml version="1.0"?>
<package format="3">
<name>my_cpp_pkg</name>
<version>0.0.1</version>
<description>这是一个示例功能包</description>
<maintainer email="your_email@example.com">你的名字</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>:版本号,建议遵循语义化版本<depend>:运行时和编译时都需要的依赖<build_depend>:仅编译时需要<exec_depend>:仅运行时需要<test_depend>:仅测试时需要
<depend> 就够了。它同时覆盖编译和运行依赖,省心。只有当你明确知道某个包只在编译时需要(比如消息生成工具),才用 <build_depend>。
CMakeLists.txt解析——C++功能包的“编译说明书”
对于C++功能包,CMakeLists.txt是核心。它告诉CMake:怎么编译你的代码,链接哪些库。
一个最小可用的CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(my_cpp_pkg)
# 使用C++17标准
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)
# 添加可执行文件
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()
这里我特别想强调ament_target_dependencies和target_link_libraries的区别。前者是ROS2封装好的,会自动处理头文件路径和库路径;后者是原生CMake的,你得自己手动指定路径。
我个人建议:在ROS2项目里,一律用ament_target_dependencies。省事,而且不容易出错。
CMakeLists.txt里加了新的依赖包,记得在package.xml里也加上对应的<depend>标签。这两个文件必须保持一致,否则编译时会报“找不到包”的错误。我曾经因为只改了CMakeLists.txt忘了改package.xml,浪费了半小时排查。
总结一下
工作空间和功能包,说白了就是ROS2项目的“地基”。地基打不好,后面盖楼全是问题。
- 工作空间:一个顶层目录,包含
src、build、install、log四个子目录 - 功能包:放在
src下,包含package.xml和CMakeLists.txt(或setup.py) - 创建流程:
mkdir -p src→ros2 pkg create→colcon build→source install/setup.bash - 关键文件:
package.xml管依赖,CMakeLists.txt管编译
下一章咱们会真正开始写代码——创建一个能跑起来的发布者-订阅者节点。到时候你会看到,今天学的这些基础,是怎么在实际项目中用起来的。