RFC-0093 - 组件清单的设计原则

RFC-0093:组件清单的设计原则
状态已接受
区域
  • 组件框架
说明

有关用于组件清单的 `.cml` 和 `.cm` 格式的设计注释。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-04-26
审核日期(年-月-日)2021-05-05

摘要

本文档记录了有关用于组件清单.cml.cm 格式的设计讨论、原则和决策。

下文所述的大部分判决都是在 2018 年至 2019 年间做出的。

  1. 组件清单包含前端和后端
  2. 组件由清单定义
  3. 组件清单是声明式的
  4. 组件清单可以来自各种来源

术语库

  • .cml组件清单源文件的常见文件扩展名。CML 文件是一种用于声明组件的 JSON5 DSL
  • ComponentDecl 是一个 FIDL 表,它是组件清单的规范有线和存储格式。
  • .cm 是包含 FIDL 信封二进制格式的 ComponentDecl 的文件的常见文件扩展名。
  • cmc 是一种命令行主机工具,用于从 .cml 文件生成 .cm 文件。它在 Fuchsia 源代码树中构建,并通过 Fuchsia SDK 作为预构建的可执行文件分发。

#1:组件清单具有前端和后端

人类和机器有不同的需求和偏好。在设计组件清单语法和格式时,一个关键的设计原则是为组件框架创建一个可读取的单一后端格式,并为开发者创建一个单独的前端(最初是单一的)。此设计决策具有以下优势:

  1. 您可以分别优化后端和前端,以满足不同的设计目标。
  2. 这样一来,您就可以在不修改后端的情况下发展前端,反之亦然。
  3. 虽然目前只有一个前端,但可以引入其他前端,例如满足不同受众群体的不同设计目标,或迎合便利性、熟悉度或风格偏好。

为了实现这些目标,SDK 提供了 [cmc](“组件清单编译器”),这是将组件清单源文件 (.cml) 转换为清单二进制文件 (.cm) 的标准工具。cmc 通常与构建系统透明地集成,这意味着开发者通常会与源文件互动,除非他们要调试清单或对清单进行分析。

ComponentDecl:组件清单后端

ComponentDecl 是组件清单的规范存储和有线格式。它旨在由代码(例如组件管理器、组件解析器和清单分析工具,如 fx scrutiny)编写和解读。这种格式需要实现以下目标:

  1. 必须明确且具有自我描述性。应该可以直接从清单的内容推导出清单的含义。
  2. 它必须能够随着时间的推移而不断发展,支持向前和向后兼容性。
  3. 必须易于解析,并避免不必要的格式转换。 否则,这会不必要地增加处理组件清单(包括组件管理器)的代码中出现 bug 或遭受攻击的风险。
  4. 它必须易于与运行时、组件和与组件清单交互的工具集成。

这些设计目标自然而然地促成了格式选择:FIDL。FIDL 是 Fuchsia 中 IPC 的标准有线格式。FIDL 值类型(即不含句柄的类型)可以持久化,并用作存储格式。具体来说,我们使用 FIDL 信封,因为它们具有跳过未知字段的额外优势,这对于向后兼容性和向前兼容性非常有用。由于任何在 Fuchsia 上运行的运行时都已存在 FIDL 绑定,因此很容易与代码集成,并且不需要额外的解析或转换支持。

ComponentDecl 的结构使得它没有默认值:换句话说,只有当某个字段不适用或来自没有该字段的清单版本时,该字段才会未填充。此外,为了支持向前和向后兼容性,ComponentDecl 及其中嵌套的结构是 FIDL 表FIDL 灵活的联合

CML:组件清单前端

CML(“组件清单语言”)是组件清单的源格式。它旨在供人类读取和写入,但也可供格式化程序和语言服务器等开发工具读取和写入。

  1. 新手在了解基本组件框架概念后,应该能够读懂该文档。
  2. 应能方便地表示常见模式,而无需过多的样板代码。
  3. 影响清单语法但不影响清单语义含义的变更应无需对清单的二进制表示形式进行任何变更。例如,从单例数组更改为单个值不应影响输出。
  4. 它应有助于提高可维护性。具体而言,它应允许添加评论。
  5. 它应足够便于机器处理,以支持自动转换,例如支持大规模重构。

CML 的诞生正是为了实现这些目标。CML 是一种基于 JSON5 的配置语言,可充当简单的 DSL,用于生成 ComponentDecl。通过使用 JSON5,CML 利用了许多开发者已经熟悉的语言,并且该语言在 Fuchsia 的其他地方也得到了广泛使用。与 ComponentDecl 不同,CML 提供了一些支持,可用于更简洁地编写清单:

  • 它允许省略某些字段的默认值。
  • 只要多个功能共享相同的选项,就可以将它们分组到单个声明中。
  • 它允许清单包含清单分片,这些分片可为清单贡献内容。例如,您可以依赖某个库并包含该库的分片,以获取该库所需的所有功能。

最后,从 CML 到 ComponentDecl 的转换虽然不是一对一的,但用户应该能够轻松理解,而无需学习相关规则。

#2:组件由清单定义

组件由清单描述。清单在启动时通过组件的网址进行解析。例如,通过 fuchsia-pkg:// 网址启动的组件将具有包含序列化 ComponentDecl.cm 扩展程序,该扩展程序是从软件包解析的。除了清单之外,组件还可以包含来自同一软件包的资源。例如,使用 ELF 运行程序的组件会指定相应软件包中 ELF 二进制文件的位置。另一方面,具有 https:// 网址的组件可能具有由 https 解析器生成的 ComponentDecl,但该 ComponentDecl 不受可通过网址获得的资源的支持。

组件的清单会完整描述其输入、输出和内部组成。目前,组件清单不能包含任何在运行时填充的参数或“悬空”值。1

不过,这并不意味着清单完全描述了组件的行为。首先,组件获得的功能由父级决定;组件无法控制谁提供这些功能。此外,每个组件都是环境的一部分,该环境会为组件提供某些类型的配置,例如用于解析组件网址的解析器。

按照清单之间的网址可生成组件实例树。组件实例树是对构成 Fuchsia 映像的软件的全面描述。这样一来,就可以放心地对给定的系统映像(例如使用 [fx scrutiny](https://fuchsia.dev/reference/tools/fx/cmd/scrutiny))执行安全审核。

#3:组件清单是声明式的

虽然具有命令式功能的配置语言功能强大,但会牺牲可读性、可预测性和可审核性。先例表明,命令式风格过强的配置语言不够灵活,用户友好度也较低。2 就组件框架而言,组件定义必须可审核且易于理解,这使得命令式风格的配置语言成为不可行的选择。

因此,CML 是一种声明式语言。如果 CML 支持生成清单的部分内容,则仅在结果非常可预测的情况下支持此功能。例如,清单支持默认值和包含,但不提供模板化或参数化功能。

CML 是一种旨在供人类读写的语言。除了开发者工具集成(例如格式设置工具或 IDE 模板)之外,CML 不应由工具生成。生成 CML 文件会增加遮盖清单底层内容的风险,因为现在涉及三个层:CM、CML 和工具。如果出于某种原因必须生成清单,您应编写单独的前端来生成 CM。

#4:组件清单可能来自各种来源

一般来说,组件清单不受任何单一分发机制的约束。组件解析器最终负责检索网址的组件清单。解析器如何实现这一点取决于给定的网址方案。例如,fuchsia-pkg:// 解析器将检索软件包并从中读取由网址的 fragment 标识符部分指定的清单。Web 解析器可能会生成清单,其内容可能会因网域、安全政策和用户偏好设置而异。

目前,分发组件的最常见方式是通过 Fuchsia 软件包。此类组件由 fuchsia-pkg:// 网址标识。相应组件的清单会作为 blob 随此软件包一起提供,通常位于 meta/ 中。

灵感

  • Kubernetes 中的声明式应用管理:Kubernetes 配置语言设计中使用的原则,以及替代方案研究。
  • 命令式与声明式:详细阐述了标题中的主题。
  • Starlark:一种基于 Python 的 DSL 和命令式配置语言。
  • Jsonnet:一种将 JSON 扩展为数据模板语言的语言,其中任何程序都会生成 JSON 文档。
  • borgcfgGCLborgmon:Google 使用的功能性配置语言,大致类似于 Kubernetes,其历史帮助我们了解了命令式语法和声明式语法之间的权衡。

备注


  1. 未来,清单很可能需要支持某种参数化功能,以支持变体和产品可配置性。在执行此操作时,我们应避免与可参数化配置相关的常见陷阱。 

  2. 如需详细了解此观点,请参阅 Kubernetes 的详尽文档,其中介绍了非声明式配置的许多缺点。