本页介绍了 Starlark 配置的优势和基本用法, Starlark 配置是 Bazel 的 API,用于自定义项目的构建方式。本页还介绍了如何定义 构建设置,并提供了示例。
这样一来,您便可以:
- 为项目定义自定义标志,从而无需使用
--define - 编写
转换,以在
不同配置中配置依赖项,而不是其父项
(例如
--compilation_mode=opt或--cpu=arm) - 将更好的默认值烘焙到规则中(例如,自动构建
//my:android_app使用指定的 SDK)
等等,所有这些都完全来自 .bzl 文件(无需 Bazel 版本)。如需查看
示例,请参阅
bazelbuild/examples代码库。
用户定义的构建设置
构建设置是一条
配置
信息。您可以将配置视为键值对映射。设置 --cpu=ppc
和 --copt="-DFoo" 会生成如下配置:
{cpu: ppc, copt: "-DFoo"}。每个条目都是一个构建设置。
像 cpu 和 copt 这样的传统标志是原生设置,
其键是在原生 Bazel Java 代码中定义的,其值是在原生 Bazel Java 代码中设置的。
Bazel 用户只能通过命令行
和其他原生维护的 API 读取和写入这些标志。更改原生标志以及公开这些标志的 API
需要 Bazel 版本。用户定义的构建设置是在 .bzl 文件中定义的(因此,无需 Bazel 版本即可注册更改)。它们也可以通过命令行设置
(如果它们被指定为 flags,请参阅下文了解详情),但也可以通过
用户定义的转换来设置。
定义构建设置
build_setting rule() 形参
构建设置与其他任何规则一样都是规则,并且使用
Starlark rule() 函数的 build_setting
属性进行区分。
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
build_setting 属性接受一个函数,该函数用于指定
构建设置的类型。该类型仅限于一组基本的 Starlark 类型,例如
bool 和 string。如需了解详情,请参阅 config 模块
文档。更复杂的类型可以在规则的实现函数中完成。下文对此进行了详细介绍。
config 模块的函数接受一个可选的布尔形参 flag,
该形参默认设置为 false。如果 flag 设置为 true,则构建设置
用户可以在命令行中设置,规则编写者也可以通过默认值和 转换在内部设置。
并非所有设置都应由用户设置。例如,如果您作为规则
编写者有一些调试模式,您希望在测试规则中启用这些模式,
那么您不希望让用户能够在其他非测试规则中随意启用该
功能。
使用 ctx.build_setting_value
与所有规则一样,构建设置规则也有实现函数。
构建设置的基本 Starlark 类型值可以通过
ctx.build_setting_value 方法访问。此方法仅适用于
ctx构建设置规则的对象。这些实现
方法可以直接转发构建设置值,也可以对其执行其他操作,例如类型检查或创建更复杂的结构。以下是如何
实现 enum-类型的构建设置:
# example/buildsettings/build_settings.bzl
TemperatureProvider = provider(fields = ['type'])
temperatures = ["HOT", "LUKEWARM", "ICED"]
def _impl(ctx):
raw_temperature = ctx.build_setting_value
if raw_temperature not in temperatures:
fail(str(ctx.label) + " build setting allowed to take values {"
+ ", ".join(temperatures) + "} but was set to unallowed value "
+ raw_temperature)
return TemperatureProvider(type = raw_temperature)
temperature = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
定义多集字符串标志
字符串设置有一个额外的 allow_multiple 形参,该形参允许在命令行或 bazelrc 中多次设置
标志。它们的默认
值仍然使用字符串类型的属性进行设置:
# example/buildsettings/build_settings.bzl
allow_multiple_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "allow_multiple_flag")
allow_multiple_flag(
name = "roasts",
build_setting_default = "medium"
)
标志的每个设置都被视为一个值:
$ bazel build //my/target --//example:roasts=blonde \
--//example:roasts=medium,dark
上述内容解析为 {"//example:roasts": ["blonde", "medium,dark"]},
ctx.build_setting_value 返回列表 ["blonde", "medium,dark"]。
实例化构建设置
使用 build_setting 形参定义的规则具有隐式强制性
build_setting_default 属性。此属性采用与
由 build_setting 形参声明的类型相同的类型。
# example/buildsettings/build_settings.bzl
FlavorProvider = provider(fields = ['type'])
def _impl(ctx):
return FlavorProvider(type = ctx.build_setting_value)
flavor = rule(
implementation = _impl,
build_setting = config.string(flag = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
预定义设置
Skylib 库包含一组预定义设置,您可以实例化这些设置,而无需 编写自定义 Starlark。
例如,如需定义一个接受一组有限的字符串值的设置,请执行以下操作:
# example/BUILD
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
string_flag(
name = "myflag",
values = ["a", "b", "c"],
build_setting_default = "a",
)
如需查看完整列表,请参阅 常见构建设置规则。
使用构建设置
依赖于构建设置
如果目标想要读取一条配置信息,它可以 直接通过常规属性依赖项依赖于构建设置。
# example/rules.bzl
load("//example/buildsettings:build_settings.bzl", "FlavorProvider")
def _rule_impl(ctx):
if ctx.attr.flavor[FlavorProvider].type == "ORANGE":
...
drink_rule = rule(
implementation = _rule_impl,
attrs = {
"flavor": attr.label()
}
)
# example/BUILD
load("//example:rules.bzl", "drink_rule")
load("//example/buildsettings:build_settings.bzl", "flavor")
flavor(
name = "favorite_flavor",
build_setting_default = "APPLE"
)
drink_rule(
name = "my_drink",
flavor = ":favorite_flavor",
)
语言可能希望创建一组规范的构建设置,该语言的所有规则
都依赖于这些设置。虽然 fragments 的原生概念在 Starlark 配置世界中不再
作为硬编码对象存在,但转换此概念的一种方法是使用一组常见的隐式属性。例如:
# kotlin/rules.bzl
_KOTLIN_CONFIG = {
"_compiler": attr.label(default = "//kotlin/config:compiler-flag"),
"_mode": attr.label(default = "//kotlin/config:mode-flag"),
...
}
...
kotlin_library = rule(
implementation = _rule_impl,
attrs = dicts.add({
"library-attr": attr.string()
}, _KOTLIN_CONFIG)
)
kotlin_binary = rule(
implementation = _binary_impl,
attrs = dicts.add({
"binary-attr": attr.label()
}, _KOTLIN_CONFIG)
在命令行中使用构建设置
与大多数原生标志类似,您可以使用命令行设置标记为标志的构建设置
。构建
设置的名称是其完整的目标路径,使用 name=value 语法:
$ bazel build //my/target --//example:string_flag=some-value # allowed
$ bazel build //my/target --//example:string_flag some-value # not allowed
支持特殊的布尔值语法:
$ bazel build //my/target --//example:boolean_flag
$ bazel build //my/target --no//example:boolean_flag
使用构建设置别名
您可以为构建设置目标路径设置别名,以便在命令行中更轻松地读取该路径 。别名的功能与原生标志类似,并且也使用 双连字符选项语法。
如需设置别名,请将 --flag_alias=ALIAS_NAME=TARGET_PATH
添加到 .bazelrc。例如,如需将别名设置为 coffee,请执行以下操作:
# .bazelrc
build --flag_alias=coffee=//experimental/user/starlark_configurations/basic_build_setting:coffee-temp
最佳实践:多次设置别名会导致最近一次设置的别名 优先。请使用唯一的别名,以避免出现意外的解析结果。
如需使用别名,请将其替换为构建设置目标路径。
在用户的 .bazelrc 中设置上述 coffee 示例后:
$ bazel build //my/target --coffee=ICED
而不是
$ bazel build //my/target --//experimental/user/starlark_configurations/basic_build_setting:coffee-temp=ICED
最佳实践:虽然可以在命令行中设置别名,但将其保留在 .bazelrc 中可以减少命令行混乱。
标签类型的构建设置
与其他构建设置不同,标签类型的设置无法使用
build_setting 规则形参进行定义。相反,Bazel 有两个内置规则:
label_flag 和 label_setting。这些规则会将
实际目标的提供程序转发到构建设置所设置的目标。label_flag 和
label_setting 可以由转换读取/写入,并且用户可以像其他 build_setting 规则一样设置 label_flag
。它们的唯一区别在于,它们
无法自定义定义。
标签类型的设置最终将取代后期绑定
默认值的功能。后期绑定默认属性是标签类型的属性,其
最终值可能会受到配置的影响。在 Starlark 中,这将取代
configuration_field
API。
# example/rules.bzl
MyProvider = provider(fields = ["my_field"])
def _dep_impl(ctx):
return MyProvider(my_field = "yeehaw")
dep_rule = rule(
implementation = _dep_impl
)
def _parent_impl(ctx):
if ctx.attr.my_field_provider[MyProvider].my_field == "cowabunga":
...
parent_rule = rule(
implementation = _parent_impl,
attrs = { "my_field_provider": attr.label() }
)
# example/BUILD
load("//example:rules.bzl", "dep_rule", "parent_rule")
dep_rule(name = "dep")
parent_rule(name = "parent", my_field_provider = ":my_field_provider")
label_flag(
name = "my_field_provider",
build_setting_default = ":dep"
)
构建设置和 select()
用户可以使用
select()配置构建设置的属性。构建设置目标可以传递给 flag_values 属性 of
config_setting。要与配置匹配的值将作为
String传递,然后解析为构建设置的类型以进行匹配。
config_setting(
name = "my_config",
flag_values = {
"//example:favorite_flavor": "MANGO"
}
)
用户定义的转换
定义
转换定义了规则之间的配置更改。例如,转换会处理“为与父项不同的 CPU 编译我的依赖项”之类的请求。
正式来说,转换是从输入配置到一个或多个
输出配置的函数。大多数转换都是 1:1 的,例如“使用 --cpu=ppc 替换输入
配置”。1:2+ 转换也可能存在,但存在特殊限制。
在 Starlark 中,转换的定义方式与规则非常相似,包括定义
transition()
函数
和实现函数。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//example:favorite_flavor" : "MINT"}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
transition() 函数接受实现函数、要读取的一组
构建设置(inputs) 和要写入的一组构建设置
(outputs)。实现函数有两个形参,即 settings 和
attr。settings 是 {String:Object} 字典,其中包含在 transition() 的 inputs 形参中声明的所有设置
。
attr 是与
转换所附加到的规则的属性和值对应的字典。当作为一
个出边转换附加时,这些
属性的值都是在 select() 解析后配置的。当作为
一个 入边转换附加时,attr 不
包含任何使用选择器来解析其值的属性。如果
--foo 上的入边转换读取属性 bar,然后还
选择 --foo 来设置属性 bar,则
入边转换可能会在转换中读取错误的 bar 值。
实现函数必须返回一个字典(或
字典列表,对于
具有多个输出配置的转换)
其中包含要应用的新构建设置值。返回的字典键集必须
完全包含传递给转换函数的 outputs
形参的构建设置集。即使在转换过程中构建设置实际上没有更改,也是如此 - 其原始值必须在返回的字典中显式传递。
定义 1:2+ 转换
出边转换可以将单个输入 配置映射到两个或多个输出配置。这对于定义 捆绑多架构代码的规则非常有用。
1:2+ 转换通过在 转换实现函数中返回字典列表来定义。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return [
{"//example:favorite_flavor" : "LATTE"},
{"//example:favorite_flavor" : "MOCHA"},
]
coffee_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
它们还可以设置自定义键,规则实现函数可以使用这些键来 读取各个依赖项:
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
附加转换
转换可以附加到两个位置:入边和出边。 实际上,这意味着规则可以转换自己的配置(入边 转换)并转换其依赖项的配置(出边 转换)。
注意:目前无法将 Starlark 转换附加到原生规则。 如果您需要执行此操作,请与 bazel-discuss@googlegroups.com 联系,以获取有关解决方法的帮助。
入边转换
入边转换通过将 transition 对象
(由 transition() 创建)附加到 rule()'s cfg 形参来激活:
# example/rules.bzl
load("example/transitions:transitions.bzl", "hot_chocolate_transition")
drink_rule = rule(
implementation = _impl,
cfg = hot_chocolate_transition,
...
入边转换必须是 1:1 转换。
出边转换
出边转换通过将 transition 对象
(由 transition() 创建)附加到属性的 cfg 形参来激活:
# example/rules.bzl
load("example/transitions:transitions.bzl", "coffee_transition")
drink_rule = rule(
implementation = _impl,
attrs = { "dep": attr.label(cfg = coffee_transition)}
...
出边转换可以是 1:1 或 1:2+。
如需了解如何读取这些键,请参阅使用转换访问属性 。
原生选项的转换
Starlark 转换还可以通过选项名称的特殊前缀声明对原生构建 配置选项的读取和写入。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//command_line_option:cpu": "k8"}
cpu_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
不受支持的原生选项
Bazel 不支持使用
"//command_line_option:define" 对 --define 进行转换。请改用自定义
构建设置。一般来说,不建议使用
--define的新用法,而建议使用构建设置。
Bazel 不支持对 --config 进行转换。这是因为 --config 是
一个“扩展”标志,它会扩展到其他标志。
至关重要的是,--config 可能包含不影响构建配置的标志,
例如
--spawn_strategy
。Bazel 在设计上无法将此类标志绑定到各个目标。这意味着
无法在转换中以一致的方式应用它们。
作为一种解决方法,您可以在转换中显式列出属于
配置的标志。这需要在两个位置维护 --config's
的扩展,这是一个已知的界面缺陷。
允许使用多个构建设置的转换
设置允许使用多个值的构建设置时,必须使用列表设置该设置的值。
# example/buildsettings/build_settings.bzl
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True)
)
# example/BUILD
load("//example/buildsettings:build_settings.bzl", "string_flag")
string_flag(name = "roasts", build_setting_default = "medium")
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
# Using a value of just "dark" here will throw an error
return {"//example:roasts" : ["dark"]},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:roasts"]
)
空操作转换
如果转换返回 {}、[] 或 None,则这是将所有
设置保留为其原始值的简写形式。这可能比显式
将每个输出设置为自身更方便。
# example/transitions/transitions.bzl
def _impl(settings, attr):
_ignore = (attr)
if settings["//example:already_chosen"] is True:
return {}
return {
"//example:favorite_flavor": "dark chocolate",
"//example:include_marshmallows": "yes",
"//example:desired_temperature": "38C",
}
hot_chocolate_transition = transition(
implementation = _impl,
inputs = ["//example:already_chosen"],
outputs = [
"//example:favorite_flavor",
"//example:include_marshmallows",
"//example:desired_temperature",
]
)
使用转换访问属性
将 转换附加到出边
时(无论转换是 1:1 转换还是 1:2+ 转换),ctx.attr如果尚不是列表,则强制将其设为列表
。此列表中元素的顺序未指定。
# example/transitions/rules.bzl
def _transition_impl(settings, attr):
return {"//example:favorite_flavor" : "LATTE"},
coffee_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//example:favorite_flavor"]
)
def _rule_impl(ctx):
# Note: List access even though "dep" is not declared as list
transitioned_dep = ctx.attr.dep[0]
# Note: Access doesn't change, other_deps was already a list
for other_dep in ctx.attr.other_deps:
# ...
coffee_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = coffee_transition)
"other_deps": attr.label_list(cfg = coffee_transition)
})
如果转换是 1:2+ 并且设置了自定义键,则可以使用 ctx.split_attr
为每个键读取各个依赖项:
# example/transitions/rules.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {
"Apple deps": {"//command_line_option:cpu": "ppc"},
"Linux deps": {"//command_line_option:cpu": "x86"},
}
multi_arch_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//command_line_option:cpu"]
)
def _rule_impl(ctx):
apple_dep = ctx.split_attr.dep["Apple deps"]
linux_dep = ctx.split_attr.dep["Linux deps"]
# ctx.attr has a list of all deps for all keys. Order is not guaranteed.
all_deps = ctx.attr.dep
multi_arch_rule = rule(
implementation = _rule_impl,
attrs = {
"dep": attr.label(cfg = multi_arch_transition)
})
请点击此处查看完整示例 。
与平台和工具链集成
如今,许多原生标志(例如 --cpu 和 --crosstool_top)都与
工具链解析相关。将来,对这些类型的
标志的显式转换可能会被对
目标平台的转换所取代。
内存和性能注意事项
向构建添加转换(以及因此添加新配置)会带来一些 成本:构建图更大、构建图更难理解,以及构建速度更慢 。在考虑在构建规则中使用转换时,值得考虑这些成本。以下示例说明了转换 如何使构建图呈指数级增长。
行为不端的构建:案例研究

图 1。可伸缩性图表,显示了顶级目标及其依赖项。
此图显示了一个顶级目标 //pkg:app,它依赖于两个目标:a
//pkg:1_0 和 //pkg:1_1。这两个目标都依赖于两个目标://pkg:2_0 和
//pkg:2_1。这两个目标都依赖于两个目标://pkg:3_0 和 //pkg:3_1。
这种情况一直持续到 //pkg:n_0 和 //pkg:n_1,这两个目标都依赖于单个
目标 //pkg:dep。
构建 //pkg:app 需要以下目标: \(2n+2\)
//pkg:app//pkg:dep//pkg:i_0和//pkg:i_1(适用于) \(i\) in \([1..n]\)
假设您实现了一个标志
--//foo:owner=<STRING>,并且//pkg:i_b应用了该标志
depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"
换句话说,//pkg:i_b 会将其所有依赖项的 --owner 的旧值附加到 b。
这会生成以下 配置的目标:
//pkg:app //foo:owner=""
//pkg:1_0 //foo:owner=""
//pkg:1_1 //foo:owner=""
//pkg:2_0 (via //pkg:1_0) //foo:owner="0"
//pkg:2_0 (via //pkg:1_1) //foo:owner="1"
//pkg:2_1 (via //pkg:1_0) //foo:owner="0"
//pkg:2_1 (via //pkg:1_1) //foo:owner="1"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_0) //foo:owner="00"
//pkg:3_0 (via //pkg:1_0 → //pkg:2_1) //foo:owner="01"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_0) //foo:owner="10"
//pkg:3_0 (via //pkg:1_1 → //pkg:2_1) //foo:owner="11"
...
//pkg:dep 会生成 \(2^n\) 配置的目标:config.owner=
"\(b_0b_1...b_n\)"(适用于所有 \(b_i\) )。 \(\{0,1\}\)
这使得构建图比目标图呈指数级增长,并带来 相应的内存和性能后果。
待办事项:添加用于衡量和缓解这些问题的策略。
深入阅读
如需详细了解如何修改构建配置,请参阅: