Skip to content

Commit e46bed7

Browse files
zhengwei143copybara-github
authored andcommitted
Prevent template_ctx.run from using map_directory outputs as inputs.
This has the following implications: - Internal actions (generated by `template_ctx.run()`) cannot take both subtrees and their parent tree artifacts as inputs, which might be problematic for remote execution as artifacts are "streamed" (potentially causing the same files from the subtree to be added twice). - Ensures encapsulation of the action graph generated by `map_directory()`. For example, internal actions generated within `map_directory(implementation)` themselves depending on the output directories of the same `map_directory()` breaks encapsulation. PiperOrigin-RevId: 884538235 Change-Id: If6c8f4990dea353460de9bdde5f5abca61f8b5a8
1 parent 721c854 commit e46bed7

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkTemplateContext.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
2828
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
2929
import com.google.devtools.build.lib.collect.nestedset.Depset;
30+
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
3031
import com.google.devtools.build.lib.starlarkbuildapi.FileApi;
3132
import com.google.devtools.build.lib.starlarkbuildapi.StarlarkTemplateContextApi;
3233
import com.google.devtools.build.lib.supplier.InterruptibleSupplier;
@@ -88,15 +89,29 @@ public void run(
8889

8990
StarlarkActionFactory.buildCommandLine(builder, arguments, repoMappingSupplier);
9091

92+
List<Artifact> inputArtifacts;
9193
switch (inputs) {
92-
case Sequence<?> sequence ->
93-
builder.addInputs(Sequence.cast(inputs, Artifact.class, "inputs"));
94-
case Depset depset ->
95-
builder.addTransitiveInputs(Depset.cast(depset, Artifact.class, "inputs"));
94+
case Sequence<?> sequence -> {
95+
inputArtifacts = Sequence.cast(inputs, Artifact.class, "inputs");
96+
builder.addInputs(inputArtifacts);
97+
}
98+
case Depset depset -> {
99+
NestedSet<Artifact> inputNestedSet = Depset.cast(depset, Artifact.class, "inputs");
100+
inputArtifacts = inputNestedSet.toList();
101+
builder.addTransitiveInputs(inputNestedSet);
102+
}
96103
default -> {
97104
throw Starlark.errorf("Expected a list or depset but got %s", Starlark.type(inputs));
98105
}
99106
}
107+
108+
for (Artifact input : inputArtifacts) {
109+
if (outputDirectories.contains(input)) {
110+
throw Starlark.errorf(
111+
"Output directory %s cannot be used as an input to template_ctx.run()", input);
112+
}
113+
}
114+
100115
switch (executableUnchecked) {
101116
case Artifact executable -> builder.setExecutable(executable);
102117
case FilesToRunProvider filesToRun -> builder.setExecutable(filesToRun);

src/test/java/com/google/devtools/build/lib/starlark/StarlarkMapActionTemplateTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,65 @@ def rule_impl(ctx):
10121012
PathFragment.create("subdir_1/f2"));
10131013
}
10141014

1015+
@Test
1016+
public void internalActionsCannotTakeTopLevelDirectoriesAsInputs() throws Exception {
1017+
SkyframeExecutorTestHelper.process(getSkyframeExecutor());
1018+
write(
1019+
"test/rule_def.bzl",
1020+
"""
1021+
load(":helpers.bzl", "create_seed_dir", "create_seed_subdir")
1022+
1023+
def combined_impl(
1024+
template_ctx,
1025+
input_directories,
1026+
output_directories,
1027+
tools,
1028+
**kwargs):
1029+
output_dir1 = output_directories["output_dir1"]
1030+
output_dir2 = output_directories["output_dir2"]
1031+
output_file = template_ctx.declare_file(
1032+
"combined.txt",
1033+
directory = output_dir2,
1034+
)
1035+
template_ctx.run(
1036+
inputs = [output_dir1],
1037+
outputs = [output_file],
1038+
executable = tools["cat_tool"],
1039+
# Args don't matter, it should fail.
1040+
arguments = [template_ctx.args()],
1041+
)
1042+
1043+
def rule_impl(ctx):
1044+
input_dir = create_seed_dir(ctx, "input_dir", 1, 1)
1045+
output_dir1 = ctx.actions.declare_directory(ctx.attr.name + "_output_dir1")
1046+
output_dir2 = ctx.actions.declare_directory(ctx.attr.name + "_output_dir2")
1047+
ctx.actions.map_directory(
1048+
implementation = combined_impl,
1049+
input_directories = {
1050+
"input_dir": input_dir,
1051+
},
1052+
output_directories = {
1053+
"output_dir1": output_dir1,
1054+
"output_dir2": output_dir2,
1055+
},
1056+
tools = {
1057+
"cat_tool": ctx.attr.cat_tool.files_to_run,
1058+
"gen_subdir_tool": ctx.attr.gen_subdir_tool.files_to_run,
1059+
},
1060+
)
1061+
return [DefaultInfo(files = depset([output_dir2]))]
1062+
""");
1063+
RecordingOutErr recordingOutErr = new RecordingOutErr();
1064+
this.outErr = recordingOutErr;
1065+
assertThrows(BuildFailedException.class, () -> buildTarget("//test:target"));
1066+
assertThat(recordingOutErr.errAsLatin1())
1067+
.containsMatch(
1068+
"ERROR: .*/test/BUILD:2:8: Expanding \\[File:\\[.*\\]test/target_input_dir\\] into"
1069+
+ " actions\\. failed: Output directory"
1070+
+ " File:\\[.*\\]test/target_output_dir1"
1071+
+ " cannot be used as an input to template_ctx\\.run\\(\\)");
1072+
}
1073+
10151074
@Test
10161075
public void actionConflicts_declaredFileWithPrefixOfSubdir() throws Exception {
10171076
SkyframeExecutorTestHelper.process(getSkyframeExecutor());

0 commit comments

Comments
 (0)