Skip to content

Commit 9835cb4

Browse files
janakdrcopybara-github
authored andcommitted
Automated rollback of commit 844e4e2.
*** Reason for rollback *** Breaks overlay builds (multiple package paths). PiperOrigin-RevId: 247429048
1 parent 942f7cf commit 9835cb4

File tree

3 files changed

+374
-142
lines changed

3 files changed

+374
-142
lines changed

src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,8 @@ private void prepare(PackageRoots packageRoots)
453453

454454
// Plant the symlink forest.
455455
try (SilentCloseable c = Profiler.instance().profile("plantSymlinkForest")) {
456-
new SymlinkForest(packageRootMap.get(), getExecRoot(), runtime.getProductName())
456+
new SymlinkForest(
457+
packageRootMap.get(), getExecRoot(), runtime.getProductName(), env.getWorkspaceName())
457458
.plantSymlinkForest();
458459
} catch (IOException e) {
459460
throw new ExecutorInitException("Source forest creation failed", e);

src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java

+194-59
Original file line numberDiff line numberDiff line change
@@ -16,116 +16,251 @@
1616

1717
import com.google.common.annotations.VisibleForTesting;
1818
import com.google.common.collect.ImmutableMap;
19+
import com.google.common.collect.ImmutableSet;
1920
import com.google.common.collect.Maps;
2021
import com.google.common.collect.Sets;
2122
import com.google.devtools.build.lib.cmdline.LabelConstants;
2223
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
23-
import com.google.devtools.build.lib.cmdline.RepositoryName;
2424
import com.google.devtools.build.lib.concurrent.ThreadSafety;
25+
import com.google.devtools.build.lib.vfs.FileSystemUtils;
2526
import com.google.devtools.build.lib.vfs.Path;
2627
import com.google.devtools.build.lib.vfs.PathFragment;
2728
import com.google.devtools.build.lib.vfs.Root;
2829
import java.io.IOException;
30+
import java.util.ArrayList;
31+
import java.util.Collections;
2932
import java.util.Map;
3033
import java.util.Set;
34+
import java.util.logging.Level;
35+
import java.util.logging.Logger;
3136

3237
/**
3338
* Creates a symlink forest based on a package path map.
3439
*/
3540
class SymlinkForest {
41+
42+
private static final Logger logger = Logger.getLogger(SymlinkForest.class.getName());
43+
private static final boolean LOG_FINER = logger.isLoggable(Level.FINER);
44+
3645
private final ImmutableMap<PackageIdentifier, Root> packageRoots;
3746
private final Path execroot;
38-
private final String prefix;
47+
private final String workspaceName;
48+
private final String productName;
49+
private final String[] prefixes;
3950

4051
SymlinkForest(
41-
ImmutableMap<PackageIdentifier, Root> packageRoots, Path execroot, String productName) {
52+
ImmutableMap<PackageIdentifier, Root> packageRoots,
53+
Path execroot,
54+
String productName,
55+
String workspaceName) {
4256
this.packageRoots = packageRoots;
4357
this.execroot = execroot;
44-
this.prefix = productName + "-";
58+
this.workspaceName = workspaceName;
59+
this.productName = productName;
60+
this.prefixes = new String[] { ".", "_", productName + "-"};
4561
}
4662

4763
/**
48-
* Delete all dir trees under a given 'dir' that don't start with a given 'prefix'. Does not
49-
* follow any symbolic links.
64+
* Returns the longest prefix from a given set of 'prefixes' that are
65+
* contained in 'path'. I.e the closest ancestor directory containing path.
66+
* Returns null if none found.
67+
* @param path
68+
* @param prefixes
5069
*/
5170
@VisibleForTesting
52-
@ThreadSafety.ThreadSafe
53-
static void deleteTreesBelowNotPrefixed(Path dir, String prefix) throws IOException {
54-
for (Path p : dir.getDirectoryEntries()) {
55-
if (!p.getBaseName().startsWith(prefix)) {
56-
p.deleteTree();
71+
static PackageIdentifier longestPathPrefix(
72+
PackageIdentifier path, ImmutableSet<PackageIdentifier> prefixes) {
73+
for (int i = path.getPackageFragment().segmentCount(); i >= 0; i--) {
74+
PackageIdentifier prefix = createInRepo(path, path.getPackageFragment().subFragment(0, i));
75+
if (prefixes.contains(prefix)) {
76+
return prefix;
5777
}
5878
}
79+
return null;
5980
}
6081

6182
/**
62-
* Plant a symlink forest under execution root to ensure sources file are available and up to
63-
* date. For the main repo: If root package ("//:") is used, link every file and directory under
64-
* the top-level directory of the main repo. Otherwise, we only link the directories that are used
65-
* in presented main repo packages. For every external repo: make a such a directory link:
66-
* <execroot>/<ws_name>/external/<repo_name> --> <output_base>/external/<repo_name>
83+
* Delete all dir trees under a given 'dir' that don't start with one of a set
84+
* of given 'prefixes'. Does not follow any symbolic links.
6785
*/
68-
void plantSymlinkForest() throws IOException {
69-
deleteTreesBelowNotPrefixed(execroot, prefix);
86+
@VisibleForTesting
87+
@ThreadSafety.ThreadSafe
88+
static void deleteTreesBelowNotPrefixed(Path dir, String[] prefixes) throws IOException {
89+
dirloop:
90+
for (Path p : dir.getDirectoryEntries()) {
91+
String name = p.getBaseName();
92+
for (String prefix : prefixes) {
93+
if (name.startsWith(prefix)) {
94+
continue dirloop;
95+
}
96+
}
97+
p.deleteTree();
98+
}
99+
}
70100

71-
Path mainRepoRoot = null;
72-
Map<Path, Path> mainRepoLinks = Maps.newHashMap();
73-
Set<Path> externalRepoLinks = Sets.newHashSet();
101+
void plantSymlinkForest() throws IOException {
102+
deleteTreesBelowNotPrefixed(execroot, prefixes);
103+
// TODO(kchodorow): this can be removed once the execution root is rearranged.
104+
// Current state: symlink tree was created under execroot/$(basename ws) and then
105+
// execroot/wsname is symlinked to that. The execution root change creates (and cleans up)
106+
// subtrees for each repository and has been rolled forward and back several times. Thus, if
107+
// someone was using a with-execroot-change version of bazel and then switched to this one,
108+
// their execution root would contain a subtree for execroot/wsname that would never be
109+
// cleaned up by this version of Bazel.
110+
Path realWorkspaceDir = execroot.getParentDirectory().getRelative(workspaceName);
111+
if (!workspaceName.equals(execroot.getBaseName()) && realWorkspaceDir.exists()
112+
&& !realWorkspaceDir.isSymbolicLink()) {
113+
realWorkspaceDir.deleteTree();
114+
}
74115

116+
// Packages come from exactly one root, but their shared ancestors may come from more.
117+
Map<PackageIdentifier, Set<Root>> dirRootsMap = Maps.newHashMap();
118+
// Elements in this list are added so that parents come before their children.
119+
ArrayList<PackageIdentifier> dirsParentsFirst = new ArrayList<>();
75120
for (Map.Entry<PackageIdentifier, Root> entry : packageRoots.entrySet()) {
76121
PackageIdentifier pkgId = entry.getKey();
77122
if (pkgId.equals(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER)) {
78123
// This isn't a "real" package, don't add it to the symlink tree.
79124
continue;
80125
}
81-
RepositoryName repository = pkgId.getRepository();
82-
if (repository.isMain() || repository.isDefault()) {
83-
// If root package of the main repo is required, we record the main repo root so that
84-
// we can later link everything under main repo's top-level directory. And in this case,
85-
// we don't need to record other links for directories under the top-level directory any
86-
// more.
87-
if (pkgId.getPackageFragment().equals(PathFragment.EMPTY_FRAGMENT)) {
88-
mainRepoRoot = entry.getValue().getRelative(pkgId.getSourceRoot());
126+
Root pkgRoot = entry.getValue();
127+
ArrayList<PackageIdentifier> newDirs = new ArrayList<>();
128+
for (PathFragment fragment = pkgId.getPackageFragment();
129+
!fragment.isEmpty();
130+
fragment = fragment.getParentDirectory()) {
131+
PackageIdentifier dirId = createInRepo(pkgId, fragment);
132+
Set<Root> roots = dirRootsMap.get(dirId);
133+
if (roots == null) {
134+
roots = Sets.newHashSet();
135+
dirRootsMap.put(dirId, roots);
136+
newDirs.add(dirId);
89137
}
90-
if (mainRepoRoot == null) {
91-
Path execrootLink = execroot.getRelative(pkgId.getPackageFragment().getSegment(0));
92-
Path sourcePath = entry.getValue().getRelative(pkgId.getSourceRoot().getSegment(0));
93-
mainRepoLinks.putIfAbsent(execrootLink, sourcePath);
138+
roots.add(pkgRoot);
139+
}
140+
Collections.reverse(newDirs);
141+
dirsParentsFirst.addAll(newDirs);
142+
}
143+
// Now add in roots for all non-pkg dirs that are in between two packages, and missed above.
144+
for (PackageIdentifier dir : dirsParentsFirst) {
145+
if (!packageRoots.containsKey(dir)) {
146+
PackageIdentifier pkgId = longestPathPrefix(dir, packageRoots.keySet());
147+
if (pkgId != null) {
148+
dirRootsMap.get(dir).add(packageRoots.get(pkgId));
94149
}
95-
} else {
96-
// For other external repositories, generate a symlink to the external repository
97-
// directory itself.
98-
// <output_base>/execroot/<main repo name>/external/<external repo name> -->
99-
// <output_base>/external/<external repo name>
100-
Path execrootLink = execroot.getRelative(repository.getPathUnderExecRoot());
101-
Path sourcePath = entry.getValue().getRelative(repository.getSourceRoot());
102-
if (externalRepoLinks.contains(execrootLink)) {
103-
continue;
150+
}
151+
}
152+
// Create output dirs for all dirs that have more than one root and need to be split.
153+
for (PackageIdentifier dir : dirsParentsFirst) {
154+
if (!dir.getRepository().isMain()) {
155+
FileSystemUtils.createDirectoryAndParents(
156+
execroot.getRelative(dir.getRepository().getPathUnderExecRoot()));
157+
}
158+
if (dirRootsMap.get(dir).size() > 1) {
159+
if (LOG_FINER) {
160+
logger.finer("mkdir " + execroot.getRelative(dir.getPathUnderExecRoot()));
104161
}
105-
if (externalRepoLinks.isEmpty()) {
106-
execroot.getRelative(LabelConstants.EXTERNAL_PACKAGE_NAME).createDirectoryAndParents();
162+
FileSystemUtils.createDirectoryAndParents(
163+
execroot.getRelative(dir.getPathUnderExecRoot()));
164+
}
165+
}
166+
167+
// Make dir links for single rooted dirs.
168+
for (PackageIdentifier dir : dirsParentsFirst) {
169+
Set<Root> roots = dirRootsMap.get(dir);
170+
// Simple case of one root for this dir.
171+
if (roots.size() == 1) {
172+
PathFragment parent = dir.getPackageFragment().getParentDirectory();
173+
if (!parent.isEmpty() && dirRootsMap.get(createInRepo(dir, parent)).size() == 1) {
174+
continue; // skip--an ancestor will link this one in from above
175+
}
176+
// This is the top-most dir that can be linked to a single root. Make it so.
177+
Root root = roots.iterator().next(); // lone root in set
178+
if (LOG_FINER) {
179+
logger.finer(
180+
"ln -s "
181+
+ root.getRelative(dir.getSourceRoot())
182+
+ " "
183+
+ execroot.getRelative(dir.getPathUnderExecRoot()));
184+
}
185+
execroot.getRelative(dir.getPathUnderExecRoot())
186+
.createSymbolicLink(root.getRelative(dir.getSourceRoot()));
187+
}
188+
}
189+
// Make links for dirs within packages, skip parent-only dirs.
190+
for (PackageIdentifier dir : dirsParentsFirst) {
191+
if (dirRootsMap.get(dir).size() > 1) {
192+
// If this dir is at or below a package dir, link in its contents.
193+
PackageIdentifier pkgId = longestPathPrefix(dir, packageRoots.keySet());
194+
if (pkgId != null) {
195+
Root root = packageRoots.get(pkgId);
196+
try {
197+
Path absdir = root.getRelative(dir.getSourceRoot());
198+
if (absdir.isDirectory()) {
199+
if (LOG_FINER) {
200+
logger.finer(
201+
"ln -s " + absdir + "/* " + execroot.getRelative(dir.getSourceRoot()) + "/");
202+
}
203+
for (Path target : absdir.getDirectoryEntries()) {
204+
PathFragment p = root.relativize(target);
205+
if (!dirRootsMap.containsKey(createInRepo(pkgId, p))) {
206+
//LOG.finest("ln -s " + target + " " + linkRoot.getRelative(p));
207+
execroot.getRelative(p).createSymbolicLink(target);
208+
}
209+
}
210+
} else {
211+
logger.fine("Symlink planting skipping dir '" + absdir + "'");
212+
}
213+
} catch (IOException e) {
214+
e.printStackTrace();
215+
}
216+
// Otherwise its just an otherwise empty common parent dir.
107217
}
108-
externalRepoLinks.add(execrootLink);
109-
execrootLink.createSymbolicLink(sourcePath);
110218
}
111219
}
112-
if (mainRepoRoot != null) {
113-
// For the main repo top-level directory, generate symlinks to everything in the directory
114-
// instead of the directory itself.
115-
for (Path target : mainRepoRoot.getDirectoryEntries()) {
220+
221+
for (Map.Entry<PackageIdentifier, Root> entry : packageRoots.entrySet()) {
222+
PackageIdentifier pkgId = entry.getKey();
223+
if (!pkgId.getPackageFragment().equals(PathFragment.EMPTY_FRAGMENT)) {
224+
continue;
225+
}
226+
Path execrootDirectory = execroot.getRelative(pkgId.getPathUnderExecRoot());
227+
// If there were no subpackages, this directory might not exist yet.
228+
if (!execrootDirectory.exists()) {
229+
FileSystemUtils.createDirectoryAndParents(execrootDirectory);
230+
}
231+
// For the top-level directory, generate symlinks to everything in the directory instead of
232+
// the directory itself.
233+
Path sourceDirectory = entry.getValue().getRelative(pkgId.getSourceRoot());
234+
for (Path target : sourceDirectory.getDirectoryEntries()) {
116235
String baseName = target.getBaseName();
117-
Path execPath = execroot.getRelative(baseName);
118-
// Create any links that don't start with bazel-.
119-
if (!baseName.startsWith(prefix)) {
236+
Path execPath = execrootDirectory.getRelative(baseName);
237+
// Create any links that don't exist yet and don't start with bazel-.
238+
if (!baseName.startsWith(productName + "-") && !execPath.exists()) {
120239
execPath.createSymbolicLink(target);
121240
}
122241
}
123-
} else {
124-
for (Map.Entry<Path, Path> entry : mainRepoLinks.entrySet()) {
125-
Path link = entry.getKey();
126-
Path target = entry.getValue();
127-
link.createSymbolicLink(target);
128-
}
129242
}
243+
244+
symlinkCorrectWorkspaceName();
245+
}
246+
247+
/**
248+
* Right now, the execution root is under the basename of the source directory, not the name
249+
* defined in the WORKSPACE file. Thus, this adds a symlink with the WORKSPACE's workspace name
250+
* to the old-style execution root.
251+
* TODO(kchodorow): get rid of this once exec root is always under the WORKSPACE's workspace
252+
* name.
253+
* @throws IOException
254+
*/
255+
private void symlinkCorrectWorkspaceName() throws IOException {
256+
Path correctDirectory = execroot.getParentDirectory().getRelative(workspaceName);
257+
if (!correctDirectory.exists()) {
258+
correctDirectory.createSymbolicLink(execroot);
259+
}
260+
}
261+
262+
private static PackageIdentifier createInRepo(
263+
PackageIdentifier repo, PathFragment packageFragment) {
264+
return PackageIdentifier.create(repo.getRepository(), packageFragment);
130265
}
131266
}

0 commit comments

Comments
 (0)