构建系统策略

本文档详细介绍了与 Fuchsia 构建应当如何运作有关的设计原则和特定技术决策。这些原则适用于使用 Fuchsia 构建的所有模式,例如是否通过交互性工程工作流或通过例如 CI/CQ 的自动化系统的方式。

构建的目标和优先级

与任何系统相同,该构建常常服从于多个冲突的需求。出现冲突时,我们通常追求满足这些优先事项,它们按重要性顺序排列:

  1. 满足客户需求,这是由 Fuchsia 技术领导制定的。
  2. 确保正确性:产生期望的输出。
  3. 促进可维护性:文档、可靠的工程流程。
  4. 提升性能:以更小代价执行相同构建。

构建的期望属性

下列内容被认为是该构建的良好属性:

  • 封闭性(hermeticity)——构建是独立的,既不会影响外部软件和配置,也不会受到外部软件和配置的影响。
  • 可重复性(repeatability)和可再现性(reproducibility)——来自同一源工作区的两个构建确定性地产生相同的输出或相同的结果。可重复性能够提高安全性和审核效率,简化故障排除。
  • 高效(efficient)——构建应仅花费时间进行与构建相关的工作,并且必须力求将对人力和基础设施代价的影响降至最低。
  • 可移植性(portability)——构建应在所有受支持的主机平台上产生一致的结果。

这些是理想。我们致力于实现这些理想,并用它们作为量度来度量我们的进度。

Python 脚本用于构建行为

Python 脚本可用于构建行为(build action)。

请遵从 Google Python 风格指南

Fuchsia 当前使用 Python 3.8。所有 Python 源文件都以下面的内容开头:

  1. #!/usr/bin/env python3.8

Shell 脚本用于构建行为

shell 脚本可以用于构建行为。

我们鼓励使用 shell 脚本执行可以用一些简单的 shell 指令表示的任务。对于复杂操作,最好使用其他语言。

请遵从 Google shell 脚本风格指南。请使用 shellcheck 寻找和更正常见 shell 编程错误。

我们更倾向于 POSIX(又名 Bourne)shell 脚本,以实现跨众多主机平台的可移植性。如果您正在维护现有 bash 脚本,请减少使用的特性至 3.2 版本,或考虑将其重写为 POSIX shell 脚本。要检查您的脚本是否与 POSIX 兼容,您可以使用:

  1. shellcheck --shell=sh

在 POSIX shell 上运行的脚本应当以下列内容开头:

  1. #!/bin/sh

特定需要 bash 的脚本应当以下列内容开头:

  1. #!/bin/bash

迁移

构建系统可以协助执行诸如编译器特性、新工具或各种最佳做法激增的迁移。遗留的不良行为通常可以表示为对于应用此行为的 config() 的依赖。对要被替换的旧版工具或模板的使用可以通过对 group() 目标的依赖来捕获。

记录计划

我们总是欢迎您为提升代码质量作出努力,但是您应当在开始之前对于您发起的工作有清晰的计划。动力不足、半途而废的迁移可能比完全不迁移还要糟糕。

建立回归终止

建立回归停止

假如代码库每 8 个月翻一番,而你尽早工作以防止引入旧行为的新实例。通过建立回归停止(regression stop),您可以按其倍增率的节奏,“被动”清理代码库,也就是说,代码库每增加一倍,您将已被动清理了一半代码库。

确保 OWNERS 文件保护了许可列表(allowlist),并且将进行迁移的 POC 列为所有者。由于所有者是由文件定义的,因此最好将许可列表细分为不同的 BUILD.gn 文件。例如,与 Rust 相关的 config() 目标被拉至 //build/config/rust 中以更好地管理OWNERS 分配。

文档迁移/清理步骤

发布清晰的文档,解释迁移的性质、如何参与以及如何进行相关维护工作。这使您的迁移工作得以扩展,并防止任何人成为正在进行的迁移工作的障碍,例如当他们受到大量支持请求的压力时或无法处理问题时。

您可以回顾范例 C++ 隐式转换

简化与自动化许可列表的维护

许可列表(allowlist)很容易表示为针对 GN 标签的 visibility(可见性)列表。这为自动化分析开启了大门,并且使得违反许可列表的改动很快故障而无法构建。

当将目标列入许可列表以使用您要迁移的旧行为时,要使得这些目标的所有者能够轻松进行简单的重构,例如:通过将基目录(base directory)(而非单个目标)列入许可列表,而在单个目标所在的目录中对其进行重命名。

记录步骤以重新生成(regenerate)和修整(trim)任何许可列表,以便任何人都可以处理它们。

请看下面的示例:

  1. group("foo_allowlist") {
  2. # ________ _________ ________ ________
  3. # |\ ____\|\___ ___\\ __ \|\ __ \
  4. # \ \ \___|\|___ \ \_\ \ \|\ \ \ \|\ \
  5. # \ \_____ \ \ \ \ \ \ \\\ \ \ ____\
  6. # \|____|\ \ \ \ \ \ \ \\\ \ \ \___|
  7. # ____\_\ \ \ \__\ \ \_______\ \__\
  8. # |\_________\ \|__| \|_______|\|__|
  9. # \|_________|
  10. # 这是一份使用了不推荐的“foo”工具的目标的允许列表。
  11. # 截至 2021 年 4 月,我们不再使用“foo”。用户应当
  12. # 迁移至新工具“bar”,这篇指南中对其进行了描述:
  13. # https://fuchsia.dev/...
  14. #
  15. # 要重新生成:
  16. # fx gn refs $(fx get-build-dir) //path/to:foo_allowlist | sed 's|\(.*\):.*|"\1/*",|' | sort | uniq
  17. #
  18. # 要修整:
  19. # scripts/gn/trim_visibility.py --target="//path/to:foo_allowlist"
  20. visibility = [
  21. "//src/project1/*",
  22. "//src/project2/*",
  23. ...
  24. ]
  25. }

接下来在其他地方,自动添加对于该列入许可列表的目标的依赖。

  1. # 调用旧 foo 工具。
  2. # 对于新用法,请考虑转为使用新的 bar 工具!
  3. # 参阅:
  4. # https://fuchsia.dev/...
  5. # ...
  6. template("foo") {
  7. action(target_name) {
  8. ...
  9. deps += [ "//build/foo:foo_allowlist" ]
  10. }
  11. }