17_apollo_tools_ros子模块软件架构分析

4 阅读5分钟

17_apollo_tools_ros子模块软件架构分析

1. 概述

  apollo_tools_ros子模块是Apollo自动驾驶平台中负责ROS(Robot Operating System)集成的构建配置模块,主要功能是检测、配置和导入ROS2库文件到Bazel构建系统中。该模块支持多种ROS2发行版(Foxy、Galactic、Humble、Iron),能够自动识别系统中的ROS安装路径,提取相关的头文件和库文件,并生成对应的Bazel构建规则。模块还支持ROS工作空间的集成,允许开发者使用自定义编译的ROS包,为Apollo与ROS生态系统的无缝集成提供了基础设施。

2. 软件架构图

graph TB
    subgraph "构建系统入口"
        B1[Bazel WORKSPACE]
        B2[ros_configure规则]
        B3[环境变量检测]
    end

    subgraph "ROS检测层"
        R1[ROS目录查找]
        R2[ROS发行版识别]
        R3[ROS工作空间检测]
        R4[ROS包发现]
    end

    subgraph "本地ROS配置"
        L1[本地ROS仓库创建]
        L2[ROS包匹配]
        L3[ROS库匹配]
        L4[头文件收集]
        L5[库文件收集]
    end

    subgraph "工作空间ROS配置"
        W1[工作空间ROS仓库创建]
        W2[工作空间包匹配]
        W3[工作空间库匹配]
        W4[工作空间头文件收集]
        W5[工作空间库文件收集]
    end

    subgraph "规则生成层"
        G1[复制规则生成]
        G2[CC库规则生成]
        G3[ROS接口规则生成]
        G4[ROS发行版头文件生成]
    end

    subgraph "输出层"
        O1[BUILD文件]
        O2[ROS库目标]
        O3[ROS头文件]
        O4[ROS发行版宏定义]
    end

    B1 --> B2
    B2 --> B3
    B3 --> R1
    R1 --> R2
    R2 --> R3
    R3 --> R4
    R4 --> L1
    R4 --> W1
    L1 --> L2
    L2 --> L3
    L3 --> L4
    L4 --> L5
    L5 --> G1
    W1 --> W2
    W2 --> W3
    W3 --> W4
    W4 --> W5
    W5 --> G1
    G1 --> G2
    G2 --> G3
    G3 --> G4
    G4 --> O1
    O1 --> O2
    O1 --> O3
    O1 --> O4

    style B2 fill:#e1f5fe
    style L1 fill:#f3e5f5
    style W1 fill:#fff3e0
    style G1 fill:#e8f5e8

3. 调用流程图

sequenceDiagram
    participant User as 用户
    participant Bazel as Bazel构建系统
    participant RosConfig as ros_configure规则
    participant RosFinder as ROS查找器
    participant LocalRepo as 本地ROS仓库
    participant WsRepo as 工作空间ROS仓库
    participant RuleGen as 规则生成器
    participant Output as 输出管理器

    User->>Bazel: 配置ros_configure
    Bazel->>RosConfig: 执行ros_configure规则
    RosConfig->>RosFinder: 查找ROS目录
    RosFinder->>RosFinder: 检查环境变量ROS_DISTRO
    RosFinder->>RosFinder: 扫描/opt/ros目录
    RosFinder-->>RosConfig: 返回ROS路径

    RosConfig->>RosFinder: 查找ROS工作空间
    RosFinder->>RosFinder: 扫描缓存目录
    RosFinder->>RosFinder: 检查ros_ws目录
    RosFinder-->>RosConfig: 返回工作空间路径

    RosConfig->>LocalRepo: 创建本地ROS仓库
    LocalRepo->>LocalRepo: 匹配ROS包
    LocalRepo->>LocalRepo: 匹配ROS库
    LocalRepo->>LocalRepo: 收集头文件
    LocalRepo->>LocalRepo: 收集库文件
    LocalRepo-->>RosConfig: 返回本地ROS配置

    RosConfig->>WsRepo: 创建工作空间ROS仓库
    WsRepo->>WsRepo: 匹配工作空间包
    WsRepo->>WsRepo: 匹配工作空间库
    WsRepo->>WsRepo: 收集工作空间头文件
    WsRepo->>WsRepo: 收集工作空间库文件
    WsRepo-->>RosConfig: 返回工作空间ROS配置

    RosConfig->>RuleGen: 生成ROS发行版规则
    RuleGen->>RuleGen: 生成发行版头文件
    RuleGen-->>RosConfig: 返回发行版规则

    RosConfig->>RuleGen: 生成本地ROS规则
    RuleGen->>RuleGen: 生成复制规则
    RuleGen->>RuleGen: 生成CC库规则
    RuleGen-->>RosConfig: 返回本地ROS规则

    RosConfig->>RuleGen: 生成工作空间ROS规则
    RuleGen->>RuleGen: 生成工作空间复制规则
    RuleGen->>RuleGen: 生成工作空间CC库规则
    RuleGen-->>RosConfig: 返回工作空间ROS规则

    RosConfig->>RuleGen: 生成ROS接口规则
    RuleGen->>RuleGen: 生成ros库目标
    RuleGen-->>RosConfig: 返回接口规则

    RosConfig->>Output: 生成BUILD文件
    Output->>Output: 应用模板
    Output->>Output: 写入BUILD文件
    Output-->>RosConfig: 返回BUILD文件

    RosConfig-->>Bazel: 返回配置结果
    Bazel-->>User: 配置完成

4. UML类图

4.1 核心配置类图

classDiagram
    class RosConfigure {
        +name: string
        +environ: list
        +local: bool
        +_ros_configure_impl()
    }

    class RosFinder {
        +_ROS_CODENAME: string
        +ROS_FOUND: bool
        +find_ros_dir()
        +find_ros_workspace_dir()
        +_ros_match_packages()
        +_ros_ws_match_package()
    }

    class LocalRosRepository {
        +ros_base_dir: string
        +incl_dir: string
        +lib_path: string
        +_create_local_ros_repository()
        +_ros_match_libraries()
    }

    class WsRosRepository {
        +ros_ws_dir: string
        +ros_ws_pkg: list
        +_create_ws_ros_repository()
        +_ros_ws_match_libraries()
    }

    class RuleGenerator {
        +CC_TPL: string
        +ROS_TPL: string
        +ROS_DISTRO_TPL: string
        +generate_cc_library()
        +generate_ros_interface()
        +generate_ros_distro_header()
    }

    class OutputManager {
        +build_tpl: string
        +copy_rules: list
        +cc_libraries: list
        +ws_cc_libraries: list
        +ros_interface: string
        +generate_build_file()
    }

    RosConfigure --> RosFinder
    RosConfigure --> LocalRosRepository
    RosConfigure --> WsRosRepository
    RosConfigure --> RuleGenerator
    RosConfigure --> OutputManager
    RosFinder --> LocalRosRepository
    RosFinder --> WsRosRepository
    LocalRosRepository --> RuleGenerator
    WsRosRepository --> RuleGenerator
    RuleGenerator --> OutputManager

4.2 ROS包管理类图

classDiagram
    class RosPackage {
        +name: string
        +path: string
        +headers: list
        +libraries: list
        +get_headers()
        +get_libraries()
    }

    class RosLibrary {
        +name: string
        +path: string
        +dependencies: list
        +get_dependencies()
    }

    class RosHeader {
        +file_path: string
        +package_name: string
        +include_path: string
        +get_include_path()
    }

    class RosDistro {
        +name: string
        +version: string
        +base_path: string
        +get_base_path()
        +get_include_path()
        +get_lib_path()
    }

    class RosWorkspace {
        +path: string
        +packages: list
        +install_path: string
        +get_packages()
        +get_install_path()
    }

    RosDistro --> RosPackage
    RosPackage --> RosLibrary
    RosPackage --> RosHeader
    RosWorkspace --> RosPackage

4.3 规则模板类图

classDiagram
    class CcLibraryTemplate {
        +name: string
        +hdrs: list
        +srcs: list
        +strip_include_prefix: string
        +visibility: list
        +render()
    }

    class RosInterfaceTemplate {
        +name: string
        +deps: list
        +hdrs: list
        +data: list
        +includes: list
        +visibility: list
        +render()
    }

    class RosDistroTemplate {
        +name: string
        +outs: list
        +cmd: string
        +render()
    }

    class CopyRuleTemplate {
        +name: string
        +srcs: list
        +outs: list
        +render()
    }

    class CopyDirRuleTemplate {
        +name: string
        +src_dir: string
        +out_dir: string
        +render()
    }

    CcLibraryTemplate --> RosInterfaceTemplate
    RosInterfaceTemplate --> RosDistroTemplate
    CopyRuleTemplate --> CopyDirRuleTemplate

5. 状态机

5.1 ROS配置状态机

stateDiagram-v2
    [*] --> INIT: 开始配置
    INIT --> CHECKING_ENV: 检查环境变量
    CHECKING_ENV --> ENV_FOUND: 找到ROS_DISTRO
    CHECKING_ENV --> SCANNING_DIR: 扫描/opt/ros
    SCANNING_DIR --> DIR_FOUND: 找到ROS目录
    SCANNING_DIR --> NOT_FOUND: 未找到ROS
    ENV_FOUND --> VALIDATING_DISTRO: 验证发行版
    DIR_FOUND --> VALIDATING_DISTRO: 验证发行版
    VALIDATING_DISTRO --> DISTRO_VALID: 发行版有效
    VALIDATING_DISTRO --> NOT_SUPPORTED: 发行版不支持
    DISTRO_VALID --> CHECKING_WS: 检查工作空间
    CHECKING_WS --> WS_FOUND: 找到工作空间
    CHECKING_WS --> NO_WS: 无工作空间
    WS_FOUND --> CREATING_LOCAL: 创建本地仓库
    NO_WS --> CREATING_LOCAL: 创建本地仓库
    CREATING_LOCAL --> LOCAL_CREATED: 本地仓库创建完成
    LOCAL_CREATED --> CREATING_WS: 创建工作空间仓库
    CREATING_WS --> WS_CREATED: 工作空间仓库创建完成
    WS_CREATED --> GENERATING_RULES: 生成构建规则
    GENERATING_RULES --> RULES_GENERATED: 规则生成完成
    RULES_GENERATED --> WRITING_BUILD: 写入BUILD文件
    WRITING_BUILD --> COMPLETED: 配置完成
    WRITING_BUILD --> ERROR: 写入失败
    NOT_FOUND --> [*]: 跳过ROS配置
    NOT_SUPPORTED --> [*]: 跳过ROS配置
    ERROR --> [*]: 配置失败
    COMPLETED --> [*]: 配置成功

    note right of CHECKING_ENV: 检查ROS_DISTRO环境变量
    note right of SCANNING_DIR: 扫描/opt/ros目录
    note right of VALIDATING_DISTRO: 验证Foxy/Galactic/Humble/Iron
    note right of CHECKING_WS: 检查ros_ws目录
    note right of CREATING_LOCAL: 创建本地ROS仓库
    note right of CREATING_WS: 创建工作空间ROS仓库
    note right of GENERATING_RULES: 生成CC库和复制规则
    note right of WRITING_BUILD: 应用BUILD模板

5.2 ROS包发现状态机

stateDiagram-v2
    [*] --> SCANNING: 开始扫描
    SCANNING --> LISTING_PACKAGES: 列出包目录
    LISTING_PACKAGES --> PACKAGES_FOUND: 找到包
    LISTING_PACKAGES --> NO_PACKAGES: 无包
    PACKAGES_FOUND --> FILTERING: 过滤包
    FILTERING --> FILTERED: 过滤完成
    FILTERED --> MATCHING_LIBS: 匹配库文件
    MATCHING_LIBS --> LIBS_MATCHED: 库匹配完成
    MATCHING_LIBS --> NO_LIBS: 无库文件
    LIBS_MATCHED --> COLLECTING_HEADERS: 收集头文件
    NO_LIBS --> COLLECTING_HEADERS: 收集头文件
    COLLECTING_HEADERS --> HEADERS_COLLECTED: 头文件收集完成
    HEADERS_COLLECTED --> GENERATING_RULES: 生成规则
    GENERATING_RULES --> RULES_GENERATED: 规则生成完成
    RULES_GENERATED --> [*]: 发现完成
    NO_PACKAGES --> [*]: 无包
    NO_LIBS --> [*]: 无库文件

    note right of LISTING_PACKAGES: 扫描include目录
    note right of FILTERING: 过滤不兼容的包
    note right of MATCHING_LIBS: 匹配lib*.so文件
    note right of COLLECTING_HEADERS: 收集.h和.hpp文件
    note right of GENERATING_RULES: 生成CC库规则

5.3 规则生成状态机

stateDiagram-v2
    [*] --> PREPARING: 准备生成
    PREPARING --> GENERATING_DISTRO: 生成发行版规则
    GENERATING_DISTRO --> DISTRO_GENERATED: 发行版规则生成完成
    DISTRO_GENERATED --> GENERATING_COPY: 生成复制规则
    GENERATING_COPY --> COPY_GENERATED: 复制规则生成完成
    COPY_GENERATED --> GENERATING_CC: 生成CC库规则
    GENERATING_CC --> CC_GENERATED: CC库规则生成完成
    CC_GENERATED --> GENERATING_WS_COPY: 生成工作空间复制规则
    GENERATING_WS_COPY --> WS_COPY_GENERATED: 工作空间复制规则生成完成
    WS_COPY_GENERATED --> GENERATING_WS_CC: 生成工作空间CC库规则
    GENERATING_WS_CC --> WS_CC_GENERATED: 工作空间CC库规则生成完成
    WS_CC_GENERATED --> GENERATING_INTERFACE: 生成接口规则
    GENERATING_INTERFACE --> INTERFACE_GENERATED: 接口规则生成完成
    INTERFACE_GENERATED --> APPLYING_TEMPLATE: 应用模板
    APPLYING_TEMPLATE --> TEMPLATE_APPLIED: 模板应用完成
    TEMPLATE_APPLIED --> WRITING_FILE: 写入文件
    WRITING_FILE --> FILE_WRITTEN: 文件写入完成
    WRITING_FILE --> ERROR: 写入失败
    FILE_WRITTEN --> [*]: 生成完成
    ERROR --> [*]: 生成失败

    note right of GENERATING_DISTRO: 生成ROS发行版头文件
    note right of GENERATING_COPY: 生成库文件复制规则
    note right of GENERATING_CC: 生成CC库规则
    note right of GENERATING_WS_COPY: 生成工作空间复制规则
    note right of GENERATING_WS_CC: 生成工作空间CC库规则
    note right of GENERATING_INTERFACE: 生成ros接口规则
    note right of APPLYING_TEMPLATE: 应用BUILD模板
    note right of WRITING_FILE: 写入BUILD文件

6. 源码分析

6.1 ROS查找机制

6.1.1 ROS目录查找

  find_ros_dir函数负责查找系统中的ROS安装目录,支持环境变量和目录扫描两种方式。

def find_ros_dir(repository_ctx):
    codename = None
    if _ROS_CODENAME in repository_ctx.os.environ:
        codename = repository_ctx.os.environ[_ROS_CODENAME].strip()
    else:
        cmd = """ls -1 /opt/ros/ 2>/dev/null"""
        ros_codename_str = execute(
            repository_ctx,
            ["sh", "-c", cmd],
            empty_stdout_fine = True,
            ignore_error = True,
        ).stdout.strip()
        codename_list = ros_codename_str.split("\n")
        if len(codename_list) > 1:
            fail("Multiple ros distributions found, " +
                 'please specify codename in env variable "ROS_DISTRO"')
        codename = codename_list[0]
    if codename == None:
        print("No ros2 repositories found, " +
              "skip to import hdrs and libs.")
        return None
    ROS_FOUND = True
    return "/opt/ros/{}".format(codename)

  该函数首先检查环境变量ROS_DISTRO,如果存在则直接使用。否则扫描/opt/ros目录,查找ROS发行版。如果找到多个发行版,则报错要求用户指定。如果未找到任何ROS安装,则返回None并跳过ROS配置。

6.1.2 ROS工作空间查找

  find_ros_workspace_dir函数负责查找ROS工作空间目录。

def find_ros_workspace_dir(repository_ctx):
    repository_path = str(repository_ctx.path("")).split("/")
    ros_ws = "/".join(repository_path[:repository_path.index(".cache")] + ["ros_ws"])
    cmd = """ls -d {} 2>/dev/null""".format(ros_ws)
    if execute(
        repository_ctx,
        ["sh", "-c", cmd],
        empty_stdout_fine = True,
        ignore_error = True,
    ).stdout == "":
        print("No ros2 workspace found: {} it not existed, skip to import.".format(ros_ws))
        return None
    return "{}/install".format(ros_ws)

  该函数通过解析仓库路径,构建ros_ws目录路径,然后检查该目录是否存在。如果存在,则返回install子目录路径;否则返回None并跳过工作空间配置。

6.2 ROS包匹配机制

6.2.1 本地ROS包匹配

  _ros_match_packages函数负责匹配本地ROS安装中的包。

def _ros_match_packages(repository_ctx, ros_dir = None):
    if ros_dir == None:
        return []
    cmd = """ls -1 {}/include""".format(ros_dir)
    result = execute(
        repository_ctx,
        ["sh", "-c", cmd],
        empty_stdout_fine = True,
        ignore_error = True,
    ).stdout.strip()

    if result == "":
        pkgs = []
    else:
        pkgs = result.split("\n")

    incompatible_pkg_removed = []
    for i in pkgs:
        if "fastrtps" in i or "fastcdr" in i or \
           "qt" in i or "rviz" in i or \
           i.endswith(".h") or i.endswith(".hpp"):
            continue
        incompatible_pkg_removed.append(i)

    return incompatible_pkg_removed

  该函数扫描ROS安装的include目录,列出所有包,然后过滤掉不兼容的包(如fastrtps、fastcdr、qt、rviz等),避免符号冲突。最后返回过滤后的包列表。

6.2.2 工作空间ROS包匹配

  _ros_ws_match_package函数负责匹配ROS工作空间中的包。

def _ros_ws_match_package(repository_ctx, ros_dir = None):
    if ros_dir == None:
        return []
    cmd = """ls -1 -d {}/*/""".format(ros_dir)
    result = execute(
        repository_ctx,
        ["sh", "-c", cmd],
        empty_stdout_fine = True,
        ignore_error = True,
    ).stdout.strip()
    if result == "":
        pkgs = []
    else:
        pkgs = [i[:len(i) - 1] for i in result.split("\n")]

    return pkgs

  该函数扫描工作空间目录,列出所有子目录作为包路径。与本地ROS包匹配不同,工作空间包匹配不过滤包,因为工作空间中的包通常是用户自定义编译的,需要全部包含。

6.3 ROS库匹配机制

6.3.1 本地ROS库匹配

  _ros_match_libraries函数负责匹配ROS包的库文件。

def _ros_match_libraries(repository_ctx, ros_dir = None, pkgs = [], ros_ws_match = False):
    libraries = {}
    if ros_dir == None:
        return libraries
    for pkg in pkgs:
        if ros_ws_match:
            cmd = """find {}/lib -name lib*.so""".format(pkg)
        else:
            cmd = """ls -1 {}/lib/lib{}*.so | grep -E --color=never 'lib{}__[a-zA-Z0-9_]*\\.so$|lib{}\\.so$' | grep -v 'connext_c'""".format(ros_dir, pkg, pkg, pkg)
        result = execute(
            repository_ctx,
            ["sh", "-c", cmd],
            empty_stdout_fine = True,
            ignore_error = True,
        ).stdout.strip()
        libraries[pkg] = []
        if result != "":
            result_libs = result.split("\n")
            for i in result_libs:
                if "fastrtps" in i or "fastcdr" in i:
                    continue
                libraries[pkg].append(i)
    return libraries

  该函数为每个包查找对应的库文件。对于本地ROS,使用正则表达式匹配特定格式的库文件名;对于工作空间ROS,直接查找所有lib*.so文件。同时过滤掉fastrtps和fastcdr相关的库文件,避免符号冲突。

6.4 本地ROS仓库创建

6.4.1 仓库创建实现

  _create_local_ros_repository函数负责创建本地ROS仓库,生成相应的构建规则。

def _create_local_ros_repository(repository_ctx):
    ros_base_dir = find_ros_dir(repository_ctx)
    incl_dir = "{}/include".format(ros_base_dir)
    lib_path = "{}/lib".format(ros_base_dir)
    _lib_path = lib_path + "/"
    _incl_dir = incl_dir + "/"

    ros_packages = _ros_match_packages(repository_ctx, ros_base_dir)

    pkg_lib_src = _ros_match_libraries(repository_ctx, ros_base_dir, ros_packages)
    pkg_lib_out = {}
    for k in pkg_lib_src:
        pkg_lib_out[k] = ["ros/lib/" + i.replace(_lib_path, "") for i in pkg_lib_src[k]]

    outs = []
    srcs = []
    for k in pkg_lib_src:
        srcs = srcs + [i for i in pkg_lib_src[k]]
        outs = outs + [i for i in pkg_lib_out[k]]

    out_hdrs = get_copy_dir_files(
        repository_ctx,
        src_dir = incl_dir,
        out_dir = "ros/include",
    )
    copy_rules = [
        make_copy_files_rule(
            repository_ctx,
            name = "ros_lib",
            srcs = srcs,
            outs = outs,
        ),
        make_copy_dir_rule(
            repository_ctx,
            name = "ros_include",
            src_dir = incl_dir,
            out_dir = "ros/include",
        ),
    ]

    out_hdrs_dict = {}
    for i in out_hdrs:
        pkg_name = i.split("/")[2]
        if pkg_name not in out_hdrs_dict:
            out_hdrs_dict[pkg_name] = [i]
        else:
            out_hdrs_dict[pkg_name].append(i)

    cc_libraries = []
    cc_libraries_name = []
    for i in ros_packages:
        strip_prefix = "ros/include"

        hdrs = []
        for j in out_hdrs_dict[i]:
            if j.strip().startswith('"{}/{}/{}/'.format("ros/include", i, i)):
                strip_prefix = "{}/{}".format("ros/include", i)
            hdrs.append(j.strip())
        hdrs_bazel_format = "".join(hdrs)
        srcs_bazel_format = ",".join(['"{}"'.format(l) for l in pkg_lib_out[i]])
        cc_libraries.append(
            CC_TPL.format(i, hdrs_bazel_format, srcs_bazel_format, strip_prefix),
        )
        cc_libraries_name.append(i)

    return (copy_rules, cc_libraries, cc_libraries_name)

  该函数首先查找ROS目录,然后匹配ROS包和库文件。接着生成复制规则,将库文件和头文件复制到构建目录。最后为每个包生成CC库规则,使用模板格式化规则内容。该函数返回复制规则、CC库规则和CC库名称列表。

6.5 工作空间ROS仓库创建

6.5.1 仓库创建实现

  _create_ws_ros_repository函数负责创建工作空间ROS仓库。

def _create_ws_ros_repository(repository_ctx):
    if not ROS_FOUND:
        return ([], [], [])
    ros_ws_dir = find_ros_workspace_dir(repository_ctx)
    ros_ws_pkg = _ros_ws_match_package(repository_ctx, ros_ws_dir)

    pkg_lib_src = _ros_match_libraries(repository_ctx, ros_ws_dir, ros_ws_pkg, True)
    pkg_lib_out = {}
    for k in pkg_lib_src:
        pkg_base_path = "{}/".format(ros_ws_dir)
        pkg_lib_out[k] = ["ros_ws/lib/" + i.replace(
            pkg_base_path,
            "",
        ).replace("/lib/", "/") for i in pkg_lib_src[k]]

    outs = []
    srcs = []
    for k in pkg_lib_src:
        srcs = srcs + [i for i in pkg_lib_src[k]]
        outs = outs + [i for i in pkg_lib_out[k]]

    pkg_hdrs_dirs = ["{}/include".format(i) for i in ros_ws_pkg]
    copy_rules = [
        make_copy_files_rule(
            repository_ctx,
            name = "ros_ws_lib",
            srcs = srcs,
            outs = outs,
        ),
    ]
    copy_rules = copy_rules + [
        make_copy_dir_rule(
            repository_ctx,
            name = "ros_ws_{}_incl".format(i),
            src_dir = i,
            out_dir = "ros_ws/include",
        )
        for i in pkg_hdrs_dirs
    ]

    ws_cc_libraries = []
    ws_cc_libraries_name = []
    for index in range(len(ros_ws_pkg)):
        pkg_name = ros_ws_pkg[index].split("/")[-1]
        strip_prefix = "ros_ws/include"
        out_hdrs = get_copy_dir_files(
            repository_ctx,
            src_dir = pkg_hdrs_dirs[index],
            out_dir = "ros_ws/include",
        )

        hdrs_bazel_format = "".join([i.strip() for i in out_hdrs])
        srcs_bazel_format = ",".join([
            '"{}"'.format(i)
            for i in pkg_lib_out[ros_ws_pkg[index]]
        ])
        ws_cc_libraries.append(
            CC_TPL.format(
                "ws_{}".format(ros_ws_pkg[index]),
                hdrs_bazel_format,
                srcs_bazel_format,
                strip_prefix,
            ),
        )
        ws_cc_libraries_name.append("ws_{}".format(ros_ws_pkg[index]))

    return (copy_rules, ws_cc_libraries, ws_cc_libraries_name)

  该函数首先检查ROS是否找到,然后查找工作空间目录并匹配包。接着匹配库文件并生成复制规则,将库文件和头文件复制到构建目录。最后为每个包生成CC库规则,使用ws_前缀区分工作空间包。该函数返回复制规则、CC库规则和CC库名称列表。

6.6 规则模板分析

6.6.1 CC库模板

  CC_TPL定义了CC库规则的模板格式。

CC_TPL = '''
cc_library(
    name = "{}",
    hdrs = [{}],
    srcs = [{}],
    strip_include_prefix = "{}",
    visibility = ["//visibility:public"],
)
'''

  该模板定义了cc_library规则的基本结构,包括名称、头文件、源文件、包含路径前缀和可见性。通过格式化字符串,可以快速生成多个CC库规则。

6.6.2 ROS接口模板

  ROS_TPL定义了ROS接口规则的模板格式。

ROS_TPL = '''
cc_library(
    name = "ros",
    deps = [{}],
    hdrs = ["ros_adapter/ros_distro.h"],
    data = [":generate_ros_distro_header"],
    includes = ["."],
    visibility = ["//visibility:public"],
)
'''

  该模板定义了ros库规则,聚合所有ROS包的依赖,并提供ROS发行版头文件。该规则作为ROS集成的统一入口点。

6.6.3 ROS发行版模板

  ROS_DISTRO_TPL定义了ROS发行版头文件生成规则的模板格式。

ROS_DISTRO_TPL = '''
genrule(
    name = "generate_ros_distro_header",
    outs = ["ros_adapter/ros_distro.h"],
    cmd = """
    if [ -d '/opt/ros' ]; then
        if [ `ls /opt/ros | wc -l` == "1" ]; then
            if [ `ls /opt/ros` == "foxy" ]; then
                echo '#define ROS_DISTRO_FOXY 1' > $@
            elif [ `ls /opt/ros` == "galactic" ]; then
                echo '#define ROS_DISTRO_GALACTIC 1' > $@
            elif [ `ls /opt/ros` == "humble" ]; then
                echo '#define ROS_DISTRO_HUMBLE 1' > $@
            elif [ `ls /opt/ros` == "iron" ]; then
                echo '#define ROS_DISTRO_IRON 1' > $@
            else
                echo '// Unknown ROS distribution' > $@
            fi
        else
            if [ "$$ROS_DISTRO" == "foxy" ]; then
                echo '#define ROS_DISTRO_FOXY 1' > $@
            elif [ "$$ROS_DISTRO" == "galactic" ]; then
                echo '#define ROS_DISTRO_GALACTIC 1' > $@
            elif [ "$$ROS_DISTRO" == "humble" ]; then
                echo '#define ROS_DISTRO_HUMBLE 1' > $@
            elif [ "$$ROS_DISTRO" == "iron" ]; then
                echo '#define ROS_DISTRO_IRON 1' > $@
            else
                echo '// Unknown ROS distribution' > $@
            fi
        fi
    else
        echo '// No ROS distribution' > $@
    fi
    """,
)
'''

  该模板定义了genrule规则,通过shell脚本检测ROS发行版并生成相应的宏定义。支持Foxy、Galactic、Humble和Iron四种发行版,同时支持环境变量ROS_DISTRO和自动检测两种方式。

6.7 主配置实现

6.7.1 ROS配置实现

  _ros_configure_impl函数是ros_configure规则的主要实现。

def _ros_configure_impl(repository_ctx):
    (
        copy_rules,
        cc_libraries,
        cc_libraries_name,
    ) = _create_local_ros_repository(repository_ctx)
    (
        ws_copy_rules,
        ws_cc_libraries,
        ws_cc_libraries_name,
    ) = _create_ws_ros_repository(repository_ctx)

    ros_interface = ROS_TPL.format(",".join([
        '"{}"'.format(i)
        for i in cc_libraries_name + ws_cc_libraries_name
    ]))

    build_tpl = repository_ctx.path(Label("//tools/ros:BUILD.tpl"))
    repository_ctx.template("BUILD", build_tpl, {
        "%{ros_distro_gen_rules}": "%s" % (ROS_DISTRO_TPL),
        "%{copy_rules}": "\n".join(copy_rules),
        "%{ws_copy_rules}": "\n".join(ws_copy_rules),
        "%{cc_libraries}": "\n".join(cc_libraries),
        "%{ws_cc_libraries}": "\n".join(ws_cc_libraries),
        "%{ros_interface}": "%s" % (ros_interface),
    })

  该函数首先创建本地ROS仓库和工作空间ROS仓库,然后生成ROS接口规则。最后应用BUILD模板,将所有规则写入BUILD文件。模板中的占位符被替换为实际生成的规则内容。

6.7.2 ROS配置规则

  ros_configure是repository_rule的定义。

ros_configure = repository_rule(
    implementation = _ros_configure_impl,
    environ = [],
    local = True,
)

  该规则定义了ros_configure的配置,指定实现函数、环境变量列表和本地标志。local=True表示该规则只在本地执行,不涉及远程执行。

6.8 BUILD模板分析

6.8.1 BUILD模板结构

  BUILD.tpl定义了BUILD文件的模板结构。

package(default_visibility = ["//visibility:public"])

%{ros_distro_gen_rules}

%{copy_rules}

%{ws_copy_rules}

%{cc_libraries}

%{ws_cc_libraries}

%{ros_interface}

  该模板定义了BUILD文件的基本结构,包括包可见性、ROS发行版生成规则、复制规则、CC库规则和ROS接口规则。占位符在配置时被替换为实际内容。

6.9 辅助函数分析

6.9.1 头文件收集

  get_copy_dir_files函数负责收集目录中的所有文件,生成Bazel格式的文件列表。

def get_copy_dir_files(repository_ctx, src_dir, out_dir):
    cmd = """find {} -type f""".format(src_dir)
    result = execute(
        repository_ctx,
        ["sh", "-c", cmd],
        empty_stdout_fine = True,
        ignore_error = True,
    ).stdout.strip()
    if result == "":
        return []
    files = result.split("\n")
    out_files = ['"{}"'.format(i.replace(src_dir, out_dir)) for i in files]
    return out_files

  该函数使用find命令查找目录中的所有文件,然后将源路径替换为输出路径,并格式化为Bazel文件列表。

6.9.2 复制文件规则生成

  make_copy_files_rule函数负责生成文件复制规则。

def make_copy_files_rule(repository_ctx, name, srcs, outs):
    return """
filegroup(
    name = "{}",
    srcs = [{}],
    visibility = ["//visibility:public"],
)
""".format(name, ",".join(['"{}"'.format(i) for i in srcs]))

  该函数生成filegroup规则,将源文件复制到输出位置。虽然实际复制可能需要其他机制,但该规则提供了文件的组织方式。

6.9.3 复制目录规则生成

  make_copy_dir_rule函数负责生成目录复制规则。

def make_copy_dir_rule(repository_ctx, name, src_dir, out_dir):
    return """
filegroup(
    name = "{}",
    srcs = glob(["{}/**"]),
    visibility = ["//visibility:public"],
)
""".format(name, src_dir)

  该函数生成filegroup规则,使用glob模式收集目录中的所有文件。与文件复制规则类似,该规则提供了目录文件的组织方式。

7. 设计模式

7.1 工厂模式(Factory Pattern)

  ros_configure规则实现了工厂模式,根据ROS安装情况创建不同类型的仓库。

def _ros_configure_impl(repository_ctx):
    (
        copy_rules,
        cc_libraries,
        cc_libraries_name,
    ) = _create_local_ros_repository(repository_ctx)
    (
        ws_copy_rules,
        ws_cc_libraries,
        ws_cc_libraries_name,
    ) = _create_ws_ros_repository(repository_ctx)

  该函数根据ROS安装情况创建本地ROS仓库和工作空间ROS仓库,生成相应的构建规则。这种工厂模式简化了复杂仓库的创建过程。

7.2 策略模式(Strategy Pattern)

  ROS查找函数实现了策略模式,根据不同的查找策略选择不同的查找方法。

def find_ros_dir(repository_ctx):
    codename = None
    if _ROS_CODENAME in repository_ctx.os.environ:
        codename = repository_ctx.os.environ[_ROS_CODENAME].strip()
    else:
        cmd = """ls -1 /opt/ros/ 2>/dev/null"""
        ros_codename_str = execute(
            repository_ctx,
            ["sh", "-c", cmd],
            empty_stdout_fine = True,
            ignore_error = True,
        ).stdout.strip()
        codename_list = ros_codename_str.split("\n")
        if len(codename_list) > 1:
            fail("Multiple ros distributions found, " +
                 'please specify codename in env variable "ROS_DISTRO"')
        codename = codename_list[0]

  该函数根据环境变量是否存在选择不同的查找策略。这种策略模式提高了代码的灵活性。

7.3 模板方法模式(Template Method Pattern)

  规则生成函数实现了模板方法模式,定义了规则生成的标准流程。

def _create_local_ros_repository(repository_ctx):
    ros_base_dir = find_ros_dir(repository_ctx)
    incl_dir = "{}/include".format(ros_base_dir)
    lib_path = "{}/lib".format(ros_base_dir)

    ros_packages = _ros_match_packages(repository_ctx, ros_base_dir)
    pkg_lib_src = _ros_match_libraries(repository_ctx, ros_base_dir, ros_packages)

    copy_rules = [...]
    cc_libraries = [...]
    cc_libraries_name = [...]

    return (copy_rules, cc_libraries, cc_libraries_name)

  该函数定义了本地ROS仓库创建的标准流程,包括查找目录、匹配包、匹配库、生成规则等步骤。这种模板方法模式确保了创建流程的一致性。

7.4 建造者模式(Builder Pattern)

  BUILD模板实现了建造者模式,逐步构建完整的BUILD文件。

repository_ctx.template("BUILD", build_tpl, {
    "%{ros_distro_gen_rules}": "%s" % (ROS_DISTRO_TPL),
    "%{copy_rules}": "\n".join(copy_rules),
    "%{ws_copy_rules}": "\n".join(ws_copy_rules),
    "%{cc_libraries}": "\n".join(cc_libraries),
    "%{ws_cc_libraries}": "\n".join(ws_cc_libraries),
    "%{ros_interface}": "%s" % (ros_interface),
})

  该函数通过替换模板中的占位符,逐步构建完整的BUILD文件。这种建造者模式简化了复杂文件的生成过程。

7.5 适配器模式(Adapter Pattern)

  库匹配函数实现了适配器模式,将不同格式的库文件名适配为统一的格式。

def _ros_match_libraries(repository_ctx, ros_dir = None, pkgs = [], ros_ws_match = False):
    libraries = {}
    if ros_dir == None:
        return libraries
    for pkg in pkgs:
        if ros_ws_match:
            cmd = """find {}/lib -name lib*.so""".format(pkg)
        else:
            cmd = """ls -1 {}/lib/lib{}*.so | grep -E --color=never 'lib{}__[a-zA-Z0-9_]*\\.so$|lib{}\\.so$' | grep -v 'connext_c'""".format(ros_dir, pkg, pkg, pkg)

  该函数根据是否为工作空间匹配选择不同的库文件匹配策略。这种适配器模式确保了不同场景下的正确匹配。

7.6 过滤器模式(Filter Pattern)

  包匹配函数实现了过滤器模式,过滤掉不兼容的包。

def _ros_match_packages(repository_ctx, ros_dir = None):
    incompatible_pkg_removed = []
    for i in pkgs:
        if "fastrtps" in i or "fastcdr" in i or \
           "qt" in i or "rviz" in i or \
           i.endswith(".h") or i.endswith(".hpp"):
            continue
        incompatible_pkg_removed.append(i)

    return incompatible_pkg_removed

  该函数过滤掉不兼容的包,避免符号冲突。这种过滤器模式提高了系统的稳定性。

8. 总结

  apollo_tools_ros子模块通过精心设计的ROS检测、配置和导入机制,实现了ROS2与Bazel构建系统的无缝集成。其核心优势在于自动化的ROS发行版检测、灵活的工作空间支持、智能的包和库匹配以及完善的规则生成机制。通过工厂模式、策略模式、模板方法模式等设计模式的应用,该模块实现了配置流程的解耦和可扩展性,为Apollo项目与ROS生态系统的集成提供了稳定可靠的基础设施。模块的多发行版支持和自定义工作空间功能显著提升了开发灵活性,为自动驾驶系统的快速迭代和生态扩展提供了有力支持。