使用Bazel构建C++项目

在本教程中,您将学习使用Bazel构建C++应用程序的基础知识。您将设置工作区并构建一个简单的C ++项目,该项目说明了Bazel的关键概念,例如targets和BUILD文件。完成本教程后,请查看Common C ++ Build Use Cases,以获取有关更高级概念(如编写和运行C++测试)的信息。

预计完成时间:30分钟。

1 您将学到什么

在本教程中,您将学习如何:

  • 建立target

  • 可视化项目的依赖关系

  • 将项目分为多个target和packages

  • 控制跨packages的target的可见性

  • 通过标签参考targets

2 在你开始之前

要准备本教程,请先安装Bazel(如果尚未安装)。然后,从Bazel的GitHub存储库中下载示例项目:

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

本教程的示例项目位于examples/cpp-tutorial目录中,其结构如下:

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

如您所见,共有三组文件,每组文件代表本教程中的一个阶段。在第一阶段,您将构建一个驻留在单个package中的单个target。在第二阶段,您将把项目分成多个targets,但将其保存在一个package中。在第三阶段(也是最后一个阶段)中,您将把项目分成多个包,并使用多个目标进行构建。

3 使用Bazel构建

3.1 设置workspace

在构建项目之前,需要设置其workspace(工作空间)。工作空间是一个目录,其中包含项目的源文件和Bazel的build输出。它还包含Bazel认为特殊的文件:

  • WORKSPACE文件,该文件将目录及其内容标识为Bazel工作区,并位于项目目录结构的根目录中

  • 一个或多个BUILD文件,这些文件告诉Bazel如何构建项目的不同部分。(工作空间中包含BUILD文件的目录是一个package。您将在本教程的后面部分中了解有关package的信息。)

要将目录指定为Bazel工作空间,请在该目录中创建一个名为WORKSPACE的空文件。

Bazel构建项目时,所有输入和依赖项必须位于同一工作空间中。除非链接,否则位于不同工作空间中的文件彼此独立,这超出了本教程的范围。

3.2 了解BUILD文件

BUILD文件包含针对Bazel的几种不同类型的指令。最重要的类型是build rule(构建规则),该规则告诉Bazel如何构建所需的输出,例如可执行二进制文件或库。BUILD文件中构建规则的每个实例都称为target,并指向一组特定的源文件和依赖项。一个target也可以指向其他targets。

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

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

在我们的示例中,hello-world 这个target实例化了Bazel内置的cc_binary规则。该规则告诉Bazel从hello-world.cc源文件构建一个独立的可执行二进制文件,而没有依赖项。

target中的属性明确声明其依赖项和选项。虽然name属性是强制性的,但许多是可选的。例如,在hello-world这个target中,name是不言自明的,并且srcs指定Bazel从其构建target的源文件。

3.3 构建项目

让我们来构建示例项目。切换到cpp-tutorial/stage1目录,然后运行以下命令:

bazel build //main:hello-world

注意target标签,//main:部分是BUILD文件相对于工作空间根目录的位置,而hello-world是我们在BUILD文件中命名该target的名称。(您将在本教程的最后详细了解target标签。)

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 target! Bazel将构建输出放置在工作空间根目录的bazel-bin目录中。浏览其内容,以了解Bazel的输出结构。

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

bazel-bin/main/hello-world

3.4 查看依赖关系图

成功的构建将其所有依赖项明确显示在BUILD文件中。Bazel使用这些语句来创建项目的依赖关系图,从而实现准确的增量构建。

让我们可视化示例项目的依赖关系。首先,生成依赖关系图的文本表示(在工作空间根目录运行命令):

bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \
  --output graph

上面的命令告诉Bazel查找target //main:hello-world的所有依赖项(不包括host和implicit依赖项),并将输出格式化为图形。

然后,将文本粘贴到GraphViz中。

在Ubuntu上,您可以通过安装GraphViz和xdot Dot Viewer在本地查看图形:

sudo apt update && sudo apt install graphviz xdot

然后,您可以通过将上面的文本输出直接传递到xdot来生成和查看图形:

xdot <(bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \
  --output graph)

如您所见,示例项目的第一阶段有一个目标,该目标构建一个没有附加依赖关系的源文件:
在这里插入图片描述
现在,您已经设置了工作区,构建了项目并检查了其依赖性,让我们增加一些复杂性。

4 优化Bazel构建

虽然单个目标足以满足小型项目的需求,但您可能希望将较大的项目拆分为多个目标和程序包,以实现快速增量构建(即,仅重建更改的内容)并通过一次构建项目的多个部分来加快构建速度。

4.1 指定多个构建target

让我们将示例项目构建分为两个target。看一下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",
    ],
)

Bazel使用此BUILD文件首先构建hello-greet库(使用Bazel的内置cc_library规则),然后构建hello-world二进制文件。hello-world这个target中的deps属性告诉Bazel,构建hello-world二进制文件需要hello-greet库。

让我们构建这个项目的新版本。切换到cpp-tutorial/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

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

bazel-bin/main/hello-world

如果现在修改hello-greet.cc并重建项目,Bazel将仅重新编译该文件。

查看依赖关系图,您可以看到hello-world与以前一样依赖于相同的输入,但是构建的结构不同:
在这里插入图片描述

现在,您已经建立了具有两个target的项目。hello-world target将构建一个源文件,并依赖于另一个target(//main:hello-greet),后者将构建另外两个源文件。

4.2 使用多个package

现在,将项目分成多个package。看一下cpp-tutorial/stage3目录的内容:

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

注意,我们现在有两个子目录,每个子目录都包含一个BUILD文件。因此,对于Bazel而言,工作空间现在包含两个package,分别未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",
    ],
)

如您所见,main package中的hello-world target依赖于lib程序包中的hello-time target(target标签//lib:hello-time),Bazel通过deps属性知道这一点。看一下依赖图:
在这里插入图片描述
注意,为使构建成功,我们使用visibility属性使lib/BUILD中的//lib:hello-time target对main/BUILD中的target明确可见。

这是因为默认情况下,target仅对同一BUILD文件中的其他target可见。 (Bazel使用target可见性来防止诸如包含实现详细信息的库之类的问题泄漏到公共API中。)

让我们构建项目的最终版本。切换到cpp-tutorial/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

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

bazel-bin/main/hello-world

现在,您已经将项目构建为带有三个target的两个package,并了解了它们之间的依赖性。

5 使用labels引用target

BUILD文件中和命令行中,Bazel使用labels来引用target,例如://main:hello-world//lib:hello-time。它们的语法是:

//path/to/package:target-name

如果target是规则target,则path/to/package是包含BUILD文件的目录的路径,而target-name是您在BUILD文件中命名target的名称(name属性)。如果target是文件target,则path/to/package是package根目录的路径,而target-name是target文件的名称,包括其完整路径。

当在存储库根目录引用target时,package路径为空,只需使用//:target-name。当在同一BUILD文件中引用目标时,甚至可以跳过//工作空间根标识符,而只使用:target-name

6 进一步阅读

恭喜你!您现在知道了使用Bazel构建C ++项目的基础知识。接下来,阅读最常见的C++构建用例。然后,检查以下内容:

构建愉快!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值