RFC-0208:使用 SDK 分发软件包

RFC-0208:使用 SDK 分发软件包
状态已接受
区域
  • 软件交付
  • 测试
说明

提出了如何在 SDK 中分发一些树内软件包以供树外子软件包使用。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2023-02-15
审核日期(年-月-日)2023-02-10

摘要

此 RFC 介绍了我们将 Fuchsia 的平台代码库作为 SDK 附录来构建和分发软件包的策略。这些软件包旨在使用子软件包机制在下游进行组合。

该策略包括引入新的 .api 文件格式,该格式本身就是 Fuchsia 的新 SDK API Surface。格式本身以及使用该格式签入的文件都将接受 API 委员会审核。

设计初衷

实现了子软件包,以支持密封嵌套软件包依赖项。此功能的主要用例是明确包含特定软件包以及树外测试 (OOT)。替代方案(包括对基本软件包的引用)将被弃用,因为它容易发生故障。例如,如果从基础映像中移除软件包,则依赖项链会中断。

本文档介绍了提交到将从 Fuchsia 平台代码库分发的一组软件包的策略,以及这些软件包将如何作为子软件包包含在下游代码库中。

请注意,虽然此 RFC 侧重于将这些软件包分包到测试中的具体情况,但这是一种通过 SDK 分发软件包的通用机制。例如,产品所有者可能希望在生产环境的会话中添加 Fuchsia 团队编写的组件。通过 SDK 分发的第一个软件包可能用于测试,因为我们可能会在测试中采用一些在正式版中不可接受的捷径。除了时间安排注意事项之外,我们的目标是全面支持测试和生产场景下的软件包分发。

利益相关方

教员

hjfreyer@google.com

Reviewers:

  • aaronwood@google.com
  • dschuyler@Google.com
  • jsankey@google.com
  • richkadel@google.com

咨询了

  • etryzelaar@google.com
  • kjharland@google.com
  • shayba@google.com
  • wittrock@google.com

社交

此 RFC 之前已与软件交付、产品组装和组件框架的代表讨论过。

定义

  • 集成开发套件 (IDK) 是一组与构建系统无关的代码、二进制文件和数据文件,用于构建以 Fuchsia 为目标平台的程序,如此处所述。
  • Fuchsia 软件开发套件 (SDK)具有构建系统感知集成的 IDK(API、工具和语言集成工件)。其中最值得注意的是 Fuchsia SDK with Bazel
  • 树内是指 https://fuchsia.googlesource.com/fuchsia/ 代码库中的代码和构建规则。此仓库会将 IDK 作为输出生成。
  • 外部 (OOT) 是指代码库中非树内且使用 SDK 生成软件和产品的代码和构建规则。
  • Fuchsia 软件包是 Fuchsia 设备的软件分发单元,引用了在 Fuchsia 系统上执行某些程序所需的二进制文件/库、组件清单和数据文件。
  • 借助子软件包,Fuchsia 软件包可以通过封装将另一个 Fuchsia 软件包的特定版本引用为依赖项。
  • 本 RFC 中所述的 SDK 软件包是指明确标记为要包含在 IDK 中的树内 Fuchsia 软件包,并且可供 SDK 使用方使用。OOT 代码库可以使用其发布名称来针对这些软件包进行构建(例如,通过下载或子打包)。请注意,此设计的扩展可能允许 OOT 仓库分发自己的软件包。

设计

本文档中的关键字“必须”“不得”“必需”“会”“不会”“应”“不应”“建议”“可以”和“可选”将按 IETF RFC 2119 中的描述进行解释。

SDK 软件包由树内 build 生成,作为 IDK 引用的新工件。由于 IDK 包含适用于所有构建系统的 SDK 的一组通用工件,因此所有 Fuchsia SDK 集成都能够引用 SDK 软件包。

以下阶段在树内完成:

  1. 选择 - 选择要分发的软件包。
  2. 验证 - 确保将分发预期的一组软件包。
  3. 构建 - 为所有必需的架构构建所选软件包。
  4. 捆绑 - 生成将包含在 IDK 中的软件包目录结构。

SDK 软件包通过 SDK 中 sdk://packages/ 的软件包目录结构在 OOT 代码库中提供,以便立即使用。

本部分的其余内容详细介绍了每个阶段的设计。

发布 SDK 软件包

选择

我们不希望自动分发树内代码库中的所有软件包以供 OOT 使用,而是必须提交一组特定的软件包,以便与平台版本一起分发。

我们将创建一个名为 sdk_fuchsia_package 的新 GN 模板,需要提供以下信息:

  • 要分发的 fuchsia_package 目标。
  • 软件包的 SDK 类别(例如“合作伙伴”)。
  • 软件包被添加到 SDK 时的 API 级别
  • (可选)安排从 SDK 中移除软件包的 API 级别。
  • 软件包中预期包含的文件及其“处置方式”的列表。 下一部分将对此进行介绍。

此 GN 模板将使用 sdk_atom 目标,以便从围绕 SDK 基元验证 / 工具的既定路径中受益。

新的 GN 目标 sdk_package_bundle 将列出当前平台 API 级别的所有 sdk_fuchsia_package 目标。此目标将使用 sdk_molecule 目标强制执行软件包级检查。

借助此机制,平台所有者可以明确承诺为特定 API 级别提供特定软件包以供 OOT 使用(即允许在过段时间后废弃和移除软件包)。

作为实现细节,我们应避免依赖 GN 元数据,而应明确列出 sdk_fuchsia_package 目标中包含的所有软件包。

验证

分发树内软件包不仅是对软件包名称的承诺,也是对软件包至少部分内容的承诺。本部分介绍了为何需要此流程、为软件包声明协定的过程,以及在构建时验证软件包内容的机械流程。

背景

下面的示例说明了为什么提交合约很重要。假设有一个包含组件 sample.cm 的软件包 sample-packagesample-package 是分发的 OOT,因此 SDK 用户可能会依赖于 fuchsia-pkg://host/sample-package#meta/sample.cm。如果重命名或移除 sample.cm,该 OOT build 现在将会失败。这类似于平台映像中分发的树内软件包发生更改时的失败模式。更细微的是,如果 sample.cm 需要新的传入功能(或进行多项不兼容的清单更改,请参阅下文),之前的使用将意外失败。

仅提交软件包名称会导致上述问题,但在另一个极端,为软件包的所有内容显式设置版本会过于繁琐。例如,如果我们分发 archivist-for-embedding,该软件包的部分内容会因 50 个依赖项中的任一依赖项发生变化而发生变化。其中绝大多数更改不会更改 archivist 组件的界面或行为。因此,我们希望仅提交软件包内容的一部分。

协定

理想情况下,我们验证的软件包内容子集代表软件包用户可能依赖的协定。在理想情况下,合同应:

  1. 在树中明确表达。
  2. 是该软件包的 OOT 用户可能依赖的确切内容。
  3. 可在 OOT 中使用,因此工具仅允许用户依赖于协定。

精确定义这种“真实合同”很难,而且没有一刀切的定义。考虑组件清单等文件的内容时,这一点尤为明显。对 capability 路由或声明所做的某些更改不会导致兼容性问题(例如公开新 capability),而其他更改通常会导致兼容性问题(例如依赖于传入的新 capability)。其他更改的兼容性在很大程度上取决于上下文(例如更改组件的命令行参数)。目前还没有关于以这种方式推理组件更改兼容性的指南。

在我们能够就兼容性提供明确的指导之前,我们打算仅在 SDK 中提供少量经过仔细审核的软件包,这些软件包需接受 Fuchsia API 委员会的审核。

本部分的其余部分介绍了一种机制,用于检测对软件包“真实协定”的不完美(过于笼统)表示的更改。该文件将包含一组当前文件以及部分文件的哈希值,如果系统在此组文件中检测到任何更改,则会触发 API 审核。我们将在 SDK 中将此表示法与软件包一起发布,以便 OOT 用户了解所提供的合同,但我们只允许 OOT 用户使用包含哈希的文件子集。

这意味着,我们会对软件包协定中的更改过于敏感(以免遗漏任何内容),并且在允许从软件包中使用的内容方面过于保守(以免限制允许的使用情况)。这样,我们就可以在树内构建时对 SDK 中分发的内容进行验证,并在 OOT 构建时确保不会出现虚假的运行时错误。

请注意,SDK 软件包中的组件使用的 FIDL API 的语义更改与此验证阶段无关。您可以使用兼容性测试来解决此问题。未来的工作可能还包括进一步扩展该协定,以涵盖引用的 FIDL 文件。

声明协定

上一部分中定义的 SDK 软件包的构建规则包含软件包中预期文件的列表,以及这些文件的“处理方式”。选择一个处理方式可灵活地定义每个文件如何适应软件包的协定。

开始处置定义如下:

  • exact - 确切的文件内容是协定。必须明确确认对文件内容所做的更改,如下一部分所述。
  • internal - 文件不属于软件包的合同。

exact 匹配适用于内容为必须长期维护以实现兼容性的协定(例如组件清单)的文件。

internal 表示文件不是协定中的一部分,但存在于本地 build 中。

如有需要,可以将其他处置方式作为此 RFC 的扩展添加到其中。

验证合同

我们将使用熟悉的 .api 金文件机制(需要进行明确的 API 审核才能更改)来验证 SDK 软件包的合约。

例如:

// sample-package.api
// The name of the file matches the name of the package with '.api' added.
// The file format is JSON.
// Note: Comments will not be present in the real .api files.
{
    // Each top-level key is a file path in the resulting package.
    "meta/sample.cm": {
        // Hash is specified for files with "exact" disposition.
        hash: "...",
    },
    "data/some-file.json": {
        // If internal, this file is simply checked for presence
        // at build time.
        internal: true,
    }
}

在构建时,系统会根据输入 build 规则和生成的软件包的内容生成新的 .api 文件(其中所有未声明的文件输出都隐式为 internal)。如果此文件的内容与已签入到源代码控制下的 //sdk/packages 下的相应 .api 文件不匹配(通过深层 JSON 等式),构建将失败。与其他 .api 文件不匹配情况类似,新 .api 文件会存储为 build 输出,并在输出新的金标准时显示用于将 .api 文件复制到相应位置的建议命令。通过此流程,您可以轻松确认预期更改并上传以供审核。

我们将记录评估 SDK 软件包 .api 不匹配步骤,并就哪些更改可能不安全提供指导。

.api 文件将在后续步骤中提供在生成的软件包归档中,以便 OOT 工具可以使用它来验证是否不依赖于软件包的内部详细信息。

这种方法可确保在 SDK 软件包中发生有意义的更改时要求进行 API 审核,并且能够定义哪些更改是有意义的。

建筑

指定要分发的软件包必须针对我们为其生成 IDK 的所有架构进行构建(在撰写本文时,这些架构为 arm64x64)。我们可能会根据需要引入其他变种(例如 debugrelease),但最初所有软件包都将构建为发布版本。

此过程的输出是一组文件内容,以及每个 SDK 软件包的软件包清单。

此流程与现有构建流程之间的唯一区别在于,有一个专门用于 SDK 软件包的单独软件包清单,供在下一个阶段使用。

系统会生成子软件包中包含的二进制文件和共享库的调试符号,并将其上传到相应的 GCS 存储分区。该过程与直接在 IDK 软件包中分发的二进制文件和共享库所用的流程相同。

捆绑

构建完成后,软件包将分发到如下目录结构中:

sdk://
├── blobs
   ├── CONTENT_MERKLE_1
   └── CONTENT_MERKLE_2
└── packages
    ├── arm64
       ├── 10
          ├── debug
             ├── PACKAGE_BAR
                ├── api
                └── package_manifest.json
             └── PACKAGE_FOO
                 ├── api
                 └── package_manifest.json
          └── release
              ├── PACKAGE_BAR
                 ├── api
                 └── package_manifest.json
              └── PACKAGE_FOO
                  ├── api
                  └── package_manifest.json
       └── 11
           ├── debug
              ├── PACKAGE_BAR
                 ├── api
                 └── package_manifest.json
              ├── PACKAGE_BAZ
                 ├── api
                 └── package_manifest.json
              └── PACKAGE_FOO
                  ├── api
                  └── package_manifest.json
           └── release
               ├── PACKAGE_BAR
                  ├── api
                  └── package_manifest.json
               ├── PACKAGE_BAZ
                  ├── api
                  └── package_manifest.json
               └── PACKAGE_FOO
                   ├── api
                   └── package_manifest.json
    ├── x64
       ├── 10
          ├── debug
             ├── PACKAGE_BAR
                ├── api
                └── package_manifest.json
             └── PACKAGE_FOO
                 ├── api
                 └── package_manifest.json
          └── release
              ├── PACKAGE_BAR
                 ├── api
                 └── package_manifest.json
              └── PACKAGE_FOO
                  ├── api
                  └── package_manifest.json
       └── 11
           ├── debug
              ├── PACKAGE_BAR
                 ├── api
                 └── package_manifest.json
              ├── PACKAGE_BAZ
                 ├── api
                 └── package_manifest.json
              └── PACKAGE_FOO
                  ├── api
                  └── package_manifest.json
           └── release
               ├── PACKAGE_BAR
                  ├── api
                  └── package_manifest.json
               ├── PACKAGE_BAZ
                  ├── api
                  └── package_manifest.json
               └── PACKAGE_FOO
                   ├── api
                   └── package_manifest.json
    └── subpackage_manifests
        ├── META_FAR_MERKLE_1
        └── META_FAR_MERKLE_2

每个软件包清单都将放入由目标架构和 API 级别构建的目录路径:sdk://packages/<ARCH>/<API_LEVEL>/{debug/release}/<PACKAGE_NAME>。这些路径将作为产品可以依赖的稳定 SDK 协定。此目录将包含软件包的 package_manifest.json 以及用于验证的 API 文件。

此外,所有子软件包的清单都将由软件包 Merkle 存储在单独的顶级目录 sdk://packages/subpackage_manifests/<META_FAR_MERKLE> 中。

最后,所有 Blob 都将以内容寻址名称的形式存储在 sdk://blobs/<CONTENT_MERKLE> 中。blob 和清单目录是软件包的实现详细信息,可能会随时间而变化。产品不应依赖于这些路径才能保持稳定。

这些软件包使用的二进制文件和库的所有依赖项的 LICENSES 文件都将打包并包含在规范 SDK 许可流程中。

采用这种基于目录的结构有几个原因:

  • 软件包可以发布到代码库或用作子软件包,而无需解析中间文件。
  • 系统会自动删除重复的 blob(即使跨 API 级别),在常见的软件包包含相同 blob(例如共享库和二进制文件)的情况下,可显著节省空间(与单独归档每个软件包相比)。
  • 目录结构为支持多个 API 级别以及提供调试版和发布版本奠定了基础。
  • SDK 中已经存在用于与软件包仓库交互的工具 (ffxpm)。
  • 通过直接引用软件包清单,即可启用子软件包工作流,从而轻松直接发布到代码库。

这种方法的一个已知缺点是,如果将某个软件包声明为顶级软件包和子软件包,在这种情况下,package_manifest.json 将被复制。软件包清单文件足够小,因此将其列为顶级命名文件并通过其软件包 Merkle 标识并不会造成问题,而且还能让我们更好地隐藏子软件包详细信息。不过,此布局的整体简单性胜过这一缺点。

使用 SDK 软件包

使用 SDK 软件包 OOT 就像使用与系统架构和所需 API 级别相关的软件包 package_manifest.json 一样简单。应根据分发的 .api 文件检查软件包中文件的使用情况。

这样,OOT 工具和 build 便可针对常见陷阱(例如)正确发出警告:

  • 依赖于将被移除的已废弃软件包。
  • 依赖于将被移除的软件包中的已废弃文件。
  • 依赖于软件包中不属于该软件包的显式协定的文件。

实现此功能是每个具体构建系统的 SDK 维护者的责任,本文 RFC 中未对此进行说明。

包括

OOT 代码库可以在本地软件包 (L) 中对 SDK 软件包 (R) 进行子软件包化,如下所示:

  1. R 的所有 blob 都存储在本地(下载或复制)。
  2. L 的子软件包列表包含对 R 的 meta.far blob 的引用。
  3. 将 L 发布到代码库后,系统还会包含 R 的所有 blob(以及 R 的子软件包的所有 blob,以递归方式)。

然后,RFC-0154 中定义的子软件包解析将适用于软件包 L。

此步骤可确保该软件包在产品专用代码库中重新发布,并且可能会在此过程中接受离线 blob 压缩和其他优化。包含已发布软件包的远程 TUF 代码库不得直接用作 Fuchsia 设备的软件包源(因为其中包含的 blob 可能需要进一步处理才能符合特定产品的要求)。

请注意,此步骤将使用现有的软件包和 blob 发布工具(例如 ffx package),这些工具可能会通过执行“insert-if-absent”操作来针对重复的 blob 进行优化。

性能

此设计不会影响设备端性能。

这种设计可能会对构建性能产生负面影响,因为它会导致创建新的归档文件。不过,只有在创建 SDK 归档文件(GN 参数中的 build_sdk_archives=true)时,才会出现这些影响。与完整构建所花费的时间相比,创建新归档文件的开销应该可以忽略不计。

工效学设计

对于平台所有者,此设计提供了一种惯用接口,用于定义应作为 Fuchsia 平台版本的一部分分发哪些软件包。此外,为此用例引入 .api 文件可减少预测代码更改对一组 SDK 软件包的影响的认知工作量。此外,内置的自动检查可确保平台所有者对 SDK 软件包所代表的合同保留完全控制权。

向后兼容性

发布供 OOT 使用的软件包,即表示承诺提供一致的语义和命名。此 RFC 建议使用 API 版本控制来支持软迁移,并提供移除或重大更改我们将要提交的合同的方法。

安全注意事项

此提案允许针对 Fuchsia SDK 编写的 OOT 软件直接包含和引用随 SDK 版本分发的软件。虽然该软件本身是开源的,但在编译和分发二进制文件时必须小心谨慎。

该过程可能与目前在 IDK 中分发预构建二进制文件的过程类似,我们会提供包含所生成二进制文件的归档文件的哈希值。由于该归档文件包含包含 SDK 软件包的辅助归档文件的哈希值,因此 IDK 发行版也会提交到该辅助归档文件中提供的已编译二进制文件。

隐私注意事项

此设计不会改变用户数据的处理方式。

测试

在树内,我们将针对要从树中公开的软件包提供金文件 (.api) 测试(请参阅上文中的“验证”)。

我们将向示例代码库添加 OOT 测试,以展示如何使用从 SDK 中包含的软件包。我们将进行一项树内测试,该测试会尝试尽可能模仿此 OOT 测试的行为;这将是对上述 OOT 阶段的端到端测试,但在没有 Bazel 支持的情况下使用底层 ffx 命令。

树内支持 Bazel 时,我们还将有一个测试来提取生成的 SDK 软件包,以确保我们可以使用包含的工具对 SDK 软件包进行分包。

文档

从 SDK 软件包集中添加、修改和移除软件包的流程将记录在 https://fuchsia.dev 上。其中包括有关如何配置 .api 测试的说明。

缺点、替代方案和未知情况

直接在 SDK 中分发软件包的替代方案

此 RFC 的上一迭代提出了一种机制,即通过 CIPD 分别归档和上传 SDK 软件包,从而减少 SDK 软件包的任何大小变化。同样,SDK 用户不需要软件包分发,因此提供可选归档文件意味着这些用户可以完全避免下载该文件。

虽然这种模式对未来的工作非常有用,但它并非 SDK build 的标准,并且需要新的 build 工具来强制执行 SDK 类别、.api 测试,以及新的工具来下载和使用这些 SDK 软件包 OOT。

为了解决 SDK 大小问题,我们将仅包含一组用于启用测试用例的初始 SDK 软件包。例如,您可以考虑添加以下内容:

  • driver_test_realm
  • archivist-for-embedding
  • log-encoding-validator
  • realm_builder_server
  • test_ui_stack

这些作为 x64 的发布 build 构建的软件包总大小约为 43 MiB(未压缩),或 18 MiB(使用默认 gzip 设置压缩)。与当前 SDK 压缩后的 700 MiB 相比,这似乎是合理的。

包含的任何其他软件包都必须接受 Fuchsia API 委员会的审核,以确保内容和大小没有更改。此外,此框架的未来迭代版本可能会指向远程存储的 blob,以进一步帮助解决大小存储问题。

使用目录结构的替代方案

顶层的文件可以以 JSON 格式呈现相同的信息,而无需使用目录结构来强制执行架构和 API 级别。与更改目录结构相比,这种设计的优势在于文件的可塑性。不过,解析此类文件需要额外的 SDK 工具,而目前还没有此类工具。

此类工具的未来迭代版本可能会不再采用目录结构,如果出现这种情况,我们会构建并提供所需的工具。

在先技术和参考文档

Fuchsia 软件传送是一种更新软件系统的新方法,该领域有很多先前技术,这些文档对此进行了详细说明: