16_apollo_tools_proto子模块软件架构分析

8 阅读14分钟

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编译基础设施。模块的模板化设计和自动化依赖管理显著提升了构建效率,为自动驾驶系统的快速迭代提供了有力支持。