46
46
import com .google .errorprone .annotations .CanIgnoreReturnValue ;
47
47
import java .util .ArrayList ;
48
48
import java .util .Collections ;
49
+ import java .util .EnumSet ;
49
50
import java .util .HashMap ;
50
51
import java .util .HashSet ;
51
52
import java .util .LinkedHashMap ;
52
53
import java .util .Map ;
53
- import java .util .Objects ;
54
54
import java .util .Set ;
55
55
import java .util .UUID ;
56
+ import java .util .function .BiConsumer ;
57
+ import java .util .function .Consumer ;
56
58
import javax .annotation .Nullable ;
57
59
import net .starlark .java .eval .EvalException ;
58
60
import net .starlark .java .eval .Printer ;
@@ -279,16 +281,11 @@ Map<PathFragment, Artifact> getSymlinksAsMap(@Nullable ConflictChecker checker)
279
281
return entriesToMap (symlinks , checker );
280
282
}
281
283
282
- /**
283
- * @param eventHandler Used for throwing an error if we have an obscuring runlink.
284
- * May be null, in which case obscuring symlinks are silently discarded.
285
- * @param location Location for reporter. Ignored if reporter is null.
286
- * @param workingManifest Manifest to be checked for obscuring symlinks.
287
- * @return map of source file names mapped to their location on disk.
288
- */
289
284
@ VisibleForTesting
290
285
static Map <PathFragment , Artifact > filterListForObscuringSymlinks (
291
- EventHandler eventHandler , Location location , Map <PathFragment , Artifact > workingManifest ) {
286
+ boolean report ,
287
+ Consumer <String > messageReceiver ,
288
+ Map <PathFragment , Artifact > workingManifest ) {
292
289
Map <PathFragment , Artifact > newManifest =
293
290
Maps .newHashMapWithExpectedSize (workingManifest .size ());
294
291
Set <PathFragment > noFurtherObstructions = new HashSet <>();
@@ -309,24 +306,22 @@ static Map<PathFragment, Artifact> filterListForObscuringSymlinks(
309
306
Artifact ancestor = workingManifest .get (prefix );
310
307
if (ancestor != null ) {
311
308
// This is an obscuring symlink, so just drop it and move on if there's no reporter.
312
- if (eventHandler == null ) {
309
+ if (! report ) {
313
310
continue outer ;
314
311
}
315
312
PathFragment suffix = source .subFragment (n - j , n );
316
313
PathFragment viaAncestor = ancestor .getExecPath ().getRelative (suffix );
317
314
PathFragment expected = symlink .getExecPath ();
318
315
if (!viaAncestor .equals (expected )) {
319
- eventHandler .handle (
320
- Event .warn (
321
- location ,
322
- "runfiles symlink "
323
- + source
324
- + " -> "
325
- + expected
326
- + " obscured by "
327
- + prefix
328
- + " -> "
329
- + ancestor .getExecPath ()));
316
+ messageReceiver .accept (
317
+ "runfiles symlink "
318
+ + source
319
+ + " -> "
320
+ + expected
321
+ + " obscured by "
322
+ + prefix
323
+ + " -> "
324
+ + ancestor .getExecPath ());
330
325
}
331
326
continue outer ;
332
327
}
@@ -337,29 +332,81 @@ static Map<PathFragment, Artifact> filterListForObscuringSymlinks(
337
332
return newManifest ;
338
333
}
339
334
335
+ /**
336
+ * Returns the symlinks as a map from {@link PathFragment} to {@link Artifact}.
337
+ *
338
+ * <p>Any errors during the conversion are ignored.
339
+ *
340
+ * @param repoMappingManifest repository mapping manifest to add as a root symlink. This manifest
341
+ * has to be added automatically for every executable and is thus not part of the Runfiles
342
+ * advertised by a configured target.
343
+ * @return {@code Map<PathFragment, Artifact>} path fragment to artifact, of normal source tree
344
+ * entries and elements that live outside the source tree. Null values represent empty input
345
+ * files.
346
+ */
347
+ public Map <PathFragment , Artifact > getRunfilesInputs (Artifact repoMappingManifest ) {
348
+ return getRunfilesInputs (EnumSet .noneOf (ConflictType .class ), null , repoMappingManifest );
349
+ }
350
+
351
+ /** Creates a receiver for runfiles conflicts that reports them on an {@link EventHandler}. */
352
+ public BiConsumer <ConflictType , String > eventRunfilesConflictReceiver (
353
+ EventHandler eventHandler , Location location ) {
354
+ return (conflictType , message ) -> {
355
+ EventKind kind =
356
+ switch (conflictType ) {
357
+ case NESTED_RUNFILES_TREE -> EventKind .ERROR ;
358
+ case PREFIX_CONFLICT ->
359
+ conflictPolicy == ConflictPolicy .ERROR ? EventKind .ERROR : EventKind .WARNING ;
360
+ default ->
361
+ switch (conflictPolicy ) {
362
+ case IGNORE -> throw new IllegalStateException ();
363
+ default ->
364
+ conflictPolicy == ConflictPolicy .ERROR ? EventKind .ERROR : EventKind .WARNING ;
365
+ };
366
+ };
367
+
368
+ eventHandler .handle (Event .of (kind , location , message ));
369
+ };
370
+ }
371
+
340
372
/**
341
373
* Returns the symlinks as a map from PathFragment to Artifact.
342
374
*
343
- * @param eventHandler Used for throwing an error if we have an obscuring runlink within the
344
- * normal source tree entries, or runfile conflicts. May be null, in which case obscuring
345
- * symlinks are silently discarded, and conflicts are overwritten.
346
- * @param location Location for eventHandler warnings. Ignored if eventHandler is null.
375
+ * @param receiver called for each conflict
347
376
* @param repoMappingManifest repository mapping manifest to add as a root symlink. This manifest
348
377
* has to be added automatically for every executable and is thus not part of the Runfiles
349
378
* advertised by a configured target.
350
379
* @return Map<PathFragment, Artifact> path fragment to artifact, of normal source tree entries
351
380
* and elements that live outside the source tree. Null values represent empty input files.
352
381
*/
353
382
public Map <PathFragment , Artifact > getRunfilesInputs (
354
- EventHandler eventHandler , Location location , @ Nullable Artifact repoMappingManifest ) {
355
- ConflictChecker checker = new ConflictChecker (conflictPolicy , eventHandler , location );
383
+ BiConsumer <ConflictType , String > receiver , @ Nullable Artifact repoMappingManifest ) {
384
+ EnumSet <ConflictType > conflictsToReport =
385
+ conflictPolicy == ConflictPolicy .IGNORE
386
+ ? EnumSet .of (
387
+ ConflictType .NESTED_RUNFILES_TREE ,
388
+ ConflictType .PREFIX_CONFLICT )
389
+ : EnumSet .allOf (ConflictType .class );
390
+
391
+ return getRunfilesInputs (conflictsToReport , receiver , repoMappingManifest );
392
+ }
393
+
394
+ private Map <PathFragment , Artifact > getRunfilesInputs (
395
+ EnumSet <ConflictType > conflictSet ,
396
+ BiConsumer <ConflictType , String > receiver ,
397
+ @ Nullable Artifact repoMappingManifest ) {
398
+ ConflictChecker checker = new ConflictChecker (receiver , conflictSet );
356
399
Map <PathFragment , Artifact > manifest = getSymlinksAsMap (checker );
357
400
// Add artifacts (committed to inclusion on construction of runfiles).
358
401
for (Artifact artifact : artifacts .toList ()) {
359
402
checker .put (manifest , artifact .getRunfilesPath (), artifact );
360
403
}
361
404
362
- manifest = filterListForObscuringSymlinks (eventHandler , location , manifest );
405
+ manifest =
406
+ filterListForObscuringSymlinks (
407
+ conflictSet .contains (ConflictType .PREFIX_CONFLICT ),
408
+ message -> receiver .accept (ConflictType .PREFIX_CONFLICT , message ),
409
+ manifest );
363
410
364
411
// TODO(bazel-team): Create /dev/null-like Artifact to avoid nulls?
365
412
for (PathFragment extraPath : emptyFilesSupplier .getExtraPaths (manifest .keySet ())) {
@@ -372,13 +419,7 @@ public Map<PathFragment, Artifact> getRunfilesInputs(
372
419
ManifestBuilder builder =
373
420
new ManifestBuilder (PathFragment .create (prefix ), legacyExternalRunfiles );
374
421
builder .addUnderWorkspace (manifest , checker );
375
-
376
- // Finally add symlinks relative to the root of the runfiles tree, on top of everything else.
377
- // This operation is always checked for conflicts, to match historical behavior.
378
- if (conflictPolicy == ConflictPolicy .IGNORE ) {
379
- checker = new ConflictChecker (ConflictPolicy .WARN , eventHandler , location );
380
- }
381
- builder .add (getRootSymlinksAsMap (checker ), checker );
422
+ builder .addRootSymlinks (getRootSymlinksAsMap (checker ), checker );
382
423
if (repoMappingManifest != null ) {
383
424
checker .put (builder .manifest , REPO_MAPPING_PATH_FRAGMENT , repoMappingManifest );
384
425
}
@@ -427,10 +468,9 @@ void addUnderWorkspace(Map<PathFragment, Artifact> inputManifest, ConflictChecke
427
468
}
428
469
}
429
470
430
- /**
431
- * Adds a map to the root directory.
432
- */
433
- public void add (Map <PathFragment , Artifact > inputManifest , ConflictChecker checker ) {
471
+ /** Adds a map to the root directory. */
472
+ public void addRootSymlinks (
473
+ Map <PathFragment , Artifact > inputManifest , ConflictChecker checker ) {
434
474
for (Map .Entry <PathFragment , Artifact > entry : inputManifest .entrySet ()) {
435
475
checker .put (manifest , checkForWorkspace (entry .getKey ()), entry .getValue ());
436
476
}
@@ -495,7 +535,7 @@ public Map<PathFragment, Artifact> getRootSymlinksAsMap(@Nullable ConflictChecke
495
535
* account.
496
536
*/
497
537
public Map <PathFragment , Artifact > asMapWithoutRootSymlinks () {
498
- Map <PathFragment , Artifact > result = entriesToMap (symlinks , null );
538
+ Map <PathFragment , Artifact > result = entriesToMap (symlinks , ConflictChecker . IGNORE_CHECKER );
499
539
// If multiple artifacts have the same output-dir-relative path, the last one in the list will
500
540
// win. That is because the runfiles tree cannot contain the same artifact for different
501
541
// configurations, because it only uses output-dir-relative paths.
@@ -551,9 +591,9 @@ public boolean isEmpty() {
551
591
*/
552
592
private static Map <PathFragment , Artifact > entriesToMap (
553
593
NestedSet <SymlinkEntry > entrySet , @ Nullable ConflictChecker checker ) {
554
- checker = (checker != null ) ? checker : ConflictChecker .IGNORE_CHECKER ;
555
594
Map <PathFragment , Artifact > map = new LinkedHashMap <>();
556
595
for (SymlinkEntry entry : entrySet .toList ()) {
596
+ // ConflictType does not matter, we ignore conflicts here
557
597
checker .put (map , entry .getPath (), entry .getArtifact ());
558
598
}
559
599
return map ;
@@ -571,73 +611,46 @@ public Runfiles setConflictPolicy(ConflictPolicy conflictPolicy) {
571
611
return this ;
572
612
}
573
613
574
- /**
575
- * Checks for conflicts between entries in a runfiles tree while putting them in a map.
576
- */
577
- public static final class ConflictChecker {
614
+ /** What kind of conflict in the runfiles tree is being reported. */
615
+ public enum ConflictType {
616
+ NESTED_RUNFILES_TREE , // A runfiles tree artifact in a runfiles tree
617
+ PREFIX_CONFLICT , // An entry is the prefix of another
618
+ };
619
+
620
+ /** Checks for conflicts between entries in a runfiles tree while putting them in a map. */
621
+ @ VisibleForTesting
622
+ static final class ConflictChecker {
578
623
/** Prebuilt ConflictChecker with policy set to IGNORE */
579
624
static final ConflictChecker IGNORE_CHECKER =
580
- new ConflictChecker (ConflictPolicy .IGNORE , null , null );
581
-
582
- /** Behavior when a conflict is found. */
583
- private final ConflictPolicy policy ;
584
-
585
- /** Used for warning on conflicts. May be null, in which case conflicts are ignored. */
586
- private final EventHandler eventHandler ;
625
+ new ConflictChecker (null , EnumSet .noneOf (ConflictType .class ));
587
626
588
- /** Location for eventHandler warnings. Ignored if eventHandler is null. */
589
- private final Location location ;
590
-
591
- /** Type of event to emit */
592
- private final EventKind eventKind ;
627
+ private final BiConsumer <ConflictType , String > receiver ;
628
+ private final EnumSet <ConflictType > conflictsToReport ;
593
629
594
630
/** Construct a ConflictChecker for the given reporter with the given behavior */
595
- public ConflictChecker (ConflictPolicy policy , EventHandler eventHandler , Location location ) {
596
- if (eventHandler == null ) {
597
- this .policy = ConflictPolicy .IGNORE ; // Can't warn even if we wanted to
598
- } else {
599
- this .policy = policy ;
600
- }
601
- this .eventHandler = eventHandler ;
602
- this .location = location ;
603
- this .eventKind = (policy == ConflictPolicy .ERROR ) ? EventKind .ERROR : EventKind .WARNING ;
631
+ public ConflictChecker (
632
+ BiConsumer <ConflictType , String > receiver , EnumSet <ConflictType > conflictsToReport ) {
633
+ this .receiver = receiver ;
634
+ this .conflictsToReport = conflictsToReport ;
604
635
}
605
636
606
637
/**
607
- * Add an entry to a Map of symlinks, optionally reporting conflicts .
638
+ * Add an entry to a Map of symlinks.
608
639
*
609
640
* @param map Manifest of runfile entries.
610
641
* @param path Path fragment to use as key in map.
611
642
* @param artifact Artifact to store in map. This may be null to indicate an empty file.
612
643
*/
613
- public void put (Map <PathFragment , Artifact > map , PathFragment path , Artifact artifact ) {
614
- if (artifact != null && artifact .isMiddlemanArtifact () && eventHandler != null ) {
615
- eventHandler .handle (
616
- Event .of (
617
- EventKind .ERROR ,
618
- location ,
619
- "Runfiles must not contain middleman artifacts: " + artifact ));
620
- return ;
621
- }
622
- Preconditions .checkArgument (
623
- artifact == null || !artifact .isMiddlemanArtifact (), "%s" , artifact );
624
- if (policy != ConflictPolicy .IGNORE && map .containsKey (path )) {
625
- // Previous and new entry might have value of null
626
- Artifact previous = map .get (path );
627
- if (!Objects .equals (previous , artifact )) {
628
- String previousStr =
629
- (previous == null ) ? "empty file" : previous .getExecPath ().toString ();
630
- String artifactStr =
631
- (artifact == null ) ? "empty file" : artifact .getExecPath ().toString ();
632
- if (!previousStr .equals (artifactStr )) {
633
- String message =
634
- String .format (
635
- "overwrote runfile %s, was symlink to %s, now symlink to %s" ,
636
- path .getSafePathString (), previousStr , artifactStr );
637
- eventHandler .handle (Event .of (eventKind , location , message ));
638
- }
644
+ void put (Map <PathFragment , Artifact > map , PathFragment path , Artifact artifact ) {
645
+ if (artifact != null && artifact .isMiddlemanArtifact ()) {
646
+ if (conflictsToReport .contains (ConflictType .NESTED_RUNFILES_TREE )) {
647
+ receiver .accept (
648
+ ConflictType .NESTED_RUNFILES_TREE ,
649
+ "Runfiles must not contain middleman artifacts: " + artifact );
639
650
}
651
+ return ;
640
652
}
653
+
641
654
map .put (path , artifact );
642
655
}
643
656
}
0 commit comments