16_apollo_tools_proto子模块软件架构分析
1. 概述
apollo_tools_proto子模块是Apollo自动驾驶平台构建系统中的Protocol Buffers(protobuf)编译和管理模块,负责处理项目中所有.proto文件的编译、依赖管理和多语言代码生成。该模块通过封装Bazel构建规则,实现了C++和Python语言的protobuf代码自动生成,支持跨平台编译和依赖解析,为Apollo各模块提供高效的数据序列化和反序列化能力。模块采用模块化设计,支持动态依赖管理和增量编译,显著提升了构建效率和代码可维护性。
2. 软件架构图
graph TB
subgraph "构建系统入口"
B1[Bazel构建系统]
B2[WORKSPACE配置]
B3[BUILD文件]
end
subgraph "Proto编译核心层"
P1[proto_library规则]
P2[external_proto_library]
P3[cc_proto_library]
P4[py_proto_library]
P5[_proto_rule聚合规则]
end
subgraph "依赖管理层"
D1[依赖解析器]
D2[标签转换器]
D3[包路径管理器]
D4[第三方依赖处理]
end
subgraph "代码生成层"
C1[C++代码生成器]
C2[Python代码生成器]
C3[头文件生成器]
C4[源文件生成器]
end
subgraph "输出管理层"
O1[cc_lib_rule]
O2[cc_bin_rule]
O3[cc_wrap_rule]
O4[清理规则]
end
subgraph "兼容性层"
F1[fake_cc_proto]
F2[fake_py_proto]
F3[filter_proto_library]
F4[fix_proto_wrap]
end
B1 --> P1
B2 --> D3
B3 --> P1
P1 --> P2
P1 --> P3
P1 --> P4
P1 --> D1
D1 --> D2
D2 --> D3
D3 --> D4
P2 --> C1
P2 --> C2
P3 --> C3
P3 --> C4
P4 --> C2
C1 --> O1
C1 --> O2
C1 --> O3
O1 --> O4
P5 --> F1
P5 --> F2
F1 --> F3
F2 --> F3
F3 --> F4
style P1 fill:#e1f5fe
style P5 fill:#fff3e0
style D1 fill:#f3e5f5
style C1 fill:#e8f5e8
3. 调用流程图
sequenceDiagram
participant User as 用户
participant Bazel as Bazel构建系统
participant ProtoRule as proto_library规则
participant DepResolver as 依赖解析器
participant ExtProto as external_proto_library
participant CCProto as cc_proto_library
participant PyProto as py_proto_library
participant CodeGen as 代码生成器
participant Output as 输出管理器
User->>Bazel: 触发构建命令
Bazel->>ProtoRule: 解析proto_library规则
ProtoRule->>ProtoRule: 验证规则名称
ProtoRule->>DepResolver: 解析依赖关系
DepResolver->>DepResolver: 分离内部和外部依赖
DepResolver->>DepResolver: 转换依赖标签
DepResolver-->>ProtoRule: 返回处理后的依赖
ProtoRule->>ExtProto: 创建原始proto目标
ExtProto->>CodeGen: 生成proto描述文件
CodeGen-->>ExtProto: 返回proto信息
ProtoRule->>CCProto: 创建C++ proto目标
CCProto->>CodeGen: 生成C++头文件
CCProto->>CodeGen: 生成C++源文件
CodeGen-->>CCProto: 返回C++代码
ProtoRule->>PyProto: 创建Python proto目标
PyProto->>CodeGen: 生成Python代码
CodeGen-->>PyProto: 返回Python代码
ProtoRule->>Output: 创建清理规则
Output->>Output: 过滤生成的文件
Output-->>ProtoRule: 返回清理后的文件
ProtoRule->>Output: 创建静态库规则
Output->>Output: 链接静态库
Output-->>ProtoRule: 返回静态库
ProtoRule->>Output: 创建共享库规则
Output->>Output: 链接共享库
Output-->>ProtoRule: 返回共享库
ProtoRule->>Output: 创建包装规则
Output->>Output: 包装输出
Output-->>ProtoRule: 返回包装结果
ProtoRule->>ProtoRule: 创建兼容性规则
ProtoRule-->>Bazel: 返回构建目标
Bazel-->>User: 构建完成
4. UML类图
4.1 核心规则类图
classDiagram
class ProtoLibraryRule {
+name: string
+srcs: list
+deps: list
+proto_library()
-_apollo_proto_impl()
-_cc_proto_clean_rule_impl()
-_filter_proto_impl()
}
class ExternalProtoLibrary {
+name: string
+srcs: list
+deps: list
+proto_library()
}
class CCProtoLibrary {
+name: string
+deps: list
+cc_proto_library()
}
class PyProtoLibrary {
+name: string
+deps: list
+py_proto_library()
}
class ProtoRule {
+srcs: list
+_apollo_proto_impl()
}
class CCProtoCleanRule {
+srcs: list
+_cc_proto_clean_rule_impl()
}
class FilterProtoLibrary {
+deps: list
+apollo_src_deps: list
+_filter_proto_impl()
}
class OutputManager {
+cc_lib_rule_name: string
+cc_bin_rule_name: string
+cc_wrap_rule_name: string
+create_cc_library()
+create_cc_binary()
+create_cc_wrap()
}
class DependencyResolver {
+external_deps: list
+external_cc_deps: list
+external_3rd_proto_deps: list
+resolve_deps()
+_get_real_dep_label()
+_get_python_dep_label()
}
class LabelTransformer {
+package_path: string
+_to_bin_target()
+_to_short_target()
+_to_wrap_target()
+_to_py_target()
}
ProtoLibraryRule --> ExternalProtoLibrary
ProtoLibraryRule --> CCProtoLibrary
ProtoLibraryRule --> PyProtoLibrary
ProtoLibraryRule --> ProtoRule
ProtoLibraryRule --> CCProtoCleanRule
ProtoLibraryRule --> FilterProtoLibrary
ProtoLibraryRule --> OutputManager
ProtoLibraryRule --> DependencyResolver
ProtoLibraryRule --> LabelTransformer
DependencyResolver --> LabelTransformer
4.2 数据流类图
classDiagram
class ProtoSource {
+file_path: string
+package_name: string
+messages: list
+enums: list
+services: list
+parse()
+validate()
}
class ProtoInfo {
+proto_files: list
+imports: list
+dependencies: list
+get_proto_info()
}
class CcInfo {
+headers: list
+sources: list
+libraries: list
+get_cc_info()
}
class PyInfo {
+modules: list
+dependencies: list
+get_py_info()
}
class GeneratedCode {
+language: string
+files: list
+output_dir: string
+generate()
}
class BuildTarget {
+name: string
+type: string
+outputs: list
+dependencies: list
+get_target()
}
class DependencyGraph {
+nodes: list
+edges: list
+add_node()
+add_edge()
+resolve()
+topological_sort()
}
ProtoSource --> ProtoInfo
ProtoInfo --> CcInfo
ProtoInfo --> PyInfo
CcInfo --> GeneratedCode
PyInfo --> GeneratedCode
GeneratedCode --> BuildTarget
BuildTarget --> DependencyGraph
4.3 配置管理类图
classDiagram
class PackageConfig {
+package_path: string
+visibility: list
+get_config()
+set_config()
}
class BuildConfig {
+rule_name: string
+tags: list
+kwargs: dict
+validate()
+get_config()
}
class DepConfig {
+internal_deps: list
+external_deps: list
+third_party_deps: list
+parse_deps()
+transform_deps()
}
class OutputConfig {
+cc_lib_config: dict
+cc_bin_config: dict
+cc_wrap_config: dict
+get_lib_config()
+get_bin_config()
+get_wrap_config()
}
class TemplateConfig {
+cc_tpl: string
+ros_tpl: string
+ros_distro_tpl: string
+get_template()
+apply_template()
}
PackageConfig --> BuildConfig
BuildConfig --> DepConfig
BuildConfig --> OutputConfig
OutputConfig --> TemplateConfig
5. 状态机
5.1 Proto编译状态机
stateDiagram-v2
[*] --> PENDING: 创建proto_library规则
PENDING --> VALIDATING: 开始验证
VALIDATING --> VALIDATED: 验证通过
VALIDATING --> ERROR: 验证失败
VALIDATED --> RESOLVING: 开始解析依赖
RESOLVING --> RESOLVED: 依赖解析完成
RESOLVING --> ERROR: 依赖解析失败
RESOLVED --> GENERATING_CC: 生成C++代码
GENERATING_CC --> CC_GENERATED: C++代码生成完成
GENERATING_CC --> ERROR: C++代码生成失败
CC_GENERATED --> GENERATING_PY: 生成Python代码
GENERATING_PY --> PY_GENERATED: Python代码生成完成
GENERATING_PY --> ERROR: Python代码生成失败
PY_GENERATED --> BUILDING_LIB: 构建静态库
BUILDING_LIB --> LIB_BUILT: 静态库构建完成
BUILDING_LIB --> ERROR: 静态库构建失败
LIB_BUILT --> BUILDING_BIN: 构建共享库
BUILDING_BIN --> BIN_BUILT: 共享库构建完成
BUILDING_BIN --> ERROR: 共享库构建失败
BIN_BUILT --> WRAPPING: 包装输出
WRAPPING --> WRAPPED: 包装完成
WRAPPING --> ERROR: 包装失败
WRAPPED --> CREATING_COMPAT: 创建兼容性规则
CREATING_COMPAT --> COMPLETED: 完成
CREATING_COMPAT --> ERROR: 创建失败
ERROR --> [*]: 失败退出
COMPLETED --> [*]: 成功完成
note right of VALIDATING: 验证规则名称格式
note right of RESOLVING: 解析内部和外部依赖
note right of GENERATING_CC: 生成.h和.cc文件
note right of GENERATING_PY: 生成_pb2.py文件
note right of BUILDING_LIB: 链接静态库
note right of BUILDING_BIN: 链接共享库
note right of WRAPPING: 创建包装规则
note right of CREATING_COMPAT: 创建向后兼容规则
5.2 依赖解析状态机
stateDiagram-v2
[*] --> INIT: 开始依赖解析
INIT --> COLLECTING: 收集依赖列表
COLLECTING --> COLLECTED: 依赖收集完成
COLLECTING --> ERROR: 收集失败
COLLECTED --> CLASSIFYING: 分类依赖
CLASSIFYING --> CLASSIFIED: 依赖分类完成
CLASSIFYING --> ERROR: 分类失败
CLASSIFIED --> TRANSFORMING: 转换依赖标签
TRANSFORMING --> TRANSFORMED: 标签转换完成
TRANSFORMING --> ERROR: 转换失败
TRANSFORMED --> VALIDATING: 验证依赖
VALIDATING --> VALIDATED: 依赖验证完成
VALIDATING --> ERROR: 验证失败
VALIDATED --> RESOLVING: 解析依赖路径
RESOLVING --> RESOLVED: 路径解析完成
RESOLVING --> ERROR: 路径解析失败
RESOLVED --> [*]: 解析完成
ERROR --> [*]: 解析失败
note right of CLASSIFYING: 分离内部、外部和第三方依赖
note right of TRANSFORMING: 转换为Bazel标签格式
note right of VALIDATING: 检查依赖是否存在
note right of RESOLVING: 解析依赖的实际路径
5.3 代码生成状态机
stateDiagram-v2
[*] --> PARSING: 解析proto文件
PARSING --> PARSED: 解析完成
PARSING --> ERROR: 解析失败
PARSED --> VALIDATING: 验证proto定义
VALIDATING --> VALIDATED: 验证通过
VALIDATING --> ERROR: 验证失败
VALIDATED --> GENERATING_HEADERS: 生成头文件
GENERATING_HEADERS --> HEADERS_GENERATED: 头文件生成完成
GENERATING_HEADERS --> ERROR: 头文件生成失败
HEADERS_GENERATED --> GENERATING_SOURCES: 生成源文件
GENERATING_SOURCES --> SOURCES_GENERATED: 源文件生成完成
GENERATING_SOURCES --> ERROR: 源文件生成失败
SOURCES_GENERATED --> COMPILING: 编译代码
COMPILING --> COMPILED: 编译完成
COMPILING --> ERROR: 编译失败
COMPILED --> LINKING: 链接库文件
LINKING --> LINKED: 链接完成
LINKING --> ERROR: 链接失败
LINKED --> [*]: 生成完成
ERROR --> [*]: 生成失败
note right of PARSING: 解析.proto文件语法
note right of VALIDATING: 检查消息、枚举、服务定义
note right of GENERATING_HEADERS: 生成.pb.h文件
note right of GENERATING_SOURCES: 生成.pb.cc文件
note right of COMPILING: 编译生成的源代码
note right of LINKING: 链接生成库文件
6. 源码分析
6.1 核心规则定义
6.1.1 proto_library规则
proto_library函数是整个proto编译系统的核心入口,负责协调所有proto相关的编译任务。该函数首先验证规则名称是否符合规范,要求必须以"proto"结尾,然后创建多个子规则来处理不同语言的代码生成。
def proto_library(tags = [], **kwargs):
rule_name = kwargs["name"]
if not rule_name.endswith("proto"):
fail("The name of the proto_library instance must end with \"proto\", e.g. \"example_proto\".")
proto_rule_name = "_%s" % kwargs["name"]
cc_proto_rule_name = "_%s_cc_proto" % rule_name
py_proto_rule_name = _to_py_target(rule_name)
cc_bin_rule_name = _to_bin_target(rule_name)
cc_lib_rule_name = "_%s_cc_lib" % rule_name
cc_wrap_rule_name = _to_wrap_target(_to_short_target(rule_name))
# ... 后续处理逻辑
该函数通过生成多个子规则名称来管理不同阶段的编译产物,包括原始proto规则、C++ proto规则、Python proto规则、二进制规则、静态库规则和包装规则。这种命名约定确保了构建产物的唯一性和可追溯性。
6.1.2 依赖解析机制
依赖解析是proto编译系统的关键功能,_get_real_dep_label函数负责将依赖标签转换为实际的Bazel标签格式。该函数根据依赖的类型和包路径进行不同的处理。
def _get_real_dep_label(dep):
if dep.startswith("@") or "third_party" in dep or (not dep.endswith("_proto") and not dep.endswith("_py_pb2")):
return dep
if not package_path.startswith("@@"):
if dep.startswith(":"):
return "{}{}{}".format("@apollo_src//", native.package_name(), dep)
else:
return "{}{}".format("@apollo_src", dep)
return dep
该函数首先检查依赖是否为外部依赖(以@开头)或第三方依赖,如果是则直接返回。对于内部依赖,根据包路径配置转换为完整的Bazel标签格式。这种机制确保了依赖在不同构建环境下的正确解析。
6.1.3 标签转换器
标签转换器负责将规则名称转换为不同类型的输出目标名称。_to_bin_target函数将proto规则名称转换为二进制库目标名称。
def _to_bin_target(name):
base_info = name.split(":")
package_name = native.package_name()
package_name_list = package_name.split("/")
label = "".join([i[0] for i in package_name_list])
base_info[-1] = "lib_%s_%s_bin.so" % (base_info[-1], label)
return ":".join(base_info)
该函数通过提取包名的首字母生成简短标签,然后将其附加到二进制库名称中,确保生成的库名称在全局范围内唯一。这种命名策略避免了不同包中同名proto文件的冲突。
6.2 规则实现分析
6.2.1 Apollo Proto规则实现
_apollo_proto_impl函数是Apollo自定义proto规则的实现,负责聚合不同语言的proto编译结果。
def _apollo_proto_impl(ctx):
ret_info = []
for t in ctx.attr.srcs:
if CcInfo in t:
ret_info.append(t[CcInfo])
elif PyInfo in t:
files = ctx.runfiles(files = _depset_to_list(t.default_runfiles.files))
ret_info.append(DefaultInfo(runfiles = files))
ret_info.append(t[InstrumentedFilesInfo])
ret_info.append(t[OutputGroupInfo])
ret_info.append(t[PyInfo])
else:
ret_info.append(t[ProtoInfo])
return ret_info
该函数遍历所有源文件,根据其提供的信息类型(CcInfo、PyInfo或ProtoInfo)进行相应的处理。对于C++信息,直接添加到返回列表;对于Python信息,创建运行文件并添加多个提供者;对于proto信息,直接添加ProtoInfo。这种设计确保了不同语言编译结果的正确聚合。
6.2.2 C++ Proto清理规则
_cc_proto_clean_rule_impl函数负责清理C++ proto编译产物,只保留.h和.cc文件。
def _cc_proto_clean_rule_impl(ctx):
cc_infos = []
files = []
for t in ctx.attr.srcs:
if DefaultInfo in t:
for item in _depset_to_list(t[DefaultInfo].files):
if item.extension == 'h' or item.extension == 'cc':
files.append(item)
if CcInfo in t:
cc_infos.append(t[CcInfo])
merged_info = cc_common.merge_cc_infos(cc_infos = cc_infos)
return [
merged_info,
DefaultInfo(files=depset(files)),
]
该函数首先收集所有C++信息和文件,然后过滤出扩展名为.h和.cc的文件,最后合并所有C++信息并返回。这种清理机制确保了只包含必要的编译产物,减少了构建输出的冗余。
6.2.3 过滤Proto库规则
_filter_proto_impl函数负责过滤proto依赖,确保使用正确的C++信息。
def _filter_proto_impl(ctx):
cc_infos = []
index = 0
for dep in ctx.attr.deps:
if CcInfo in dep and PyInfo in dep and ProtoInfo in dep:
cc_infos.append(ctx.attr.apollo_src_deps[index][CcInfo])
else:
cc_infos.append(dep[CcInfo])
index = index + 1
merged_info = cc_common.merge_cc_infos(cc_infos = cc_infos)
return [
DefaultInfo(),
merged_info
]
该函数检查每个依赖是否同时包含CcInfo、PyInfo和ProtoInfo,如果是则使用apollo_src_deps中的CcInfo,否则使用依赖本身的CcInfo。这种机制确保了在混合依赖场景下使用正确的编译信息。
6.3 辅助函数分析
6.3.1 Depset转换函数
_depset_to_list函数是一个辅助函数,用于将depset转换为列表。
def _depset_to_list(x):
"""Helper function to convert depset to list."""
iter_list = x.to_list() if type(x) == "depset" else x
return iter_list
该函数检查输入类型,如果是depset则调用to_list()方法转换,否则直接返回。这种设计确保了函数能够处理不同类型的输入,提高了代码的健壮性。
6.3.2 Python依赖标签转换
_get_python_dep_label函数负责将Python依赖标签转换为正确的格式。
def _get_python_dep_label(dep):
if not package_path.startswith("@@"):
if dep.startswith(":"):
return "{}{}{}".format("@apollo_src//", native.package_name(), dep)
else:
return "{}{}".format("@apollo_src", dep)
return dep
该函数根据包路径配置将本地依赖转换为完整的Bazel标签格式,对于外部依赖则直接返回。这种机制确保了Python依赖在不同构建环境下的正确解析。
6.3.3 Select解析函数
parse_select函数负责解析select表达式,提取其中的依赖列表。
def parse_select(deps):
parsed_deps = []
apollo_src_deps = []
select_dict_list = []
temp = {}
if type(deps) == "select":
deps_list = []
for group_str in str(deps).strip().split(" + "):
if "select({" in group_str.strip():
select_dict_list.append(select2dict(group_str))
elif group_str.strip() == "[]":
continue
else:
deps_list += list_str2list(group_str.strip())
parsed_deps = deps_list
for s in parsed_deps:
temp[s] = s
parsed_deps = [s for s in temp]
apollo_src_deps = [_get_real_dep_label(s) for s in temp]
if len(select_dict_list) != 0:
for i in select_dict_list:
parsed_deps += select(i)
apollo_src_deps += select(i)
else:
parsed_deps = deps
apollo_src_deps = [_get_real_dep_label(s) for s in deps]
return parsed_deps, apollo_src_deps
该函数处理select表达式,将其分解为多个依赖组,然后解析每个组中的依赖。对于select字典,使用select2dict函数转换,对于普通列表,使用list_str2list函数转换。最后返回解析后的依赖列表和apollo_src依赖列表。
6.4 包装函数分析
6.4.1 C++二进制包装函数
fix_proto_cc_binary_wrap函数负责包装C++二进制目标,确保使用正确的依赖。
def fix_proto_cc_binary_wrap(name, deps, **kwargs):
parsed_deps, apollo_src_deps = parse_select(deps)
filter_proto_library(name=name + "_filter", deps=parsed_deps, apollo_src_deps=apollo_src_deps)
native.cc_binary(**dict(kwargs, name=name, deps=[name + "_filter", "@apollo_src//:hdrs"]))
该函数首先解析依赖,然后创建过滤规则,最后创建C++二进制目标并使用过滤后的依赖和头文件。这种包装机制确保了二进制目标使用正确的编译依赖。
6.4.2 C++库包装函数
fix_proto_cc_library_wrap函数负责包装C++库目标,确保使用正确的依赖。
def fix_proto_cc_library_wrap(name, deps, **kwargs):
parsed_deps, apollo_src_deps = parse_select(deps)
filter_proto_library(name=name + "_filter", deps=parsed_deps, apollo_src_deps=apollo_src_deps)
native.cc_library(**dict(kwargs, name=name, deps=[name + "_filter", "@apollo_src//:hdrs"]))
该函数与二进制包装函数类似,但创建的是C++库目标。这种统一的包装机制简化了依赖管理。
6.4.3 C++测试包装函数
fix_proto_cc_test_wrap函数负责包装C++测试目标,确保使用正确的依赖。
def fix_proto_cc_test_wrap(name, deps, **kwargs):
parsed_deps, apollo_src_deps = parse_select(deps)
filter_proto_library(name=name + "_filter", deps=parsed_deps, apollo_src_deps=apollo_src_deps)
native.cc_test(**dict(kwargs, name=name, deps=[name + "_filter", "@apollo_src//:hdrs"]))
该函数与库包装函数类似,但创建的是C++测试目标。这种统一的包装机制确保了测试目标使用正确的编译依赖。
6.5 Python规则分析
6.5.1 Apollo Python库规则
apollo_py_library函数负责创建Python库目标,处理依赖标签转换。
def apollo_py_library(**kwargs):
if "deps" not in kwargs:
py_library(**dict(kwargs))
else:
replaced_deps = []
for i in kwargs["deps"]:
replaced_deps.append(_get_python_dep_label(i))
py_library(**dict(kwargs, deps = replaced_deps))
该函数检查是否有依赖,如果有则转换所有依赖标签,然后创建Python库目标。这种机制确保了Python依赖的正确解析。
6.5.2 Apollo Python二进制规则
apollo_py_binary函数负责创建Python二进制目标,处理依赖标签转换。
def apollo_py_binary(**kwargs):
if "deps" not in kwargs:
py_binary(**dict(kwargs))
else:
replaced_deps = []
for i in kwargs["deps"]:
replaced_deps.append(_get_python_dep_label(i))
py_binary(**dict(kwargs, deps = replaced_deps))
该函数与Python库规则类似,但创建的是Python二进制目标。这种统一的依赖转换机制简化了Python目标的创建。
6.6 兼容性规则分析
6.6.1 向后兼容规则
proto_library函数创建了多个向后兼容规则,确保旧代码能够继续工作。
additional_fake_cc_proto_name = "_".join(
rule_info_list[:len(rule_info_list)-1] + ["cc", "proto"])
additional_fake_py_proto_name = "_".join(
rule_info_list[:len(rule_info_list)-1] + ["py", "pb2"])
这些规则名称遵循旧的命名约定,允许使用旧名称的代码继续工作。这种兼容性设计确保了构建系统的平滑升级。
6.6.2 兼容性规则创建
proto_library函数为每个兼容性名称创建规则,聚合相同的源文件。
_proto_rule(
name = additional_fake_cc_proto_name,
srcs = [
":%s" % proto_rule_name,
":%s" % py_proto_rule_name,
":%s" % cc_wrap_rule_name,
],
)
这些规则聚合了原始proto、Python proto和C++包装规则,确保向后兼容性。这种设计允许渐进式迁移到新的命名约定。
6.7 构建配置分析
6.7.1 包路径配置
package_path变量定义了包路径,用于依赖标签转换。
package_path = "@@REPLACE@@"
这个变量在构建时被替换为实际的包路径,支持不同的构建配置。这种设计允许proto.bzl文件在不同构建环境中使用。
6.7.2 模板配置
proto.bzl.tpl文件是proto.bzl的模板,支持动态配置。
package_path = "@@REPLACE@@"
模板中的占位符在构建时被替换,允许根据不同的构建环境生成不同的配置。这种设计提高了构建系统的灵活性。
6.8 头文件管理分析
6.8.1 Apollo头文件库
apollo.BUILD文件定义了Apollo头文件库。
package(default_visibility = ["//visibility:public"])
cc_library(
name = "hdrs",
includes = [
".",
],
)
这个库提供了Apollo项目的头文件包含路径,确保所有模块能够正确访问头文件。这种集中式的头文件管理简化了构建配置。
6.8.2 通用消息头文件库
common_msgs_header.BUILD文件定义了通用消息头文件库。
package(default_visibility = ["//visibility:public"])
cc_library(
name = "common_msgs_header",
hdrs = glob(["modules/common_msgs/**/*.h"]),
visibility = ["//visibility:public"],
)
这个库提供了通用消息模块的头文件,使用glob模式自动收集所有.h文件。这种自动化的头文件收集减少了手动维护的工作量。
7. 设计模式
7.1 工厂模式(Factory Pattern)
proto_library函数实现了工厂模式,根据输入参数创建不同类型的构建规则。
def proto_library(tags = [], **kwargs):
# 创建原始proto规则
external_proto_library(
**(dict(kwargs, deps=external_3rd_proto_deps + external_cc_deps, tags = ["exclude"],)),
)
# 创建C++ proto规则
cc_proto_library(
name = cc_proto_rule_name,
deps = [":%s" % proto_rule_name],
tags = ["exclude"],
)
# 创建Python proto规则
py_proto_library(
name = py_proto_rule_name,
deps = [":%s" % proto_rule_name] + [_to_py_target(d) for d in external_cc_deps],
tags = ["exclude"],
)
该函数根据输入参数创建多个相关的构建规则,包括原始proto规则、C++ proto规则和Python proto规则。这种工厂模式简化了复杂构建规则的创建过程。
7.2 策略模式(Strategy Pattern)
标签转换函数实现了策略模式,根据不同的转换需求选择不同的转换策略。
def _to_bin_target(name):
base_info = name.split(":")
package_name = native.package_name()
package_name_list = package_name.split("/")
label = "".join([i[0] for i in package_name_list])
base_info[-1] = "lib_%s_%s_bin.so" % (base_info[-1], label)
return ":".join(base_info)
def _to_py_target(name):
base_info = name.split(":")
base_info[-1] = "%s_py_pb2" % base_info[-1]
return ":".join(base_info)
def _to_wrap_target(name):
base_info = name.split(":")
base_info[-1] = "_%s_w" % base_info[-1]
return ":".join(base_info)
这些函数实现了不同的标签转换策略,根据目标类型选择相应的转换逻辑。这种策略模式提高了代码的可扩展性。
7.3 适配器模式(Adapter Pattern)
依赖解析函数实现了适配器模式,将不同格式的依赖标签适配为统一的Bazel标签格式。
def _get_real_dep_label(dep):
if dep.startswith("@") or "third_party" in dep or (not dep.endswith("_proto") and not dep.endswith("_py_pb2")):
return dep
if not package_path.startswith("@@"):
if dep.startswith(":"):
return "{}{}{}".format("@apollo_src//", native.package_name(), dep)
else:
return "{}{}".format("@apollo_src", dep)
return dep
该函数根据依赖的类型和格式进行适配,确保所有依赖都转换为统一的Bazel标签格式。这种适配器模式简化了依赖管理。
7.4 装饰器模式(Decorator Pattern)
包装函数实现了装饰器模式,为原生构建规则添加额外的功能。
def fix_proto_cc_library_wrap(name, deps, **kwargs):
parsed_deps, apollo_src_deps = parse_select(deps)
filter_proto_library(name=name + "_filter", deps=parsed_deps, apollo_src_deps=apollo_src_deps)
native.cc_library(**dict(kwargs, name=name, deps=[name + "_filter", "@apollo_src//:hdrs"]))
该函数在原生cc_library规则的基础上添加了依赖过滤和头文件包含功能。这种装饰器模式增强了原生规则的功能。
7.5 模板方法模式(Template Method Pattern)
proto_library函数实现了模板方法模式,定义了proto编译的标准流程。
def proto_library(tags = [], **kwargs):
# 1. 验证规则名称
if not rule_name.endswith("proto"):
fail("The name of the proto_library instance must end with \"proto\", e.g. \"example_proto\".")
# 2. 解析依赖
external_deps = kwargs["deps"] if "deps" in kwargs else []
external_cc_deps = []
external_3rd_proto_deps = []
for dep in external_deps:
if not dep.startswith("@"):
external_cc_deps.append(_get_real_dep_label(dep))
else:
external_3rd_proto_deps.append(dep)
# 3. 创建原始proto规则
external_proto_library(...)
# 4. 创建C++ proto规则
cc_proto_library(...)
# 5. 创建Python proto规则
py_proto_library(...)
# 6. 创建输出规则
native.cc_library(...)
native.cc_binary(...)
native.cc_library(...)
# 7. 创建兼容性规则
_proto_rule(...)
该函数定义了proto编译的标准流程,包括验证、解析、创建规则和创建兼容性规则等步骤。这种模板方法模式确保了编译流程的一致性。
7.6 组合模式(Composite Pattern)
_apollo_proto_impl函数实现了组合模式,将不同类型的编译结果组合成一个统一的结果。
def _apollo_proto_impl(ctx):
ret_info = []
for t in ctx.attr.srcs:
if CcInfo in t:
ret_info.append(t[CcInfo])
elif PyInfo in t:
files = ctx.runfiles(files = _depset_to_list(t.default_runfiles.files))
ret_info.append(DefaultInfo(runfiles = files))
ret_info.append(t[InstrumentedFilesInfo])
ret_info.append(t[OutputGroupInfo])
ret_info.append(t[PyInfo])
else:
ret_info.append(t[ProtoInfo])
return ret_info
该函数将不同类型的编译结果(CcInfo、PyInfo、ProtoInfo)组合成一个统一的返回列表。这种组合模式简化了复杂结果的处理。
8. 总结
apollo_tools_proto子模块通过精心设计的构建规则和依赖管理机制,实现了高效的Protocol Buffers编译和多语言代码生成。其核心优势在于模块化的规则设计、灵活的依赖解析、统一的标签转换和完善的兼容性支持。通过工厂模式、策略模式、适配器模式等设计模式的应用,该模块实现了构建规则的解耦和可扩展性,为Apollo项目提供了稳定可靠的proto编译基础设施。模块的模板化设计和自动化依赖管理显著提升了构建效率,为自动驾驶系统的快速迭代提供了有力支持。