https://docs.bazel.build/versions/4.0.0/build-ref.html

Bazel根据workspace目录中的源文件构建软件。workspace目录的结构可以是多层嵌套的,每一个目录都是一个package,package目录下有一系列源文件和一个BUILD文件。这个BUILD文件定义了如何从源文件构建输出文件。

workspace、packages and targets

Workspace

Bazel认为一个包含WORKSPACE文件的目录为根Workspace,workspace目录下包含bazel构建所需要的源文件,以及bazel构建后创建的指向输出文件目录的symbolic link。
根workspace目录之外的其他子目录中的WORKSPACE文件会被忽略。
WORKSPACE文件可以定义external depencencies,也可以是空的。

Repositories

代码被组织在repository中。Workspace目录对应main repository的根目录,可以用”@“来表示。external repository由WORKSPACE文件通过workspace rule定义。

由于external repository也是repository,它们经常会有自己的WORKSPACE文件,但是这些WORKSPACE文件会被bazel忽略,因此transitive依赖在bazel中行不通。

Packages

package是一系列源文件,以及这些源文件构建时所需的依赖的集合。
bazal认为一个包含BUILD文件的目录是package。一个package包含package目录及其所有不是package目录的子目录。

Targets

一个package是一个容器。package内定义的元素被称为target。多数target可以被归类为两种基本类别之一:file,或者rule。此外还有一种很少见的target类别:package group

file细分为两个类型:source file、generated file。

rule规定如何从一组输入文件构建出一组输出文件。rule的输出文件一定是generated file,但输入文件可以是source file也可以是generated file。
对所有rule来说,输出文件都属于这个rule所属的package;不过,rule的输入文件可以来自其他package。

Labels

使用label来表示一个target,label的组成结构如下:
<@repository名称>//:

例如,下面的这个label:@myrepo//my/app/main:app_binary,就表示myrepo内的my/app/main package下的app_binary这个target。

relative label

在下面这些场景下可以省略label的某些部分,这种label被称为relative label:

  • 当表示当前repository内的target时,可以省略target部分,例如:
    • //my/app/main:app_binary
  • 当target名称和package名称的最后一部分完全一样时,可以省略package部分,例如:
    • //my/app,表示//my/app:app
  • 在BUILD文件中使用label表示BUILD文件所属package内的target时,repository名称、package名称、包括冒号也可以忽略,例如:
    • 位于my/app package目录下时,:app,以及app,都表示//my/app:app

当引用其他package内的label时,不能使用relative label。

相对于relative label,absolute label不能省略任何一个部分,下面的两个label里,只有显示写出repository的才是absolute label:

  • //my/app:app,这个是relative label
  • @myrepo//my/app:app,这个才是absolute label
  • @//my/app:app,这个也是absolute label,使用@表示main repository

Rules

Rule用来定义一组输入文件和输出文件,以及从输出文件构造输出文件的步骤。

所有Rule必须有一个name属性,而且该属性必须被指定为一个合法的target名称。有的场景下,name属性和rule的输出文件名称无关,不过对于某些rule而言,例如 _binary和_test,name指定了rule的输出文件名称。

每个rule都有一组属性,但并不是每个属性都是必须指定的。

Build files

BUILD文件的内容是由Starlake语言编写的,会被解析为一系列声明。

BUILD文件中变量必须定义在被使用之前。不过,rule定义之间的相对位置是无关紧要的。

为了实现代码和数据的分离,BUILD文件内禁止声明函数、使用for statements或者使用if statements。函数必须定义在.bzl文件中。

loading an extension

文件名以.bzl结尾的文件被Bazel视作extension file。使用load statement来引入extension中定义的符号:
load(“//foo/bar:file.bzl”, “some_library”)
上面的代码将指定的extension file中的符号添加到当前环境中。可以使用这种方式加载rule、函数或常量。如果要一次加载多个符号,只要追加要添加的符号名称即可。

.bzl文件中定义的以下划线_开头的符号被认为是私有符号,不可以被加载到其他环境中。

Types of build rules

大多数build rule的名称都有如下格式:
<语言前缀>_<构建类型>
例如,对于C++而言,有cc_binary、cc_library、cc_test这些rule,对于java,也有将cc替换为java的类似rule。

Dependencies

Actual and declared dependencies

在Bazel中,一般来说实际的依赖和声明的依赖都是一致的,但有的时候会发生实际的依赖和声明的依赖不一致的情况,这种情况非常危险,需要尽可能避免,下面描述这种情况的产生原因:
假如我们有三个target,分别是A、B和C,在BUILD文件中声明了A对B的依赖,以及B对C的依赖,根据依赖的声明情况来看,A对C是没有依赖的,假如有一天B做了重构,解除了对C的依赖,构建应该依然能够正常进行。
可是,如果有一天一个程序员在没有声明A对C的依赖的情况下,在A的源代码里使用了C导出的符号,这时候构建其实依然可以成功,因为A通过B,间接地引入了C。但是,如果有一天B对C的依赖被解除了,构建就会失败。失败的原因是A对C有依赖,却没有在BUILD中做声明。为了避免这种情况,程序员必须清楚自己在使用的符号究竟来自哪里,不要使用没有声明过的依赖中的符号。

Types of dependencies

大多数build rule都有三个用来指定依赖的属性:srcs、deps、data,下面会对这三个属性进行介绍,某些build rule可能会定义额外的属性,这些就需要查阅Bazel文档了。

srcs:构建所需要的源文件;

deps:构建所需要的预先编译好的文件;

data比较特殊,下面展开说说:
我们构建出来的可执行文件在运行时可能需要某些配置文件或者资源才能够正确运行,这些文件并不属于构建阶段所需要的源文件,但在运行阶段又是必须的。
bazel在一个隔离的目录中执行test,data依赖中指定的文件只有在这个隔离目录中才是可用的。data指定的这些文件会被添加到runfiles,和编译产物一同被放到隔离目录中。

Using labels to reference directories

虽然Bazel支持用label引用目录,但这是及其不推荐的,原因是bazel为了确保增量构建的正确,bazel必须在每次依赖变更后找出所有变化的依赖,如果目录被指定为依赖,bazel只会在目录发生变化时才会对目录做重新构建,而目录内文件的修改不会触发目录变化,只有目录内文件的增删才会触发。

推荐的做法是,将所有依赖文件挨个枚举出来,或者使用glob配合来引用目录,例如:
data = glob([“testdata/
“])

只有一种情况下应该使用目录作为依赖:不得不这么干时。当文件名称不符合bazel label规范时,就只能通过目录来一并引用文件了。但是,一般来说工程中文件的名称不应该不符合bazel label规范。