6 可重现构建
6 定义
Yocto 项目所说的可重现构建,是指给定相同的输入构建配置, 无论在何时构建(现在或五年后)、位于文件系统的哪个路径, 以及底层构建主机使用什么发行版和工具,都能生成相同的二进制输出。
6 重要性
本项目与 可重现构建项目 的目标一致, 后者提供了关于可重现性价值的说明。该项目主要关注检测被引入的安全问题。 对于 Yocto 项目,构建的确定性同样至关重要:给定同一组输入元数据, 我们期望得到一致的输出。这一直是项目重点; 从 3.1(dunfell)版本开始, 这一要求已经落实到二进制层面,包括时间戳。
例如,在产品生命周期的某个时刻,需要重新构建以加入安全修复。 此时,只有实际修改过的组件才应在二进制层面发生变化, 从而让必须重新验证的范围更容易确定,也更加清晰。
可重现性还会改善项目构建自身:二进制输出保持不变时, 用于复用 共享状态 对象的 哈希等价 会更加有效。
备注
强烈建议在确定生产镜像之前,先确保项目能够可重现地构建。 等到首次需要发布更新时再处理这个问题,往往已经太晚。
6 实现方式
构建可重现性涉及很多方面,构建系统采取的具体措施包括:
向编译器选项添加路径映射,使调试文件路径映射到一致、与目标兼容的路径。 DEBUG_PREFIX_MAP 会正确设置
-fmacro-prefix-map和-fdebug-prefix-map编译选项,完成这种映射。明确声明配方依赖与配置,避免不确定的配置选项或主机依赖混入构建。 特别要确保 PACKAGECONFIG 覆盖那些可能自动探测主机依赖的配置选项。
为每个配方提供独立 sysroot,让配方只能看到自身依赖。 这些目录位于配方的 WORKDIR 中,名为
recipe-sysroot和recipe-sysroot-native,只填充该配方声明的依赖。用缩减的软件包集合构建镜像,只包含镜像所依赖配方提供的软件包。
通过 HOSTTOOLS 过滤主机
PATH,仅开放指定的一组工具。
6 能否证明项目具有可重现性?
可以,项目会在 Autobuilder 上定期验证。本文撰写时,即 3.3(hardknott)版本, 除 Go 语言和 Ruby 文档软件包之外,OpenEmbedded-Core (OE-Core) 的 所有配方(即 world 构建)都达到 100% 可重现。 当时的 Go 语言实现始终依赖构建路径,因此存在根本性的可重现性问题。
备注
只有 BitBake 和 OpenEmbedded-Core (OE-Core) 保证完整的可重现性。
一旦加入其他层,这一保证就不再成立,因为额外层可能增加配置文件、
bbappend 文件、覆盖类等内容。
要运行 Autobuilder CI 使用的自动化自测,可以执行:
oe-selftest -r reproducible.ReproducibleTests.test_reproducible_builds
默认测试包含 world 构建,因此加入其他层后,也会测试新增层中的配方。
可以在 build/conf/local.conf 中通过
OEQA_REPRODUCIBLE_TEST_TARGET 指定其他构建目标。
例如,只测试 python3-numpy 时,可以先设置:
OEQA_REPRODUCIBLE_TEST_TARGET = "python3-numpy"
然后执行上述 oe-selftest 命令。
可重现性测试会把目标列表构建两次。第一次在可用时使用 共享状态; 第二次除 OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS 中列出的配方之外, 明确禁用 共享状态, 并在当前主机上实际构建。因此,Autobuilder 可以在不同时期、不同主机发行版之间 测试构建的可重现性。
如果设置了 OEQA_DEBUGGING_SAVED_OUTPUT,存在差异的软件包会保存到该位置。
测试还可以对输出运行 diffoscope,生成显示软件包差异的 HTML 文件,辅助调试。
Autobuilder 上的结果位于 https://valkyrie.yocto.io/pub/repro-fail/,
目录名称采用 oe-reproducible + <date> + <random ID> 的形式,
例如 oe-reproducible-20200202-1lm8o1th。
项目当前的可重现性状态见 https://www.yoctoproject.org/reproducible-build-results/。
也可以在 Autobuilder 上查看:https://autobuilder.yoctoproject.org/valkyrie/#/builders/reproducible。
6 如何测试自己的层或配方?
6 使用 world 构建
可以使用前面给出的
oe-selftest
命令运行 world 测试。其实现位于
meta/lib/oeqa/selftest/cases/reproducible.py。
6 派生测试类
可以继承测试类,并将 targets 改为所需目标。
也可以修改 sstate_targets,在测试之前预先缓存一组配方,
使它们不参与可重现性测试。例如,将 sstate_targets 设为
core-image-sato,再将 targets 设为 core-image-sato-sdk,
就只会测试 core-image-sato-sdk 独有的目标。
6 使用 OEQA_REPRODUCIBLE_TEST_* 变量
要测试一组配方的可重现性,可以在 build/conf/local.conf 中定义 OEQA_REPRODUCIBLE_TEST_LEAF_TARGETS:
OEQA_REPRODUCIBLE_TEST_LEAF_TARGETS = "my-recipe"
这样会测试 my-recipe 的可重现性,但对大多数依赖使用
共享状态。
这里主要指在 DEPENDS 中显式列出的依赖,并不一定包括所有依赖;
还需考虑 [depends] 变量标志、PACKAGE_DEPENDS 和其他实现方式。
以下变量可以提供更细致的控制:
OEQA_REPRODUCIBLE_TEST_TARGET:列出要构建的配方。
OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS:列出使用 共享状态 构建的配方。