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

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

提议如何在 SDK 中分发一些树内软件包以实现树外子打包。

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

摘要

此 RFC 说明我们以 SDK 附录的形式从 Fuchsia 的平台代码库构建和分发软件包的策略。这些软件包旨在使用子打包机制在下游组成。

该策略包括引入新的 .api 文件格式,这本身是 Fuchsia 的新 SDK API Surface。格式本身以及使用此格式的签到文件需接受 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、工具和语言集成工件)。其中最引人注目的是搭配 Bazel 的 Fuchsia SDK
  • 树内是指位于 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-package 为例。sample-package 是 OOT 的,因此 SDK 用户可能依赖于 fuchsia-pkg://host/sample-package#meta/sample.cm。现在,如果重命名或移除 sample.cm,该 OOT 构建将会失败。这与平台映像中分发的树内软件包的失败模式类似。较容易忽略的是,如果 sample.cm 需要新的传入功能(或任何不兼容的清单更改,请参阅下文),则之前的使用会意外失败。

只需提交软件包的名称即可解决上述问题,但对软件包的所有内容进行明确版本控制的另一种极端情况会过于繁琐。例如,如果我们分发 archivist-for-embedding,该软件包的部分内容会由于 50 个依赖项中任何一个的变化而发生变化。其中绝大多数更改都不会更改 archivist 组件的接口或行为。因此,我们希望仅提交软件包中选定的部分内容。

合约

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

  1. 在树内明确表示。
  2. 这正是软件包的 OOT 用户可能依赖的内容。
  3. 提供 OOT,以便工具仅允许用户依赖于合同。

精确定义这种“真正的协定”非常困难,而且没有放之四海而皆准的定义。考虑到组件清单等文件的内容,这一点尤其明显。一些对功能路由或声明的更改不会导致兼容性问题(例如公开新功能),而其他更改通常不会引起兼容性问题(例如,依赖于新传入功能)。其他更改的兼容性与上下文密切相关(例如,更改组件的命令行参数)。目前,尚不提供相应的指南来推断以这种方式进行组件更改的兼容性。

在能够提供明确的兼容性指导之前,我们打算仅在 SDK 中搭载少量经过仔细审核的软件包,并等待 Fuchsia API Council 审核。

本部分的其余内容介绍了一种机制,该机制用于检测软件包“真实合同”的不完美(过于笼统)表示法的变化。这将由一组现有文件以及部分文件的哈希值组成,如果检测到这组文件发生更改,则会触发 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,
    }
}

构建时,系统会根据输入构建规则和所生成软件包的内容生成新的 .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 将被重复。软件包清单文件足够小,因此无需将其同时列为顶级、已命名的文件并由其软件包 Merrkle 标识,这样可以让我们更好地隐藏子打包详细信息。这种布局的整体简洁性优于这种布局的优点。

使用 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),这些工具可以通过执行“如果不存在时插入”操作针对重复的 blob 进行优化。

性能

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

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

工效学设计

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

向后兼容性

发布软件包以供 OOT 使用是为了确保提供一致的语义和命名。此 RFC 提议使用 API 版本控制以支持软迁移,并提供一个途径来移除或大幅更改我们将承诺的合同。

安全注意事项

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

该流程可能与目前在 IDK 中分发预构建二进制文件的流程类似,在该 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 的标准,需要新的构建工具来强制执行 SDK 类别、.api 测试,以及用于下载和使用这些 SDK 软件包 OOT 的新工具。

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

  • driver_test_realm
  • 用于嵌入的档案家
  • 日志编码验证器
  • realm_builder_server
  • test_ui_stack

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

包含的所有其他软件包都要接受 Fuchsia API 委员会在内容和大小方面的审核。此外,此框架的未来迭代可能会指向远程存储的 blob,以进一步帮助解决存储空间大小方面的问题。

使用目录结构的替代方案

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

此工具的未来迭代可能会脱离目录结构,我们将构建并提供所需的工具,以防出现此类情况。

现有艺术和参考资料

Fuchsia Software Delivery 是一种更新软件系统的新方法,以下文档中对相关先前技术进行了详细介绍: