Starlark 是一种类似于 Python 的
配置语言,最初是为在 Bazel 中使用而开发的,后来被其他工具采用
。Bazel 的 BUILD 和 .bzl 文件是用 Starlark 的一种方言编写的,这种方言通常被称为“构建语言”,但通常简称为“Starlark”,尤其是在强调某个功能是用构建语言表达的,而不是 Bazel 的内置或“原生”部分时。Bazel 使用许多与构建相关的函数
(例如 glob、genrule、java_binary 等)来扩充核心语言。
如需了解详情,请参阅 Bazel 和 Starlark 文档,并使用 Rules SIG 模板 作为 新规则集的起点。
空规则
如需创建您的第一个规则,请创建文件 foo.bzl:
def _foo_binary_impl(ctx):
pass
foo_binary = rule(
implementation = _foo_binary_impl,
)
调用 rule 函数时,您
必须定义回调函数。逻辑将在此处执行,但您
可以暂时将该函数留空。The ctx 实参
提供有关目标的信息。
您可以加载该规则并从 BUILD 文件中使用它。
在同一目录中创建一个 BUILD 文件:
load(":foo.bzl", "foo_binary")
foo_binary(name = "bin")
现在,可以构建目标:
$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)
即使该规则不执行任何操作,它的行为也与其他规则类似:它具有
强制性名称,并且支持 visibility、testonly 和
tags 等常见属性。
评估模型
在继续之前,务必了解如何评估代码。
使用一些 print 语句更新 foo.bzl:
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
和 BUILD:
load(":foo.bzl", "foo_binary")
print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")
ctx.label
对应于要分析的目标的标签。ctx 对象具有
许多有用的字段和方法;您可以在
API 参考文档中找到详尽的列表。
查询代码:
$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1
进行一些观察:
- 首先输出“bzl file evaluation”。在评估
BUILD文件之前, Bazel 会评估它加载的所有文件。如果有多个BUILD文件加载 foo.bzl,您只会看到一次“bzl file evaluation”,因为 Bazel 会缓存评估结果。 - 回调函数
_foo_binary_impl未被调用。Bazel 查询会加载BUILD文件,但不会分析目标。
如需分析目标,请使用 cquery(“配置的
查询”)或 build 命令:
$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
如您所见,_foo_binary_impl 现在被调用了两次,每个目标一次。
请注意,“bzl file evaluation”和“BUILD file”都不会再次输出,
因为在调用 bazel query 后,foo.bzl 的评估结果会被缓存。
Bazel 仅在实际执行 print 语句时才会发出这些语句。
创建文件
如需使您的规则更有用,请更新该规则以生成文件。首先,声明该 文件并为其命名。在此示例中,创建一个与目标同名的文件:
ctx.actions.declare_file(ctx.label.name)
如果您现在运行 bazel build :all,将会收到错误:
The following files have no generating action:
bin2
每当您声明文件时,都必须通过
创建操作来告知 Bazel 如何生成该文件。使用 ctx.actions.write,
创建具有给定内容的文件。
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
该代码有效,但不会执行任何操作:
$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)
ctx.actions.write 函数注册了一个操作,该操作告知 Bazel
如何生成文件。但 Bazel 不会创建该文件,除非实际请求该文件。因此,最后要做的是告知 Bazel 该文件
是规则的输出,而不是在规则
实现中使用的临时文件。
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello!\n",
)
return [DefaultInfo(files = depset([out]))]
稍后查看 DefaultInfo 和 depset 函数。目前,
假设最后一行是选择规则输出的方式。
现在,运行 Bazel:
$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
bazel-bin/bin1
$ cat bazel-bin/bin1
Hello!
您已成功生成文件!
属性
如需使规则更有用,请使用
attr模块添加新属性并更新规则定义。
添加一个名为 username 的字符串属性:
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
接下来,在 BUILD 文件中进行设置:
foo_binary(
name = "bin",
username = "Alice",
)
如需在回调函数中访问该值,请使用 ctx.attr.username。例如:
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello {}!\n".format(ctx.attr.username),
)
return [DefaultInfo(files = depset([out]))]
请注意,您可以将属性设为强制性属性或设置默认值。请参阅 attr.string 的文档。
您还可以使用其他类型的属性,例如布尔值
或 整数列表。
依赖项
依赖项属性(例如 attr.label
和 attr.label_list)声明从拥有该属性的目标到其
标签显示在该属性值中的目标的依赖项。此类属性构成了目标图的基础
。
在 BUILD 文件中,目标标签显示为字符串对象,例如
//pkg:name。在实现函数中,目标将作为
Target 对象进行访问。例如,使用 Target.files 查看目标返回的文件
。
多个文件
默认情况下,只有通过规则创建的目标才能显示为依赖项(例如
foo_library() 目标)。如果您希望该属性接受作为
输入文件的目标(例如代码库中的源文件),可以使用
allow_files 并指定接受的文件扩展名列表(或 True 以
允许任何文件扩展名):
"srcs": attr.label_list(allow_files = [".java"]),
可以使用 ctx.files.<attribute name> 访问文件列表。例如,可以通过以下方式访问 srcs 属性中的文件列表
ctx.files.srcs
单个文件
如果您只需要一个文件,请使用 allow_single_file:
"src": attr.label(allow_single_file = [".java"])
然后,可以在 ctx.file.<attribute name> 下访问此文件:
ctx.file.src
使用模板创建文件
您可以创建一个规则,该规则基于模板生成 .cc 文件。此外,您
还可以使用 ctx.actions.write 输出在规则
实现函数中构造的字符串,但这存在两个问题。首先,随着模板越来越
大,将其放在单独的文件中并避免
在分析阶段构造大型字符串会更节省内存。其次,使用单独的
文件对用户来说更方便。因此,请改用
ctx.actions.expand_template,
该函数会对模板文件执行替换。
创建一个 template 属性,以声明对模板
文件的依赖项:
def _hello_world_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".cc")
ctx.actions.expand_template(
output = out,
template = ctx.file.template,
substitutions = {"{NAME}": ctx.attr.username},
)
return [DefaultInfo(files = depset([out]))]
hello_world = rule(
implementation = _hello_world_impl,
attrs = {
"username": attr.string(default = "unknown person"),
"template": attr.label(
allow_single_file = [".cc.tpl"],
mandatory = True,
),
},
)
用户可以按如下方式使用该规则:
hello_world(
name = "hello",
username = "Alice",
template = "file.cc.tpl",
)
cc_binary(
name = "hello_bin",
srcs = [":hello"],
)
如果您不想向最终用户公开模板并始终使用同一模板,可以设置默认值并将该属性设为私有属性:
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
以下划线开头的属性是私有属性,无法在
BUILD文件中设置。该模板现在是一个 隐式依赖项:每个 hello_world
目标都依赖于此文件。请务必通过更新 BUILD 文件并使用
exports_files使此文件对其他软件包可见
:
exports_files(["file.cc.tpl"])