2022 年 BazelCon 将于 11 月 16 日至 17 日在纽约和线上举办。
立即报名!

配置

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

本页介绍了 Starlark 配置的优势和基本用法,Bazel' 的 API 可用于自定义项目构建方式。其中包括如何定义构建设置以及提供示例。

这样一来,您就可以:

  • 为您的项目定义自定义标志,无需 --define
  • 编写转换以配置与其父级不同的配置(如 --compilation_mode=opt--cpu=arm)中的依赖项
  • 将更好的默认值融入到规则中(例如,使用指定的 SDK 自动构建 //my:android_app

还有更多,全部来自 .bzl 文件(无需 Bazel 版本)。如需查看示例,请参阅 bazelbuild/examples 代码库。

用户定义的构建设置

构建设置是一条配置信息。您可以将配置视为键值对映射。设置 --cpu=ppc--copt="-DFoo" 会生成类似于 {cpu: ppc, copt: "-DFoo"} 的配置。每个条目都是构建设置。

cpucopt 等传统标志是原生设置 - 它们的键已定义,其值是在原生 bazel java 代码中设置的。Bazel 用户只能通过命令行以及以原生方式维护的其他 API 读取和写入这些日志。更改原生标志以及公开这些标志的 API 需要 bazel 版本。用户定义的构建设置在 .bzl 文件中定义(因此,不需要 bazel 版本来注册更改)。您也可以通过命令行进行设置(如果它们指定为 flags,请参见下文),但也可以通过用户定义的过渡进行设置。

定义构建设置

端到端示例

build_setting rule() 参数

构建设置与其他任何规则一样,使用 Starlark rule() 函数build_setting attribute 进行区分。

# example/buildsettings/build_settings.bzl
string_flag = rule(
    implementation = _impl,
    build_setting = config.string(flag = True)
)

build_setting 属性接受用于指定构建设置类型的函数。类型仅限于一组基本的 Starlark 类型,例如 boolstring。如需了解详情,请参阅 config 模块文档。您可以在规则的实现函数中执行更复杂的输入。有关详情,请参阅下文。

config 模块的函数会接受一个可选的布尔值参数 flag,该参数默认设为 false。如果 flag 设为 true,则可以由用户在命令行上以及 build 写入程序通过默认值和转换在内部进行构建设置。 并非所有设置都应由用户设定。例如,作为规则编写器并且希望在测试规则中启用某些调试模式,您一定不希望用户在其他非测试规则中随意开启该功能。

使用 ctx.build_setting_value

与所有规则一样,构建设置规则也具有实现函数。可以通过 ctx.build_setting_value 方法访问构建设置的基本 Starlark 类型值。此方法仅适用于构建设置规则的 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 参数,允许在命令行或 bazelrcs 中多次设置该标志。其默认值仍使用字符串类型的属性进行设置:

# example/buildsettings/build_settings.bzl
allow_multiple_flag = rule(
    implementation = _impl,
    build_setting = config.string(flag = True, allow_multiple = True)
)
# example/buildsettings/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/buildsettings/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",
)

如需完整列表,请参阅常见的构建设置规则

使用构建设置

具体取决于 build 设置

如果目标需要读取一部分配置信息,则可以通过常规属性依赖项直接依赖于构建设置。

# 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_flaglabel_setting。这些规则会将设置构建设置到的实际目标的目标提供程序转发给。可以通过转换读取/写入 label_flaglabel_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() 在构建设置中配置属性。构建设置目标可以传递给 config_settingflag_values 属性。与配置匹配的值将作为 String 传递,然后解析为匹配的构建设置类型。

config_setting(
    name = "my_config",
    flag_values = {
        "//example:favorite_flavor": "MANGO"
    }
)

用户定义的过渡

配置转换会在构建图中将转换从一个配置的目标映射到另一个配置的目标。

设置它们的规则必须包含一个特殊属性:

  "_allowlist_function_transition": attr.label(
      default = "@bazel_tools//tools/allowlists/function_transition_allowlist"
  )

通过添加过渡,您可以轻松地分解构建图的大小。此操作会针对您可以在其中创建此规则的目标软件包设置许可名单。上述代码块中的默认值将所有内容列入许可名单。但是,如果您希望限制谁使用您的规则,您可以将该属性设置为指向您自己的自定义许可名单。如果您想了解转换会对 build 性能产生哪些影响或帮助,请发送电子邮件至 bazel-discuss@googlegroups.com。

定义

过渡定义了规则之间的配置更改。例如,“为另一个依赖项编译父级依赖项”等请求由过渡处理。

在某种程度上,过渡是一个从输入配置到一项或多项输出配置的函数。大多数过渡均为 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)。实现函数有两个参数:settingsattrsettings 是在 transition()inputs 参数中声明的所有设置的 {String:Object}。

attr 是附加到过渡的规则的属性和值的字典。作为出站边缘过渡附加时,这些属性的值均会配置 select-select() 后。作为传入的边缘过渡附加时,attr 不会包含使用选择器解析其值的任何属性。如果 --foo 的传入边缘过渡会读取属性 bar,然后也选择 --foo 来设置 bar 属性,那么传入的边缘过渡有可能读取过渡中错误的 bar 值。

实现函数必须返回要应用的新 build 设置值的字典(如果是多输出配置转换,则返回字典列表)。返回的字典键集必须包含传递给转换函数的 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()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 不支持在 --define 上使用 "//command_line_option:define" 进行转换。请改用自定义构建设置。一般而言,不建议使用 --define 以支持 build 设置。

Bazel 不支持在 --config 转换。这是因为 --config 是会展开为其他标志的“展开”标志。

重要的是,--config 可能包含不影响构建配置的标志,例如 --spawn_strategy。根据设计,Bazel 不能将此类标记绑定到单个目标。这意味着,在过渡期间没有统一的方式应用它们。

解决方法是,您可以在转换中明确逐项列出配置中的标志。这需要在两个位置维护 --config 的展开,这是已知的界面缺陷。

允许多个 build 设置时的过渡

在设定允许多个值的构建设置时,必须使用列表设定设置的值。

# 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)都与工具链解析相关。将来,在目标平台上转换时,这些类型标志的显式过渡很可能会被替换。

内存和性能注意事项

向 build 添加过渡(以及新配置)会产生费用:构建图较大、难以理解的构建图和构建速度较慢。在构建规则中使用过渡时,最好考虑这些费用。以下示例展示了过渡如何使构建图呈指数增长。

不良行为 build:案例研究

可伸缩性图

图 1. 显示顶级目标及其依赖项的可伸缩性图表。

此图显示了 //pkg:app 的顶级目标,它取决于两个目标,即 //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
  • \(i\) \([1..n]\)的//pkg:i_0//pkg:i_1

假设您实现了标记 --//foo:owner=<STRING>//pkg:i_b 标记

depConfig = myConfig + depConfig.owner="$(myConfig.owner)$(b)"

换句话说,//pkg:i_b 会将 b 附加到所有依赖项的 --owner 的旧值。

这会生成以下配置的目标

//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\}\)

这会使得 build 图成倍地高于目标图,并对内存和性能造成相应的影响。

待办事项:添加用于衡量和缓解这些问题的策略。

更多详情

如需详细了解如何修改构建配置,请参阅: