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生态系统的集成提供了稳定可靠的基础设施。模块的多发行版支持和自定义工作空间功能显著提升了开发灵活性,为自动驾驶系统的快速迭代和生态扩展提供了有力支持。