本文档介绍了如何更新或扩展组件检查文件格式。
在扩展格式时,切勿破坏任何现有功能,尤其是检查读取器和验证测试。但是,将所有更改打包到一项更改中可能会使审核工作量过大。通常,如果要更改 VMO 格式,应在链中包含 5-9 个更改或步骤:
- (可能不适用)通过更新 VMO 格式文档来选择类型编号。
- 更新 Rust 读取器。
- 更新 C++ 读取器。
- 更新 Rust 编写器。
- 更新 C++ 编写器。
- (可能)更新验证程序。
- 更新文档。
- (没有变化)(可选)发送功能通知。
选择数字类型
在检查文件格式中查看类型表,并选择可用的类型编号。当前规范中总共有 256 种可能的类型。
如需预留新类型,请更新检查文件格式。
实现
在没有读取器或写入器的情况下测试读取器或写入器非常麻烦;如果没有读取器和写入器,则很难获得适当的块级 API。
如需测试读取器和写入器,请执行以下操作:
选择一种语言,设计并完全用该语言设计并实现功能,同时通过单元测试对 API 的实际使用情况进行建模。
将更改拆分为单独的读取器和写入器更改,并将写入器堆叠在读取器上。
此时,读取器中依赖于 writer API 的测试可能会在读取器更改中中断。
根据该变更进行 rebase 操作,并使用较低级别的功能重写测试(保留原始版本的测试)。
通常,您可以将对块代码的所有更改都放入读取器更改中,这样一来,为读取器 API 编写测试就可能又麻烦,
考试太难了。衍合到写入者变更并移除修改后的测试,将它们替换为根据高级 API 编写的原始测试。
参考这两个更改,并将其复制到第二种语言中。
(可选)根据更改的内容,您可能需要随时更新validator测试,因为更改现有代码块的现有格式可能会破坏这些测试。
下面几部分概述了如何整合各项更改,但实际上,请在按上述方法拆分设计之前,将这些更改作为提示来统一设计整个系统。
更新 Rust 实现
本部分中的示例将创建一个名为 MyFoo
的新类型。
设置
- 包含测试:
fx set core.x64 --with //src/lib/diagnostics:tests
运行测试:
fx test inspect-format-tests fuchsia-inspect-tests
读取器更改
Bitfield 更新
如果要定义新的块类型,请更新
BlockType
枚举。更新为
BlockType
定义的方法和函数。如果更改现有块中的字段或创建新的
BlockType
,请更新位字段布局。运行
inspect-format-tests
以验证更改是否编译:
fx test inspect-format-tests
更新块定义,以包含用于读取和写入新字段的方法。此时,在块库中添加写入功能是恰当的做法;如果没有该功能,几乎不可能编写读取器测试。
编写用于运用新功能的块测试。
编写对块的前 8-16 个字节进行断言的测试。
通常,这意味着将预期内容编写为包含十六进制值的
&[u8]
,并声明其与块用作容器的缓冲区的等效性。
更新读取器
您可以在 mod.rs 中找到读者代码。此变更中的测试可能会比较棘手,因为高级 API 编写程序尚不存在。
写入者变更
状态
此处的主要更改将发生在 State
功能中。您可以在此处分配块并将其转换为新类型。如果您只是修改现有块,则可能是您唯一需要更改的地方。
创建新的值类型
利用 types 目录,您可以为自己的类型添加新文件。
在所创建的文件中创建新类型。使用现有类型作为示例。类型始终可以访问内部
State
。使用此方法可为新类型创建必要的方法,并调用在State
中创建的方法。向
Node
添加用于创建新类型的方法。确保您的类型在 VMO 中具有 RAII 语义。如果您的类型是一个值,则系统可能会通过在第 1 步中从现有类型复制的样板自动完成此操作。
最后,返回并更新读取器更改中的测试,以使用新的 API!
更新 C++ 实现
本部分中的示例将创建一个名为 MyFoo
的新类型。
如上所述,此部分应该是 Gerrit 中的两项更改。
设置
- 包含测试:
fx set core.x64 --with //zircon/system/ulib/inspect:tests
- 运行测试。
fx test inspect-cpp-unittest
读取器更改
Bitfield 更新
本部分介绍了如何为新类型定义位字段。
更新块定义。
更改
BlockType
以包含您的新类型。例如:kMyFoo = #;
如果您的类型需要新标头(通常如果它不是
VALUE
),请使用结构体定义该类型的标头位字段。例如:struct MyFooBlockFields final : public BlockFields
。如果您的类型需要新的载荷(需要使用分块的后 8 个字节),请使用结构体为您的类型定义载荷位字段。例如:
struct MyFooBlockPayload final
。如果您的类型包含枚举(例如格式),请在 block.h 的顶部定义一个新枚举,例如:
enum class MyFooBlockFormat : uint8_t
。
实现类型读取器
本部分介绍如何使新类型可读。
根据您的类型更新 Inspect 层次结构:
值(Node
的子项)
使用类型的新数字更新
PropertyFormat
枚举。此枚举在此特定枚举中必须依序排列,无需与您选择的格式类型序数匹配。创建一个新的值类型。例如
using MyFooValue = internal::Value<T, static_cast<size_t>(PropertyFormat::kMyFoo)>;
。使用新值更新
PropertyValue
变体。 注意:fit::internal::variant
中的索引必须与PropertyFormat
的值匹配。
不是值
- 您需要在层次结构文件中创建自己的内存中表示形式对象。
更新实际读取器。
更新
InnerScanBlocks
以分派类型。如果要创建新的Property
,可能只需添加BlockType
。如果您需要自定义解析器,请实现
InnerParseMyFoo
,它会接受父级(如果需要)和指向已扫描块的指针。
写入者变更
类型封装容器声明
本部分介绍了如何为新类型声明 C++ RAII 样式的封装容器。
类型封装容器包含相应类型拥有的块索引。您负责在状态操作更新中针对这些块执行操作,包括创建和删除操作。
更新写入者类型定义。
确定您可以重复使用现有封装容器,还是需要定制类型:
重复使用
如果您需要支持“Add”(添加)、“Subtract”(减法)和“Set”(设置),请执行以下操作:
using MyFoo = internal::NumericProperty<T>
,其中T
是这些操作的参数类型。如果您需要支持 Set:
using MyFoo = internal::Property<T>
,其中T
是 Set 的参数类型。如果您需要支持对数组执行数字操作,请使用
using MyFood = internal::ArrayProperty<T>
,其中T
是数组中槽的参数类型。如果您需要支持向直方图插入内容:
using MyFoo = internal::{Linear,Exponential}Histogram<T>
,其中T
是插入的参数。
定制
创建一个新的类型封装容器。例如
class MyFoo final
。确保您的类将
internal::State
作为好友类。注意:如需了解可复制的起点,请参阅class Link
。
状态操作更新
State
类是针对所有类型的所有操作的实际实现。本部分介绍如何实现完成封装容器实现所需的操作。
更新
State
标头:添加了 Create 和 Free 方法。例如:
MyFoo CreateMyFoo(<args>); void FreeMyFoo(MyFoo* property);
,其中args
通常包括名称、父级和一些初始值。为您需要支持的每种操作添加方法。例如,如果您的类型可以是 Set,则
void SetMyFoo(MyFoo* property, T)
,其中T
是对 types.h 的更新中的类型。
更新
State
:实现新类型的方法。实现方式因类型而异。本部分简要介绍了每种方法必须执行的操作:
MyFoo CreateMyFoo(Args...)
负责分配一些块、设置它们的值,并返回封装在MyFoo
中的块。您可以使用私有构造函数根据其封装的BlockIndex
对象创建MyFoo
。各种内部帮助程序可以简化此操作。如需查看示例,请参阅CreateIntProperty
。void FreeMyFoo(MyFoo* property)
负责释放由MyFoo
封装的所有块。有时,释放块会有一些特定的排序要求或更新。如需查看有关如何释放值的示例,请参阅InnerFreeValue
。void SetMyFoo(MyFoo* property, T value)
等操作会更改分配给MyFoo
的块的值,以实现相应操作。如需查看示例,请参阅SetIntProperty
。
实现类型封装容器
本部分介绍了如何实现之前声明的封装容器方法。
更新写入者类型定义:
如果您使用现有的模板化类型,则需要为新基本类型
T
替换每个方法。例如,如果您输入using MyFoo = internal::Property<T>
,则需要输入:template<> void internal::Property<T>::OPERATION(...) { ... }
如果您创建了自己的类型,只需为您声明的方法创建定义即可。您需要执行以下操作:
让您的构造函数调用
state_->CreateMyFoo(...);
让析构函数调用
state_->FreeMyFoo(...);
让其他方法调用 State 上的相应实现。
让您的所有构造函数和方法在调用之前检查
state_
是否不为 null。