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
个文件。这可以通过 action
和
action_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
用途。
您可以在此处找到修复构建操作以生成依赖项的示例:
- 472565:[build] 在 generate_fidl_json.py 中生成 depfile
- 472657:[build] 修复了 hotsort_target_internal 的封闭性
- 473980:[build] 修复了 fidl-c-header 的封闭性
- 472658:[build] 让 go_library 封闭构建
- 472637:[build] 修复了平面缓冲区的封闭性
输入/输出中缺少操作参数
构建操作通常是将某些文件路径作为参数的脚本。
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.txt
和 data/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_dir
或 target_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 以外的文件类型可能会因类似原因而无法重现。
另请参阅:开放项目中的封闭操作