Starlark 是类似于 Python 的
最初为用于 Bazel 而开发,但后来被引入的配置语言
其他工具生成的报告Bazel 的 BUILD
和 .bzl
文件采用下列语言编写:
Starlark 通常简称为“Build Language”,
称为“Starlark”,特别是在强调某个地图项
而不是以构建语言表示,部分
Vertex AI SDK。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
函数时,
必须定义回调函数。逻辑就在那里,但是您
可以暂时将该函数留空。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
。
评估模型
在继续之前,请务必先了解如何评估代码。
使用一些输出语句更新 foo.bzl
:
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
和构建:
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 文件评估”输出结果。在评估
BUILD
文件之前, Bazel 会评估其加载的所有文件。如果正在加载多个BUILD
文件 foo.bzl,则只会看到一次“bzl file measurement”(bzl 文件评估)因为 Bazel 会缓存评估结果。 - 回调函数
_foo_binary_impl
未被调用。Bazel 查询加载BUILD
文件,但不会分析目标。
若要分析这些目标,请使用cquery
(“已配置
query”)或 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 评估”也不使用“BUILD 文件”输出结果
因为在调用 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
target 依赖于此文件。别忘了将此文件设为可见
更新 BUILD
文件并使用
exports_files
:
exports_files(["file.cc.tpl"])