怎样理解bazel构建的项目

理解 Bazel 构建的项目,可以从以下几个核心概念和特点入手:

  1. 它是一个构建和测试工具 (Build and Test Tool):

    • 核心目标: 自动化地将源代码转换成可执行文件、库、部署包等产物,并运行测试。类似于 Make、Maven、Gradle、npm scripts 等,但有其独特的设计哲学。

    • 来源: 源自 Google 内部使用的 Blaze 工具,设计初衷是为了解决 Google 内部超大规模、多语言、庞大代码库(Monorepo)的构建挑战。

  2. 核心设计原则:

    • 速度 (Speed): 通过精细的依赖分析、高级缓存(本地和远程共享)、并行执行和增量构建,最大化构建和测试的速度。只重新构建或测试真正发生变化的部分及其依赖项。

    • 正确性/可复现性 (Correctness/Reproducibility):

      • 密封性 (Hermeticity): 构建过程在受控、隔离的环境中进行,尽可能减少外部环境(如本地安装的库、环境变量)的影响。理想情况下,相同的源代码和构建指令在任何机器上都应该产生完全相同的输出。

      • 确定性 (Determinism): 构建规则被设计成确定性的,即给定相同的输入,总是产生相同的输出。

    • 可伸缩性 (Scalability): 能够高效处理包含数十万个文件、多种编程语言、跨多个团队协作的大型代码库(Monorepo)。支持分布式构建和缓存。

    • 多语言支持 (Multi-language): 不局限于特定语言,通过可扩展的规则系统支持 C++, Java, Python, Go, JavaScript/TypeScript, Rust 等多种语言。

  3. 关键概念和文件结构:

    • 工作区 (Workspace):

      • 项目的根目录,包含一个 WORKSPACE 或 WORKSPACE.bazel 文件。

      • WORKSPACE 文件定义项目的名称,更重要的是 管理外部依赖(如第三方库、其他 Bazel 项目)。它告诉 Bazel 去哪里下载和设置外部代码或工具。

    • 包 (Package):

      • 代码库中的一个目录,包含一个 BUILD 或 BUILD.bazel 文件。

      • BUILD 文件定义了该目录及其子目录(如果子目录没有自己的 BUILD 文件)中源代码的 构建方式

    • BUILD 文件:

      • 这是核心配置文件,使用一种类似 Python 的语言 Starlark 编写。

      • 它包含一系列 规则 (Rules) 的调用。

    • 规则 (Rules):

      • 如 cc_library (C++ 库), java_binary (Java 可执行文件), py_test (Python 测试), go_binary 等。

      • 每个规则描述了如何从一组 输入文件 (srcs),通过特定的 动作 (Actions)(如编译、链接),生成一组 输出文件 (outs),并声明其 依赖项 (deps)

    • 目标 (Target):

      • BUILD 文件中定义的 具体构建单元,由规则调用产生。

      • 每个目标有一个唯一的 标签 (Label),格式通常是 //path/to/package:target_name。

      • 例如 //src/server:server_binary 指的是 src/server 目录下 BUILD 文件中定义的名为 server_binary 的目标。

      • 目标可以是库、可执行文件、测试、代码生成器或其他构建产物。

    • 依赖关系图 (Dependency Graph):

      • Bazel 通过分析所有 BUILD 文件中的 deps 声明,构建一个有向无环图 (DAG),精确表示目标之间的依赖关系。这是实现增量构建和并行执行的基础。

  4. 工作流程(简化版):

    • 加载阶段 (Loading Phase): Bazel 读取 WORKSPACE 文件和所有相关的 BUILD 文件,解析 Starlark 代码,创建包和目标。

    • 分析阶段 (Analysis Phase): Bazel 根据目标及其依赖关系,构建完整的依赖图,确定每个目标需要执行哪些具体的构建动作(编译、链接等),并检查依赖是否满足。

    • 执行阶段 (Execution Phase): Bazel 按照依赖图的顺序执行构建动作。

      • 缓存检查: 在执行每个动作前,检查其输入(源文件、依赖项、构建命令)的哈希值是否在缓存中存在对应的输出。如果命中缓存,则直接复用结果,跳过执行。

      • 执行动作: 如果未命中缓存,则在沙箱 (Sandbox) 环境中执行动作(编译、链接等)。

      • 并行执行: 没有依赖关系的动作可以并行执行。

      • 分布式执行: 可以配置将动作分发到远程构建服务器集群执行。

  5. 如何与 Bazel 项目交互:

    • 你通常会在项目根目录(包含 WORKSPACE 文件的目录)运行 bazel 命令。

    • 常用命令:

      • bazel build //path/to:target:构建指定的目标。

      • bazel build //...:构建项目中的所有目标。

      • bazel test //path/to:target:运行指定的测试目标。

      • bazel test //...:运行项目中的所有测试目标。

      • bazel run //path/to:executable_target:构建并运行一个可执行目标。

      • bazel query "some_query_expression":查询构建图,例如 bazel query 'deps(//path/to:target)' 查看目标的依赖。

总结一下,理解 Bazel 项目的关键在于:

  • 认识到它的目标是构建速度、正确性和大规模代码管理。

  • 理解 WORKSPACE (管理外部依赖) 和 BUILD 文件 (定义内部构建单元) 的作用。

  • 知道构建的基本单位是“目标 (Target)”,通过“规则 (Rule)”定义,并使用“标签 (Label)”引用。

  • 明白其核心优势来自于精确的依赖分析、强大的缓存机制和密封的构建环境。

当你接触一个 Bazel 项目时,首先查看 WORKSPACE 文件了解外部依赖,然后深入到各个子目录的 BUILD 文件,理解代码是如何被组织成库、二进制文件和测试的。使用 bazel build, bazel test, bazel run 等命令进行交互,并利用 bazel query 来探索复杂的依赖关系。虽然学习曲线可能比一些简单的构建工具陡峭,但它为大型、复杂、多语言项目提供了强大的支持。

你可能感兴趣的:(信息可视化)