构建图收敛

“构建图收敛”属性是单次构建调用将 按照正确的顺序执行所有必要的操作, 输出于输入。

Fuchsia 使用由时间戳驱动的 Ninja 构建系统。忍者表达 将 build 呈现为一个图表,其中包含输入/输出文件,以及接受输入和 生成输出。

例如,当您运行构建时,使用 fx build,Ninja 将遍历 build 图 并执行输出不存在或其输入具有 自上次运行以来发生了更改,全部按拓扑顺序(即之前的依赖项) 依赖项)。

不过,build 图操作没有经过验证,无法实现 输出比输入新,这可能会导致收敛问题。

常见根本原因

创建 Ninja 收敛问题的方法有很多。尽管如此, 以往的经验告诉我们,这些问题有共同的根本原因。

未生成输出

如果声明构建操作会生成输出,但实际上并未生成 输出(在某些情况下或始终),这样会导致收敛 问题。例如,某个操作可能声明会在 成功,但未能生成或触摸此图章文件,或者将其保存到错误 位置。

输出已过时(未比所有输入更新)

Ninja 知道,如果输出新于所有输入,则输出会是最新的。如果其中一位或 保存输出后,还有更多输入发生更改,则 Ninja 会重复 生成输出所需的步骤。

但是,如果生成输出的操作在 输入的内容发生更改,这就形成了 过时。

导致这种情况的一个常见错误是,操作检查输入、确定 它们对输出的内容没什么可做/更改的,但是没有 更新输出(即“触摸”或“印章”)的修改时间戳 其输出)。

修改输入

操作可以修改其输入。通常是操作的输入 应以只读方式打开,但其实也不例外 写入数据。也就是说,如果您的操作需要修改输入, 因此在写入任何输出之前会发生此错误或者,如果必须在写入后修改输入 请务必在退出 操作。否则,您需要将一项或多项输入更新为较新的内容 一个或多个输出内容,这样让 Ninja 误以为 输出过时

修改操作中的输入也会引入竞态条件, 重现不确定的问题。如果多项操作都依赖于 并且其中一个代码修改了输入,那么构建将无法 如果其中一项操作导致输入时间戳更新,则进行收敛 比任何操作输出。在按依赖项排序的执行中, 我们无法保证独立操作的顺序。

避免修改输入。

Ninja 构建系统通过符号链接来确定时间戳。这可以 如果软符号链接参与忍者规则,会带来意想不到的后果, 输入依赖项或输出。符号链接本身的时间戳(与 不会考虑过时和新鲜度。请参阅 ninja#1186,了解有关 stat()lstat() 以及 演示。硬链接(不带 -sln),存在多个 引用都指向同一个文件系统对象,因此具有相同的 时间戳。

即使是简单的链接操作也可能会导致问题。假设有一个包含 输入 src,输出 $target_out_dir/dst,调用的操作是 ln src $target_out_dir/dst。从表面上看,此动作的收敛正确。但是 action() 的行为可能会被在构建系统中的其他位置替换,例如 用于将操作与其他操作封装在一起。因此,你的内部操作 当 src 的时间戳早于封装容器操作的时间戳时,会收敛到空操作 脚本,继而会被视为早于 dst(其输出, 包含输入的时间戳)。copy() 的不同之处 问题,因为它从来没有换行。

避免在操作输入和输出中使用符号链接和硬链接。

如需制作副本,请首选内置的 copy() 目标。

时间戳粒度

现代文件系统在文件上存储时间戳(例如,上次 修改)。Python 2.7 等一些较旧的运行时 以较低的分辨率(例如毫秒)保留文件时间戳。时间是 因此,操作读取输入和写入输出时, 时间戳(它认为是“现在”)但实际上比时间戳早 例如,输入和输出都以相同的方式 写入, 毫秒,输出的时间戳在毫秒后被截断 数字。

在撰写本文时,我们部署了相关机制来确保 构建中的 Python 操作使用 Python 3.x 运行,部分原因在于可以避免此问题。

构建收敛诊断

我们使用以下工具来诊断构建收敛问题:

  • 提交队列中的忍者无操作检查
  • 文件系统访问操作跟踪

忍者无操作检查

Fuchsia 的提交队列 (CQ) 用于验证更改不仅成功构建, 同时还要使构建系统处于在单次 构建调用。

来自 CQ 的 build 收敛错误示例:

fuchsia confirm no-op
ninja build does not converge to a no-op

先在 CQ 中运行同一 build,然后再将更改合并到源代码树中, 以确保更改不会破坏构建完成构建后 成功,CQ 将再次调用 Ninja 并等待 Ninja 报告 "no work to do"。这用于进行可靠性检查,因为需要正确的 build 图 以“收敛”空操作。

如果此可靠性检查失败,则 CQ 将在名为 fuchsia confirm no-op

重现 Ninja 收敛问题

将源代码树同步到您的更改之后,只需尝试以下操作即可:

fx build

此命令应输出以下内容:

ninja: no work to do.

如果情况并非如此,而是要执行实际的构建操作,请运行 执行同样的命令如果第二次调用仍未生成“no work”, 那么您就重现了问题如果到了“没有工作”时请尝试 以下:

# Clean your build cache
rm -rf out
# Set up the build specification again
fx set ...
# Build
fx build
# Build again, expecting no-op
fx build

排查 Ninja 收敛问题

在 CQ 结果页面的失败步骤 confirm no-op 下,您会看到 多个链接:

  • 执行详情
  • 忍者 -d 说明 -n -v
  • 脏路径

指向 ninja -d explain -n -v 的链接显示了您应该能够查看的信息 使用以下命令在本地重现:

fx ninja -C $(fx get-build-dir) -d explain -n -v

此链接指向“脏路径”会显示与您最相关的广告 信息。您会看到一个文本文件,最可能的开头如下所示:

ninja explain: output <...> doesn't exist
...

此文件中的每一行都像是多米诺骨牌。您应该开始进行问题排查 通过观察引发连锁反应的第一块多米诺砖块,来解决问题 需要完成的额外工作例如,在上面的示例中,某个特定输出 文件不存在,这会导致 Ninja 重新运行 它应该生成此输出,然后重新运行相关操作。

文件系统访问跟踪

还有一些构建器可以文件系统访问。诊断信息 例如:

Not all outputs of //your:label were written or touched, which can cause subsequent
build invocations to re-execute actions due to a missing file or old timestamp.

Required writes:
...

Missing outputs:
...

Stale outputs:
...

有关不允许写入输入的诊断如下所示:

Unexpected file accesses building //your/target:label, following the order they are accessed:
(FileAccessType.WRITE /path/to/input-that-should-not-be-touched.txt)

与 Ninja 无操作检查相比,这项检查是针对每项操作执行的 并立即诊断收敛问题的众多原因之一, 操作,而不是稍后通过完整的 fx build 命令执行。这个 方法可以发现一些问题,这些问题原本会因为 竞态条件。

排查跟踪到的操作失败问题

如需在本地启用操作跟踪,请执行以下操作之一:

  • 运行 fx set ... --args=build_should_trace_actions=true
  • 运行 fx args,在编辑器中添加 build_should_trace_actions=true, 保存并退出

之后价格为 fx build //your/failing:target

在操作的脚本中检查邮件中的文件,或 命令,看看它们是否属于以下某个类别: 常见问题