Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Bazel学习笔记

7
Jan
2019

Bazel学习笔记

By Alex
/ in C,C++,Java
5 Comments
简介

Bazel是Google开源的,类似于Make、Maven或Gradle的构建和测试工具。它使用可读性强的、高层次的构建语言,支持多种编程语言,以及为多种平台进行交叉编译。

Bazel的优势:

  1. 高层次的构建语言:更加简单,Bazel抽象出库、二进制、脚本、数据集等概念,不需要编写调用编译器或链接器的脚本
  2. 快而可靠:能够缓存所有已经完成的工作步骤,并且跟踪文件内容、构建命令的变动情况,避免重复构建。此外Bazel还支持高度并行构建、增量构建
  3. 多平台支持:可以在Linux/macOS/Windows上运行,可以构建在桌面/服务器/移动设备上运行的应用程序
  4. 可扩容性:处理10万以上源码文件时仍然能保持速度
  5. 可扩展性:支持Android、C/C++、Java、Objective-C、Protocol Buffer、Python…还支持扩展以支持其它语言
如何工作

当运行构建或者测试时,Bazel会:

  1. 加载和目标相关的BUILD文件
  2. 分析输入及其依赖,应用指定的构建规则,产生一个Action图。这个图表示需要构建的目标、目标之间的关系,以及为了构建目标需要执行的动作。Bazel依据此图来跟踪文件变动,并确定哪些目标需要重新构建
  3. 针对输入执行构建动作,直到最终的构建输出产生出来
如何使用

当你需要构建或者测试一个项目时,通常执行以下步骤:

  1. 下载并安装Bazel
  2. 创建一个工作空间。Bazel从此工作空间寻找构建输入和BUILD文件,同时也将构建输出存放在(指向)工作空间(的符号链接中)
  3. 编写BUILD文件,以及可选的WORKSPACE文件,告知Bazel需要构建什么,如何构建。此文件基于Starlark这种DSL
  4. 从命令行调用Bazel命令,构建、测试或者运行项目
概念和术语
Workspace

工作空间是一个目录,它包含:

  1. 构建目标所需要的源码文件,以及相应的BUILD文件
  2. 指向构建结果的符号链接
  3. WORKSPACE文件,可以为空,可以包含对外部依赖的引用
Package

包是工作空间中主要的代码组织单元,其中包含一系列相关的文件(主要是代码)以及描述这些文件之间关系的BUILD文件

包是工作空间的子目录,它的根目录必须包含文件BUILD.bazel或BUILD。除了那些具有BUILD文件的子目录——子包——以外,其它子目录属于包的一部分

Target

包是一个容器,它的元素定义在BUILD文件中,包括:

  1. 规则(Rule),指定输入集和输出集之间的关系,声明从输入产生输出的必要步骤。一个规则的输出可以是另外一个规则的输入
  2. 文件(File),可以分为两类:
    1. 源文件
    2. 自动生成的文件(Derived files),由构建工具依据规则生成
  3. 包组:一组包,包组用于限制特定规则的可见性。包组由函数package_group定义,参数是包的列表和包组名称。你可以在规则的visibility属性中引用包组,声明那些包组可以引用当前包中的规则

任何包生成的文件都属于当前包,不能为其它包生成文件。但是可以从其它包中读取输入

Label

引用一个目标时需要使用“标签”。标签的规范化表示: @project//my/app/main:app_binary, 冒号前面是所属的包名,后面是目标名。如果不指定目标名,则默认以包路径最后一段作为目标名,例如:

Shell
1
2
//my/app
//my/app:app

这两者是等价的。在BUILD文件中,引用当前包中目标时,包名部分可以省略,因此下面四种写法都可以等价:

Shell
1
2
3
4
5
# 当前包为my/app
//my/app:app
//my/app
:app
app

在BUILD文件中,引用当前包中定义的规则时,冒号不能省略。引用当前包中文件时,冒号可以省略。 例如: generate.cc。

但是,从其它包引用时、从命令行引用时,都必须使用完整的标签: //my/app:generate.cc

@project这一部分通常不需要使用,引用外部存储库中的目标时,project填写外部存储库的名字。

Rule

规则指定输入和输出之间的关系,并且说明产生输出的步骤。

规则有很多类型。每个规则都具有一个名称属性,此名称亦即目标名称。对于某些规则,此名称就是产生的输出的文件名。

在BUILD中声明规则的语法时:

Python
1
2
3
4
规则类型(
    name = "...",
    其它属性 = ...
)
BUILD文件

BUILD文件定义了包的所有元数据。其中的语句被从上而下的逐条解释,某些语句的顺序很重要, 例如变量必须先定义后使用,但是规则声明的顺序无所谓。

BUILD文件仅能包含ASCII字符,且不得声明函数、使用for/if语句,你可以在Bazel扩展——扩展名为.bzl的文件中声明函数、控制结构。并在BUILD文件中用load语句加载Bazel扩展:

Python
1
load("//foo/bar:file.bzl", "some_library")

上面的语句加载foo/bar/file.bzl并添加其中定义的符号some_libraray到当前环境中,load语句可以用来加载规则、函数、常量(字符串、列表等)。

load语句必须出现在顶级作用域,不能出现在函数中。第一个参数说明扩展的位置,你可以为导入的符号设置别名。

规则的类型,一般以编程语言为前缀,例如cc,java,后缀通常有:

  1. *_binary 用于构建目标语言的可执行文件
  2. *_test 用于自动化测试,其目标是可执行文件,如果测试通过应该退出0
  3. *_library 用于构建目标语言的库 
Dependency

目标A依赖B,就意味着A在构建或执行期间需要B。所有目标的依赖关系构成非环有向图(DAG)称为依赖图。

距离为1的依赖称为直接依赖,大于1的依赖则称为传递性依赖。

依赖分为以下几种:

  1. srcs依赖:直接被当前规则消费的文件
  2. deps依赖:独立编译的模块,为当前规则提供头文件、符号、库、数据
  3. data依赖:不属于源码,不影响目标如何构建,但是目标在运行时可能依赖之
安装
Bazel
Ubuntu

参考下面的步骤安装Bazel:

Shell
1
2
3
4
echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
 
sudo apt-get update && sudo apt-get install bazel

可以用如下命令升级到最新版本的Bazel:

Shell
1
sudo apt-get install --only-upgrade bazel
Bazelisk

这是基于Go语言编写的Bazel启动器,它会为你的工作区下载最适合的Bazel,并且透明的将命令转发给该Bazel。

由于Bazellisk提供了和Bazel一样的接口,因此通常直接将其命名为bazel:

Shell
1
2
sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/v0.0.8/bazelisk-linux-amd64
sudo chmod +x /usr/local/bin/bazel 
入门
构建C++项目
示例项目

执行下面的命令下载示例项目:

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

你可以看到stage1、stage2、stage3这几个WORKSPACE:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

本节后续内容会依次使用到这三个WORKSPACE。

通过Bazel构建

第一步是创建工作空间。工作空间中包含以下特殊文件:

  1. WORKSPACE,此文件位于根目录中,将当前目录定义为Bazel工作空间
  2. BUILD,告诉Bazel项目的不同部分如何构建。工作空间中包含BUILD文件的目录称为包

当Bazel构建项目时,所有的输入和依赖都必须位于工作空间中。除非被链接,不同工作空间的文件相互独立没有关系。

每个BUILD文件包含若干Bazel指令,其中最重要的指令类型是构建规则(Build Rule),构建规则说明如何产生期望的输出——例如可执行文件或库。 BUILD中的每个构建规则也称为目标(Target),目标指向若干源文件和依赖,也可以指向其它目标。

下面是stage1的BUILD文件:

cpp-tutorial/stage1/main/BUILD
Python
1
2
3
4
cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

这里定义了一个名为hello-world的目标,它使用了内置的cc_binary规则。该规则告诉Bazel,从源码hello-world.cc构建一个自包含的可执行文件。

执行下面的命令可以触发构建:

Shell
1
2
3
#   //main: BUILD文件相对于工作空间的位置
#          hello-world 是BUILD文件中定义的目标
bazel build //main:hello-world

构建完成后,工作空间根目录会出现bazel-bin等目录,它们都是指向$HOME/.cache/bazel某个后代目录的符号链接。执行:

Shell
1
bazel-bin/main/hello-world

可以运行构建好的二进制文件。

查看依赖图

Bazel会根据BUILD中的声明产生一张依赖图,并根据这个依赖图实现精确的增量构建。

要查看依赖图,先安装:

Shell
1
sudo apt install graphviz xdot

然后执行:

Shell
1
bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph | xdot
指定多个目标 

大型项目通常会划分为多个包、多个目标,以实现更快的增量构建、并行构建。工作空间stage2包含单个包、两个目标:

cpp-tutorial/stage2/main/BUILD
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 首先构建hello-greet库,cc_library是内建规则
cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    # 头文件
    hdrs = ["hello-greet.h"],
)
 
# 然后构建hello-world二进制文件
cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        # 提示Bazel,需要hello-greet才能构建当前目标
        # 依赖当前包中的hello-greet目标
        ":hello-greet",
    ],
)
使用多个包

工作空间stage3更进一步的划分出新的包,提供打印时间的功能:

cpp-tutorial/stage3/lib/BUILD
Python
1
2
3
4
5
6
7
cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    # 让当前目标对于工作空间的main包可见。默认情况下目标仅仅被当前包可见
    visibility = ["//main:__pkg__"],
)

cpp-tutorial/stage3/main/BUILD
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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目标
        ":hello-greet",
        # 依赖工作空间根目录下的lib包中的hello-time目标
        "//lib:hello-time",
    ],
)
如何引用目标

在BUILD文件或者命令行中,你都使用标签(Label)来引用目标,其语法为:

Shell
1
2
3
4
5
6
//path/to/package:target-name
 
# 当引用当前包中的其它目标时,可以:
//:target-name
# 当引用当前BUILD文件中其它目标时,可以:
:target-name
目录布局
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
workspace-name>/                          # 工作空间根目录
  bazel-my-project => <...my-project>     # execRoot的符号链接,所有构建动作在此目录下执行
  bazel-out => <...bin>                   # outputPath的符号链接
  bazel-bin => <...bin>                   # 最近一次写入的二进制目录的符号链接,即$(BINDIR)
  bazel-genfiles => <...genfiles>         # 最近一次写入的genfiles目录的符号链接,即$(GENDIR)
 
 
 
/home/user/.cache/bazel/                  # outputRoot,所有工作空间的Bazel输出的根目录
  _bazel_$USER/                           # outputUserRoot,当前用户的Bazel输出的根目录
    install/
      fba9a2c87ee9589d72889caf082f1029/   # installBase,Bazel安装清单的哈希值
        _embedded_binaries/               # 第一次运行时从Bazel可执行文件的数据段解开的可执行文件或脚本
    7ffd56a6e4cb724ea575aba15733d113/     # outputBase,某个工作空间根目录的哈希值
      action_cache/                       # Action cache目录层次
      action_outs/                        # Action output目录
      command.log                         # 最近一次Bazel命令的stdout/stderr输出
      external/                           # 远程存储库被下载、链接到此目录
      server/                             # Bazel服务器将所有服务器有关的文件存放在此
        jvm.out                           # Bazel服务器的调试输出
      execroot/                           # 所有Bazel Action的工作目录
        <workspace-name>/                 # Bazel构建的工作树
          _bin/                           # 助手工具链接或者拷贝到此
          bazel-out/                      # outputPath,构建的实际输出目录
            local_linux-fastbuild/        # 每个独特的BuildConfiguration实例对应一个子目录
              bin/                        # 单个构建配置二进制输出目录,$(BINDIR)
                foo/bar/_objs/baz/        # 命名为//foo/bar:baz的cc_*规则的Object文件所在目录
                  foo/bar/baz1.o          # //foo/bar:baz1.cc对应的Object文件
                  other_package/other.o   # //other_package:other.cc对应的Object文件
                foo/bar/baz               # //foo/bar:baz这一cc_binary生成的构件
                foo/bar/baz.runfiles/     # //foo/bar:baz生成的二进制构件的runfiles目录
                  MANIFEST
                  <workspace-name>/
                    ...
              genfiles/                   # 单个构建配置生成的源文件目录,$(GENDIR)
              testlogs/                   # Bazel的内部测试运行器将日志文件存放在此
              include/                    # 按需生成的include符号链接树,符号链接bazel-include指向这里
            host/                         # 本机的BuildConfiguration
        <packages>/                       # 构建引用的包,对于此包来说,它就像一个正常的WORKSPACE 
Starlark

Bazel配置文件使用Starlark(原先叫Skylark)语言,具有短小、简单、线程安全的特点。

这种语言的语法和Python很类似,Starlark是Python2/Python3的子集。不支持的Python特性包括:

不支持的特性 说明
隐含字符串连接 需要明确使用 + 操作符
链式比较操作符 例如:1 < x < 5
class 使用struct函数
import 使用load语句
is 使用==代替
以下关键字:while、yield、try、raise、except、finally 、global、nonlocal
以下数据类型:float、set
生成器、生成器表达式
lambda以及嵌套函数
绝大多数内置函数、方法
数据类型

Starlark支持的数据类型包括:None、bool、dict、function、int、list、string,以及两种Bazel特有的类型:depset、struct。

代码示例
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 定义一个数字
number = 18
 
# 定义一个字典
people = {
    "Alice": 22,
    "Bob": 40,
    "Charlie": 55,
    "Dave": 14,
}
 
names = ", ".join(people.keys())
 
# 定义一个函数
def greet(name):
  """Return a greeting."""
  return "Hello {}!".format(name)
# 调用函数
greeting = greet(names)
 
 
def fizz_buzz(n):
  """Print Fizz Buzz numbers from 1 to n."""
  # 循环结构
  for i in range(1, n + 1):
    s = ""
    # 分支结构
    if i % 3 == 0:
      s += "Fizz"
    if i % 5 == 0:
      s += "Buzz"
    print(s if s else i)
变量

你可以在BUILD文件中声明和使用变量。使用变量可以减少重复的代码:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
COPTS = ["-DVERSION=5"]
 
cc_library(
  name = "foo",
  copts = COPTS,
  srcs = ["foo.cc"],
)
 
cc_library(
  name = "bar",
  copts = COPTS,
  srcs = ["bar.cc"],
  deps = [":foo"],
)
跨BUILD变量

如果要声明跨越多个BUILD文件共享的变量,必须把变量放入.bzl文件中,然后通过load加载bzl文件。

Make变量

所谓Make变量,是一类特殊的、可展开的字符串变量,这种变量类似Shell中变量替换那样的展开。

Bazel提供了:

  1. 预定义变量,可以在任何规则中使用
  2. 自定义变量,在规则中定义。仅仅在依赖该规则的那些规则中,可以使用这些变量
使用Make变量

仅仅那些标记为Subject to 'Make variable' substitution的规则属性,才可以使用Make变量。例如:

Python
1
2
3
# 使用Make变量FOO
my_attr = "prefix $(FOO) suffix"
# 如果变量FOO的值为bar,则实际my_attr的值为prefix bar suffix

如果要使用$字符,需要用 $$代替。 

一般预定义变量

执行命令: bazel info --show_make_env [build options]可以查看所有预定义变量的列表。

任何规则可以使用以下变量:

变量 说明
COMPILATION_MODE 编译模式:fastbuild、dbg、opt
BINDIR 目标体系结构的二进制树的根目录
GENDIR 目标体系结构的生成代码树的根目录
TARGET_CPU 目标体系结构的CPU
genrule预定义变量

下表中的变量可以在genrule规则的cmd属性中使用:

变量 说明
OUTS genrule的outs列表,如果只有一个输出文件,可以用 $@
SRCS genrule的srcs列表,如果只有一个输入文件,可以用 $<
@D

输出目录,如果:

  1. outs仅仅包含一个文件名,则展开为包含该文件的目录
  2. outs包含多个文件,则此变量展开为在genfiles树中,当前包的根目录
输入输出路径变量

下表中的变量以Bazel的Label为参数,获取包的某类输入/输出路径:

变量 说明
execpath

获取指定标签对应的规则(此规则必须仅仅输出单个文件)或文件(必须是单个文件),位于execroot下的对应路径

对于项目myproject,所有构建动作在工作空间根目录下的符号链接bazel-myproject对应的目录下执行,此目录即execroot。源码empty.source被链接到bazel-myproject/testapp/empty.source,因此其execpath为testapp/empty.source

对于目标:

testapp/BUILD
Python
1
2
3
4
cc_binary(
  name = "app",
  srcs = ["app.cc"]
)

执行构建: bazel build //testapp:app时: 

Shell
1
2
$(execpath :app)  # bazel-out/host/bin/testapp/app
$(execpath empty.source) # testapp/empty.source 
execpaths
rootpath

获取runfiles路径,二进制文件通过此路径在运行时寻找其依赖

对于上面的//testapp:app目标:

Shell
1
2
$(rootpath :app)  # testapp/app
$(rootpath empty.source)  # testapp/empty.source 
rootpaths
location

根据当前所声明的属性,等价于execpath或rootpath

对于上面的//testapp:app目标:

Shell
1
2
$(location :app) # bazel-out/host/bin/testapp/app
$(location empty.source) # testapp/empty.source 
locations
一般规则
规则列表
filegroup

为一组目标指定一个名字,你可以从其它规则中方便的引用这组目标。

Bazel鼓励使用filegroup,而不是直接引用目录。Bazel构建系统不能完全了解目录中文件的变化情况,因而文件发生变化时,可能不会进行重新构建。而使用filegroup,即使联用glob,目录中所有文件仍然能够被构建系统正确的监控。

示例:

Python
1
2
3
4
5
6
7
filegroup(
    name = "exported_testdata",
    srcs = glob([
        "testdata/*.dat",
        "testdata/logs/**/*.log",
    ]),
)

要引用filegroup,只需要使用标签:

Python
1
2
3
4
5
6
7
8
cc_library(
    name = "my_library",
    srcs = ["foo.cc"],
    data = [
        "//my_package:exported_testdata",
        "//my_package:mygroup",
    ],
)
test_suite

定义一组测试用例,给出一个有意义的名称,便于在特定时机  —— 例如迁入代码、执行压力测试 —— 时执行这些测试用例。

示例:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 匹配当前包中所有small测试
test_suite(
    name = "small_tests",
    tags = ["small"],
)
# 匹配不包含flaky标记的测试
test_suite(
    name = "non_flaky_test",
    tags = ["-flaky"],
)
# 指定测试列表
test_suite(
    name = "smoke_tests",
    tests = [
        "system_unittest",
        "public_api_unittest",
    ],
)
alias

为规则设置一个别名:

Python
1
2
3
4
5
6
7
8
9
filegroup(
    name = "data",
    srcs = ["data.txt"],
)
# 定义别名
alias(
    name = "other",
    actual = ":data",
)
config_setting

通过匹配以Bazel标记或平台约束来表达的“配置状态”,config_setting能够触发可配置的属性。

下面这个例子,匹配针对ARM平台的构建:

Python
1
2
3
4
config_setting(
    name = "arm_build",
    values = {"cpu": "arm"},
)

下面的例子,匹配任何定义了宏FOO=bar的针对X86平台的调试(-c dbg)构建:

Python
1
2
3
4
5
6
7
8
config_setting(
    name = "x86_debug_build",
    values = {
        "cpu": "x86",
        "compilation_mode": "dbg",
        "define": "FOO=bar"
    },
)

下面的库,通过select来声明可配置属性:

Python
1
2
3
4
5
6
7
8
9
10
11
12
cc_binary(
    name = "mybinary",
    srcs = ["main.cc"],
    deps = select({
        # 如果config_settings arm_build匹配正在进行的构建,则依赖arm_lib这个目标
        ":arm_build": [":arm_lib"],
        # 如果config_settings x86_debug_build匹配正在进行的构建,则依赖x86_devdbg_lib
        ":x86_debug_build": [":x86_devdbg_lib"],
        # 默认情况下,依赖generic_lib
        "//conditions:default": [":generic_lib"],
    }),
) 
genrule

一般性的规则 —— 使用用户指定的Bash命令,生成一个或多个文件。使用genrule理论上可以实现任何构建行为,例如压缩JavaScript代码。但是在执行C++、Java等构建任务时,最好使用相应的专用规则,更加简单。

不要使用genrule来运行测试,如果需要一般性的测试规则,可以考虑使用sh_test。

genrule在一个Bash shell环境下执行,当任意一个命令或管道失败(set -e -o pipefail),整个规则就失败。你不应该在genrule中访问网络。

示例:

Python
1
2
3
4
5
6
7
8
9
10
genrule(
    name = "foo",
    # 不需要输入
    srcs = [],
    # 生成一个foo.h
    outs = ["foo.h"],
    # 运行当前规则所在包下的一个Perl脚本
    cmd = "./$(location create_foo.pl) > \"$@\"",
    tools = ["create_foo.pl"],
) 
C++规则
规则列表
cc_binary

隐含输出:

  1. name.stripped,仅仅当显式要求才会构建此输出,针对生成的二进制文件运行strip -g以驱除debug符号。额外的strip选项可以通过命令行--stripopt=-foo传入
  2. name.dwp,仅仅当显式要求才会构建此输出,如果启用了 Fission ,则此文件包含用于远程调试的调试信息,否则是空文件

属性列表:

属性 说明
name 目标的名称
deps

需要链接到此二进制目标的其它库的列表,以Label引用

这些库可以是cc_library或objc_library定义的目标

srcs

C/C++源文件列表,以Label引用

这些文件是C/C++源码文件或头文件,可以是自动生成的或人工编写的。

所有cc/c/cpp文件都会被编译。如果某个声明的文件在其它规则的outs列表中,则当前规则自动依赖于那个规则

所有.h文件都不会被编译,仅仅供源码文件包含之。所有.h/.cc等文件都可以包含srcs中声明的、deps中声明的目标的hdrs中声明的头文件。也就是说,任何#include的文件要么在此属性中声明,要么在依赖的cc_library的hdrs属性中声明

如果某个规则的名称出现在srcs列表中,则当前规则自动依赖于那个规则:

  1. 如果那个规则的输出是C/C++源文件,则它们被编译进当前目标
  2. 如果那个规则的输出是库文件,则被链接到当前目标

允许的文件类型:

  1. C/C++源码,扩展名.c, .cc, .cpp, .cxx, .c++, .C
  2. C/C++头文件,扩展名.h, .hh, .hpp, .hxx, .inc
  3. 汇编代码,扩展名.S
  4. 归档文件,扩展名.a, .pic.a
  5. 共享库,扩展名.so, .so.version,version为soname版本号
  6. 对象文件,扩展名.o, .pic.o
  7. 任何能够产生上述文件的规则
copts

字符串列表

为C++编译器提供的选项,在编译目标之前,这些选项按顺序添加到COPTS。这些选项仅仅影响当前目标的编译,而不影响其依赖。选项中的任何路径都相对于当前工作空间而非当前包

也可以在bazel build时通过--copts选项传入,例如:

Shell
1
--copt "-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1" 
defines

字符串列表

为C++编译器传递宏定义,实际上会前缀以-D并添加到COPTS。与copts属性不同,这些宏定义会添加到当前目标,以及所有依赖它的目标

includes

字符串列表

为C++编译器传递的头文件包含目录,实际上会前缀以-isystem并添加到COPTS。与copts属性不同,这些头文件包含会影响当前目标,以及所有依赖它的目标

如果不清楚有何副作用,可以传递-I到copts,而不是使用当前属性

linkopts 

字符串列表

为C++链接器传递选项,在链接二进制文件之前,此属性中的每个字符串被添加到LINKOPTS

此属性列表中,任何不以$和-开头的项,都被认为是deps中声明的某个目标的Label,目标产生的文件会添加到链接选项中

linkshared

布尔,默认False。用于创建共享库

要创建共享库,指定属性linkshared = True,对于GCC来说,会添加选项-shared。生成的结果适合被Java这类应用程序加载

需要注意,这里创建的共享库绝不会被链接到依赖它的二进制文件,而只适用于被其它程序手工的加载。因此,不能代替cc_library

如果同时指定 linkopts=['-static']和linkshared=True,你会得到一个完全自包含的单元。如果同时指定linkstatic=True和linkshared=True会得到一个基本是完全自包含的单元

linkstatic

布尔,默认True

对于cc_binary和cc_test,以静态形式链接二进制文件。对于cc_binary此选项默认True,其它目标默认False

如果当前目标是binary或test,此选项提示构建工具,尽可能链接到用户库的.a版本而非.so版本。某些系统库可能仍然需要动态链接,原因是没有静态库,这导致最终的输出仍然使用动态链接,不是完全静态的

链接一个可执行文件时,实际上有三种方式:

  1. STATIC,使用完全静态链接特性。所有依赖都被静态链接,GCC命令示例:
    Shell
    1
    gcc -static foo.o libbar.a libbaz.a -lm
  2. STATIC,所有用户库静态链接(如果存在静态库版本),但是系统库(除去C/C++运行时库)动态链接,GCC命令示例:
    Shell
    1
    2
    # 此方式可以由linkstatic=True 启用
    gcc foo.o libfoo.a libbaz.a -lm 
  3. DYNAMIC,所有依赖被动态链接(如果存在动态库版本),GCC命令示例:
    Shell
    1
    2
    # 此方式可以由linkstatic=False 启用
    gcc foo.o libfoo.so libbaz.so -lm 

对于cc_library来说,linkstatic属性的含义不同。对于C++库来说:

  1. linkstatic=True表示仅仅允许静态链接,也就是不产生.so文件
  2. linkstatic=False表示允许动态链接,同时产生.a和.so文件
malloc 

指向标签,默认@bazel_tools//tools/cpp:malloc

覆盖默认的malloc依赖,默认情况下C++二进制文件链接到//tools/cpp:malloc,这是一个空库,这导致实际上链接到libc的malloc

nocopts

字符串

从C++编译命令中移除匹配的选项,此属性的值是正则式,任何匹配正则式的、已经存在的COPTS被移除 

stamp 

整数,默认-1

用于将构建信息嵌入到二进制文件中,可选值:

  1. stamp = 1,将构建信息嵌入,目标二进制仅仅在其依赖变化时重新构建
  2. stamp = 0,总是将构建信息替换为常量值,有利于构建结果缓存
  3. stamp = -1 ,由--[no]stamp标记控制是否嵌入
toolchains 

标签列表

提供构建变量(Make variables,这些变量可以被当前目标使用)的工具链的标签列表 

win_def_file

标签

传递给链接器的Windows DEF文件。在Windows上,此属性可以在链接共享库时导出符号 

cc_import

导入预编译好的C/C++库。

属性列表:

属性 说明
hdrs 此预编译库对外发布的头文件列表,依赖此库的规则(dependent rule)会直接将这些头文件包含在源码列表中
alwayslink

布尔,默认False

如果为True,则依赖此库的二进制文件会将此静态库归档中的对象文件链接进去,就算某些对象文件中的符号并没有被二进制文件使用

interface_library 用于链接共享库时使用的接口(导入)库
shared_library 共享库,Bazel保证在运行时可以访问到共享库
static_library 静态库
system_provided 提示运行时所需的共享库由操作系统提供,如果为True则应该指定interface_library,shared_library应该为空
cc_library

对于所有cc_*规则来说,构建所需的任何头文件都要在hdrs或srcs中声明。

对于cc_library规则,在hdrs声明的头文件构成库的公共接口。这些头文件可以被当前库的hdrs/srcs中的文件直接包含,也可以被依赖(deps)当前库的其它cc_*的hdrs/srcs直接包含。位于srcs中的头文件,则仅仅能被当前库的hdrs/srcs包含。

cc_binary和cc_test不会暴露接口,因此它们没有hdrs属性。

属性列表:

属性 说明
name 库的名称
deps 需要链接到(into)当前库的其它库
srcs 头文件和源码列表
hdrs 导出的头文件列表
copts/nocopts 传递给C++编译命令的参数
defines 宏定义列表
include_prefix hdrs中头文件的路径前缀
includes

字符串列表

需要添加到编译命令的包含文件列表

linkopts 链接选项
linkstatic 是否生成动态库
strip_include_prefix

字符串

需要脱去的头文件路径前缀,也就是说使用hdrs中头文件时,要把这个前缀去除,路径才匹配

textual_hdrs

标签列表

头文件列表,这些头文件是不能独立编译的。依赖此库的目标,直接以文本形式包含这些头文件到它的源码列表中,这样才能正确编译这些头文件

常见用例
通配符

可以使用Glob语法为目标添加多个文件:

Python
1
2
3
4
5
cc_library(
    name = "build-all-the-files",
    srcs = glob(["*.cc"]),
    hdrs = glob(["*.h"]),
)
传递性依赖

如果源码依赖于某个头文件,则该源码的规则需要dep头文件的库,仅仅直接依赖才需要声明:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 三明治依赖面包
cc_library(
    name = "sandwich",
    srcs = ["sandwich.cc"],
    hdrs = ["sandwich.h"],
    # 声明当前包下的目标为依赖
    deps = [":bread"],
)
# 面包依赖于面粉,三明治间接依赖面粉,因此不需要声明
cc_library(
    name = "bread",
    srcs = ["bread.cc"],
    hdrs = ["bread.h"],
    deps = [":flour"],
)
 
cc_library(
    name = "flour",
    srcs = ["flour.cc"],
    hdrs = ["flour.h"],
)
添加头文件路径 

有些时候你不愿或不能将头文件放到工作空间的include目录下,现有的库的include目录可能不符合

导入已编译库

导入一个库,用于静态链接:

Python
1
2
3
4
5
6
7
cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  static_library = "libmylib.a",
  # 如果为1则libmylib.a总会链接到依赖它的二进制文件
  alwayslink = 1,
)

导入一个库,用于共享链接(UNIX): 

Python
1
2
3
4
5
cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  shared_library = "libmylib.so",
)

通过接口库(Interface library)链接到共享库(Windows):

Python
1
2
3
4
5
6
7
8
cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  # mylib.lib是mylib.dll的导入库,此导入库会传递给链接器
  interface_library = "mylib.lib",
  # mylib.dll在运行时需要,链接时不需要
  shared_library = "mylib.dll",
)

在二进制目标中选择链接到共享库还是静态库(UNIX):

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  # 同时声明共享库和静态库
  static_library = "libmylib.a",
  shared_library = "libmylib.so",
)
 
# 此二进制目标链接到静态库
cc_binary(
  name = "first",
  srcs = ["first.cc"],
  deps = [":mylib"],
  linkstatic = 1, # default value
)
 
# 此二进制目标链接到共享库
cc_binary(
  name = "second",
  srcs = ["second.cc"],
  deps = [":mylib"],
  linkstatic = 0,
)
包含外部库

你可以在WORKSPACE中调用new_*存储库函数,来从网络中下载依赖。下面的例子下载Google Test库:

Python
1
2
3
4
5
6
7
8
9
10
11
# 下载归档文件,并让其在工作空间的存储库中可用
new_http_archive(
    name = "gtest",
    url = "https://github.com/google/googletest/archive/release-1.7.0.zip",
    sha256 = "b58cb7547a28b2c718d1e38aee18a3659c9e3ff52440297e965f5edffe34b6d0",
    # 外部库的构建规则编写在gtest.BUILD
    # 如果此归档文件已经自带了BUILD文件,则可以调用不带new_前缀的函数
    build_file = "gtest.BUILD",
    # 去除路径前缀
    strip_prefix = "googletest-release-1.7.0",
)

构建此外部库的规则如下:

gtest.BUILD
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cc_library(
    name = "main",
    srcs = glob(
        # 前缀去除,原来是googletest-release-1.7.0/src/*.cc
        ["src/*.cc"],
        # 排除此文件
        exclude = ["src/gtest-all.cc"]
    ),
    hdrs = glob([
        # 前缀去除
        "include/**/*.h",
        "src/*.h"
    ]),
    copts = [
        # 前缀去除,原来是external/gtest/googletest-release-1.7.0/include
        "-Iexternal/gtest/include"
    ],
    # 链接到pthread
    linkopts = ["-pthread"],
    visibility = ["//visibility:public"],
)
使用外部库

沿用上面的例子,下面的目标使用gtest编写测试代码:

Python
1
2
3
4
5
6
7
8
9
10
11
cc_test(
    name = "hello-test",
    srcs = ["hello-test.cc"],
    # 前缀去除
    copts = ["-Iexternal/gtest/include"],
    deps = [
        # 依赖gtest存储库的main目标
        "@gtest//:main",
        "//lib:hello-greet",
    ],
)
外部依赖

Bazel允许依赖其它项目中定义的目标,这些来自其它项目的依赖叫做“外部依赖“。当前工作空间的WORKSPACE文件声明从何处下载外部依赖的源码。

外部依赖可以有自己的1-N个BUILD文件,其中定义自己的目标。当前项目可以使用这些目标。例如下面的两个项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
/
  home/
    user/
      project1/
        WORKSPACE
        BUILD
        srcs/
          ...
      project2/
        WORKSPACE
        BUILD
        my-libs/

如果project1需要依赖定义在project2/BUILD中的目标:foo,则可以在其WORKSPACE中声明一个存储库(repository),名字为project2,位于/home/user/project2。然后,可以在BUILD中通过标签@project2//:foo引用目标foo。

除了依赖来自文件系统其它部分的目标、下载自互联网的目标以外,用户还可以编写自己的存储库规则(repository rules )以实现更复杂的行为。

WORKSPACE的语法格式和BUILD相同,但是允许使用不同的规则集。

Bazel会把外部依赖下载到 $(bazel info output_base)/external目录中,要删除掉外部依赖,执行:

Shell
1
bazel clean --expunge
外部依赖类型
Bazel项目

可以使用local_repository、git_repository或者http_archive这几个规则来引用。

引用本地Bazel项目的例子:

my_project/WORKSPACE
Python
1
2
3
4
local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)

在BUILD中,引用coworkers_project中的目标//foo:bar时,使用标签@coworkers_project//foo:bar 

非Bazel项目

可以使用new_local_repository、new_git_repository或者new_http_archive这几个规则来引用。你需要自己编写BUILD文件来构建这些项目。

引用本地非Bazel项目的例子:

Python
1
2
3
4
5
new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)

coworker.BUILD
Python
1
2
3
4
5
cc_library(
    name = "some-lib",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"],
)&nbsp;

在BUILD文件中,使用标签@coworkers_project//:some-lib引用上面的库。 

外部包

对于Maven仓库,可以使用规则maven_jar/maven_server来下载JAR包,并将其作为Java依赖。

依赖拉取

默认情况下,执行bazel Build时会按需自动拉取依赖,你也可以禁用此特性,并使用bazel fetch预先手工拉取依赖。

使用代理

Bazel可以使用HTTPS_PROXY或HTTP_PROXY定义的代理地址。

依赖缓存

Bazel会缓存外部依赖,当WORKSPACE改变时,会重新下载或更新这些依赖。

.bazelrc

Bazel命令接收大量的参数,其中一部分很少变化,这些不变的配置项可以存放在.bazelrc中。

位置

Bazel按以下顺序寻找.bazelrc文件:

  1. 除非指定--nosystem_rc,否则寻找/etc/bazel.bazelrc
  2. 除非指定--noworkspace_rc,否则寻找工作空间根目录的.bazelrc
  3. 除非指定--nohome_rc,否则寻找当前用户的$HOME/.bazelrc
语法
元素 说明
import 导入其它bazelrc文件,例如: import %workspace%/tools/bazel.rc
默认参数

可以提供以下行:

startup ... 启动参数
common... 适用于所有命令的参数
command...为某个子命令指定参数,例如buildquery、

以上三类行,都可以出现多次

--config

用于定义一组参数的组合,在调用bazel命令时指定--config=memcheck,可以引用名为memcheck的参数组。此参数组的定义示例:

Shell
1
build:memcheck --strip=never --test_timeout=3600
扩展

所谓Bazel扩展,是扩展名为.bzl的文件。你可以使用load语句加载扩展中定义的符号到BUILD中。

构建阶段

一次Bazel构建包含三个阶段:

  1. 加载阶段:加载、eval本次构建需要的所有扩展、所有BUILD文件。宏在此阶段执行,规则被实例化。BUILD文件中调用的宏/函数,在此阶段执行函数体,其结果是宏里面实例化的规则被填充到BUILD文件中
  2. 分析阶段:规则的代码——也就是它的implementation函数被执行,导致规则的Action被实例化,Action描述如何从输入产生输出
  3. 执行阶段:执行Action,产生输出,测试也在此阶段执行

Bazel会并行的读取/解析/eval BUILD文件和.bzl文件。每个文件在每次构建最多被读取一次,eval的结果被缓存并重用。每个文件在它的全部依赖被解析之后才eval。加载一个.bzl文件没有副作用,仅仅是定义值和函数

宏

宏(Macro)是一种函数,用来实例化(instantiates)规则。如果BUILD文件太过重复或复杂,可以考虑使用宏,以便减少代码。宏的函数在BUILD文件被读取时就立即执行。BUILD被读取(eval)之后,宏被替换为它生成的规则。bazel query只会列出生成的规则而非宏。

编写宏时需要注意:

  1. 所有实例化规则的公共函数,都必须具有一个无默认值的name参数
  2. 公共函数应当具有docstring
  3. 在BUILD文件中,调用宏时name参数必须是关键字参数
  4. 宏所生成的规则的name属性,必须以调用宏的name参数作为后缀
  5. 大部分情况下,可选参数应该具有默认值None
  6. 应当具有可选的visibility参数
示例

要在宏中实例化原生规则(Native rules,不需要load即可使用的那些规则),可以使用native模块:

path/generator
Python
1
2
3
4
5
6
7
8
9
10
# 该宏实例化一个genrule规则
def file_generator(name, arg, visibility=None):
  // 生成一个genrule规则
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location generator) %s > $@" % arg,
    tools = ["//test:generator"],
    visibility = visibility,
  )

使用上述宏的BUILD文件:

BUILD
Python
1
2
3
4
5
6
load("//path:generator.bzl", "file_generator")
 
file_generator(
    name = "file",
    arg = "some_arg",
)

执行下面的命令查看宏展开后的情况:

Python
1
2
3
4
5
6
7
8
# bazel query --output=build //label
 
genrule(
  name = "file",
  tools = ["//test:generator"],
  outs = ["//test:file.txt"],
  cmd = "$(location generator) some_arg > $@",
)
规则

规则(Rule)比宏更强大,能够对Bazel内部特性进行访问,并可以完全控制Bazel。

规则定义了为了产生输出,需要在输入上执行的一系列动作。例如,C++二进制文件规则以一系列.cpp文件为输入,针对输入调用g++,输出一个可执行文件。注意,从Bazel的角度来说,不但cpp文件是输入,g++、C++库也是输入。当编写自定义规则时,你需要注意,将执行Action所需的库、工具作为输入看待。

Bazel内置了一些规则,这些规则叫原生规则,例如cc_library、cc_library,对一些语言提供了基础的支持。通过编写自定义规则,你可以实现对任何语言的支持。

定义在.bzl中的规则,用起来就像原生规则一样 —— 规则的目标具有标签、可以出现在bazel query。

规则在分析阶段的行为,由它的implementation函数决定。此函数不得调用任何外部工具,它只是注册在执行阶段需要的Action。

自定义规则

在.bzl文件中,你可以调用rule创建自定义规则,并将其保存到全局变量:

Python
1
2
3
4
5
def _empty_impl(ctx):
    # 分析阶段此函数被执行
    print("This rule does nothing")
 
empty = rule(implementation = _empty_impl)

然后,规则可以通过load加载到BUILD文件:

Python
1
2
3
4
load("//empty:empty.bzl", "empty")
 
# 实例化规则
empty(name = "nothing")
规则属性 

属性即实例化规则时需要提供的参数,例如srcs、deps。在自定义规则的时候,你可以列出所有属性的名字和Schema:

Python
1
2
3
4
5
6
7
8
sum = rule(
    implementation = _impl,
    attrs = {
        # 定义一个整数属性,一个列表属性
        "number": attr.int(default = 1),
        "deps": attr.label_list(),
    },
)

实例化规则的时候,你需要以参数的形式指定属性:

Python
1
2
3
4
5
6
7
8
sum(
    name = "my-target",
    deps = [":other-target"],
)
 
sum(
    name = "other-target",
)

如果实例化规则的时候,没有指定某个属性的值(且没指定默认值),规则的实现函数会在ctx.attr中看到一个占位符,此占位符的值取决于属性的类型。

使用default为属性指定默认值,使用 mandatory=True 声明属性必须提供。

默认属性

任何规则自动具有以下属性:deprecation, features, name, tags, testonly, visibility。

任何测试规则具有以下额外属性:args, flaky, local, shard_count, size, timeout。

特殊属性

有两类特殊属性需要注意:

  1. 依赖属性:例如attr.label、attr.label_list,用于声明拥有此属性的目标所依赖的其它目标
  2. 输出属性:例如attr.output、attr.output_list,声明目标的输出文件,较少使用

上面两类属性的值都是Label类型。 

隐含依赖

具有默认值的依赖属性,称为隐含依赖(implicit dependency)。如果要硬编码规则和工具(例如编译器)之间的关系,可通过隐含依赖。从规则的角度来看,这些工具仍然属于输入,就像源代码一样。

私有属性

某些情况下,我们会为规则添加具有默认值的属性,同时还想禁止用户修改属性值,这种情况下可以使用私有属性。

私有属性以下划线 _ 开头,必须具有默认值。

目标

实例化规则不会返回值,但是会定义一个新的目标。

规则实现

任何规则都需要提供一个实现函数。提供在分析阶段需要严格执行的逻辑。此函数不能有任何读写行为,仅仅用于注册Action。

实现函数具有唯一性入参  —— 规则上下文,通常将其命名为ctx。通过规则上下文你可以:

  1. 访问规则属性
  2. 获得输入输出文件的handle
  3. 创建Actions
  4. 通过providers向依赖于当前规则的其它规则传递信息
ctx

规则上下文对象的具有以下主要方法或属性:

方法/属性 说明
action 废弃,使用ctx.actions.run()或ctx.actions.run_shell()代替
actions.run

创建一个调用可执行文件的Action,参数:Bazel加载阶段

outputs 此动作的输出文件列表
inputs 此动作输入文件的列表/depset
executable 执行此动作需要调用的可执行文件
tools 执行此动作需要的工具的列表/depset
arguments 传递给可执行文件的参数列表
mnemonic 动作的描述
progress_message 动作执行时,显示给用户的信息
use_default_shell_env 是否在内建Shell环境下运行可执行文件
env 环境变量字典
execution_requirements 调度此动作需要的信息
input_manifests 输入runfiles元数据,通常由resolve_command生成

示例:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def _impl(ctx):
    # The list of arguments we pass to the script.
    args = [ctx.outputs.out.path] + [f.path for f in ctx.files.chunks]
 
    # 调用可执行文件
    ctx.actions.run(
        inputs = ctx.files.chunks,
        outputs = [ctx.outputs.out],
        arguments = args,
        progress_message = "Merging into %s" % ctx.outputs.out.short_path,
        executable = ctx.executable._merge_tool,
    )
// 规则定义
concat = rule(
    implementation = _impl,
    attrs = {
        "chunks": attr.label_list(allow_files = True),
        "out": attr.output(mandatory = True),
        "_merge_tool": attr.label(
            executable = True,
            cfg = "host",
            allow_files = True,
            default = Label("//actions_run:merge"),
        ),
    },
) 
actions.run_shell

创建一个执行Shell脚本的Action

示例:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def _impl(ctx):
    output = ctx.outputs.out
    input = ctx.file.file
 
    # 访问inputs中声明的文件
    ctx.actions.run_shell(
        inputs = [input],
        outputs = [output],
        progress_message = "Getting size of %s" % input.short_path,
        command = "stat -L -c%%s '%s' > '%s'" % (input.path, output.path),
    )
 
# 规则定义
size = rule(
    implementation = _impl,
    attrs = {"file": attr.label(mandatory = True, allow_single_file = True)},
    outputs = {"out": "%{name}.size"},
)
actions.write 此Action写入内容到文件
actions.declare_file 此Action创建新的文件
actions.do_nothing 不做任何事情的Action
ctx.attr 用于访问属性值的结构 
bin_dir 二进制目录的根
genfiles_dir genfiles目录的根
build_file_path 相对于源码目录根的,当前BUILD文件的路径
executable  一个结构,可以引用任何通过 attr.label(executable=True)定义的规则属性
expand_location 

展开input中定义的所有$(location //x)为目标x的真实路径。仅仅对当前规则的直接依赖、明确列在targets属性中的目标使用

Python
1
string ctx.expand_location(input, targets=[])
features 列出此规则明确启用的特性列表 
file

此结构包含任何通过 attr.labe(allow_single_file=True)定义的属性所指向的文件。此结构的字段名即文件属性名,结构字段值是file或Node类型

此结构是表达式 list(ctx.attr.<ATTR>.files)[0]的快捷方式

fragments 用于访问目标配置中的配置片断(configuration fragments ) 
host_configuration 返回主机配置的configuration对象。configuration包含构建所在的运行环境信息 
host_fragments  用于访问host配置中的配置片断(configuration fragments )  
label 当前正在分析的目标的标签 
outputs 一个包含所有预声明的输出文件的伪结构 
resolve_command

解析一个命令,返回(inputs, command, input_manifests)元组:

inputs,表示解析后的输入列表
command,解析后的命令的argv列表
input_manifests,执行命令需要的runfiles元数据

resolve_tools  解析工具,返回(inputs, input_manifests)元组 
runfiles  创建一个Runfiles
toolchains 此规则需要的工具链 
var  配置变量的字典
workspace_name  当前工作空间的名称 
存储库规则

存储库规则用于定义外部存储库。外部存储库是一种规则,这种规则只能用在WORKSPACE文件中,可以在Bazel加载阶段启用非封闭性( non-hermetic,所谓封闭是指自包含,不依赖于外部环境)操作。每个外部存储库都创建自己的WORKSPACE,具有自己的BUILD文件和构件。

外部存储库可以用来:

  1. 加载第三方依赖,例如Maven打包的库
  2. 为运行构件的主机生成特化的BUILD文件

在bzl文件中,调用repository_rule函数可以创建一个存储库规则,你需要将其存放在全局变量中:

Python
1
2
3
4
5
6
local_repository = repository_rule(
    # 实现函数
    implementation=_impl,
    local=True,
    # 属性列表
    attrs={"path": attr.string(mandatory=True)}) 

每个存储库规则都必须提供实现函数,其中包含在Bazel加载阶段需要执行的严格的逻辑。该函数具有唯一的入参repository_ctx:

Python
1
2
3
def _impl(repository_ctx):
  # 你可以通过repository_ctx访问属性值、调用非密封性函数(例如查找、执行二进制文件,创建或下载文件到存储库)
  repository_ctx.symlink(repository_ctx.attr.path, "") 

引用存储库中规则时,可以使用 @REPO_NAMAE//package:target这样的标签。

repository_ctx

存储库规则上下文对象的具有以下主要方法或属性:

方法/属性 说明
attr 用于访问所有属性的结构
download
Python
1
struct repository_ctx.download(url, output='', sha256='', executable=False)

 下载文件到输出路径,返回包含字段sha256的结构

download_and_extract 下载并解压
execute 执行指定的命令
file 以指定的内容在存储库目录下生成文件
name 此规则生成的外部存储库的名称
path 返回字符串/路径/标签对应的实际路径
symlink 在文件系统中创建符号链接
Python
1
2
3
# from 符号链接的源,string/Label/path类型
# to 相对于存储库目录的符号链接文件的路径
None repository_ctx.symlink(from, to)
template 使用模板创建文件
which 返回指定程序的路径
命令
bazel
子命令 说明
analyze-profile 分析构建配置数据(build profile data)
aquery 针对post-analysis操作图执行查询
build 构建指定的目标:
Shell
1
2
3
4
5
6
7
8
9
10
11
12
# 构建foo/bar包中的wiz目标
bazel build //foo/bar:wiz
# 构建foo/bar包中的bar目标
bazel build //foo/bar
# 构建foo/bar包中的所有规则
bazel build //foo/bar:all
# 构建foo目录下所有子代包的所有规则
bazel build //foo/...
bazel build //foo/...:all
# 构建foo目录下所有子代包的所有目标(规则和文件)
bazel build //foo/...:*
bazel build //foo/...:all-targets

如果目标标签不以 //开头,则相对于当前目录。如果当前目录是foo则bar:wiz等价于//foo/bar:wiz

Bazel支持通过符号链接来寻找子包,除了:

  1. 那些指向输出目录的子目录的符号链接,例如bazel-bin
  2. 包含了名为DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN文件的目录

指定了 tags = ["manual"]的目标必须手工构建,无法通过...、:*、:all等自动构建

常用选项:

--loading_phase_threads   加载阶段使用的线程数量,可以防止并发太多导致下载缓慢,进而超时

canonicalize-flags 规范化Bazel标记
clean 清除输出文件,可选的停止服务器
cquery 针对post-analysis依赖图查询
dump 输出Bazel服务器的内部状态
info 输出Bazel服务器的运行时信息
fetch

拉取某个目标的外部依赖

使用 --fetch=false标记可以禁止在构建时进行自动的外部依赖(本地系统依赖除外)抓取,通过local_repository、new_local_repository声明的“本地”外部存储库,总是会抓取

如果禁用了自动抓取,你需要在以下时机手工抓取:

  1. 第一次构建之前
  2. 每当新增了外部依赖之后

示例:

Shell
1
2
3
4
# 抓取两个外部依赖
bazel fetch //foo:bar //bar:baz
# 抓取工作空间的全部外部依赖
bazel fetch //...

存储库缓存

Bazel会避免反复抓取同一个文件,即使:

  1. 多个工作空间使用同一外部依赖
  2. 外部存储库的定义改变了,但是需要下载的还是那个文件

Bazel在本地文件系统维护外部存储库的缓存,默认位置在~/.cache/bazel/_bazel_$USER/cache/repos/v1/。可以使用选项--repository_cache指定不同的缓存位置。缓存可以被所有命名空间、所有Bazel版本共享

避免下载

你可以指定--distdir选项,其值是一个只读的目录,bazel会在目录中寻找文件,而非去网络上下载。匹配方式是URL中的Basename + 文件哈希。如果不指定哈希值,则Bazel不会去--distdir寻找文件

mobile-install 在移动设备上安装目标
query 执行依赖图查询
run 运行指定的目标
shutdown 关闭Bazel服务器
test 构建并运行指定的测试目标

 

← 通过自定义资源扩展Kubernetes
利用perf剖析Linux应用程序 →
5 Comments On This Topic
  1. 回复
    dandelion
    2020/09/10

    赞 太详细了 看了好久

  2. 回复
    atuter
    2020/10/15

    发现宝藏

  3. 回复
    core
    2020/10/19

    太好了,开发刚好需要

  4. 回复
    smileyihui
    2020/11/01

    写的太棒了,多谢分享。学习了。

  5. 回复
    fx_carrot
    2021/02/01

    写的很棒!进我的收仓库积灰吧~

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Eclipse 4.3.2开发环境搭建
  • 基于C/C++的WebSocket库
  • Native编程知识集锦
  • CLion知识集锦
  • Visual Studio知识集锦

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2