配置

报告问题 查看来源 每晚 · 7.2。 · 7.1敬上 · 7.0 · 6.5 · 6.4

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

这样,您就可以:

  • 为项目定义自定义标志, --define
  • 写入 transitions 来配置依赖项 配置不同于父级配置 (例如 --compilation_mode=opt--cpu=arm
  • 将更好的默认值纳入规则(例如自动构建 //my:android_app 使用指定 SDK)

等等,全部来自 .bzl 文件(无需 Bazel 版本)。请参阅 bazelbuild/examples 代码库 样本

用户定义的构建设置

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

cpucopt 等传统标志是原生设置 - 其键均已定义,其值在原生 bazel Java 代码中进行设置。 Bazel 用户只能通过命令行进行读取和写入 以及其他 API更改原生标志和 API 需要一个 Bazel 版本。用户定义的 build 设置是在 .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 属性接受一个函数,该函数指定 build 设置。该类型仅限于一组基本的 Starlark 类型,例如 boolstring。请参阅 config 模块 如需了解详情,请参阅文档。更为复杂的输入 规则的实施函数中所完成的操作有关详情,请参见下文。

config 模块的函数接受可选的布尔值参数 flag, 该参数默认为 false。如果 flag 设为 true,则 build 设置 可由用户在命令行上设置,也可由规则制定者在内部设置 通过默认值和过渡来实现。 并非所有设置都应该由用户设定。例如,如果作为规则 编写一些要在测试规则内打开的调试模式, 您肯定不希望让用户随意开启 功能。

使用 ctx.build_setting_value

与所有规则一样,构建设置规则具有实现函数。 如需访问 build 设置的基本 Starlark 类型值,可通过 ctx.build_setting_value 方法结合使用。此方法仅适用于 build 设置规则的 ctx 对象。这些实现方法 方法可以直接转发构建设置值, 例如类型检查或更复杂的结构体创建。您可以按照以下方法 实现 enum 类型的 build 设置:

# 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 中多次设置。他们的默认行为 value 仍然使用字符串类型的属性进行设置:

# 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",
)

有关完整列表,请参阅 常见的 build 设置规则

使用构建设置

取决于 build 设置

如果目标想要读取一段配置信息 通过常规属性依赖项直接依赖于 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",
)

语言可能希望创建一组规范的 build 设置,这些设置的所有规则 所需的语言虽然 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)

在命令行中使用构建设置

与大多数原生标志类似,您可以使用命令行来设置 build 设置 被标记为“已标记”的注释build 设置的名称是使用 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

使用 build 设置别名

您可以为构建设置目标路径设置别名,以便于阅读 。别名的功能与原生标志类似 也就是双短划线选项语法的一部分

通过添加 --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 设置

端到端示例

与其他构建设置不同,标签类型的设置无法使用 build_setting 规则参数。bazel 有两条内置规则: label_flaglabel_setting。这些规则会将 构建设置所设置的实际目标。label_flag和 可以通过转换读取/写入 label_setting,并且可以设置 label_flag 就像其他 build_setting 规则一样。唯一的区别是 无法自定义 。

标签类型的设置最终将取代延迟绑定的功能 默认值。后期绑定默认属性是指标签类型的属性,其 最终值可能会受配置的影响。在 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()。build 设置目标可以传递给 flag_values 属性 config_setting。与配置匹配的值会作为 然后,String 会解析为 build 设置的类型以进行匹配。

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

用户定义的过渡

配置 过渡 将一个配置目标映射到另一个 build 图。

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

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

通过添加转场效果,你可以很容易地放大 构建图。这会为软件包设置许可名单 为此规则创建目标上述代码块中的默认值 将所有内容列入许可名单。如果您想限制使用规则的用户 则可以设置该属性,使其指向您自己的自定义许可名单。 如果您需要建议或帮助,请发送电子邮件至 bazel-discuss@googlegroups.com 了解过渡对构建性能的影响。

定义

过渡定义了规则之间的配置更改。例如,一个请求 例如“针对与其父项不同的 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() 函数可接受一个实现函数, 要读取的 build 设置(inputs),以及要写入的一组 build 设置 (outputs).该实现函数有两个参数:settingsattr。“settings”是声明的所有设置的字典 {String:Object} 设置为 transition()inputs

attr 是规则的属性和值字典, 已附加过渡效果。以 传出边缘转换的值, 属性都是已配置的 post-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 转换还可以在原生 build 上声明读取和写入 通过在选项名称前添加一个特殊前缀来配置选项

# 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 可能包含不会影响 build 配置的标志, 例如 --spawn_strategy ,了解所有最新动态。根据设计,Bazel 无法将此类标志绑定到单个目标。这意味着 没有连贯的方法可以将它们应用到转场中

如需解决此问题,您可以明确列举属于 过渡期间的配置这需要维护 --config 的 扩展,这是已知的界面缺陷。

开启“允许多个构建设置”的转换

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

# 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
  • //pkg:i_0//pkg:i_1: \(i\) 在 \([1..n]\)内

假设您实现了一个标志 适用 --//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\)”为 \(\{0,1\}\)中的所有广告客户 \(b_i\) 投放广告。

这会使 build 图以指数方式大于目标图,并且 相应的内存和性能后果。

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

深入阅读

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