1313// limitations under the License.
1414package com .google .devtools .build .lib .exec ;
1515
16+ import static com .google .common .collect .ImmutableList .toImmutableList ;
1617
1718import com .google .common .annotations .VisibleForTesting ;
1819import com .google .common .base .Preconditions ;
2223import com .google .devtools .build .lib .actions .Artifact ;
2324import com .google .devtools .build .lib .actions .Artifact .ArtifactExpander ;
2425import com .google .devtools .build .lib .actions .Artifact .MissingExpansionException ;
26+ import com .google .devtools .build .lib .actions .Artifact .SpecialArtifact ;
2527import com .google .devtools .build .lib .actions .Artifact .TreeFileArtifact ;
2628import com .google .devtools .build .lib .actions .FileArtifactValue ;
2729import com .google .devtools .build .lib .actions .FilesetManifest ;
3941import com .google .devtools .build .lib .vfs .Path ;
4042import com .google .devtools .build .lib .vfs .PathFragment ;
4143import java .io .IOException ;
42- import java .util .Arrays ;
4344import java .util .HashMap ;
4445import java .util .List ;
4546import java .util .Map ;
@@ -266,14 +267,19 @@ public SortedMap<PathFragment, ActionInput> getInputMapping(
266267
267268 /** The interface for accessing part of the input hierarchy. */
268269 public interface InputWalker {
270+
271+ /** Returns the leaf nodes at this point in the hierarchy. */
269272 SortedMap <PathFragment , ActionInput > getLeavesInputMapping ()
270273 throws IOException , ForbiddenActionInputException ;
271274
272- void visitNonLeaves (InputVisitor visitor ) throws IOException , ForbiddenActionInputException ;
275+ /** Invokes the visitor on the non-leaf nodes at this point in the hierarchy. */
276+ default void visitNonLeaves (InputVisitor visitor )
277+ throws IOException , ForbiddenActionInputException {}
273278 }
274279
275280 /** The interface for visiting part of the input hierarchy. */
276281 public interface InputVisitor {
282+
277283 /**
278284 * Visits a part of the input hierarchy.
279285 *
@@ -305,13 +311,8 @@ public void walkInputs(
305311
306312 RunfilesSupplier runfilesSupplier = spawn .getRunfilesSupplier ();
307313 visitor .visit (
308- // The list of variables affecting the functional expressions below.
309- Arrays .asList (
310- // Assuming that artifactExpander and actionInputFileCache, different for each spawn,
311- // always expand the same way.
312- this , // For accessing addRunfilesToInputs.
313- runfilesSupplier ,
314- baseDirectory ),
314+ // Cache key for the sub-mapping containing the runfiles inputs for this spawn.
315+ ImmutableList .of (runfilesSupplier , baseDirectory ),
315316 new InputWalker () {
316317 @ Override
317318 public SortedMap <PathFragment , ActionInput > getLeavesInputMapping ()
@@ -321,20 +322,14 @@ public SortedMap<PathFragment, ActionInput> getLeavesInputMapping()
321322 inputMap , runfilesSupplier , actionInputFileCache , artifactExpander , baseDirectory );
322323 return inputMap ;
323324 }
324-
325- @ Override
326- public void visitNonLeaves (InputVisitor childVisitor ) {}
327325 });
328326
329327 Map <Artifact , ImmutableList <FilesetOutputSymlink >> filesetMappings = spawn .getFilesetMappings ();
330328 // filesetMappings is assumed to be very small, so no need to implement visitNonLeaves() for
331329 // improved runtime.
332330 visitor .visit (
333- // The list of variables affecting the functional expressions below.
334- Arrays .asList (
335- this , // For accessing addFilesetManifests.
336- filesetMappings ,
337- baseDirectory ),
331+ // Cache key for the sub-mapping containing the fileset inputs for this spawn.
332+ ImmutableList .of (filesetMappings , baseDirectory ),
338333 new InputWalker () {
339334 @ Override
340335 public SortedMap <PathFragment , ActionInput > getLeavesInputMapping ()
@@ -343,32 +338,32 @@ public SortedMap<PathFragment, ActionInput> getLeavesInputMapping()
343338 addFilesetManifests (filesetMappings , inputMap , baseDirectory );
344339 return inputMap ;
345340 }
346-
347- @ Override
348- public void visitNonLeaves (InputVisitor childVisitor ) {}
349341 });
350342 }
351343
352- /** Walks through one level of a {@link NestedSet} of {@link ActionInput}s . */
344+ /** Visits a {@link NestedSet} occurring in {@link Spawn#getInputFiles} . */
353345 private void walkNestedSetInputs (
354346 PathFragment baseDirectory ,
355347 NestedSet <? extends ActionInput > someInputFiles ,
356348 ArtifactExpander artifactExpander ,
357349 InputVisitor visitor )
358350 throws IOException , ForbiddenActionInputException {
359351 visitor .visit (
360- // addInputs is static so no need to add 'this' as dependent key.
361- Arrays .asList (
362- // Assuming that artifactExpander, different for each spawn, always expands the same
363- // way.
364- someInputFiles .toNode (), baseDirectory ),
352+ // Cache key for the sub-mapping containing the files in this nested set.
353+ ImmutableList .of (someInputFiles .toNode (), baseDirectory ),
365354 new InputWalker () {
366355 @ Override
367356 public SortedMap <PathFragment , ActionInput > getLeavesInputMapping () {
368357 TreeMap <PathFragment , ActionInput > inputMap = new TreeMap <>();
358+ // Consider files inside tree artifacts to be non-leaves. This caches better when a
359+ // large tree is not the sole direct child of a nested set.
360+ ImmutableList <? extends ActionInput > leaves =
361+ someInputFiles .getLeaves ().stream ()
362+ .filter (a -> !isTreeArtifact (a ))
363+ .collect (toImmutableList ());
369364 addInputs (
370365 inputMap ,
371- NestedSetBuilder .wrap (someInputFiles .getOrder (), someInputFiles . getLeaves () ),
366+ NestedSetBuilder .wrap (someInputFiles .getOrder (), leaves ),
372367 artifactExpander ,
373368 baseDirectory );
374369 return inputMap ;
@@ -377,18 +372,53 @@ public SortedMap<PathFragment, ActionInput> getLeavesInputMapping() {
377372 @ Override
378373 public void visitNonLeaves (InputVisitor childVisitor )
379374 throws IOException , ForbiddenActionInputException {
375+ for (ActionInput input : someInputFiles .getLeaves ()) {
376+ if (isTreeArtifact (input )) {
377+ walkTreeInputs (
378+ baseDirectory , (SpecialArtifact ) input , artifactExpander , childVisitor );
379+ }
380+ }
380381 for (NestedSet <? extends ActionInput > subInputs : someInputFiles .getNonLeaves ()) {
381382 walkNestedSetInputs (baseDirectory , subInputs , artifactExpander , childVisitor );
382383 }
383384 }
384385 });
385386 }
386387
388+ /** Visits a tree artifact occurring in {@link Spawn#getInputFiles}. */
389+ private void walkTreeInputs (
390+ PathFragment baseDirectory ,
391+ SpecialArtifact tree ,
392+ ArtifactExpander artifactExpander ,
393+ InputVisitor visitor )
394+ throws IOException , ForbiddenActionInputException {
395+ visitor .visit (
396+ // Cache key for the sub-mapping containing the files in this tree artifact.
397+ ImmutableList .of (tree , baseDirectory ),
398+ new InputWalker () {
399+ @ Override
400+ public SortedMap <PathFragment , ActionInput > getLeavesInputMapping () {
401+ TreeMap <PathFragment , ActionInput > inputMap = new TreeMap <>();
402+ addInputs (
403+ inputMap ,
404+ NestedSetBuilder .create (Order .STABLE_ORDER , tree ),
405+ artifactExpander ,
406+ baseDirectory );
407+ return inputMap ;
408+ }
409+ });
410+ }
411+
412+ private static boolean isTreeArtifact (ActionInput input ) {
413+ return input instanceof SpecialArtifact && ((SpecialArtifact ) input ).isTreeArtifact ();
414+ }
415+
387416 /**
388417 * Exception signaling that an input was not a regular file: most likely a directory. This
389418 * exception is currently never thrown in practice since we do not enforce "strict" mode.
390419 */
391420 private static final class ForbiddenNonFileException extends ForbiddenActionInputException {
421+
392422 ForbiddenNonFileException (ActionInput input ) {
393423 super ("Not a file: " + input .getExecPathString ());
394424 }
0 commit comments