Contents

ROS:ROS2编译

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

[TOC]

ROS工作空间

ROS工作空间是一个具有特定结构的目录。通常有一个 src 子目录。在该子目录下是ROS软件包的源代码所在位置。通常该目录开始时为空。

Colcon 工具

Colcon 编译代码的原理

  1. 工作空间结构
    • Colcon 使用特定的目录结构来组织 ROS 2 项目。一个典型的工作空间包含以下目录:
      • src:存放所有软件包的源代码。
      • build:存放编译过程中生成的中间文件。对于每个软件包,将在其中创建一个子文件夹,例如在该子文件夹中调用 CMake。
      • install:存放编译完成后的安装文件。默认情况下,每个软件包将被安装到单独的子目录中。
      • log:存放编译过程中的日志信息。
  2. 依赖解析
    • Colcon 在编译前会解析每个软件包的依赖关系。依赖关系在软件包的 package.xml 文件中声明,包括构建时依赖和运行时依赖。
    • Colcon 使用拓扑排序算法来确定构建顺序,确保每个软件包在所有依赖项构建完成后才开始编译。
  3. 并行构建
    • Colcon 支持并行构建,可以通过多线程或分布式构建来提高编译效率。这使得多个包可以同时编译,从而大大缩短了整体构建时间。
  4. 构建过程
    • Colcon 通过调用 CMake 或其他构建系统(如 ament_cmakeament_python)来执行具体的编译任务。对于 C++ 包,CMakeLists.txt 文件定义了编译规则;对于 Python 包,setup.py 文件定义了构建规则

Colcon 如何识别软件包

  1. package.xml 文件
    • 每个 ROS 2 软件包必须包含一个 package.xml 文件。这个文件是软件包的元数据描述文件,定义了软件包的基本信息(如名称、版本、描述)、依赖关系、维护者信息等。
    • Colcon 通过解析 package.xml 文件来识别软件包的存在,并获取其依赖关系和构建顺序。
  2. CMakeLists.txtsetup.py 文件
    • 对于 C++ 包,CMakeLists.txt 文件定义了编译规则、目标文件、依赖查找等内容。
    • 对于 Python 包,setup.py 文件定义了 Python 包的元数据和安装规则。
    • Colcon 通过这些文件来确定每个软件包的具体构建逻辑。
  3. 目录结构
    • Colcon 会自动扫描 src 目录下的所有子目录。每个子目录被视为一个独立的软件包,只要该目录包含 package.xml 文件。
    • 如果需要指定额外的搜索路径,可以使用 --base-paths 参数。
    • 如果没有 src 文件夹也没有指定额外的搜索路径,Colcon 会尝试在当前目录及其子目录中查找包含 package.xml 文件的目录。
  4. 构建类型
    • Colcon 支持多种构建类型,如 ament_cmakeament_python。根据软件包的构建类型,Colcon 会调用相应的构建系统来完成编译。
    • colcon build参数支持输入cmake参数,如通过--cmake-args输入cmake的-D参数。
    • console_cohesion+表示构建结束后输出到终端,+表示启用,-表示不启用,默认不启用。

C++ 编译编译 :ament_cmake

构建信息会收集在两个文件中:package.xmlCMakeLists.txt,它们必须位于同一目录中。

package.xml 必须包含所有依赖项和一些元数据,以允许 colcon 找到正确的构建顺序,以在CI中安装所需的依赖项,并为使用 bloom 进行发布提供信息。CMakeLists.txt 包含了构建和打包可执行文件和库的命令。

1
2
3
4
5
cmake_minimum_required(VERSION 3.8)
# 参数将是包名,并且必须与 package.xml 中的包名完全相同。
project(my_project)

ament_package()

ament_cmake 命令汇总

命令 作用 是否必须 使用场景
ament_package() 定义一个 ROS 2 包 ✅ 必须 所有 ROS 2 包
ament_target_dependencies() 给 target 添加 ROS 包依赖 ✅ 强烈推荐 链接 rclcpp、std_msgs 等
ament_export_dependencies() 导出构建依赖给下游包 ✅ 定义接口类/消息时必用 下游 find_package() 可用
ament_export_include_directories() 导出头文件路径 ✅ 如果你有头文件 C++ 库封装时
ament_export_libraries() 导出链接库 ✅ 如果你定义库 提供 .so.a
ament_export_targets() 导出 CMake target 可选 高级 target control
ament_export_interfaces() 导出 interface targets 可选 用于 INTERFACE target
ament_index_register_resource() 注册资源到 ament 索引 可选 pluginlib 或 rqt 插件
ament_lint_auto_find_test_dependencies() 自动添加 linter 测试依赖 可选 启用代码检查工具

1. 包定义与注册类

🔹 ament_package()

  • 作用:声明“这是一个 ROS 2 包”
  • 必须:✅ 所有 ROS 2 包必须调用
  • 行为
    • 安装 package.xml
    • 创建 CMake your_pkg-config.cmake
    • 导出 ament_export_* 声明的信息给下游使用

2. 依赖管理类

🔹 ament_target_dependencies(target dep1 dep2 ...)

  • 作用:自动添加包含路径、链接库等依赖信息

  • 等价于

    1
    2
    
    target_link_libraries(...)
    target_include_directories(...)
    
  • 推荐使用:在 ROS 2 项目中替代手动设置 target_link_libraries

🔹 ament_export_dependencies(...)

  • 作用:让依赖你的包的下游包也自动依赖这些依赖
  • 典型用途
    • 如果你依赖了 rclcpp,且你导出了接口(如类/消息),就要加上它
  • 配合 ament_package() 使用

3. 导出接口类(库)相关

🔹 ament_export_include_directories(...)

  • 作用:将头文件路径导出给下游包

  • 什么时候必须

    • 如果你的库对外提供 .hpp,就必须 export 出包含路径
  • 配套写法

    1
    2
    
    target_include_directories(my_lib PUBLIC include)
    ament_export_include_directories(include)
    

🔹 ament_export_libraries(...)

  • 作用:将你定义的库导出给下游包链接使用
  • 适用场景
    • 提供了静态库 .a 或共享库 .so

🔹 ament_export_targets(...)

  • 高级功能:如果你使用了 add_library(... EXPORT) 并希望 export target 到下游,可以使用它

4. 索引注册类(插件)

🔹 ament_index_register_resource(NAME resource_name ... )

  • 用途:注册资源路径(如 pluginlib 插件、rqt 插件)
  • 结果:在构建后 share/ament_index/resource_index/<resource_name> 中生成注册记录

5. 代码质量和测试(可选)

🔹 ament_lint_auto_find_test_dependencies()

  • 自动添加 linter 依赖,如 ament_cmake_lint_cmake, ament_cmake_cppcheck, ament_cmake_uncrustify 等。

PYTHON 编译编译 :setup.py

总结

source install/setup.bash 给了 PYTHONPATH 追加了包的位置

setup.py 生成的脚本 会解析生成的配置文件 取找到入口函数

1
2
3
4
5
6
7
8
9
  'console_scripts': [
      # 这里定义了命令行工具 `patrol_agent` 的入口点
      # 当用户在终端运行 `patrol_agent` 时,setuptools 会自动查找并加载 `robot_patrol_system.core.patrol_agent` 模块中的 `main` 函数
      # 具体流程如下:
      # 1. setuptools 根据 `entry_points` 配置生成可执行脚本。
      # 2. 可执行脚本通过 `importlib` 或 `pkg_resources` 动态加载指定模块。
      # 3. 加载完成后,调用模块中的 `main()` 函数。
      'patrol_agent = robot_patrol_system.core.patrol_agent:main',
  ],

local_setup.bash 添加了当前可执行文件的路径

1
2
3
# 添加日志,打印 COLCON_PREFIX_PATH 和 PYTHONPATH
echo "COLCON_PREFIX_PATH: $COLCON_PREFIX_PATH"
echo "PYTHONPATH: $PYTHONPATH"
1
2
COLCON_PREFIX_PATH: /home/nvidia/yj_test/product/install
PYTHONPATH: /home/nvidia/yj_test/product/install/robot_patrol_system/lib/python3.10/site-packages:/home/nvidia/yj_test/product/install/intelligent_perception/lib/python3.10/site-packages:/home/nvidia/yj_test/product/install/intelligent_interaction/lib/python3.10/site-packages:/home/nvidia/yj_test/product/install/embodied_manipulation/lib/python3.10/site-packages:/home/nvidia/yj_test/product/install/custom_interface_pkg/local/lib/python3.10/dist-packages:/home/nvidia/yj_test/product/install/cloud_service/local/lib/python3.10/dist-packages:/home/nvidia/yj_test/product/install/camera_node/lib/python3.10/site-packages:/opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages

分步解析过程

1. setup.py 中定义了 entry_points

在你的 setup.py 文件中,有如下配置:

1
2
3
4
5
6
7
python

entry_points={
    'console_scripts': [
        'patrol_agent = robot_patrol_system.core.patrol_agent:main',
    ],
},

这段代码告诉 setuptools

当用户运行 patrol_agent 命令时,你应该去加载 robot_patrol_system.core.patrol_agent 模块,并调用它的 main() 函数。


2. 安装包时生成入口脚本

当你使用以下命令安装包时:

1
2
3
4
5
bash

pip install .
# 或者
colcon build

setuptools 会根据 entry_points 配置自动生成一个可执行脚本文件,比如:

1
/usr/local/bin/patrol_agent

或者你当前看到的是:

1
/home/nvidia/yj_test/product/install/robot_patrol_system/lib/robot_patrol_system/patrol_agent

该脚本内容如下(简化版):

1
2
3
4
5
6
7
8
#!/usr/bin/python3
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.exit(
        load_entry_point('robot-patrol-system==0.3.6', 'console_scripts', 'patrol_agent')()
    )

3. load_entry_point 解析模块路径

load_entry_point() 函数是 pkg_resourcesimportlib.metadata 提供的工具函数,它内部会做以下事情:

  1. 读取元数据
    • 它会从已安装的包中读取 .dist-info.egg-info 目录下的 entry_points.txt 文件。
    • 这个文件里保存了你在 setup.py 中定义的 entry_points 内容。
  2. 匹配 entry point 名称
    • 它会查找 [console_scripts] 组下名为 patrol_agent 的条目。
    • 条目值为:robot_patrol_system.core.patrol_agent:main
  3. 动态导入模块并获取函数
    • 它会通过 Python 的 importlib.import_module("robot_patrol_system.core.patrol_agent") 加载模块。
    • 然后通过 getattr(module, "main") 获取 main() 函数对象。
  4. 执行函数
    • 最终调用 main() 函数。

使用nuitka 打包为模块

1
2
3
4
nuitka robot_patrol_system/core/patrol_agent.py \
  --module \
  --include-package=robot_patrol_system \
  --output-dir=build_nuitka_out
1
2
3
4
5
6
7
import importlib.util

spec = importlib.util.spec_from_file_location("robot_patrol_system.core.patrol_agent", "./patrol_agent.cpython-310-aarch64-linux-gnu.so")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)

mod.main()

发布工具 Bloom

Bloom 是一个将 ROS 源码包转换为 Debian 等二进制包的自动化发布工具

Bloom 发布流程

假设你要发布 ROS 2 包 my_robot_package,流程如下:

  1. 准备包结构:满足 ROS 包格式(含 package.xml
  2. 版本控制:使用 Git,并准备好 tag
  3. 使用 bloom 创建 release 配置
1
2
cd ~/ros2_ws/src/my_robot_package
bloom-generate rosrelease
  1. 生成 Debian 元数据和 release 分支
1
bloom-release my_robot_package --rosdistro humble
  1. 自动创建 Git tag、release 分支(如 release/humble/my_robot_package/1.0.0
  2. 将生成的 source.tar.gz 上传到 ROS build farm
  3. 提交到 ROS distro 的 rosdistro 仓库 PR(由你或机器人完成)
  4. ROS build farm 自动构建并发布二进制包

ROS build farm

ROS Build Farm 是一个分布式自动化系统,它将 ROS 源代码转换为官方发布的二进制包(如 .deb),并提供持续集成(CI)和持续发布(CD)功能。

官方站点和面板

地址 内容
https://build.ros2.org ROS 2 各版本构建状态总览
https://repo.ros2.org 构建产物仓库
https://github.com/ros-infrastructure/ros_buildfarm 构建脚本仓库
https://github.com/ros/rosdistro 各版本包信息仓库(维护 bloom 提交)

source install/setup.bash 的行为

install/setup.bash 本质上是一个 汇总脚本,其核心行为是:

1. 设置环境变量(以 PATH、PYTHONPATH、AMENT_PREFIX_PATH 为主)

1
2
3
4
5
export AMENT_PREFIX_PATH=/path/to/install:$AMENT_PREFIX_PATH
export CMAKE_PREFIX_PATH=/path/to/install:$CMAKE_PREFIX_PATH
export LD_LIBRARY_PATH=/path/to/install/lib:$LD_LIBRARY_PATH
export PYTHONPATH=/path/to/install/lib/python3.10/site-packages:$PYTHONPATH
export PATH=/path/to/install/bin:$PATH

这些变量用于告诉编译器、运行时环境、Python 解释器去哪里找 ROS 2 资源。


2. 设置 ROS 特有变量

1
2
3
export ROS_VERSION=2
export ROS_PYTHON_VERSION=3
export ROS_DISTRO=humble

这些环境变量告诉工具链(如 ros2 runcolcon)当前使用的是哪个 ROS 版本。


3. 递归调用各个包的 local_setup 脚本

setup.bash 会递归执行 install/<package>/share/<package>/local_setup.bash 脚本:

1
source install/share/my_custom_pkg/local_setup.bash

每个包的 local_setup.bash 负责设置它自己特有的路径(如 pluginlib、msg 定义、lib 路径)。


常见影响目录

变量 意义
install/setup.bash 整个工作区的入口脚本
install/setup.sh POSIX 兼容版本
install/local_setup.bash 通常不会手动执行,供顶层调用
install/share/<pkg>/package.dsv 构建时生成的 shell 变量定义
install/share/<pkg>/local_setup.bash 每个包自有环境配置
COLCON_CURRENT_PREFIX 指示当前工作空间路径(colcon 使用)

ROS2 RUN

ros2 run 的行为流程:

  1. 读取环境变量(特别是 AMENT_PREFIX_PATH / COLCON_PREFIX_PATH);

  2. 在这些路径下查找你指定的 <package_name> 安装目录;

  3. 定位 <package_name>/share/<package_name>/package.xml,确认存在;

  4. 读取该 package 的 ament index 元信息(在构建或安装时生成);

  5. 查找该 package 下是否声明了 <executable_name>

  6. 运行实际路径为:

    1
    
    <install_prefix>/<package_name>/lib/<package_name>/<executable_name>
    

依赖的环境变量

ros2 run 依赖以下环境变量的正确设置(由 source install/setup.bash 提供):

环境变量 用途
AMENT_PREFIX_PATH / COLCON_PREFIX_PATH 查找安装路径中有哪些包
AMENT_INDEX_ROOT 查找 ament index 中的资源描述
PATH, LD_LIBRARY_PATH 运行依赖库和命令

自己模拟 ros2 run 行为

1
2
source install/setup.bash
./install/my_robot/lib/my_robot/my_robot_node

文档

colcon

colcon构建工具