Bazel 教程:构建 C++ 项目

<ph type="x-smartling-placeholder"></ph> 报告问题 <ph type="x-smartling-placeholder"></ph> 查看来源 敬上 每晚 · 7.3。 · 7.2 条 · 7.1 · 7.0 · 6.5

简介

第一次使用 Bazel?你来对地方了。请按照这个“首次构建”教程 简要介绍如何使用 Bazel本教程定义了一些关键术语 将在 Bazel 的上下文中使用,并将为您详细介绍 Bazel 的基础知识 工作流。从您需要的工具开始,您将构建并运行三个 并了解它们如何及为什么变得更加复杂。

虽然 Bazel 是一个构建系统, 支持多语言构建,本教程以 C++ 项目为例 并提供了适用于大多数语言的一般准则和流程。

预计所需时长:30 分钟。

前提条件

首先安装 Bazel(如果尚未安装) 。本教程使用 Git 进行源代码控制,因此为了获得最佳效果,请安装 Git

接下来,运行 运行以下代码:

git clone https://github.com/bazelbuild/examples

本教程的示例项目位于 examples/cpp-tutorial 目录。

看看它的结构:

examples
└── cpp-tutorial
    ├──stage1
    │  ├── main
    │  │   ├── BUILD
    │  │   └── hello-world.cc
    │  └── MODULE.bazel
    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── MODULE.bazel
    └──stage3
       ├── main
       │   ├── BUILD
       │   ├── hello-world.cc
       │   ├── hello-greet.cc
       │   └── hello-greet.h
       ├── lib
       │   ├── BUILD
       │   ├── hello-time.cc
       │   └── hello-time.h
       └── MODULE.bazel

有三组文件,每组文件代表本教程中的一个阶段。 在第一阶段,您将构建位于单个 package 中的单个目标。在第二阶段,您需要 通过单个软件包同时构建二进制文件和库。在第三个区域,也是最后一个 阶段时,您将构建一个包含多个软件包的项目,并使用 多个目标。

摘要:简介

通过安装 Bazel(和 Git)并克隆本教程的代码库 已经为您使用 Bazel 完成首次构建奠定了基础。继续前往下一条 部分定义一些术语 工作区

使用入门

您需要先设置项目工作区,然后才能构建项目。工作区 是一个目录,用于保存项目的源文件和 Bazel 的构建输出。 它还包含以下重要文件:

  • MODULE.bazel 文件,用于将目录及其内容标识为 Bazel 工作区,位于项目目录的根目录 结构。您还可以在此目录中指定外部依赖项。
  • 一个或多个 BUILD 文件,用于将这些文件告诉 Bazel 如何构建项目的不同部分。此存储分区中的 包含 BUILD 文件的工作区是 package 中的可用属性。(有关软件包的详细信息 )。

在以后的项目中,要将目录指定为 Bazel 工作区, 空文件(名为 MODULE.bazel)。在此 那么每个阶段都已经有一个 MODULE.bazel 文件。

了解 BUILD 文件

BUILD 文件包含几种不同类型的 Bazel 说明。每个 BUILD 个文件至少需要一个 规则作为一组指令, 告知 Bazel 如何构建所需的输出,例如可执行二进制文件 或库。BUILD 文件中的构建规则的每个实例都称为 target 并指向特定的 一组源文件 依赖项。目标可以 也会指向其他目标

看一下 cpp-tutorial/stage1/main 目录中的 BUILD 文件:

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

在我们的示例中,hello-world 目标会实例化 Bazel 的内置 cc_binary 规则。规则 会告诉 Bazel,使用 hello-world.cc&gt;无依赖项的源文件

摘要:使用入门

现在,您已经熟悉了一些关键术语,以及这些术语在 常规项目和 Bazel在下一部分中,您将构建和测试 项目的第 1 阶段。

第 1 阶段:单个目标,单个软件包

现在该构建项目的第一部分了。作为直观的参考, 项目“第一阶段”部分的结构是:

examples
└── cpp-tutorial
    └──stage1
       ├── main
       │   ├── BUILD
       │   └── hello-world.cc
       └── MODULE.bazel

运行以下命令以移至 cpp-tutorial/stage1 目录:

cd cpp-tutorial/stage1

然后运行:

bazel build //main:hello-world

在目标标签中,//main: 部分是 BUILD 文件的位置 相对于工作区根目录,hello-world 是该路径中的目标名称 BUILD 文件。

Bazel 会生成类似于以下内容的内容:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

您刚刚构建了您的第一个 Bazel 目标。Bazel 会将构建输出 bazel-bin 目录。

现在,测试新构建的二进制文件,即:

bazel-bin/main/hello-world

这将显示输出的“Hello world”消息。

以下是第一阶段的依赖关系图:

hello-world 的依赖关系图显示具有单个来源的单个目标
文件。

摘要:第 1 阶段

现在,您已经完成了第一个构建,对 都是结构化的在下一阶段,您将添加 另一个目标。

第 2 阶段:多个构建目标

虽然单个目标对小型项目来说已经足够,但您可能希望进行拆分 多个目标和软件包。这样,您便可快速 增量构建(也就是说,Bazel 仅重新构建已更改的内容) 通过同时构建项目的多个部分来更好地构建。此阶段的 下一个教程会添加一个目标,下一个教程中会添加一个软件包。

以下是您在第 2 阶段使用的目录:

    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── MODULE.bazel

看一下 cpp-tutorial/stage2/main 目录中的 BUILD 文件:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

借助此 BUILD 文件,Bazel 首先构建了 hello-greet 库(使用 Bazel 的内置 cc_library 规则),则 hello-world 二进制文件。hello-world 目标中的 deps 属性用于告知 构建 hello-world 所需的 hello-greet 库所需的 Bazel 二进制文件

在构建这个新版本的项目之前,您需要 目录中,运行以下命令切换到 cpp-tutorial/stage2 目录:

cd ../stage2

现在,您可以使用下面熟悉的命令构建新的二进制文件:

bazel build //main:hello-world

Bazel 再次生成了如下内容:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.399s, Critical Path: 0.30s

现在,您可以测试新构建的二进制文件,该二进制文件会返回另一个“Hello world”:

bazel-bin/main/hello-world

如果您现在修改 hello-greet.cc 并重新构建项目,则只需使用 Bazel 重新编译该文件。

通过查看依赖关系图,您可以看到 hello-world 依赖于 名为 hello-greet 的额外输入:

“hello-world”的依赖关系图显示以下项目后的依赖关系变化:
修改文件。

摘要:第 2 阶段

现在,您已经构建了具有两个目标的项目。hello-world 目标 build 一个源文件并依赖于另一个目标 (//main:hello-greet),该目标 构建两个额外的源文件。在下一部分中,您可以更进一步 然后添加另一个软件包

第 3 阶段:多个软件包

接下来的这一阶段会添加另一层复杂功能,并使用 多个软件包先看一下 v3 组件的结构和内容, cpp-tutorial/stage3 目录:

└──stage3
   ├── main
   │   ├── BUILD
   │   ├── hello-world.cc
   │   ├── hello-greet.cc
   │   └── hello-greet.h
   ├── lib
   │   ├── BUILD
   │   ├── hello-time.cc
   │   └── hello-time.h
   └── MODULE.bazel

您可以看到,现在有两个子目录,每个子目录都包含一个 BUILD 文件。因此,对于 Bazel,现在工作区包含两个软件包:libmain

查看 lib/BUILD 文件:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

main/BUILD 文件中:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

主软件包中的 hello-world 目标依赖于 hello-time 目标 在 lib 软件包中(因此是目标标签 //lib:hello-time)- Bazel 知道 通过 deps 属性实现这一目的。您可以看到 图表:

“hello-world”的依赖关系图显示主软件包中的目标
取决于“lib”中的目标
软件包。

为确保构建成功,请在 lib/BUILD 中创建 //lib:hello-time 目标 使用 visibility 属性对 main/BUILD 中的目标明确可见。 这是因为,在默认情况下,只有同一个 BUILD 文件。Bazel 使用目标可见性来防止库等问题 包含泄露到公共 API 的实现详情。

现在,构建项目的最终版本。切换到cpp-tutorial/stage3 目录中的内容:

cd  ../stage3

再次运行以下命令:

bazel build //main:hello-world

Bazel 会生成类似于以下内容的内容:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.167s, Critical Path: 0.00s

现在,测试本教程的最后一个二进制文件以获取最终的 Hello world 消息:

bazel-bin/main/hello-world

摘要:第 3 阶段

现在,您已将项目构建为两个软件包,其中包含三个目标,并且了解了 它们之间的依赖关系,让您大胆创新,打造美好未来 项目。在下一节中,您将了解如何 Bazel 的历程

后续步骤

现在,您已经使用 Bazel 完成了第一个基本构建,但这只是 start 的值。下面提供了更多资源,可帮助您继续学习 Bazel:

祝您构建顺利!