Bazel简介:构建C ++项目

Bazel简介:构建C ++项目

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

预计完成时间:30分钟。

您将学到什么

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

  • 建立目标
  • 可视化项目的依赖关系
  • 将项目分为多个目标和程序包
  • 控制整个程序包的目标可见性
  • 通过标签参考目标

内容

在你开始之前

要开始本教程,请先安装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

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

用Bazel构建

设置工作区

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

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

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

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

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

了解BUILD文件

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

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

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

在我们的示例中,hello-world目标实例化了Bazel内置的 cc_binaryrule。该规则告诉Bazel从hello-world.cc源文件构建一个自包含的可执行二进制文件,而没有任何依赖关系。

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

建立项目

让我们构建您的示例项目。切换到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的输出结构。

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

bazel-bin/main/hello-world

查看依赖关系图

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

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

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

上面的命令告诉Bazel查找目标的所有依赖关系 //main:hello-world(不包括主机和隐式依赖关系),并将输出格式化为图形。

然后,将文本粘贴到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)

如您所见,示例项目的第一阶段有一个目标,该目标构建一个没有附加依赖项的源文件:

“ hello-world”的依赖图

既然您已经设置了工作区,构建了项目并检查了其依赖性,那么让我们增加一些复杂性。

优化您的Bazel构建

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

指定多个构建目标

让我们将示例项目构建分为两个目标。看一下 目录BUILD中的cpp-tutorial/stage2/main文件:

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",
    ],
)

BUILDBazel 使用此文件首先构建hello-greet库(使用Bazel的内置cc_library规则),然后构建hello-world二进制文件。目标中的deps属性hello-world告诉Bazel,该hello-greet库是构建hello-world 二进制文件所必需的。

让我们构建这个项目的新版本。切换到 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依赖与以前相同的输入,但是构建的结构不同:

“ hello-world”的依赖图

您现在已经用两个目标构建了该项目。的hello-world目标建立一个源文件,并依赖于目标(//main:hello-greet),它建立两个附加的源文件。

使用多个包

现在让我们将项目分成多个包。看一下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,工作空间现在包含两个包,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在目标main包依赖于 hello-time目标lib包(因此目标标签 //lib:hello-time) -Bazel通过知道这个deps属性。看一下依赖图:

“ hello-world”的依赖图

注意,为使构建成功,我们使用 属性使//lib:hello-time目标对于目标 lib/BUILD明确可见。这是因为默认情况下,目标仅对同一文件中的其他目标可见。(Bazel使用目标可见性来防止诸如包含实现详细信息的库之类的问题泄漏到公共API中。)main/BUILD``visibility``BUILD

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

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

使用标签参考目标

BUILD文件中和命令行中,Bazel使用标签来引用目标(例如//main:hello-world或)//lib:hello-time。它们的语法是:

//path/to/package:target-name

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

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

进一步阅读

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

构建愉快!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容