封闭的构建操作

Fuchsia 的构建系统使用工具跟踪 构建操作,以便检测构建操作 输入和输出。

如果您遇到类似如下所示的错误,请继续阅读本指南:

Unexpected file accesses building //some/target:label ...
(FileAccessType.READ /path/to/file/not/declared/as/input)

或者,当您查看 action()action_foreach() 时, target ,如下所示:

action("foo") {
  ...
  hermetic_deps = false
}

构建图正确性

build 定义为一个有向无环图,使操作具有 这些输入流经这些输入流,而它们的输出流出。例如, 将 .cc 文件编译成 .o 文件的操作将包含源文件为 一个输入和一个作为输出的对象文件。编译中使用的任何 .h 头文件 被视为同一操作的输入。

这种图表表示形式可确保构建系统可以正确执行 增量构建。增量构建是指 但某些操作的之前我们更改了输入 系统被要求重建在增量构建中,构建系统会 尝试执行所需的最少工作,仅重新构建操作 无论是由于用户对来源进行修改还是 需要重新运行的其他操作的输出发生了变化。

对于构建图中的任何操作,都必须将所有输入和输出 以确保构建图表正确无误, 是封闭的。但是,这未由底层构建系统 Ninja 进行验证。 构建操作在用户的本地环境中运行,拥有 整个文件系统,包括源代码树和 out/ 中的所有文件 这样,它们就不会被沙盒屏蔽,因此它们可以访问任何位置。

未能声明输入将导致无法重新运行操作(并且 下游的所有内容)。未能声明输出 是另一个操作的输入,会在相关任务之间 操作,其中单次构建调用可能会错过时间戳更新;以及 设置为未能在单次调用中收敛(请参阅 Ninja no-op)。

如果您正在阅读此内容,则可能您正在处理的构建操作 完整说明其一个或多个输入或输出。

使用自定义操作扩展构建

开发者可以使用 GN 元构建系统在自己的 BUILD.gn 个文件。这可以通过 actionaction_foreach。自定义操作允许开发者调用 自定义工具,以及将它们连接到依赖关系图, 这些工具可在构建时调用,并针对 增量构建。

操作使用以下参数声明其输入:

  • script:要运行的工具。通常是 Python 脚本, 可在主机上执行的程序。
  • inputs:用作工具数据输入的文件。例如,如果 则要压缩的文件会被列为 输入。
  • sources:被视为与 inputs 相同。只有 sources 通常用于 工具的 script,例如依赖 Python 或脚本库。

操作使用以下参数声明其输出:

  • outputs:每项操作必须生成至少一个输出文件。会 请勿生成输出文件,例如 为正确性输入的内容通常会生成“印章文件”, 作为操作的指示符,可以为空。

Depfile

如果在运行操作之前不知道操作的一些输入, 那么,操作还可以指定 depfile。Depfile 列表 在运行时发现的操作的一个或多个输出的输入。通过 depfile 是一行或多行,如下所示:

[output_file1] [output_file2...]: [input_file1] [input_file2...]

depfile 中的所有路径都必须相对于 root_build_dir(设为 当前工作目录中执行操作)。另请参阅: 首选 rebase_path() 中的相对路径

像编译器这样的工具应该(和确实)支持发出所有 以 depfile 形式编译所用的文件。

用于检测非封闭操作的文件系统操作跟踪

Fuchsia 构建系统使用文件系统操作跟踪工具来检测 操作读取或写入未被列为输入或输出的文件, 在 BUILD.gn 文件中或 Depfile 中明确指定,如上所示。操作完成 来代替沙盒来运行操作,以及用作各种运行时排错程序。

如果您正在阅读此页面,则说明可能遇到了 这个系统。错误会准确列出已读取或 写入,但在 BUILD.gn 或 depfile 中未指定为输入/输出。 您应更正这些遗漏并尝试重新构建,直到错误消失为止 。

若要在本地构建中重现此错误, 启用操作跟踪:

fx set what --args=build_should_trace_actions=true

或者以交互方式运行 fx args,添加一行代码 build_should_trace_actions=true, 保存并退出。

请注意,如果您的操作未进行封闭定义,并且您尚未更正 那么在尝试重建操作时,你可能不会遇到 错误。由于该操作未进行封闭定义,因此可能 这是增量构建中所出现的问题 )。如需强制运行所有构建操作,您需要清理 先检查构建的输出缓存:

fx clean

默认情况下,CQ 会对所有更改执行这些封闭性检查。它的作用是: 使用上面提到的 build_should_trace_actions=true 实参,因此 开发者可以在本地重现完全相同的跟踪 build。

抑制封闭操作检查

当前未封闭的操作设置了以下参数:

action("foo") {
  ...
  # TODO(https://fxbug.dev/xxxxx): delete the line below and fix this
  hermetic_deps = false
}

这会抑制上述检查。如果您发现有 您就应该取消此抑制操作,尝试重现 如上文所述,并解决问题。

如果您提交 bug 而不是立即修复,请将 bug 命名为 “[hermetic]”并将失败构建操作产生的跟踪输出包含到 。如果您知道访问违规行为的出现位置,请针对该违规行为发表评论 来源。

常见问题及解决方法

缺少输入/输出

有时,输入/输出在构建时很熟悉,只是未指定, 或者指定的值不正确。这些问题比较常见,解决方法也很简单。 例如:

操作运行时未知输入

如上所述,有时在构建时并非所有输入都是已知的,因此 无法在 BUILD.gn 定义中指定。这就是 depfiles 用途。

您可以在此处找到修复构建操作以生成依赖项的示例:

输入/输出中缺少操作参数

构建操作通常是将某些文件路径作为参数的脚本。

action("foo") {
  script = "concatenate.py"
  outputs = [ "$target_out_dir/file1_file2.txt" ]
  args = [
    "--concat-from",
    rebase_path("data/file1.txt", root_build_dir),
    rebase_path("data/file2.txt", root_build_dir),
    "--output",
  ] + outputs
}

在上述示例中,您会得到一个 concatenate.py 的操作跟踪错误 从 data/file1.txtdata/file2.txt 读取。错误很容易发现 因为您可以看到这些路径以参数形式传递到脚本中 未列为输入或输出。尽管从技术上讲可以传递路径 但实际上没有让脚本对这些路径执行读/写操作, 不太可能。

解决方法如下:

action("foo") {
  script = "concatenate.py"
  sources = [
    "data/file1.txt",
    "data/file2.txt",
  ]
  outputs = [ "$target_out_dir/file1_file2.txt" ]
  args = [
    "--concat-from",
  ] + rebase_path(sources, root_build_dir) + [
    "--output",
  ] + outputs
}

从文件扩展参数

在 Python 脚本中,有一种常见的模式可以扩展 作为参数(也称为“响应文件”)。BUILD.gn后 您会看到:

action("foo") {
   script = "myaction.py"
   args = [ "@" + rebase_path(args_file, root_build_dir) ]
   ...
}

然后在关联的 Python 文件 myaction.py 中,您可以找到 使用 fromfile_prefix_chars 的参数解析器:

def main():
    parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
    args = parser.parse_args()
    ...

上述内容存在的问题是 Python 在运行时会读取 args_file 脚本,应指定为输入。要解决此问题,请执行以下操作:

action("foo") {
   script = "myaction.py"
   inputs = [ args_file ]
   args = [ "@" + rebase_path(args_file, root_build_dir) ]
   ...
}

如果您需要从 GN 中的列表快速填充此类文件,可以使用 write_file():

action("foo") {
  args_file = "${target_gen_dir}/${target_name}.args"
  write_file(args_file, a_very_long_list_of_args)
  args = [ "@" + rebase_path(args_file, root_build_dir) ]
  ...
}

请注意,GN 提供 response_file_contents 作为 而不是 write_file。然而,由于 来自 Ninja 的 bug,但目前我们不允许 response_file_contents

创建和删除临时文件

这是构建操作中创建临时文件的常用模式。 也可以不将临时文件列为输出内容,只要该操作 创建临时文件也会在返回前删除这些文件。

临时文件应保存在 target_out_dirtarget_gen_dir 下。 使用全局临时存储空间(如 /tmp$TMPDIR)或任何读取和 在 Checkout 或输出目录之外写入数据,因为 这可能会增加对构建失败的问题排查难度,因为可能需要 以便从文件系统中的其他位置恢复 错误。

创建和删除临时目录

有时,您需要在临时目录中创建临时文件。 同样,只要创建临时目录的操作 也会在返回前以递归方式将其删除。

shutil.rmtree 是用于删除临时数据的常用函数 目录。不过,由于跟踪器的限制,这有时 会导致意外的虚假读取另请参阅:问题 75057:正确处理 在 Action 跟踪程序中通过 closeil.rmtree 删除目录

绕过此限制的一种方法是仅创建临时文件, 临时目录。临时文件应采用 target_out_dir 写入 或 target_gen_dir

有时,这是无法实现的,例如,当临时目录 由外部构建工具创建且无法修改。在此例中, 另一种方法是为临时目录指定一个特殊名称,例如 __untraced_foo_tmp_outputs__,并在 操作跟踪器。访问此 会被跟踪程序忽略。因此, 请勿轻易使用该功能

例如,假设 bar.py 始终删除 --tmp-dir 中的所有文件 然后再重新填充:

action(target_name) {
  script = "bar.py"
  args = [
    "--tmp-dir"
    rebase_path("${target_gen_dir}/${target_name}/__untraced_bar_tmp_outputs__", root_build_dir)
  ]
  ...
}

然后,在操作跟踪器的 ignored_path_parts 中添加一个条目:

ignored_path_parts = {
  # Comment with clear explanation on why this is necessary,
  # preferably with a link to an associated bug for more context.
  "__untraced_bar_tmp_outputs__",
  ...
}

CQ 中报告且无法在本地重现的错误

首先,确保使用构建参数 build_should_trace_actions=true,如 。

如果 CQ 报告 action_tracer.py 意外读取了 Python 文件,但您无法 在本地重现此问题,原因可能是 整个树中的 __pycache__ 目录(例如 find third_party -type d -name __pycache__)。 快速解决方案是删除这些目录中的所有 *.pyc 文件。导致 假负例是指文件系统从未打开原始的 .py 文件, 不会报告为被触摸,因此不会触发失败的封闭性检查。

Python 以外的文件类型可能会因类似原因而无法重现。

另请参阅:开放项目中的封闭操作