moz.build 文件¶
moz.build 文件是定义树元数据(特别是构建配置)的机制。
树中的目录包含 moz.build 文件,这些文件声明了它们各自树部分的功能。这包括要编译的 C++ 文件列表、在哪里查找测试等。
moz.build 文件实际上是 Python 脚本。但是,它们的执行受特殊规则约束。这将在下面解释。
moz.build Python 沙箱¶
如上所述,moz.build 文件是 Python 脚本。但是,它们在特殊的 Python 沙箱中执行,该沙箱会显着更改和限制执行环境。环境如此不同,以至于大多数 moz.build 文件如果由普通的 Python 解释器执行(例如 python moz.build),则可能无法正常执行。
以下属性使 moz.build 文件的执行变得特殊
执行环境公开了一组有限的 Python 子集。
有一组特殊的全局符号和强制的符号命名约定。
某些符号继承自先前执行的
moz.build文件。
Python 的有限子集实际上是一个极其有限的子集。只有 __builtin__ 中的几个符号被公开。这些包括 True、False、None、sorted、int 和 set。像 import、print 和 open 这样的全局函数不可用。没有这些,moz.build 文件能做的事情非常少。这是有意为之的。
执行沙箱对所有 UPPERCASE 变量进行特殊处理。任何 UPPERCASE 变量都必须在脚本执行之前为沙箱所知。任何尝试读取或写入未知 UPPERCASE 变量的操作都将导致引发异常。此外,所有 UPPERCASE 变量的类型都严格执行。尝试将不兼容的类型分配给 UPPERCASE 变量将导致引发异常。
对 UPPERCASE 变量的行为严格性是一个非常有意的设计决策。通过确保严格的行为,任何涉及 UPPERCASE 变量的操作都保证具有明确定义的副作用。以前,当构建配置在 Makefiles 中定义时,对什么也不做的变量的赋值会不被注意到。 moz.build 文件通过消除虚假承诺的可能性解决了此问题。
moz.build 文件执行完成后,仅使用 UPPERCASE 变量来检索状态。
Python 沙箱可用的变量和函数集由 mozbuild.frontend.context 模块定义。此模块中的数据结构由 mozbuild.frontend.reader.MozbuildSandbox 类使用来构建沙箱。有测试可以确保公开给空沙箱的符号集都在 context 模块中定义。此模块还包含每个符号的文档,因此没有任何内容可以偷偷进入沙箱而没有被明确定义和记录。
读取和遍历 moz.build 文件¶
读取 moz.build 文件的过程大致包括
从根
moz.build(<topsrcdir>/moz.build)开始。在一个新的沙箱中评估
moz.build文件。发出从执行的沙箱中获得的主要上下文和任何子上下文。
提取一组要接下来执行的
moz.build文件。对于每个额外的
moz.build文件,转到 #2 并重复,直到所有引用的文件都执行完毕。
从消费者的角度来看,读取的输出是一系列 mozbuild.frontend.reader.context.Context 实例。每个 Context 定义了数据的特定方面。消费者迭代这些对象并对其中的数据执行某些操作。每个对象本质上都是在其执行期间填充的所有 UPPERCASE 变量的字典。
注意
历史上,每个 moz.build 文件只有一个 context。随着 moz.build 文件跟踪的事物数量的增加以及对越来越复杂的处理的需求,有必要将这些上下文拆分为多个逻辑部分。现在,每个 moz.build 文件发出多个上下文很常见。
构建系统读取模式¶
评估 moz.build 文件的传统模式称为构建系统遍历模式。在此模式下,每个 moz.build 沙箱中的 CONFIG 变量都由来自 config.status 的数据填充,该数据由 configure 生成。
在评估期间,moz.build 文件通常会根据构建配置的状态做出决策。例如,仅当启用功能 X 时才编译 foo.cpp。
在此模式下,moz.build 文件的遍历由 DIRS 和 TEST_DIRS 等变量控制。例如,要执行子目录 foo,您需要将 DIRS += ['foo'] 添加到 moz.build 文件中,并且将评估 foo/moz.build。
文件系统读取模式¶
有一种替代的读取模式,它不涉及构建系统并且不使用 DIRS 变量来控制进入子目录的遍历。此模式称为文件系统读取模式。
在此读取模式下,CONFIG 变量是一个虚拟的、大部分为空的对象。访问除少数几个特殊变量之外的所有变量都将返回空值。这意味着几乎所有 if CONFIG['FOO']: 分支都不会被执行。
读取器控制要评估的文件集,而不是使用已评估的 moz.build 文件中的内容来驱动进入后续 moz.build 文件的遍历。
单个 moz.build 文件不能保证能够独立执行。相反,我们必须首先评估所有父 moz.build 文件。例如,为了评估 /foo/moz.build,必须执行 /moz.build 并使其状态影响 /foo/moz.build 的执行。
文件系统读取模式用于为 文件元数据 功能提供支持。
技术细节¶
读取 moz.build 文件的代码位于 mozbuild.frontend.reader 中。Python 沙箱评估结果(mozbuild.frontend.context.Context)被传递到 mozbuild.frontend.emitter,后者将其转换为 mozbuild.frontend.data 中定义的类。此模块中的每个类都定义了树元数据的特定于域的组件。例如,将有单独的类来表示 JavaScript 文件与编译的 C++ 文件或测试清单。这意味着此数据的下游使用者可以根据类类型进行筛选,只使用他们感兴趣的内容。
在 moz.build 文件实例和从每个文件派生的 mozbuild.frontend.data 类数量之间没有明确的映射。根据 moz.build 文件的内容,可能派生 1 个对象或 100 个对象。
低级沙箱执行和元数据表示之间 emitter 层的目的是促进统一的规范化和验证步骤。 moz.build 派生数据的下游使用者有很多,并且许多使用者将执行相同的操作。此逻辑可能很复杂,因此我们有一个专门用于此的组件。
mozbuild.frontend.reader.BuildReader` 和 mozbuild.frontend.reader.TreeMetadataEmitter` 由于使用了生成器,因此具有基于流的 API。当您正确地将它们连接起来时,在所有 moz.build 文件都读取之前,就会发出 mozbuild.frontend.data 类。这意味着下游错误会在沙箱执行后立即引发。
评估 Python 沙箱的大部分代码都适用于非 Mozilla 系统。理论上,它可以提取到一个独立且通用的包中。但是,在有需要之前,可能存在一些紧密耦合的部分。