Skip to content

Commit 73ed46c

Browse files
fmeumcopybara-github
authored andcommitted
Properly account for symlinks and root symlinks in repo mapping manifest
A runfiles symlink entry `path/to/pkg/file -> path/to/artifact` is implicitly prefixed with the workspace name and should thus result in repo mappings to the main repository being added to the repo mapping manifest, not mappings for the owner of the artifact. A runfiles root symlink entry `first_segment/path/to/pkg/file -> path/to/artifact` should result result in repo mappings to the repository with canonical name equal to the first segment being added to the repo mapping manifest, if such a repository exists. Along the way clarifies in the documentation of `ctx.runfiles(symlinks = ...)` that the workspace name will be prefixed implicitly. Closes #18381. PiperOrigin-RevId: 541934743 Change-Id: I2931772c3b337d76e65fd81e4a6b783ed80ac05b
1 parent 11a9deb commit 73ed46c

File tree

12 files changed

+256
-88
lines changed

12 files changed

+256
-88
lines changed

site/en/extending/rules.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,8 @@ need to be different for some reason, you can specify the `root_symlinks` or
884884
`symlinks` arguments. The `root_symlinks` is a dictionary mapping paths to
885885
files, where the paths are relative to the root of the runfiles directory. The
886886
`symlinks` dictionary is the same, but paths are implicitly prefixed with the
887-
name of the workspace.
887+
name of the main workspace (*not* the name of the repository containing the
888+
current target).
888889

889890
```python
890891
...

src/main/java/com/google/devtools/build/lib/analysis/BUILD

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ java_library(
359359
":starlark/starlark_toolchain_context",
360360
":starlark/starlark_transition",
361361
":starlark/template_dict",
362+
":symlink_entry",
362363
":target_and_configuration",
363364
":template_variable_info",
364365
":test/analysis_failure",
@@ -1041,6 +1042,7 @@ java_library(
10411042
deps = [
10421043
":actions/abstract_file_write_action",
10431044
":actions/deterministic_writer",
1045+
":symlink_entry",
10441046
"//src/main/java/com/google/devtools/build/lib/actions",
10451047
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
10461048
"//src/main/java/com/google/devtools/build/lib/actions:commandline_item",
@@ -1143,6 +1145,18 @@ java_library(
11431145
],
11441146
)
11451147

1148+
java_library(
1149+
name = "symlink_entry",
1150+
srcs = ["SymlinkEntry.java"],
1151+
deps = [
1152+
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
1153+
"//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",
1154+
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
1155+
"//src/main/java/net/starlark/java/eval",
1156+
"//third_party:guava",
1157+
],
1158+
)
1159+
11461160
java_library(
11471161
name = "target_and_configuration",
11481162
srcs = ["TargetAndConfiguration.java"],

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

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
// limitations under the License.
1414
package com.google.devtools.build.lib.analysis;
1515

16-
import static com.google.common.collect.ImmutableSet.toImmutableSet;
1716
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
1817
import static java.nio.charset.StandardCharsets.ISO_8859_1;
1918
import static java.util.Comparator.comparing;
@@ -30,6 +29,7 @@
3029
import com.google.devtools.build.lib.actions.ExecException;
3130
import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
3231
import com.google.devtools.build.lib.analysis.actions.DeterministicWriter;
32+
import com.google.devtools.build.lib.cmdline.Label;
3333
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
3434
import com.google.devtools.build.lib.cmdline.RepositoryName;
3535
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -63,19 +63,35 @@ public final class RepoMappingManifestAction extends AbstractFileWriteAction {
6363
});
6464
};
6565

66+
private static final MapFn<Artifact> OWNER_REPO_FN =
67+
(artifact, args) -> {
68+
args.accept(
69+
artifact.getOwner() != null ? artifact.getOwner().getRepository().getName() : "");
70+
};
71+
72+
private static final MapFn<SymlinkEntry> FIRST_SEGMENT_FN =
73+
(symlink, args) -> args.accept(symlink.getPath().getSegment(0));
74+
6675
private final NestedSet<Package> transitivePackages;
6776
private final NestedSet<Artifact> runfilesArtifacts;
77+
private final boolean hasRunfilesSymlinks;
78+
private final NestedSet<SymlinkEntry> runfilesRootSymlinks;
6879
private final String workspaceName;
6980

7081
public RepoMappingManifestAction(
7182
ActionOwner owner,
7283
Artifact output,
7384
NestedSet<Package> transitivePackages,
7485
NestedSet<Artifact> runfilesArtifacts,
86+
NestedSet<SymlinkEntry> runfilesSymlinks,
87+
NestedSet<SymlinkEntry> runfilesRootSymlinks,
7588
String workspaceName) {
76-
super(owner, NestedSetBuilder.emptySet(Order.STABLE_ORDER), output, /*makeExecutable=*/ false);
89+
super(
90+
owner, NestedSetBuilder.emptySet(Order.STABLE_ORDER), output, /* makeExecutable= */ false);
7791
this.transitivePackages = transitivePackages;
7892
this.runfilesArtifacts = runfilesArtifacts;
93+
this.hasRunfilesSymlinks = !runfilesSymlinks.isEmpty();
94+
this.runfilesRootSymlinks = runfilesRootSymlinks;
7995
this.workspaceName = workspaceName;
8096
}
8197

@@ -97,7 +113,9 @@ protected void computeKey(
97113
throws CommandLineExpansionException, EvalException, InterruptedException {
98114
fp.addUUID(MY_UUID);
99115
actionKeyContext.addNestedSetToFingerprint(REPO_AND_MAPPING_DIGEST_FN, fp, transitivePackages);
100-
actionKeyContext.addNestedSetToFingerprint(fp, runfilesArtifacts);
116+
actionKeyContext.addNestedSetToFingerprint(OWNER_REPO_FN, fp, runfilesArtifacts);
117+
fp.addBoolean(hasRunfilesSymlinks);
118+
actionKeyContext.addNestedSetToFingerprint(FIRST_SEGMENT_FN, fp, runfilesRootSymlinks);
101119
fp.addString(workspaceName);
102120
}
103121

@@ -107,11 +125,28 @@ public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx)
107125
return out -> {
108126
PrintWriter writer = new PrintWriter(out, /* autoFlush= */ false, ISO_8859_1);
109127

110-
ImmutableSet<RepositoryName> reposContributingRunfiles =
111-
runfilesArtifacts.toList().stream()
112-
.filter(a -> a.getOwner() != null)
113-
.map(a -> a.getOwner().getRepository())
114-
.collect(toImmutableSet());
128+
var reposInRunfilePaths = ImmutableSet.<String>builder();
129+
130+
// The runfiles paths of symlinks are always prefixed with the main workspace name, *not* the
131+
// name of the repository adding the symlink.
132+
if (hasRunfilesSymlinks) {
133+
reposInRunfilePaths.add(RepositoryName.MAIN.getName());
134+
}
135+
136+
// Since root symlinks are the only way to stage a runfile at a specific path under the
137+
// current repository's runfiles directory, recognize canonical repository names that appear
138+
// as the first segment of their runfiles paths.
139+
for (SymlinkEntry symlink : runfilesRootSymlinks.toList()) {
140+
reposInRunfilePaths.add(symlink.getPath().getSegment(0));
141+
}
142+
143+
for (Artifact artifact : runfilesArtifacts.toList()) {
144+
Label owner = artifact.getOwner();
145+
if (owner != null) {
146+
reposInRunfilePaths.add(owner.getRepository().getName());
147+
}
148+
}
149+
115150
transitivePackages.toList().stream()
116151
.collect(
117152
toImmutableSortedMap(
@@ -123,14 +158,14 @@ public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx)
123158
(first, second) -> first))
124159
.forEach(
125160
(repoName, mapping) ->
126-
writeRepoMapping(writer, reposContributingRunfiles, repoName, mapping));
161+
writeRepoMapping(writer, reposInRunfilePaths.build(), repoName, mapping));
127162
writer.flush();
128163
};
129164
}
130165

131166
private void writeRepoMapping(
132167
PrintWriter writer,
133-
ImmutableSet<RepositoryName> reposContributingRunfiles,
168+
ImmutableSet<String> reposInRunfilesPaths,
134169
RepositoryName repoName,
135170
RepositoryMapping repoMapping) {
136171
for (Entry<String, RepositoryName> mappingEntry :
@@ -140,8 +175,8 @@ private void writeRepoMapping(
140175
// Rlocation paths can't reference an empty apparent name anyway.
141176
continue;
142177
}
143-
if (!reposContributingRunfiles.contains(mappingEntry.getValue())) {
144-
// We only write entries for repos that actually contribute runfiles.
178+
if (!reposInRunfilesPaths.contains(mappingEntry.getValue().getName())) {
179+
// We only write entries for repos whose canonical names appear in runfiles paths.
145180
continue;
146181
}
147182
// The canonical name of the main repo is the empty string, which is not a valid name for a

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

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
4242
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
4343
import com.google.devtools.build.lib.starlarkbuildapi.RunfilesApi;
44-
import com.google.devtools.build.lib.starlarkbuildapi.SymlinkEntryApi;
4544
import com.google.devtools.build.lib.util.Fingerprint;
4645
import com.google.devtools.build.lib.vfs.PathFragment;
4746
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -56,7 +55,6 @@
5655
import java.util.UUID;
5756
import javax.annotation.Nullable;
5857
import net.starlark.java.eval.EvalException;
59-
import net.starlark.java.eval.Printer;
6058
import net.starlark.java.eval.Sequence;
6159
import net.starlark.java.eval.Starlark;
6260
import net.starlark.java.eval.StarlarkSemantics;
@@ -93,73 +91,6 @@ public void fingerprint(Fingerprint fp) {
9391
@SerializationConstant @AutoCodec.VisibleForSerialization
9492
static final EmptyFilesSupplier DUMMY_EMPTY_FILES_SUPPLIER = new DummyEmptyFilesSupplier();
9593

96-
/**
97-
* An entry in the runfiles map.
98-
*
99-
* <p>build-runfiles.cc enforces the following constraints: The PathFragment must not be an
100-
* absolute path, nor contain "..". Overlapping runfiles links are also refused. This is the case
101-
* where you ask to create a link to "foo" and also "foo/bar.txt". I.e. you're asking it to make
102-
* "foo" both a file (symlink) and a directory.
103-
*
104-
* <p>Links to directories are heavily discouraged.
105-
*/
106-
//
107-
// O intrepid fixer or bugs and implementor of features, dare not to add a .equals() method
108-
// to this class, lest you condemn yourself, or a fellow other developer to spending two
109-
// delightful hours in a fancy hotel on a Chromebook that is utterly unsuitable for Java
110-
// development to figure out what went wrong, just like I just did.
111-
//
112-
// The semantics of the symlinks nested set dictates that later entries overwrite earlier
113-
// ones. However, the semantics of nested sets dictate that if there are duplicate entries, they
114-
// are only returned once in the iterator.
115-
//
116-
// These two things, innocent when taken alone, result in the effect that when there are three
117-
// entries for the same path, the first one and the last one the same, and the middle one
118-
// different, the *middle* one will take effect: the middle one overrides the first one, and the
119-
// first one prevents the last one from appearing on the iterator.
120-
//
121-
// The lack of a .equals() method prevents this by making the first entry in the above case not
122-
// equal to the third one if they are not the same instance (which they almost never are).
123-
//
124-
// Goodnight, prince(ss)?, and sweet dreams.
125-
public static final class SymlinkEntry implements SymlinkEntryApi {
126-
private final PathFragment path;
127-
private final Artifact artifact;
128-
129-
SymlinkEntry(PathFragment path, Artifact artifact) {
130-
this.path = Preconditions.checkNotNull(path);
131-
this.artifact = Preconditions.checkNotNull(artifact);
132-
}
133-
134-
@Override
135-
public String getPathString() {
136-
return path.getPathString();
137-
}
138-
139-
public PathFragment getPath() {
140-
return path;
141-
}
142-
143-
@Override
144-
public Artifact getArtifact() {
145-
return artifact;
146-
}
147-
148-
@Override
149-
public boolean isImmutable() {
150-
return true;
151-
}
152-
153-
@Override
154-
public void repr(Printer printer) {
155-
printer.append("SymlinkEntry(path = ");
156-
printer.repr(getPathString());
157-
printer.append(", target_file = ");
158-
artifact.repr(printer);
159-
printer.append(")");
160-
}
161-
}
162-
16394
// It is important to declare this *after* the DUMMY_SYMLINK_EXPANDER to avoid NPEs
16495
public static final Runfiles EMPTY = new Builder().build();
16596

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,9 @@ private static Artifact createRepoMappingManifestAction(
554554
ruleContext.getActionOwner(),
555555
repoMappingManifest,
556556
ruleContext.getTransitivePackagesForRunfileRepoMappingManifest(),
557-
runfiles.getAllArtifacts(),
557+
runfiles.getArtifacts(),
558+
runfiles.getSymlinks(),
559+
runfiles.getRootSymlinks(),
558560
ruleContext.getWorkspaceName()));
559561
return repoMappingManifest;
560562
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2014 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.analysis;
16+
17+
import com.google.common.base.Preconditions;
18+
import com.google.devtools.build.lib.actions.Artifact;
19+
import com.google.devtools.build.lib.starlarkbuildapi.SymlinkEntryApi;
20+
import com.google.devtools.build.lib.vfs.PathFragment;
21+
import net.starlark.java.eval.Printer;
22+
23+
/**
24+
* An entry in the runfiles map.
25+
*
26+
* <p>build-runfiles.cc enforces the following constraints: The PathFragment must not be an absolute
27+
* path, nor contain "..". Overlapping runfiles links are also refused. This is the case where you
28+
* ask to create a link to "foo" and also "foo/bar.txt". I.e. you're asking it to make "foo" both a
29+
* file (symlink) and a directory.
30+
*
31+
* <p>Links to directories are heavily discouraged.
32+
*/
33+
//
34+
// O intrepid fixer or bugs and implementor of features, dare not to add a .equals() method
35+
// to this class, lest you condemn yourself, or a fellow other developer to spending two
36+
// delightful hours in a fancy hotel on a Chromebook that is utterly unsuitable for Java
37+
// development to figure out what went wrong, just like I just did.
38+
//
39+
// The semantics of the symlinks nested set dictates that later entries overwrite earlier
40+
// ones. However, the semantics of nested sets dictate that if there are duplicate entries, they
41+
// are only returned once in the iterator.
42+
//
43+
// These two things, innocent when taken alone, result in the effect that when there are three
44+
// entries for the same path, the first one and the last one the same, and the middle one
45+
// different, the *middle* one will take effect: the middle one overrides the first one, and the
46+
// first one prevents the last one from appearing on the iterator.
47+
//
48+
// The lack of a .equals() method prevents this by making the first entry in the above case not
49+
// equal to the third one if they are not the same instance (which they almost never are).
50+
//
51+
// Goodnight, prince(ss)?, and sweet dreams.
52+
public final class SymlinkEntry implements SymlinkEntryApi {
53+
private final PathFragment path;
54+
private final Artifact artifact;
55+
56+
SymlinkEntry(PathFragment path, Artifact artifact) {
57+
this.path = Preconditions.checkNotNull(path);
58+
this.artifact = Preconditions.checkNotNull(artifact);
59+
}
60+
61+
@Override
62+
public String getPathString() {
63+
return path.getPathString();
64+
}
65+
66+
public PathFragment getPath() {
67+
return path;
68+
}
69+
70+
@Override
71+
public Artifact getArtifact() {
72+
return artifact;
73+
}
74+
75+
@Override
76+
public boolean isImmutable() {
77+
return true;
78+
}
79+
80+
@Override
81+
public void repr(Printer printer) {
82+
printer.append("SymlinkEntry(path = ");
83+
printer.repr(getPathString());
84+
printer.append(", target_file = ");
85+
artifact.repr(printer);
86+
printer.append(")");
87+
}
88+
}

src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141
import com.google.devtools.build.lib.analysis.ResolvedToolchainContext;
4242
import com.google.devtools.build.lib.analysis.RuleContext;
4343
import com.google.devtools.build.lib.analysis.Runfiles;
44-
import com.google.devtools.build.lib.analysis.Runfiles.SymlinkEntry;
4544
import com.google.devtools.build.lib.analysis.RunfilesProvider;
4645
import com.google.devtools.build.lib.analysis.ShToolchain;
46+
import com.google.devtools.build.lib.analysis.SymlinkEntry;
4747
import com.google.devtools.build.lib.analysis.ToolchainCollection;
4848
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
4949
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;

src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetFingerprintCache.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ public static <T> String describedNestedSetFingerprint(
7979
return "<empty>";
8080
}
8181
StringBuilder sb = new StringBuilder();
82-
sb.append("order: ").append(nestedSet.getOrder()).append('\n');
82+
sb.append("order: ")
83+
.append(nestedSet.getOrder())
84+
.append(
85+
" (fingerprinting considers internal"
86+
+ " nested set structure, which is not reflected in values reported below)\n");
8387
ImmutableList<T> list = nestedSet.toList();
8488
sb.append("size: ").append(list.size()).append('\n');
8589
for (T item : list) {

src/main/java/com/google/devtools/build/lib/rules/python/PyBuiltins.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,9 @@ public void repoMappingAction(
322322
ruleContext.getActionOwner(),
323323
repoMappingManifest,
324324
ruleContext.getTransitivePackagesForRunfileRepoMappingManifest(),
325-
runfiles.getAllArtifacts(),
325+
runfiles.getArtifacts(),
326+
runfiles.getSymlinks(),
327+
runfiles.getRootSymlinks(),
326328
ruleContext.getWorkspaceName()));
327329
}
328330

0 commit comments

Comments
 (0)