本页介绍了 Starlark 的基本样式指南,还包含有关宏和规则的信息。
Starlark 是一种 用于定义软件构建方式的语言,因此它既是 编程语言,也是配置语言。
您将使用 Starlark 编写 BUILD 文件、宏和构建规则。宏和规则本质上是元语言,它们定义了 BUILD 文件的编写方式。
BUILD 文件旨在简单且重复。
所有软件的读取频率都高于编写频率。对于 Starlark 来说尤其如此,因为工程师会读取 BUILD 文件,以了解其目标的依赖项及其构建的详细信息。这种读取通常会匆忙进行,或者与其他任务并行进行。因此,简单性和可读性非常重要,这样用户才能快速解析和理解 BUILD 文件。
当用户打开 BUILD 文件时,他们希望快速了解文件中的目标列表;或查看该 C++ 库的来源列表;或从该 Java 二进制文件中移除依赖项。每次添加一层抽象,都会让用户更难完成这些任务。
许多不同的工具也会分析和更新 BUILD 文件。如果 BUILD 文件使用抽象,工具可能无法对其进行修改。保持 BUILD 文件的简单性将有助于您获得更好的工具。随着代码库的增长,为了更新库或进行清理,跨多个 BUILD 文件进行更改的情况会越来越频繁。
总体建议
- 使用 Buildifier 作为格式设置工具和 lint 工具。
- 遵循测试指南。
样式
Python 样式
如有疑问,请尽可能遵循 PEP 8 样式指南。 特别是,请使用四个空格而不是两个空格进行缩进,以遵循 Python 惯例。
由于
Starlark 不是 Python,
因此 Python 样式的某些方面不适用。例如,PEP 8 建议使用 is 对单例进行比较,而 is 在 Starlark 中不是运算符。
文档字符串
使用文档字符串记录文件和函数。
在每个 .bzl 文件的顶部使用文档字符串,并为每个公共函数使用文档字符串。
记录规则和方面
规则和方面及其属性,以及提供程序及其字段,应使用 doc 实参进行记录。
命名惯例
- 变量和函数名称使用小写字母,字词之间用下划线分隔 (
[a-z][a-z0-9_]*),例如cc_library。 - 顶级私有值以一个下划线开头。Bazel 强制规定私有值不能从其他文件使用。局部变量不应使用下划线前缀。
行长
与 BUILD 文件一样,由于标签可能很长,因此没有严格的行长限制。
如果可以,请尽量每行最多使用 79 个字符(遵循 Python 的
样式指南,PEP 8)。此指南不应严格执行:编辑器应显示 80 列以上,自动化更改会经常引入更长的行,并且人们不应花费时间拆分已可读的行。
关键字实参
在关键字实参中,最好在等号两边加空格:
def fct(name, srcs):
filtered_srcs = my_filter(source = srcs)
native.cc_library(
name = name,
srcs = filtered_srcs,
testonly = True,
)
布尔值
对于布尔值(例如在规则中使用布尔属性时),最好使用值 True 和 False(而不是 1 和 0)。
仅将 print 用于调试
请勿在生产代码中使用 print() 函数;它仅用于调试,并且会向 .bzl 文件的所有直接和间接用户发送垃圾内容。唯一的例外是,您可以提交使用 print() 的代码,前提是该代码默认处于停用状态,并且只能通过修改源代码来启用,例如,如果 print() 的所有用法都受 if DEBUG: 的保护,其中 DEBUG 硬编码为 False。请注意,这些语句是否足够有用,足以证明其对可读性的影响是合理的。
宏
宏是一种函数,可在加载阶段实例化一个或多个规则。一般来说,请尽可能使用规则而不是宏。用户看到的构建图与 Bazel 在构建期间使用的构建图不同 - 宏在 Bazel 执行任何构建图分析之前展开。
因此,如果出现问题,用户需要了解宏的实现,才能排查构建问题。此外,bazel
query 结果可能难以解读,因为结果中显示的目标
来自宏展开。最后,方面不知道宏,因此依赖于方面的工具(IDE 等)可能会失败。
宏的一种安全用法是定义旨在直接在 Bazel CLI 或 BUILD 文件中引用的其他目标:在这种情况下,只有这些目标的最终用户需要按需知密,并且宏引入的任何构建问题都不会远离其用法。
对于定义生成的目标的宏(宏的实现详细信息,不应在 CLI 中引用,也不应由该宏未实例化的目标依赖),请遵循以下最佳实践:
- 宏应采用
name实参,并使用该名称定义目标。 该目标将成为该宏的主要目标。 - 生成的目标,即宏定义的所有其他目标,应:
- 其名称以
<name>或_<name>为前缀。例如,使用name = '%s_bar' % (name)。 - 具有受限可见性 (
//visibility:private),并且 - 具有
manual标记,以避免在通配符目标(:all、...、:*等)中展开。
- 其名称以
name只能用于派生宏定义的目标的名称,而不能用于其他任何用途。例如,请勿使用名称来派生并非由宏本身生成的依赖项或输入文件。- 在宏中创建的所有目标都应以某种方式与主要目标耦合。
- 按照惯例,
name应是定义宏时的第一个实参。 - 保持宏中的参数名称一致。如果参数作为属性值传递给主要目标,请保持其名称不变。如果宏形参与常见规则属性(例如
deps)的用途相同,请按照属性的命名方式进行命名(请参阅下文)。 - 调用宏时,请仅使用关键字实参。这与规则一致,并且大大提高了可读性。
工程师通常会在相关规则的 Starlark API 不足以满足其特定用例时编写宏,无论规则是在 Bazel 中以原生代码定义,还是在 Starlark 中定义。如果您遇到此问题,请询问规则作者是否可以扩展 API 以实现您的目标。
根据经验,宏越像规则越好。
另请参阅宏。
规则
- 规则、切面及其属性应使用 lower_case 名称(“蛇形命名法”)。
- 规则名称是名词,用于从其依赖项(或对于叶规则,用户)的角度描述规则生成的主要工件类型。这不一定是文件后缀。例如,生成旨在用作 Python 扩展的 C++ 制品的规则可以称为
py_extension。对于大多数语言,典型规则包括:*_library- 编译单元或“模块”。*_binary- 生成可执行文件或部署单元的目标。*_test- 测试目标。这可以包括多个测试。预计*_test目标中的所有测试都是同一主题的变体,例如测试单个库。*_import:封装预编译工件(例如.jar或在编译期间使用的.dll)的目标。
- 为属性使用一致的名称和类型。一些普遍适用的属性包括:
srcs:label_list,允许文件:源文件,通常由人工编写。deps:label_list,通常不允许文件:编译依赖项。data:label_list,允许文件:数据文件,例如测试数据等。runtime_deps:label_list:编译不需要的运行时依赖项。
- 对于任何行为不明显的属性(例如具有特殊替换的字符串模板,或使用特定要求调用的工具),请使用
doc关键字实参向属性的声明(attr.label_list()或类似项)提供文档。 - 规则实现函数几乎总是私有函数(以一个下划线命名)。一种常见的样式是为
myrule的实现函数命名为_myrule_impl。 - 使用明确定义的 提供程序接口在规则之间传递信息。声明和记录提供程序字段。
- 设计规则时请考虑可扩展性。请考虑其他规则可能想要与您的规则互动、访问您的提供程序以及重复使用您创建的操作。
- 在规则中遵循性能指南。