Bazel的概念和技术
这个文章应该是要长期不定时更新的,因为涉及到的很多概念的理解,技术的理解,这些内容仅通过初次接触Bazel是远远不到位的,甚至不能保证下面缩写内容的正确性。
从整体上来看Bazel的实际对项目的控制和管理是长这个模样的:
1、Bazel对于源码的组织在一个文件夹之下称之为workspace(这个文件夹下面包含一个WORKSPACE文件)
2、源码文件被一系列的分层结构的package所组织
3、每一个package就是一个包含一系列相关的源码文件以及一个BUILD文件
4、BUILD文件指示了如何从源码中输出build结果
开始一些干货:
从整体上来看Bazel的实际对项目的控制和管理是长这个模样的:
1、Bazel对于源码的组织在一个文件夹之下称之为workspace(这个文件夹下面包含一个WORKSPACE文件)
2、源码文件被一系列的分层结构的package所组织
3、每一个package就是一个包含一系列相关的源码文件以及一个BUILD文件
4、BUILD文件指示了如何从源码中输出build结果
开始一些干货:
- Workspace
其实workspace就是一个文件夹。这个文件夹下面包含一个
WORKSPACE文件,以及一系列的源码文件,这些源码被package组织起来,也就是在各个package中存在的BUILD文件。
WORKSPACE文件,以及一系列的源码文件,这些源码被package组织起来,也就是在各个package中存在的BUILD文件。
而且随着在workspace中进行bazel build这样的操作(可以不在workspace root目录下build),会在workspace的root目录下面产生诸如:bazel-bin bazel-genfiles bazel-java-tutorial bazel-out bazel-testlogs这样的输出文件夹(软连接)
当然简单的项目中WORKSPACE文件可以是空文件,但是对于正常的工程来说,这里面要写一些整个工程的外部依赖及其书写rules:Workspace Rules,这两块内容会在下一篇博文中介绍。
- Package
在workspace中,代码是被一个个的package所管理,每个带BUILD文件的文件夹就是一个package,这里面包括了自己的源码文件以及规定了内外依赖关系。
package将自己所在的目录下面所有包括子文件夹下面的文件都视为属于自己package的。除非子文件夹下面有存在BUILD文件,那么这个子文件夹就是一个新的package。
- Target
package是一个容器,那么这个容器里面所包含的元素就是target。大部分的target都属于这两个最主要的类型:files和rules。还有一种target叫package groups。
对于file类型的target,主要指的就是从源码文件中build生成出来的,生成的过程是由源码根据指定的rule生成。
对于rule类型的target,rule的意思就是定义了一个关于一系列输入以及一系列输出的关系。其中输入可以是源码,是产生文件,还可以是别的rule。当然这个rule产生的输出还是属于rule所在的package,不过接受别的package中的rule作为输入是很常见的。
package group类型的target是一个packages的集合,目的是限制特点rule的访问权限。
- Label
label就是target的名字。target的名字也就是label是由两个部分组成,因为每个target都属于某个package,所以label的前半部分就是package的名字,后半部分是target的名字,中间用:连接。
label的命名有几种特殊情况:
1、当target的名字和package的名字最后一部分相同时:
//may/app 和 //my/app:app就是等价的
2、在同一个BUILD文件中时,package名部分可以省略:
//my/app:app 和 //my/app 和 :app 和 app 都是等价的。
为了避免与shell命令中的特殊字符所表示的含义冲突,在label的命名中严禁出现*>&|等字符。
- Rule
rule定义了输入与输出的关系。简单来说,rule就是一个类定义,实例化的rule就是一个个的target。
- BUILD文件
在每个package的目录下存在,内容基本上由一系列build rules的声明组成
在BUILD文件中如果要使用一个变量,那么必须在使用前声明出来。不过对于大多数只存放build rules的BUILD文件来说,各个target的先后顺序是可以自由的。
而且鼓励在BUILD文件中写注释,Python格式的#注释以及三引号"""的多行注释都是支持的。
- build rule类型
1、*_binary
表示生成可执行结果。输出目录对应关系为
/my:program
[->] (e.g.) $(BINDIR)/my/program
.
2、*_test
是一个特殊的*_binary rule,用于自动测试,可以自动运行
3、*_library
是根据特定的语言编译特定的模块
是一个特殊的*_binary rule,用于自动测试,可以自动运行
3、*_library
是根据特定的语言编译特定的模块
- 依赖
依赖关系可以组成一个DAG(有向无环图),称之为依赖图。
实际上有两种依赖图:1、actual dependencies;2、declared dependencies
所谓的目标X实际依赖于目标Y就是指build目标X时,目标Y必须已经存在或者build生成或者更新至最新了。(感觉是从源码中推断出来的)
所谓的目标X声明依赖于目标Y就是指X的package有一个依赖边(在DAG中)到Y。(感觉是在BUILD里面rule中明确写明的)
在BUILD文件中每一个rule都必须显式的声明出他所有的实际依赖。不过不应该在其中指明任何非直接依赖。
实际上有两种依赖图:1、actual dependencies;2、declared dependencies
所谓的目标X实际依赖于目标Y就是指build目标X时,目标Y必须已经存在或者build生成或者更新至最新了。(感觉是从源码中推断出来的)
所谓的目标X声明依赖于目标Y就是指X的package有一个依赖边(在DAG中)到Y。(感觉是在BUILD里面rule中明确写明的)
在BUILD文件中每一个rule都必须显式的声明出他所有的实际依赖。不过不应该在其中指明任何非直接依赖。
举个例子:
1、在package a中target a依赖于package b中的目标b,package b中的target b依赖于package c的target c:
1、在package a中target a依赖于package b中的目标b,package b中的target b依赖于package c的target c:
Declared dependency graph: a --> b --> c Actual dependency graph: a --> b --> c
2、接上面,在target a的源码中加入了对c的import和调用,但是没有在a的BUILD中声明出来。
Declared dependency graph: a --> b --> c Actual dependency graph: a --> b -->_c \_________/|
此时build是没有问题的,虽然a没有给出对c的依赖,但是由于所依赖的b已经给出了,所以此时也是能build通过的,但是可能会告警吧?masks a problem:
a
has an actual but undeclared dependency on c
3、接第二步,此时b不在需要对c的依赖了,所以在b的BUILD中删除了对c的依赖声明,此时:
Declared dependency graph: a --> b c Actual dependency graph: a --> b _c \_________/|
毫无疑问,build会妥妥的失败。
- 依赖的类型
1、srcs
rule所需的源码
2、deps
其他模块的依赖关系,在头文件符号链接中所使用的。
3、data
build中不会使用,但是在执行中所需要的数据文件。比如单元测试之类
4、compiler、resources等
- 使用label来去关联目录
在data label关联目录时,不推荐在label中直接写目录,而是建议使用glob()(使用**可以让函数强制递归索引子目录)
有一种不幸的例外是如果文件目录中包含的文件名不满足bazel严格的label syntax,那么就不能使用glob()函数了,此时就不得不使用目录了。
评论
发表评论