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

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

提议了如何在 SDK 中分发一些树内软件包以进行树外子封装。

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

摘要

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

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

设计初衷

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

此 RFC 描述了一种策略,用于提交将从 Fuchsia 平台代码库分发的一组软件包,以及如何将这些软件包作为子软件包包含在下游代码库中。

请注意,虽然此 RFC 侧重于将这些软件包子打包到测试中的特定情况,但这是通过 SDK 分发软件包的通用机制。例如,产品所有者可能希望在生产会话中包含 Fuchsia 团队编写的组件。通过 SDK 分发的第一个软件包很可能用于测试,因为我们可能会以在生产环境中不可接受的方式来节省测试时间。 除了时间安排方面的考虑因素外,我们的目标是全面支持在测试和生产场景中分发软件包。

利益相关方

辅导员

hjfreyer@google.com

审核者

  • 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、工具和语言集成工件),具有可感知 build 系统的集成。 其中最值得一提的是 Fuchsia SDK with Bazel
  • In-tree 是指 https://fuchsia.googlesource.com/fuchsia/ 仓库中存在的代码和 build 规则。此代码库会生成 IDK 作为输出。
  • 树外 (OOT):指不在树内且使用 SDK 来生成软件和产品的代码和 build 规则。
  • Fuchsia 软件包是 Fuchsia 设备的软件分发单元,其中包含在 Fuchsia 系统上执行某些程序所需的二进制文件/库、组件清单和数据文件。
  • 子封装允许 Fuchsia 软件包通过包含关系将另一个 Fuchsia 软件包的特定版本作为依赖项进行引用。
  • 本 RFC 中描述的 SDK 软件包是树内 Fuchsia 软件包,明确标记为包含在 IDK 中,并且可供 SDK 使用者使用。OOT 代码库可能会使用其发布名称来构建这些软件包(例如,通过下载或子打包)。请注意,此设计方案的扩展版本可能会允许 OOT 代码库分发自己的软件包。

设计

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

SDK 软件包由树内 build 生成,作为 IDK 引用的新制品。由于 IDK 包含适用于所有 build 系统的 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 Target sdk_package_bundle 将列出当前平台 API 级别的所有 sdk_fuchsia_package target。此目标将使用 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. 可开箱即用,因此工具仅允许用户依赖于合约。

很难精确定义这种“真实合同”,而且没有通用的定义。在考虑组件清单等文件的内容时,这一点尤为明显。对功能路由或声明的一些更改不会导致兼容性问题(例如,公开新功能),而另一些更改通常会(例如,依赖于新的传入功能)。其他更改的兼容性高度依赖于具体情况(例如,更改组件的命令行实参)。目前还没有关于以这种方式推理组件更改兼容性的指南。

在 Fuchsia API 委员会审核通过之前,我们打算仅在 SDK 中提供少量经过仔细审核的软件包,直到我们能够提供明确的兼容性指南。

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

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

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

声明合约

上一部分中定义的 SDK 软件包的 build 规则包含软件包中的预期文件列表以及这些文件的“处置”。选择处置方式可灵活定义每个文件在软件包合约中的作用。

初始处置的定义如下:

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

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

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

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

建筑

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

此流程的输出是每个 SDK 软件包的一组文件内容和一个软件包清单。

此流程与现有 build 流程的唯一区别在于,有一个单独的软件包清单列表专门用于下一阶段的 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 软件包

使用 OOT SDK 软件包将非常简单,只需使用与系统架构和所需 API 级别相关的软件包 package_manifest.json 即可。应根据分发的 .api 文件检查软件包中文件的使用情况。

这样一来,OOT 工具和 build 就可以正确警告常见陷阱,例如:

  • 依赖于将被移除的已废弃软件包。
  • 依赖于将被移除的软件包中的已弃用文件。
  • 依赖于软件包中不属于软件包显式合约的文件。

实现此功能是每个单独构建系统的 SDK 维护者的责任,并且此 RFC 中未对此进行描述。

包括

OOT 代码库可能会将 SDK 软件包 (R) 子封装在本地软件包 (L) 中,如下所示:

  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。

性能

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

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

工效学设计

对于平台所有者,此设计提供了一个惯用接口,用于定义哪些软件包应作为 Fuchsia 平台发布版本的一部分进行分发。此外,针对此用例引入 .api 文件可减少预测代码更改对 SDK 软件包集的影响所需的认知努力。此外,内置的自动检查功能可确保平台所有者完全掌控我们的 SDK 软件包所代表的合同。

向后兼容性

发布供 OOT 使用的软件包意味着承诺提供一致的语义和命名。此 RFC 建议使用 API 版本控制来支持软迁移,并提供一条可用于移除或大幅更改我们将要提交的合同的途径。

安全注意事项

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

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

隐私注意事项

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

测试

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

我们将向一个示例代码库添加 OOT 测试,以展示如何使用 SDK 中包含的软件包。我们将有一个树内测试,尽可能地模拟此 OOT 测试的行为;这将是对上述 OOT 阶段的端到端测试,但就底层 ffx 命令而言,没有 Bazel 支持。

in-tree 支持 Bazel 时,我们还将有一个测试来提取生成的 SDK Bundle,以确保我们可以使用随附的工具对 SDK 软件包进行子打包。

文档

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

缺点、替代方案和未知因素

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

此 RFC 的先前迭代版本提出了一种通过 CIPD 单独归档和上传 SDK 软件包的机制,以减少对 SDK 软件包的任何大小更改。同样,SDK 用户不需要软件包分发,因此如果归档是可选的,这些用户就可以完全避免下载。

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

为了解决 SDK 的大小问题,我们只会包含一小部分用于启用测试用例的初始 SDK 软件包。例如,以下内容可考虑纳入:

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

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

任何包含的其他软件包都将接受 Fuchsia API 委员会的审核,以确定其内容和大小是否发生变化。此外,此框架的未来迭代版本可能会指向远程存储的 blob,以进一步解决存储空间大小问题。

使用目录结构的替代方案

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

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

在先技术和参考资料

Fuchsia 软件交付是一种更新软件系统的新方法,该领域有许多现有技术,这些技术在以下文档中进行了进一步说明: