[Bazel]自定义命令行编译标志

时间:2022-07-24
本文章向大家介绍[Bazel]自定义命令行编译标志,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1 名词

名词

释义

aspect

将自定义行为附加到规则的逻辑包。这与配置相似,但不同的是 aspect 不会更改原始规则。

build flag

构建标志,设置配置的命令行标志,比如 --cpu,它好比 key-value 的 key。根据定义,用户可以直接在任何构建上进行设置。

build setting(configuration setting)

构建设置,是一条配置信息。可以认为配置为 key-value 映射。构建标志产生构建设置,但是可以通过其他方式(例如通过transitions)来设置构建设置。没有附带标志的构建设置对用户不可见。规则设计者可以利用它,例如使规则在其依赖项上设置隐式属性。

transition

表示跨依赖项边缘的配置转换。即可以实现读入一组构建设置,并输出一组构建设置。

provider

简单值对象的构造函数,称为提供 provider 实例

这里名词只做个索引,方便理解,可能现在反而让理解变得更加复杂,不过没关系,我们主要是实现的就是自定义 build flag。更多参见这里[1]

2 背景

Starlark Configurations 是 Bazel 的 API,用于自定义项目的构建方式。使用 Starlark Configurations 可以让你:

  • 定义项目自己的编译标志,而不再需要 --define
  • 对于规则,可以实现默认的编译配置
  • 不像传统的 --cpu--copt--compilation_mode=(-c) 等方式,是 Bazel 版本内置,而用户自定义的编译设置可以在 .bzl 文件中实现,不需要重新编译 Bazel 源码就可以实现

我们最终实现:

$ bazel build //my:binary --some_feature_specific_to_my_app=fancy_mode

或者 some_feature_specific_to_my_app 内部默认配置:

$ bazel build //my:binary

3 自定义构建设置的定义

我们可以自定义构建标志以及规范它的构建设置,即比如我们定义了构建设置 week 规则,它可能的值只能是星期{1,2,3,4,5,6,7}。当然,简单的,我们可以定义构建设置,只限制值类型,而不限制值内容。

构建设置相关的规则跟其他规则定义差不多,区别就是看有没有 build_setting 属性。示例:

# 定义构建设置规则 string_flag
string_flag = rule(
    implementation = _string_impl,
    build_setting = config.string(flag = True)
)

config 规定了该构建设置规则的值类型为 string 类型,还可以设置 intboolstring_list 类型[2]flag = True 表示该构建设置能够允许用户在命令行上设置,否则的话只能由规则编写者在内部默认设置或者通过 transitions 设置。

4 自定义构建设置的实现和实例化

同我们之前文章介绍的自定义规则一样,自定义构建设置规则也需要有实现,即 implementation = _string_impl_string_impl 函数。我们可以在 _string_impl 函数中通过 ctx.build_setting_value 获取构建标志的值:

def _string_impl(ctx):
    # do something...
    value = ctx.build_setting_value
    # do something...

比如前面说的利用 string_flag 实现一个构建设置目标 week,需要对 week 的值做约束,那么需要在 _string_impl 里做检测,如果不匹配,则提示错误:

BuildSettingInfo = provider(
    doc = "A singleton provider that contains the raw value of a build setting",
    fields = {
        "value": "The value of the build setting in the current configuration. " +
                 "This value may come from the command line or an upstream transition, " +
                 "or else it will be the build setting's default.",
    },
)

def _string_impl(ctx):
    allowed_values = ctx.attr.values
    value = ctx.build_setting_value
    if len(allowed_values) == 0 or value in ctx.attr.values:
        return BuildSettingInfo(value = value)
    else:
        fail("Error setting " + str(ctx.label) + ": invalid value '" + value + "'. Allowed values are " + str(allowed_values))

string_flag = rule(
    implementation = _string_impl,
    build_setting = config.string(flag = True),
    attrs = {
        "values": attr.string_list(
            doc = "The list of allowed values for this setting. An error is raised if any other value is given.",
        ),
    },
    doc = "A string-typed build setting that can be set on the command line",
)

实例化 week 构建设置:

# BUILD file

string_flag(
    name = "week",
    build_setting_default = "1",
    values = [
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7"
    ]
)

build_setting_default 是内置保留属性,表示对构建设置的默认值设置。

完成 week 构建设置的定义(实例化),可以通过命令行传入值设置:

$ bazel build :week --//:week=2
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:13:10: allowed_values =  ["1", "2", "3", "4", "5", "6", "7"]
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:14:10: value =  2

如果我们设定的值不是 {1, 2, 3, 4, 5, 6, 7},则会提示错误。当然,如果你定义 week 目标(构建设置)的时候,不设置 values 属性,则对命令行传入的值没有限制。

注意:传递自定义命令行参数时 -- 是紧跟构建设置目标的。string_flag 构建设置规则在实际工程中我们也不需要自己去实现,可以通过 `bazel-skylib`[3] 加载:load("@bazel_skylib//rules:common_settings.bzl", "string_flag")

5 自定义规则绑定自定义构建设置

比如我们定义了一个 date 规则,我们在构建 date 的目标时,希望能够在命令行获取 week 参数,则我们需要在 date 的规则实现中能够获取 week 的配置值。

def _date_impl(ctx):                                                            
    print("星期", ctx.attr._today[BuildSettingInfo].value)                      
    return []
    
date = rule(
    implementation = _date_impl,
    attrs = {
        "_today" : attr.label(default = ":week")
    }
)

通过在 date 规则中声明私有属性 _today,即 date 规则使用用户无法直接更改 _today 属性值,保证了只能在命令行上设置值或者设置默认值。同时 _today 属性绑定 :week 目标,从而在 date 规则实现中可以获得 _today 属性值。

完成规则实现后,就可以在 BUILD 文件中定义 date 目标:

load("//:deps.bzl", "string_flag", "date")

date(
    name = "today"
)

构建 today 目标:

$ bazel build :today --//:week=3
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:13:10: allowed_values =  ["1", "2", "3", "4", "5", "6", "7"]
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:14:10: value =  3
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:33:10: 星期 3

$ bazel build :today
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:13:10: allowed_values =  ["1", "2", "3", "4", "5", "6", "7"]
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:14:10: value =  1
DEBUG: /home/biedamingming/workspace/bazel/build_setting_test/deps.bzl:33:10: 星期 1

6 小结

本文对实现自定义命令行构建设置有了个基本的了解,包括规则定义、实例化和实际应用。更多的使用场景可以参考官方文档,包括 Bazel 内置的 label_flaglabel_setting;结合 select() 使用;

参考资料

[1]

这里: https://docs.google.com/document/d/1vc8v-kXjvgZOdQdnxPTaV0rrLxtP2XwnD2tAZlYJOqw

[2]

类型: https://docs.bazel.build/versions/master/skylark/lib/config.html

[3]

bazel-skylib: https://github.com/bazelbuild/bazel-skylib/releases