定义 Bazel 规则、宏和函数

概览

本页包含特定于 Fuchsia 的样式和最佳实践,用于编写规则、宏和函数。这些内容始终在 .bzl文件中定义,并且适用于这些文件的指南也适用于此处 。此外, BUILD.bazel 文件中与目标相关的指南也适用于由规则 和宏定义的目标。

本页是 Bazel 样式指南和最佳实践的一部分。

考虑是否适合使用新的宏和函数

与 Bazel 样式指南一样,Fuchsia 偏好使用 DAMP(描述性且有意义的短语)BUILD 文件,而不是 DRY (不要重复)。

具体而言,与 GN 中使用此类模板相比,Fuchsia build 在 Bazel 中使用特定于区域的宏和函数的频率要低得多。

使用共享的静态列表(其中内容必须保持同步,例如常见依赖项),而不是编写特定于区域的宏。

虽然特定于区域的模板可以在初始创建目标时减少输入,但它们会成为维护和迁移问题,尤其是对于自动化工具,因为特定于区域的宏会隐藏各个目标。如果您认为使用特定于区域的宏和函数会带来很大的好处,请咨询 build 团队(根据以往的经验,门槛很高)。

尽可能使用现有宏和规则

此类宏或规则的潜在使用范围越大,这一点就越重要。

尤其是当您的需求仅限于以下情况时,请考虑是否可以使用现有宏和/或规则来满足这些需求:

  • 生成元数据

  • 强制执行属性值

  • 最大限度地减少调用方必须指定的属性数量

在前两种情况下,尤其要考虑 Bazel 查询、 SHAC 规则、测试或其他机制是否可以 满足您的需求。Bazel 查询可以在依赖项树或目录树中定义的所有目标上运行。您可以编写一个运行查询并检查结果的测试吗?如果您的用例需要额外的属性,请考虑使用 标记。

使用现有规则和宏的原因包括:

  • 更易于理解,开发人员的磨合期更短

    • 熟悉 Bazel 的开发人员可以立即上手并了解目标。

    • 团队中一部分的开发人员可以了解代码库其他部分的目标。

  • AI 更容易理解和生成使用通用规则和宏的 Bazel 文件

  • 所需的 load() 语句更少

    • 内置标识符不需要任何 load 语句。

    • 文件中的其他目标可能已加载。

  • 出现 bug 的机会更少

    • 例如,方面仅在遍历所有相关目标时才能正常运行。自定义宏或规则可能会引入不会遵循的路径。请参阅 使用标准属性名称。

偏好使用符号宏而不是旧版宏

偏好编写 符号宏,其 使用 macro() 定义,而不是旧版宏(使用 def 定义的类 Python 函数,用于创建目标)。符号宏可以更清楚地记录属性及其类型,对属性执行类型检查,并确保在调用站点评估标签。它们还支持属性继承 (通过 inherit_attrs)。如需了解更多背景信息,请参阅 为什么不应使用旧版宏。

切勿在符号宏的实现函数中为实参指定默认值,因为默认值由 attrs 条目定义,并且系统会为每个属性提供一个值。

在某些情况下,有必要在符号宏周围使用旧版宏封装容器,但对于大多数开发者来说,这种情况应该非常罕见。

对宏中定义的目标进行可见性和访问权限检查

概览

就可见性而言,您可以将旧版宏视为在实例化位置内联展开。在 BUILD.bazel 文件中使用时,旧版宏定义的目标实际上是在该文件中定义的。因此:

  • 已定义目标的默认 visibility 与实例化 软件包相同,包括软件包 default_visibility(如果已指定)。

  • 宏可以使用(例如,添加到 deps)实例化软件包可见的任何目标。

    • 定义旧版宏的 .bzl 文件的位置无关紧要。

不过,就可见性而言,您可以将符号宏定义的目标视为在与定义宏的 .bzl 文件位于同一软件包(目录)的 BUILD.bazel 文件中定义。因此:

  • 已定义目标的默认 visibility 是包含 .bzl 文件的软件包(目录)。

  • 宏可以使用(例如,添加到 deps)包含 .bzl 文件的软件包(目录)可见的任何目标。

    • 例如,这对于 fidl_library() 添加的 FIDL 绑定支持库非常有用。

    • 但对于 HLCPP 支持库许可清单等内容,这会带来问题。

    • 如需了解详情,请参阅问题 446911800。

为宏中定义的目标指定可见性

公开目标

将传递给宏的 visibility 属性转发给宏定义的主要目标(即传递给 name 的目标)。visibility 属性也可以根据需要转发给宏的 doc 字符串中提及的其他已定义的公开目标。

私有目标

在符号宏中,所有其他已定义目标的可见性将默认为 ["//visibility:private"]。不过,对于旧版宏,您必须 为旧版 宏定义的所有目标指定可见性。

为旧版宏定义的所有目标指定可见性

为所有不使用 传递给宏的 visibility 属性的目标指定 visibility = ["//visibility:private"]。这是必要的,以防止 它们默认为软件包的 default_visibility(如果已指定)。

使用标准属性名称

在宏和规则中使用标准属性名称。例如,使用 "deps"、 "data" 甚至是 "tools",而不是 "images" 或 "scripts"。请参阅 一些普遍适用的 属性。

原因包括:

  • 为了提高可读性,以及与 尽可能使用现有宏和规则中的原因类似的其他原因 。

  • 方面仅在遍历所有相关目标时才能正常运行,并且 宏通常会配置为应用于 "deps" 和其他 常见属性(视情况而定)。不过,它们不太可能知道 例如,"rust_deps",并且未能应用于该属性可能会 排除与该方面相关的目标。

要求使用命名实参

公开函数和旧版宏(一种特殊类型的函数)通常应声明仅关键字实参(所有实参都在 *, 之后声明)。对于最多只有几个实参的函数,如果实参从符号名称中清晰可见,并且实参不会被错误地用于错误的位置(包括由于重构/重新排序),则可以例外。布尔实参和具有默认值的实参应始终出现在 * 之后。

虽然这对于旨在供代码库其他部分使用的符号最为重要,但也适用于所有 .bzl 文件中的公开符号。

这有助于强制执行 Bazel .bzl 样式指南的 指导原则:“调用 宏时,仅使用关键字实参。这与规则一致,并大大提高了可读性。”

声明宏使用的所有实参

如果宏(可选)使用实参,请在宏实现的形参列表中显式声明该实参,而不是从 kwargs 中提取该实参。例如,避免以下情况:

# Do NOT do this:
testonly = kwargs.get("testonly", False),

声明实参可以更清楚地了解哪些实参与宏实现相关(例如,与宏调用的宏相关),并提供一个查看默认值的位置。对于旧版宏,它还可以确保实参有文档记录。(这主要与 Fuchsia Bazel SDK 相关。)

与任何其他实参一样,尤其是 所有 build 规则共有的属性 和 所有测试规则共有的属性 (_test),您必须确保将 实参传递给所有支持它的宏和规则,因为这些实参不会在 **kwargs 中。

评论

  • 为所有公开函数(不以下划线开头的函数)提供函数级注释。

  • 为所有规则和宏提供 doc 字符串。

  • 为所有规则和宏属性提供 doc 字符串。

    • 对于私有属性(以下划线开头的属性),如果含义从 default 值中显而易见,则可以省略 doc 字符串。

doc 字符串

  • 在 Fuchsia 平台中,doc 字符串旨在在源文件中读取,而不是在某些生成的文档中读取。因此,偏好针对前者进行优化,而不是针对某些生成的文档的外观进行优化。

  • 长 doc 字符串:

    • 在合理的情况下,偏好将顶级单句说明完全写在与 doc= 同一行。

    • Bazel 中没有 严格的行长度 限制。

    • 如果需要多行,请使用三引号编写多行字符串。

    • 避免附加常规字符串。

  • 多行 doc 字符串

    • 在与 doc 实参同一行开始多行字符串 (doc = """Begin the comment...)。

    • 在 doc 中的 d 下开始后续行,类似于 Python 注释在第一个 " 下开始下一行的方式。

    • 这针对一致性和可读性进行了优化,同时承认它对于生成的文本来说并不理想。

    • 使用与 doc 对齐的单独一行上的三引号 (""") 结束多行字符串。

规则和宏实现函数名称

使用前导下划线为 rule() 和 macro() 实例命名 implementation 函数,后跟规则或函数的名称,并以 _impl 结尾。

在宏中引用目标名称时使用变量

如果宏定义了一个目标,然后在另一个目标中依赖于该目标,请使用前一个目标的名称定义一个变量,并将其用于其 name 属性以及后一个目标的 deps。

这可以明确地链接这两个目标,尤其是在复杂情况下,其中存在基于其他目标名称的多个级别的目标名称,并且更容易突出显示和搜索。

除非可以提高可读性(例如,当所有目标名称都在一个位置定义时),否则请勿对内部未使用的目标使用变量。

封装内置规则、宏和函数

在编写宏以在 Fuchsia 平台代码库中替代常见的 Bazel 规则、宏或函数时,请在封装的名称前添加 fx_ 前缀。例如,应使用 fx_cc_library() 而不是 cc_library()。 “常见”包括 Bazel 中内置的符号(包括 native.*)以及常见代码库(例如 rules_cc)中的符号。对于其他 冲突(例如与 package 无关的 fx_package()),也请使用此前缀。请勿使用 a fuchsia_ 前缀,因为 fuchsia_... 文件和符号位于 Fuchsia Bazel SDK 中。

避免配置转换

Fuchsia 平台目标应已在正确的配置中构建。如果您认为需要转换,则很可能不需要。请与 build 团队联系;只有 build 团队才能添加转换或新平台。

将工具作为可执行标签属性传递

运行工具的规则应将其作为 attr.label(executable = True, cfg = "exec"),并使用 ctx.executable 读取它。

反模式

  • 通过 PATH 查找工具。基础架构沙盒不会设置它,并且 Bazel 无法跟踪它不知道的工具。请改为将该工具作为可执行标签属性传递。

  • 将嵌入标签的 JSON 或字典 blob 传递给规则或宏。Bazel 仅在标签类型属性中看到标签,因此它不会构建或跟踪这些依赖项。请改为使用标签属性和提供程序。

  • 通过 bazel-bin 符号链接查找 build 输出。它指向一个配置,并且转换可以将您想要的输出放在不同的 bazel-out 目录中。通过提供程序传递输出,或明确报告其路径。

  • 依赖于上游规则集的实现详细信息,例如 rules_cc 使用的 _solib_ 目录前缀。当规则集更新时,它们会在不通知的情况下更改。

避免缓存或远程处理大型工件

请参阅避免缓存大型工件。