3333import com .google .devtools .build .lib .actions .RunfilesSupplier ;
3434import com .google .devtools .build .lib .actions .Spawn ;
3535import com .google .devtools .build .lib .actions .cache .VirtualActionInput ;
36+ import com .google .devtools .build .lib .collect .nestedset .NestedSet ;
3637import com .google .devtools .build .lib .collect .nestedset .NestedSetBuilder ;
3738import com .google .devtools .build .lib .collect .nestedset .Order ;
3839import com .google .devtools .build .lib .vfs .Path ;
3940import com .google .devtools .build .lib .vfs .PathFragment ;
4041import java .io .IOException ;
42+ import java .util .Arrays ;
4143import java .util .HashMap ;
4244import java .util .List ;
4345import java .util .Map ;
@@ -95,7 +97,7 @@ public SpawnInputExpander(
9597 this .relSymlinkBehavior = relSymlinkBehavior ;
9698 }
9799
98- private void addMapping (
100+ private static void addMapping (
99101 Map <PathFragment , ActionInput > inputMappings ,
100102 PathFragment targetLocation ,
101103 ActionInput input ,
@@ -215,13 +217,12 @@ void addFilesetManifest(
215217 }
216218 }
217219
218- private void addInputs (
220+ private static void addInputs (
219221 Map <PathFragment , ActionInput > inputMap ,
220- Spawn spawn ,
222+ NestedSet <? extends ActionInput > inputFiles ,
221223 ArtifactExpander artifactExpander ,
222224 PathFragment baseDirectory ) {
223- List <ActionInput > inputs =
224- ActionInputHelper .expandArtifacts (spawn .getInputFiles (), artifactExpander );
225+ List <ActionInput > inputs = ActionInputHelper .expandArtifacts (inputFiles , artifactExpander );
225226 for (ActionInput input : inputs ) {
226227 addMapping (inputMap , input .getExecPath (), input , baseDirectory );
227228 }
@@ -243,7 +244,7 @@ public SortedMap<PathFragment, ActionInput> getInputMapping(
243244 MetadataProvider actionInputFileCache )
244245 throws IOException , ForbiddenActionInputException {
245246 TreeMap <PathFragment , ActionInput > inputMap = new TreeMap <>();
246- addInputs (inputMap , spawn , artifactExpander , baseDirectory );
247+ addInputs (inputMap , spawn . getInputFiles () , artifactExpander , baseDirectory );
247248 addRunfilesToInputs (
248249 inputMap ,
249250 spawn .getRunfilesSupplier (),
@@ -254,6 +255,126 @@ public SortedMap<PathFragment, ActionInput> getInputMapping(
254255 return inputMap ;
255256 }
256257
258+ /** The interface for accessing part of the input hierarchy. */
259+ public interface InputWalker {
260+ SortedMap <PathFragment , ActionInput > getLeavesInputMapping ()
261+ throws IOException , ForbiddenActionInputException ;
262+
263+ void visitNonLeaves (InputVisitor visitor ) throws IOException , ForbiddenActionInputException ;
264+ }
265+
266+ /** The interface for visiting part of the input hierarchy. */
267+ public interface InputVisitor {
268+ /**
269+ * Visits a part of the input hierarchy.
270+ *
271+ * <p>{@code nodeKey} can be used as key when memoizing visited parts of the hierarchy.
272+ */
273+ void visit (Object nodeKey , InputWalker walker )
274+ throws IOException , ForbiddenActionInputException ;
275+ }
276+
277+ /**
278+ * Visits the input files hierarchy in a depth first manner.
279+ *
280+ * <p>Similar to {@link #getInputMapping} but allows for early exit, by not visiting children,
281+ * when walking through the input hierarchy. By applying memoization, the retrieval process of the
282+ * inputs can be speeded up.
283+ *
284+ * <p>{@code baseDirectory} is prepended to every path in the input key. This is useful if the
285+ * mapping is used in a context where the directory relative to which the keys are interpreted is
286+ * not the same as the execroot.
287+ */
288+ public void walkInputs (
289+ Spawn spawn ,
290+ ArtifactExpander artifactExpander ,
291+ PathFragment baseDirectory ,
292+ MetadataProvider actionInputFileCache ,
293+ InputVisitor visitor )
294+ throws IOException , ForbiddenActionInputException {
295+ walkNestedSetInputs (baseDirectory , spawn .getInputFiles (), artifactExpander , visitor );
296+
297+ RunfilesSupplier runfilesSupplier = spawn .getRunfilesSupplier ();
298+ visitor .visit (
299+ // The list of variables affecting the functional expressions below.
300+ Arrays .asList (
301+ // Assuming that artifactExpander and actionInputFileCache, different for each spawn,
302+ // always expand the same way.
303+ this , // For accessing addRunfilesToInputs.
304+ runfilesSupplier ,
305+ baseDirectory ),
306+ new InputWalker () {
307+ @ Override
308+ public SortedMap <PathFragment , ActionInput > getLeavesInputMapping ()
309+ throws IOException , ForbiddenActionInputException {
310+ TreeMap <PathFragment , ActionInput > inputMap = new TreeMap <>();
311+ addRunfilesToInputs (
312+ inputMap , runfilesSupplier , actionInputFileCache , artifactExpander , baseDirectory );
313+ return inputMap ;
314+ }
315+
316+ @ Override
317+ public void visitNonLeaves (InputVisitor childVisitor ) {}
318+ });
319+
320+ Map <Artifact , ImmutableList <FilesetOutputSymlink >> filesetMappings = spawn .getFilesetMappings ();
321+ // filesetMappings is assumed to be very small, so no need to implement visitNonLeaves() for
322+ // improved runtime.
323+ visitor .visit (
324+ // The list of variables affecting the functional expressions below.
325+ Arrays .asList (
326+ this , // For accessing addFilesetManifests.
327+ filesetMappings ,
328+ baseDirectory ),
329+ new InputWalker () {
330+ @ Override
331+ public SortedMap <PathFragment , ActionInput > getLeavesInputMapping ()
332+ throws ForbiddenRelativeSymlinkException {
333+ TreeMap <PathFragment , ActionInput > inputMap = new TreeMap <>();
334+ addFilesetManifests (filesetMappings , inputMap , baseDirectory );
335+ return inputMap ;
336+ }
337+
338+ @ Override
339+ public void visitNonLeaves (InputVisitor childVisitor ) {}
340+ });
341+ }
342+
343+ /** Walks through one level of a {@link NestedSet} of {@link ActionInput}s. */
344+ private void walkNestedSetInputs (
345+ PathFragment baseDirectory ,
346+ NestedSet <? extends ActionInput > someInputFiles ,
347+ ArtifactExpander artifactExpander ,
348+ InputVisitor visitor )
349+ throws IOException , ForbiddenActionInputException {
350+ visitor .visit (
351+ // addInputs is static so no need to add 'this' as dependent key.
352+ Arrays .asList (
353+ // Assuming that artifactExpander, different for each spawn, always expands the same
354+ // way.
355+ someInputFiles .toNode (), baseDirectory ),
356+ new InputWalker () {
357+ @ Override
358+ public SortedMap <PathFragment , ActionInput > getLeavesInputMapping () {
359+ TreeMap <PathFragment , ActionInput > inputMap = new TreeMap <>();
360+ addInputs (
361+ inputMap ,
362+ NestedSetBuilder .wrap (someInputFiles .getOrder (), someInputFiles .getLeaves ()),
363+ artifactExpander ,
364+ baseDirectory );
365+ return inputMap ;
366+ }
367+
368+ @ Override
369+ public void visitNonLeaves (InputVisitor childVisitor )
370+ throws IOException , ForbiddenActionInputException {
371+ for (NestedSet <? extends ActionInput > subInputs : someInputFiles .getNonLeaves ()) {
372+ walkNestedSetInputs (baseDirectory , subInputs , artifactExpander , childVisitor );
373+ }
374+ }
375+ });
376+ }
377+
257378 /**
258379 * Exception signaling that an input was not a regular file: most likely a directory. This
259380 * exception is currently never thrown in practice since we do not enforce "strict" mode.
0 commit comments