元功能包:是什么、和普通包什么区别、什么时候用、怎么用
有一种包永远不包含代码,却能让一条 apt 命令装齐整套依赖——这就是元功能包(metapackage)。本课按四问展开:是什么、和普通包什么区别、什么时候该用(以及什么时候不该用)、怎么用——从场景自建到打成 apt 可装的 .deb。
目标本课学完,你应该能做到
能用自己的话说出定义:不含代码的空壳包,用一串 exec_depend 把一套功能聚合成一个名字。
能从源代码、依赖声明、构建产物三方面,说清元包和普通功能包的差别。
拿到真实需求,能判断该建元包、该用 bringup 包,还是 launch 文件就够了。
能按"交付 car1"的场景建出 car1_meta、一条命令回读验证,并说清怎么打成 apt 能装的 .deb。
01是什么
<exec_depend> 声明对一组包的依赖。它的作用是把一整套相关功能"打包成一个名字",方便安装和整体启动。没有 src/、没有节点、没有配置文件。(ROS 1 硬性规定不能有代码;ROS 2 放宽了,但惯例仍是"纯聚合"。)
package.xml 里只用 exec_depend 声明"运行时我需要谁",不用 build_depend 链接代码。
package 格式 3,在 <export> 里写 <metapackage/>,构建类型仍是 ament_cmake。
apt install 它之后为什么整棵子包树都在了?02和普通包有什么区别
| 维度 | 普通功能包 | 元功能包 |
|---|---|---|
| 源代码 | 有 src/ 或 Python 模块 | 完全没有 |
| 可执行文件 | 有节点 / 库 | 无 |
| build 类型 | ament_cmake / ament_python | 仅 ament_cmake |
| CMakeLists | 完整构建逻辑 | 仅 ament_package() 一行有效指令 |
| 依赖声明 | build_depend + exec_depend 按需 | 只写 exec_depend(聚合用) |
| colcon build | 编译本包 | 空操作,约 0.5 秒仅注册关系 |
| 典型用途 | 实现功能 | 一键安装 / 版本锁定一套子包 |
拆开一对真实样本对照——nav2 家族里正好一个元包 + 一个普通包:
# 元包 navigation2:本体没有代码,清单里全是运行时依赖 $ ros2 pkg xml navigation2 | grep exec_depend <exec_depend>nav2_amcl</exec_depend> <exec_depend>nav2_planner</exec_depend> <exec_depend>nav2_controller</exec_depend> # ……一口气 20 余行:这就是它"聚合"的全部内容 # 普通包 nav2_planner:有代码要编译,所以多了 build_depend $ ros2 pkg xml nav2_planner | grep -E "depend" <build_depend>nav2_core</build_depend> <depend>nav2_msgs</depend> <exec_depend>nav2_costmap_2d</exec_depend> # 它还有 src/ 与编译目标——这正是两类包的分界线
03什么时候用、什么时候不用
项目以安装包形式发布给别人:sudo apt install ros-humble-car1 装一个 = 装齐整套驱动。官方 desktop、nav2 正是这么做的。
底盘组、导航组各发各的仓库,用一个元包声明"完整系统 = 这三者的某个版本组合",不必搬别人的源码。
colcon build --packages-up-to car1_meta:靠元包的依赖关系,把所有兄弟包一次编出来。
② 只想一次启动多个节点 → launch 文件就够了(EXP-17)。
③ 单人/小团队直接源码 colcon build → 依赖自动解析,元包没有实际收益。
| 维度 | 纯元包 | bringup 包(如 car1_bringup) |
|---|---|---|
| 内容 | 纯依赖清单,零内容 | launch 文件 + 实际代码结构 |
| export 标记 | ament_cmake + metapackage | ament_python / ament_cmake,无 metapackage |
| 职责 | 安装入口:"装我 = 装全家" | 启动入口:"一条 launch 拉起整机" |
| 常见度 | apt 发布的官方包常用 | ROS 2 日常开发里更常见 |
04怎么用
先定场景,再照四步做——2 分钟从零到一个可回读的元包。
sudo apt install 装齐能跑的最小集合——car1_base_driver(底盘驱动)、controller(控制)、car1_description(模型)、car1_navigation(导航)。现在给这四个包造一个安装入口:car1_meta。sudo apt install ros-humble-<元包名>(官方 desktop、nav2 都提供这种入口)。自己造,跟下面四步走:ros2 pkg create car1_meta --build-type ament_cmake(选 ament_python 会去找 setup.py 直接失败)<exec_depend>;export 段写上 <metapackage/>colcon build → source install/setup.bash → ros2 pkg xml car1_meta | grep exec_depend 看到四行清单即成功# ① 进工作空间创建(必须 ament_cmake;选 ament_python 会去找 setup.py 直接失败) $ cd ros2_ws/src/xflz_eai_car1 $ ros2 pkg create car1_meta --build-type ament_cmake # ② package.xml:换成场景四个包,export 段打上元包标记 <package format="3"> ... <exec_depend>car1_base_driver</exec_depend> <exec_depend>controller</exec_depend> <exec_depend>car1_description</exec_depend> <exec_depend>car1_navigation</exec_depend> <export> <build_type>ament_cmake</build_type> <metapackage/> </export> </package> # ③ CMakeLists.txt 只留四行(project 换成 car1_meta) cmake_minimum_required(VERSION 3.8) project(car1_meta) find_package(ament_cmake REQUIRED) ament_package() # ④ 编译(约 0.5 秒,没代码可编,属正常)→ source → 回读验证 $ colcon build --packages-select car1_meta $ source install/setup.bash $ ros2 pkg xml car1_meta | grep exec_depend <exec_depend>car1_base_driver</exec_depend> <exec_depend>controller</exec_depend> <exec_depend>car1_description</exec_depend> <exec_depend>car1_navigation</exec_depend> # 四行齐 = 成功,这就是 car1_meta 的全部"内容"
sudo apt install ros-humble-car1-meta 一键装齐(这个 .deb 从哪来,见下方进阶);自己仓库内:colcon build --packages-up-to car1_meta 顺带把所有依赖的兄弟包一起编出来。进阶:打成 apt 能装的 .deb,发布出去
car1_meta 在你自己的工作空间里可用。要让客户执行 sudo apt install ros-humble-car1-meta,还得把源码打成 Debian 安装包(.deb):自建交付源用 bloom 本地打包(四条命令见下);进 ROS 官方 apt 源则走 bloom-release → rosdistro → buildfarm,打包逻辑相同、只是托管平台不同。# ① 装打包工具(一台机器装一次即可) $ sudo apt install python3-bloom build-essential # ② 进入元包目录:补构建依赖 → 生成 Debian 打包骨架 $ cd ros2_ws/src/xflz_eai_car1/car1_meta $ rosdep install --from-paths . --ignore-src -y $ bloom-generate rosdebian --ros-distro humble # ③ 真正产出 .deb(产物落在包目录的上一级) $ fakeroot debian/rules binary # → ../ros-humble-car1-meta_0.1.0-1jammy..._amd64.deb # ④ 本机试装,并查看它到底会拉起哪些子包 $ sudo dpkg -i ../ros-humble-car1-meta_*.deb $ apt show ros-humble-car1-meta | grep Depends Depends: ros-humble-car1-base-driver, ros-humble-controller, ros-humble-car1-description, ros-humble-car1-navigation, ... # 这串 Depends 就是"装我=装全家"在 apt 里的真身
ros-humble-car1-meta 这个 .deb 本体几乎为空,全家桶全靠 Depends 字段拉起——所以 四个子包也要各自用 bloom 打出 .deb(ros-humble-car1-base-driver 等),和元包一起放进同一个 apt 源(reprepro / aptly 自建内网源即可)。少任何一个,客户安装时直接报 unmet dependencies。source install/setup.bash。② 元包里混入
build_depend → 安装时不会拉起该依赖,聚合失效。05小结与自测
不含代码,只用 exec_depend 聚合依赖——装我等于装全家桶。
普通包有代码实现功能;元包零代码,只有一张依赖清单。
源码小团队用 bringup 包即可;对外交付、多仓库集成才上元包。
create → 改 package.xml → 四行 CMakeLists → 回读验证;对外交付再用 bloom 打成 .deb 放进 apt 源。
参考答案与得分点
得分点:①只要安装入口 → 建纯元包 ②还要整机启动 → 元包管安装 + bringup/launch 包管启动,或直接用带 launch 的 bringup 包一步到位(ROS 2 里更常见)③能说出"装我=装全家"与"一条 launch 拉起"是两个职责。(①②各 40%,③ 20%)<exec_depend>sensor_msgs</exec_depend> 改成 <build_depend>sensor_msgs</build_depend>。colcon build 照样秒过,但用户 sudo apt install 你的元包后运行时会怎样?
参考答案与得分点
得分点:①build_depend 只是构建依赖,安装时不会把 sensor_msgs 作为运行时依赖拉起 ②运行期缺包,节点起不来/报缺依赖 ③元包必须只用 exec_depend,聚合才成立。(①②各 40%,③ 20%)ros2 pkg create home_robot_meta --build-type ament_python 后直接 colcon build,会发生什么?正确写法是什么?
参考答案与得分点
得分点:①ament_python 靠 setup.py 打包,colcon 找不到 setup.py,构建失败或跳过该包 ②正确写法:--build-type ament_cmake ③元包没有可打包的模块,不适合 ament_python。(①②各 40%,③ 20%)