Skip to content

Commit f192362

Browse files
coeuvrejanakdr
andauthored
[5.1] Remote: Action should not be successful and cached if outputs were not created (bazelbuild#15071)
* Add a method, Spawn#isOutputMandatory, to indicate whether a Spawn must create an output by the end of its execution. If not, a Spawn (like a test) may succeed without necessarily creating that output. The 4 categories of actions that do this are: 1. Tests (tests can create XML and other files, but may not). 2. Java compilations with reduced classpaths (the initial compilation with a reduced classpath may fail, but produce a usable jdeps proto file, so the Spawn will claim that it succeeded so that the action can process the proto file). 3. Extra actions with a dummy output that is produced locally, not by the Spawn. However, by changing the outputs of the Spawn to be empty, this situation is avoided. 4. C++ compilations with coverage (a .gcno coverage file is not produced for an empty translation unit). In particular, all SpawnActions' Spawns have #isMandatoryOutput always return true, and there should be no new cases of #isMandatoryOutput returning false besides the ones added in this CL. PiperOrigin-RevId: 425616085 * Remote: Don't upload action result if declared outputs are not created. Even if the exit code is 0. Missing declared outputs will be detected by Bazel later and fail the build, so avoid uploading this false positive cache entry. Wrap buildUploadManifest inside a `Single.fromCallable` since there are many IOs (both the check we add in this PR and stats already there). If `--experimental_remote_cache_async` is set, these IOs will now be executed in a background thread. Fixes bazelbuild#14543. Closes bazelbuild#15016. PiperOrigin-RevId: 434448255 * Remote: Check declared outputs when downloading outputs. An AC entry that misses declared outputs of an action is invalid because, if Bazel accepts this from remote cache, it will detect the missing declared outputs error at a later stage and fail the build. This PR adds a check for mandatory outputs when downloading outputs from remote cache. If a mandatory output is missing from AC entry, Bazel will ignore the cache entry so build can continue. Also fixes an issue introduced by bazelbuild#15016 that tests result are not uploaded to remote cache. Test spawns declare all the possible outputs but they usually don't generate them all. This change fixes that by only check for mandatory outputs via `spawn.isMandatoryOutput()`. Fixes bazelbuild#14543. Closes bazelbuild#15051. PiperOrigin-RevId: 435307260 Co-authored-by: janakr <[email protected]>
1 parent 267142f commit f192362

20 files changed

+364
-136
lines changed

src/main/java/com/google/devtools/build/lib/actions/BaseSpawn.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
import com.google.common.collect.ImmutableList;
1818
import com.google.common.collect.ImmutableMap;
19+
import com.google.common.collect.ImmutableSet;
1920
import com.google.common.collect.Iterables;
2021
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
2122
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
2223
import com.google.devtools.build.lib.vfs.PathFragment;
23-
import java.util.Collection;
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
@@ -113,7 +113,7 @@ public NestedSet<? extends ActionInput> getInputFiles() {
113113
}
114114

115115
@Override
116-
public Collection<? extends ActionInput> getOutputFiles() {
116+
public ImmutableSet<Artifact> getOutputFiles() {
117117
return action.getOutputs();
118118
}
119119

src/main/java/com/google/devtools/build/lib/actions/DelegateSpawn.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class DelegateSpawn implements Spawn {
2929

3030
private final Spawn spawn;
3131

32-
public DelegateSpawn(Spawn spawn){
32+
public DelegateSpawn(Spawn spawn) {
3333
this.spawn = spawn;
3434
}
3535

@@ -73,6 +73,11 @@ public Collection<? extends ActionInput> getOutputFiles() {
7373
return spawn.getOutputFiles();
7474
}
7575

76+
@Override
77+
public boolean isMandatoryOutput(ActionInput output) {
78+
return spawn.isMandatoryOutput(output);
79+
}
80+
7681
@Override
7782
public ActionExecutionMetadata getResourceOwner() {
7883
return spawn.getResourceOwner();

src/main/java/com/google/devtools/build/lib/actions/SimpleSpawn.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@
2222
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
2323
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
2424
import com.google.devtools.build.lib.collect.nestedset.Order;
25+
import java.util.Set;
2526
import javax.annotation.Nullable;
2627
import javax.annotation.concurrent.Immutable;
2728

28-
/**
29-
* Immutable implementation of a Spawn that does not perform any processing on the parameters.
30-
* Prefer this over all other Spawn implementations.
31-
*/
29+
/** Immutable implementation of a Spawn that does not perform any processing on the parameters. */
3230
@Immutable
3331
public final class SimpleSpawn implements Spawn {
3432
private final ActionExecutionMetadata owner;
@@ -41,6 +39,8 @@ public final class SimpleSpawn implements Spawn {
4139
private final ImmutableMap<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings;
4240
private final ImmutableList<? extends ActionInput> outputs;
4341
private final ResourceSet localResources;
42+
// If null, all outputs are mandatory.
43+
@Nullable private final Set<? extends ActionInput> mandatoryOutputs;
4444

4545
public SimpleSpawn(
4646
ActionExecutionMetadata owner,
@@ -52,6 +52,7 @@ public SimpleSpawn(
5252
NestedSet<? extends ActionInput> inputs,
5353
NestedSet<? extends ActionInput> tools,
5454
ImmutableSet<? extends ActionInput> outputs,
55+
@Nullable Set<? extends ActionInput> mandatoryOutputs,
5556
ResourceSet localResources) {
5657
this.owner = Preconditions.checkNotNull(owner);
5758
this.arguments = Preconditions.checkNotNull(arguments);
@@ -63,6 +64,7 @@ public SimpleSpawn(
6364
runfilesSupplier == null ? EmptyRunfilesSupplier.INSTANCE : runfilesSupplier;
6465
this.filesetMappings = filesetMappings;
6566
this.outputs = Preconditions.checkNotNull(outputs).asList();
67+
this.mandatoryOutputs = mandatoryOutputs;
6668
this.localResources = Preconditions.checkNotNull(localResources);
6769
}
6870

@@ -73,7 +75,7 @@ public SimpleSpawn(
7375
ImmutableMap<String, String> executionInfo,
7476
NestedSet<? extends ActionInput> inputs,
7577
ImmutableSet<? extends ActionInput> outputs,
76-
ResourceSet localResources) {
78+
ResourceSet resourceSet) {
7779
this(
7880
owner,
7981
arguments,
@@ -84,7 +86,8 @@ public SimpleSpawn(
8486
inputs,
8587
NestedSetBuilder.emptySet(Order.STABLE_ORDER),
8688
outputs,
87-
localResources);
89+
/*mandatoryOutputs=*/ null,
90+
resourceSet);
8891
}
8992

9093
@Override
@@ -127,6 +130,11 @@ public ImmutableList<? extends ActionInput> getOutputFiles() {
127130
return outputs;
128131
}
129132

133+
@Override
134+
public boolean isMandatoryOutput(ActionInput output) {
135+
return mandatoryOutputs == null || mandatoryOutputs.contains(output);
136+
}
137+
130138
@Override
131139
public ActionExecutionMetadata getResourceOwner() {
132140
return owner;

src/main/java/com/google/devtools/build/lib/actions/Spawn.java

+22-5
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,35 @@ public interface Spawn extends DescribableExecutionUnit {
9999
NestedSet<? extends ActionInput> getInputFiles();
100100

101101
/**
102-
* Returns the collection of files that this command must write. Callers should not mutate
103-
* the result.
102+
* Returns the collection of files that this command will write. Callers should not mutate the
103+
* result.
104104
*
105105
* <p>This is for use with remote execution, so remote execution does not have to guess what
106-
* outputs the process writes. While the order does not affect the semantics, it should be
107-
* stable so it can be cached.
106+
* outputs the process writes. While the order does not affect the semantics, it should be stable
107+
* so it can be cached.
108108
*/
109109
Collection<? extends ActionInput> getOutputFiles();
110110

111111
/**
112-
* Returns the resource owner for local fallback.
112+
* Returns true if {@code output} must be created for the action to succeed. Can be used by remote
113+
* execution implementations to mark a command as failed if it did not create an output, even if
114+
* the command itself exited with a successful exit code.
115+
*
116+
* <p>Some actions, like tests, may have optional files (like .xml files) that may be created, but
117+
* are not required, so their spawns should return false for those optional files. Note that in
118+
* general, every output in {@link ActionAnalysisMetadata#getOutputs} is checked for existence in
119+
* {@link com.google.devtools.build.lib.skyframe.SkyframeActionExecutor#checkOutputs}, so
120+
* eventually all those outputs must be produced by at least one {@code Spawn} for that action, or
121+
* locally by the action in some cases.
122+
*
123+
* <p>This method should not be overridden by any new Spawns if possible: outputs should be
124+
* mandatory.
113125
*/
126+
default boolean isMandatoryOutput(ActionInput output) {
127+
return true;
128+
}
129+
130+
/** Returns the resource owner for local fallback. */
114131
ActionExecutionMetadata getResourceOwner();
115132

116133
/**

src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java

+29-14
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,11 @@ private ActionExecutionException createCommandLineException(CommandLineExpansion
340340
return new ActionExecutionException(e, this, /*catastrophe=*/ false, detailedExitCode);
341341
}
342342

343+
@VisibleForTesting
344+
public ResourceSetOrBuilder getResourceSetOrBuilder() {
345+
return resourceSetOrBuilder;
346+
}
347+
343348
/**
344349
* Returns a Spawn that is representative of the command that this Action will execute. This
345350
* function must not modify any state.
@@ -361,20 +366,22 @@ final Spawn getSpawn(NestedSet<Artifact> inputs)
361366
/*envResolved=*/ false,
362367
inputs,
363368
/*additionalInputs=*/ ImmutableList.of(),
364-
/*filesetMappings=*/ ImmutableMap.of());
369+
/*filesetMappings=*/ ImmutableMap.of(),
370+
/*reportOutputs=*/ true);
365371
}
366372

367373
/**
368-
* Return a spawn that is representative of the command that this Action will execute in the given
369-
* client environment.
374+
* Returns a spawn that is representative of the command that this Action will execute in the
375+
* given client environment.
370376
*/
371377
public Spawn getSpawn(ActionExecutionContext actionExecutionContext)
372378
throws CommandLineExpansionException, InterruptedException {
373379
return getSpawn(
374380
actionExecutionContext.getArtifactExpander(),
375381
actionExecutionContext.getClientEnv(),
376382
/*envResolved=*/ false,
377-
actionExecutionContext.getTopLevelFilesets());
383+
actionExecutionContext.getTopLevelFilesets(),
384+
/*reportOutputs=*/ true);
378385
}
379386

380387
/**
@@ -385,11 +392,12 @@ public Spawn getSpawn(ActionExecutionContext actionExecutionContext)
385392
* effective environment. Otherwise they will be used as client environment to resolve the
386393
* action env.
387394
*/
388-
Spawn getSpawn(
395+
protected Spawn getSpawn(
389396
ArtifactExpander artifactExpander,
390397
Map<String, String> env,
391398
boolean envResolved,
392-
Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings)
399+
Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings,
400+
boolean reportOutputs)
393401
throws CommandLineExpansionException, InterruptedException {
394402
ExpandedCommandLines expandedCommandLines =
395403
commandLines.expand(artifactExpander, getPrimaryOutput().getExecPath(), commandLineLimits);
@@ -399,7 +407,8 @@ Spawn getSpawn(
399407
envResolved,
400408
getInputs(),
401409
expandedCommandLines.getParamFiles(),
402-
filesetMappings);
410+
filesetMappings,
411+
reportOutputs);
403412
}
404413

405414
Spawn getSpawnForExtraAction() throws CommandLineExpansionException, InterruptedException {
@@ -554,10 +563,11 @@ public Map<String, String> getExecutionInfo() {
554563
}
555564

556565
/** A spawn instance that is tied to a specific SpawnAction. */
557-
private class ActionSpawn extends BaseSpawn {
566+
private final class ActionSpawn extends BaseSpawn {
558567
private final NestedSet<ActionInput> inputs;
559568
private final Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings;
560569
private final ImmutableMap<String, String> effectiveEnvironment;
570+
private final boolean reportOutputs;
561571

562572
/**
563573
* Creates an ActionSpawn with the given environment variables.
@@ -571,7 +581,8 @@ private ActionSpawn(
571581
boolean envResolved,
572582
NestedSet<Artifact> inputs,
573583
Iterable<? extends ActionInput> additionalInputs,
574-
Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings)
584+
Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings,
585+
boolean reportOutputs)
575586
throws CommandLineExpansionException {
576587
super(
577588
arguments,
@@ -592,16 +603,15 @@ private ActionSpawn(
592603
this.inputs = inputsBuilder.build();
593604
this.filesetMappings = filesetMappings;
594605

595-
/**
596-
* If the action environment is already resolved using the client environment, the given
597-
* environment variables are used as they are. Otherwise, they are used as clientEnv to
598-
* resolve the action environment variables
599-
*/
606+
// If the action environment is already resolved using the client environment, the given
607+
// environment variables are used as they are. Otherwise, they are used as clientEnv to
608+
// resolve the action environment variables.
600609
if (envResolved) {
601610
effectiveEnvironment = ImmutableMap.copyOf(env);
602611
} else {
603612
effectiveEnvironment = SpawnAction.this.getEffectiveEnvironment(env);
604613
}
614+
this.reportOutputs = reportOutputs;
605615
}
606616

607617
@Override
@@ -618,6 +628,11 @@ public ImmutableMap<Artifact, ImmutableList<FilesetOutputSymlink>> getFilesetMap
618628
public NestedSet<? extends ActionInput> getInputFiles() {
619629
return inputs;
620630
}
631+
632+
@Override
633+
public ImmutableSet<Artifact> getOutputFiles() {
634+
return reportOutputs ? super.getOutputFiles() : ImmutableSet.of();
635+
}
621636
}
622637

623638
/**

src/main/java/com/google/devtools/build/lib/analysis/actions/StarlarkAction.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,8 @@ public Spawn getSpawn(ActionExecutionContext actionExecutionContext)
324324
actionExecutionContext.getArtifactExpander(),
325325
getEffectiveEnvironment(actionExecutionContext.getClientEnv()),
326326
/*envResolved=*/ true,
327-
actionExecutionContext.getTopLevelFilesets());
327+
actionExecutionContext.getTopLevelFilesets(),
328+
/*reportOutputs=*/ true);
328329
}
329330

330331
@Override

src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraAction.java

+18-13
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
import com.google.devtools.build.lib.actions.ActionExecutionException;
2727
import com.google.devtools.build.lib.actions.Artifact;
2828
import com.google.devtools.build.lib.actions.CommandLine;
29+
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
2930
import com.google.devtools.build.lib.actions.CommandLines;
3031
import com.google.devtools.build.lib.actions.CommandLines.CommandLineLimits;
3132
import com.google.devtools.build.lib.actions.CompositeRunfilesSupplier;
3233
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
3334
import com.google.devtools.build.lib.actions.ExecException;
3435
import com.google.devtools.build.lib.actions.RunfilesSupplier;
36+
import com.google.devtools.build.lib.actions.Spawn;
3537
import com.google.devtools.build.lib.actions.SpawnResult;
3638
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
3739
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -54,17 +56,8 @@ public final class ExtraAction extends SpawnAction {
5456
private final boolean createDummyOutput;
5557
private final NestedSet<Artifact> extraActionInputs;
5658

57-
/**
58-
* A long way to say (ExtraAction xa) -> xa.getShadowedAction().
59-
*/
6059
public static final Function<ExtraAction, Action> GET_SHADOWED_ACTION =
61-
new Function<ExtraAction, Action>() {
62-
@Nullable
63-
@Override
64-
public Action apply(@Nullable ExtraAction extraAction) {
65-
return extraAction != null ? extraAction.getShadowedAction() : null;
66-
}
67-
};
60+
e -> e != null ? e.getShadowedAction() : null;
6861

6962
ExtraAction(
7063
NestedSet<Artifact> extraActionInputs,
@@ -153,6 +146,20 @@ public NestedSet<Artifact> getAllowedDerivedInputs() {
153146
return shadowedAction.getAllowedDerivedInputs();
154147
}
155148

149+
@Override
150+
public Spawn getSpawn(ActionExecutionContext actionExecutionContext)
151+
throws CommandLineExpansionException, InterruptedException {
152+
if (!createDummyOutput) {
153+
return super.getSpawn(actionExecutionContext);
154+
}
155+
return getSpawn(
156+
actionExecutionContext.getArtifactExpander(),
157+
actionExecutionContext.getClientEnv(),
158+
/*envResolved=*/ false,
159+
actionExecutionContext.getTopLevelFilesets(),
160+
/*reportOutputs=*/ false);
161+
}
162+
156163
@Override
157164
protected void afterExecute(
158165
ActionExecutionContext actionExecutionContext, List<SpawnResult> spawnResults)
@@ -171,9 +178,7 @@ protected void afterExecute(
171178
}
172179
}
173180

174-
/**
175-
* Returns the action this extra action is 'shadowing'.
176-
*/
181+
/** Returns the action this extra action is 'shadowing'. */
177182
public Action getShadowedAction() {
178183
return shadowedAction;
179184
}

src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java

+7-8
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ public TestRunnerSpawn createTestRunnerSpawn(
151151
? action.getTools()
152152
: NestedSetBuilder.emptySet(Order.STABLE_ORDER),
153153
createSpawnOutputs(action),
154+
/*mandatoryOutputs=*/ ImmutableSet.of(),
154155
localResourceUsage);
155156
Path execRoot = actionExecutionContext.getExecRoot();
156157
ArtifactPathResolver pathResolver = actionExecutionContext.getPathResolver();
@@ -558,13 +559,10 @@ private static Spawn createXmlGeneratingSpawn(
558559
Order.STABLE_ORDER, action.getTestXmlGeneratorScript(), action.getTestLog()),
559560
/*tools=*/ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
560561
/*outputs=*/ ImmutableSet.of(createArtifactOutput(action, action.getXmlOutputPath())),
562+
/*mandatoryOutputs=*/ null,
561563
SpawnAction.DEFAULT_RESOURCE_SET);
562564
}
563565

564-
/**
565-
* A spawn to generate a test.xml file from the test log. This is only used if the test does not
566-
* generate a test.xml file itself.
567-
*/
568566
private static Spawn createCoveragePostProcessingSpawn(
569567
ActionExecutionContext actionExecutionContext,
570568
TestRunnerAction action,
@@ -588,18 +586,19 @@ private static Spawn createCoveragePostProcessingSpawn(
588586
ImmutableMap.copyOf(testEnvironment),
589587
ImmutableMap.copyOf(action.getExecutionInfo()),
590588
action.getLcovMergerRunfilesSupplier(),
591-
/* filesetMappings= */ ImmutableMap.of(),
592-
/* inputs= */ NestedSetBuilder.<ActionInput>compileOrder()
589+
/*filesetMappings=*/ ImmutableMap.of(),
590+
/*inputs=*/ NestedSetBuilder.<ActionInput>compileOrder()
593591
.addTransitive(action.getInputs())
594592
.addAll(expandedCoverageDir)
595593
.add(action.getCollectCoverageScript())
596594
.add(action.getCoverageDirectoryTreeArtifact())
597595
.add(action.getCoverageManifest())
598596
.addTransitive(action.getLcovMergerFilesToRun().build())
599597
.build(),
600-
/* tools= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
601-
/* outputs= */ ImmutableSet.of(
598+
/*tools=*/ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
599+
/*outputs=*/ ImmutableSet.of(
602600
ActionInputHelper.fromPath(action.getCoverageData().getExecPath())),
601+
/*mandatoryOutputs=*/ null,
603602
SpawnAction.DEFAULT_RESOURCE_SET);
604603
}
605604

0 commit comments

Comments
 (0)