Skip to content

Commit 57d25be

Browse files
Wyveraldlberki
andauthored
[8.3.1] Remove the "overwrote runfiles" warning (#26374)
Original commits: * e8b2790 * 1e9c127 --------- Co-authored-by: Googler <[email protected]>
1 parent add421c commit 57d25be

File tree

10 files changed

+222
-347
lines changed

10 files changed

+222
-347
lines changed

src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java

Lines changed: 106 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@
4646
import com.google.errorprone.annotations.CanIgnoreReturnValue;
4747
import java.util.ArrayList;
4848
import java.util.Collections;
49+
import java.util.EnumSet;
4950
import java.util.HashMap;
5051
import java.util.HashSet;
5152
import java.util.LinkedHashMap;
5253
import java.util.Map;
53-
import java.util.Objects;
5454
import java.util.Set;
5555
import java.util.UUID;
56+
import java.util.function.BiConsumer;
57+
import java.util.function.Consumer;
5658
import javax.annotation.Nullable;
5759
import net.starlark.java.eval.EvalException;
5860
import net.starlark.java.eval.Printer;
@@ -279,16 +281,11 @@ Map<PathFragment, Artifact> getSymlinksAsMap(@Nullable ConflictChecker checker)
279281
return entriesToMap(symlinks, checker);
280282
}
281283

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-
*/
289284
@VisibleForTesting
290285
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) {
292289
Map<PathFragment, Artifact> newManifest =
293290
Maps.newHashMapWithExpectedSize(workingManifest.size());
294291
Set<PathFragment> noFurtherObstructions = new HashSet<>();
@@ -309,24 +306,22 @@ static Map<PathFragment, Artifact> filterListForObscuringSymlinks(
309306
Artifact ancestor = workingManifest.get(prefix);
310307
if (ancestor != null) {
311308
// This is an obscuring symlink, so just drop it and move on if there's no reporter.
312-
if (eventHandler == null) {
309+
if (!report) {
313310
continue outer;
314311
}
315312
PathFragment suffix = source.subFragment(n - j, n);
316313
PathFragment viaAncestor = ancestor.getExecPath().getRelative(suffix);
317314
PathFragment expected = symlink.getExecPath();
318315
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());
330325
}
331326
continue outer;
332327
}
@@ -337,29 +332,81 @@ static Map<PathFragment, Artifact> filterListForObscuringSymlinks(
337332
return newManifest;
338333
}
339334

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+
340372
/**
341373
* Returns the symlinks as a map from PathFragment to Artifact.
342374
*
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
347376
* @param repoMappingManifest repository mapping manifest to add as a root symlink. This manifest
348377
* has to be added automatically for every executable and is thus not part of the Runfiles
349378
* advertised by a configured target.
350379
* @return Map<PathFragment, Artifact> path fragment to artifact, of normal source tree entries
351380
* and elements that live outside the source tree. Null values represent empty input files.
352381
*/
353382
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);
356399
Map<PathFragment, Artifact> manifest = getSymlinksAsMap(checker);
357400
// Add artifacts (committed to inclusion on construction of runfiles).
358401
for (Artifact artifact : artifacts.toList()) {
359402
checker.put(manifest, artifact.getRunfilesPath(), artifact);
360403
}
361404

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);
363410

364411
// TODO(bazel-team): Create /dev/null-like Artifact to avoid nulls?
365412
for (PathFragment extraPath : emptyFilesSupplier.getExtraPaths(manifest.keySet())) {
@@ -372,13 +419,7 @@ public Map<PathFragment, Artifact> getRunfilesInputs(
372419
ManifestBuilder builder =
373420
new ManifestBuilder(PathFragment.create(prefix), legacyExternalRunfiles);
374421
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);
382423
if (repoMappingManifest != null) {
383424
checker.put(builder.manifest, REPO_MAPPING_PATH_FRAGMENT, repoMappingManifest);
384425
}
@@ -427,10 +468,9 @@ void addUnderWorkspace(Map<PathFragment, Artifact> inputManifest, ConflictChecke
427468
}
428469
}
429470

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) {
434474
for (Map.Entry<PathFragment, Artifact> entry : inputManifest.entrySet()) {
435475
checker.put(manifest, checkForWorkspace(entry.getKey()), entry.getValue());
436476
}
@@ -495,7 +535,7 @@ public Map<PathFragment, Artifact> getRootSymlinksAsMap(@Nullable ConflictChecke
495535
* account.
496536
*/
497537
public Map<PathFragment, Artifact> asMapWithoutRootSymlinks() {
498-
Map<PathFragment, Artifact> result = entriesToMap(symlinks, null);
538+
Map<PathFragment, Artifact> result = entriesToMap(symlinks, ConflictChecker.IGNORE_CHECKER);
499539
// If multiple artifacts have the same output-dir-relative path, the last one in the list will
500540
// win. That is because the runfiles tree cannot contain the same artifact for different
501541
// configurations, because it only uses output-dir-relative paths.
@@ -551,9 +591,9 @@ public boolean isEmpty() {
551591
*/
552592
private static Map<PathFragment, Artifact> entriesToMap(
553593
NestedSet<SymlinkEntry> entrySet, @Nullable ConflictChecker checker) {
554-
checker = (checker != null) ? checker : ConflictChecker.IGNORE_CHECKER;
555594
Map<PathFragment, Artifact> map = new LinkedHashMap<>();
556595
for (SymlinkEntry entry : entrySet.toList()) {
596+
// ConflictType does not matter, we ignore conflicts here
557597
checker.put(map, entry.getPath(), entry.getArtifact());
558598
}
559599
return map;
@@ -571,73 +611,46 @@ public Runfiles setConflictPolicy(ConflictPolicy conflictPolicy) {
571611
return this;
572612
}
573613

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 {
578623
/** Prebuilt ConflictChecker with policy set to IGNORE */
579624
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));
587626

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;
593629

594630
/** 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;
604635
}
605636

606637
/**
607-
* Add an entry to a Map of symlinks, optionally reporting conflicts.
638+
* Add an entry to a Map of symlinks.
608639
*
609640
* @param map Manifest of runfile entries.
610641
* @param path Path fragment to use as key in map.
611642
* @param artifact Artifact to store in map. This may be null to indicate an empty file.
612643
*/
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);
639650
}
651+
return;
640652
}
653+
641654
map.put(path, artifact);
642655
}
643656
}

src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,7 @@ public PathFragment getExecPath() {
153153
@Override
154154
public Map<PathFragment, Artifact> getMapping() {
155155
if (cachedMapping == null) {
156-
return runfiles.getRunfilesInputs(
157-
/* eventHandler= */ null, /* location= */ null, repoMappingManifest);
156+
return runfiles.getRunfilesInputs(repoMappingManifest);
158157
}
159158

160159
Map<PathFragment, Artifact> result = cachedMapping.get();
@@ -168,9 +167,7 @@ public Map<PathFragment, Artifact> getMapping() {
168167
return result;
169168
}
170169

171-
result =
172-
runfiles.getRunfilesInputs(
173-
/* eventHandler= */ null, /* location= */ null, repoMappingManifest);
170+
result = runfiles.getRunfilesInputs(repoMappingManifest);
174171
cachedMapping = new WeakReference<>(result);
175172
return result;
176173
}

0 commit comments

Comments
 (0)