|
16 | 16 |
|
17 | 17 | import com.google.common.annotations.VisibleForTesting;
|
18 | 18 | import com.google.common.collect.ImmutableMap;
|
19 |
| -import com.google.common.collect.ImmutableSet; |
20 | 19 | import com.google.common.collect.Maps;
|
21 | 20 | import com.google.common.collect.Sets;
|
22 | 21 | import com.google.devtools.build.lib.cmdline.LabelConstants;
|
23 | 22 | import com.google.devtools.build.lib.cmdline.PackageIdentifier;
|
| 23 | +import com.google.devtools.build.lib.cmdline.RepositoryName; |
24 | 24 | import com.google.devtools.build.lib.concurrent.ThreadSafety;
|
25 |
| -import com.google.devtools.build.lib.vfs.FileSystemUtils; |
26 | 25 | import com.google.devtools.build.lib.vfs.Path;
|
27 | 26 | import com.google.devtools.build.lib.vfs.PathFragment;
|
28 | 27 | import com.google.devtools.build.lib.vfs.Root;
|
29 | 28 | import java.io.IOException;
|
30 |
| -import java.util.ArrayList; |
31 |
| -import java.util.Collections; |
32 | 29 | import java.util.Map;
|
33 | 30 | import java.util.Set;
|
34 |
| -import java.util.logging.Level; |
35 |
| -import java.util.logging.Logger; |
36 | 31 |
|
37 | 32 | /**
|
38 | 33 | * Creates a symlink forest based on a package path map.
|
39 | 34 | */
|
40 | 35 | 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 |
| - |
45 | 36 | private final ImmutableMap<PackageIdentifier, Root> packageRoots;
|
46 | 37 | private final Path execroot;
|
47 |
| - private final String workspaceName; |
48 |
| - private final String productName; |
49 |
| - private final String[] prefixes; |
| 38 | + private final String prefix; |
50 | 39 |
|
51 | 40 | SymlinkForest(
|
52 |
| - ImmutableMap<PackageIdentifier, Root> packageRoots, |
53 |
| - Path execroot, |
54 |
| - String productName, |
55 |
| - String workspaceName) { |
| 41 | + ImmutableMap<PackageIdentifier, Root> packageRoots, Path execroot, String productName) { |
56 | 42 | this.packageRoots = packageRoots;
|
57 | 43 | this.execroot = execroot;
|
58 |
| - this.workspaceName = workspaceName; |
59 |
| - this.productName = productName; |
60 |
| - this.prefixes = new String[] { ".", "_", productName + "-"}; |
| 44 | + this.prefix = productName + "-"; |
61 | 45 | }
|
62 | 46 |
|
63 | 47 | /**
|
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 |
69 |
| - */ |
70 |
| - @VisibleForTesting |
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; |
77 |
| - } |
78 |
| - } |
79 |
| - return null; |
80 |
| - } |
81 |
| - |
82 |
| - /** |
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. |
| 48 | + * Delete all dir trees under a given 'dir' that don't start with a given 'prefix'. Does not |
| 49 | + * follow any symbolic links. |
85 | 50 | */
|
86 | 51 | @VisibleForTesting
|
87 | 52 | @ThreadSafety.ThreadSafe
|
88 |
| - static void deleteTreesBelowNotPrefixed(Path dir, String[] prefixes) throws IOException { |
89 |
| - dirloop: |
| 53 | + static void deleteTreesBelowNotPrefixed(Path dir, String prefix) throws IOException { |
90 | 54 | for (Path p : dir.getDirectoryEntries()) {
|
91 |
| - String name = p.getBaseName(); |
92 |
| - for (String prefix : prefixes) { |
93 |
| - if (name.startsWith(prefix)) { |
94 |
| - continue dirloop; |
95 |
| - } |
| 55 | + if (!p.getBaseName().startsWith(prefix)) { |
| 56 | + p.deleteTree(); |
96 | 57 | }
|
97 |
| - p.deleteTree(); |
98 | 58 | }
|
99 | 59 | }
|
100 | 60 |
|
| 61 | + /** |
| 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> |
| 67 | + */ |
101 | 68 | 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 |
| - } |
| 69 | + deleteTreesBelowNotPrefixed(execroot, prefix); |
| 70 | + |
| 71 | + Path mainRepoRoot = null; |
| 72 | + Map<Path, Path> mainRepoLinks = Maps.newHashMap(); |
| 73 | + Set<Path> externalRepoLinks = Sets.newHashSet(); |
115 | 74 |
|
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<>(); |
120 | 75 | for (Map.Entry<PackageIdentifier, Root> entry : packageRoots.entrySet()) {
|
121 | 76 | PackageIdentifier pkgId = entry.getKey();
|
122 | 77 | if (pkgId.equals(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER)) {
|
123 | 78 | // This isn't a "real" package, don't add it to the symlink tree.
|
124 | 79 | continue;
|
125 | 80 | }
|
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); |
| 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()); |
137 | 89 | }
|
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)); |
| 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); |
149 | 94 | }
|
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())); |
| 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; |
161 | 104 | }
|
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. |
| 105 | + if (externalRepoLinks.isEmpty()) { |
| 106 | + execroot.getRelative(LabelConstants.EXTERNAL_PACKAGE_NAME).createDirectoryAndParents(); |
217 | 107 | }
|
| 108 | + externalRepoLinks.add(execrootLink); |
| 109 | + execrootLink.createSymbolicLink(sourcePath); |
218 | 110 | }
|
219 | 111 | }
|
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()) { |
| 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()) { |
235 | 116 | String baseName = target.getBaseName();
|
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()) { |
| 117 | + Path execPath = execroot.getRelative(baseName); |
| 118 | + // Create any links that don't start with bazel-. |
| 119 | + if (!baseName.startsWith(prefix)) { |
239 | 120 | execPath.createSymbolicLink(target);
|
240 | 121 | }
|
241 | 122 | }
|
| 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 | + } |
242 | 129 | }
|
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); |
265 | 130 | }
|
266 | 131 | }
|
0 commit comments