From 844e4e297b404d6ff28b818d8150d4b9c47de887 Mon Sep 17 00:00:00 2001 From: pcloudy Date: Fri, 3 May 2019 09:23:53 -0700 Subject: [PATCH] Make SymlinkForest simpler and more efficient Bazel rebuilds the symlink tree under the execution root before every build to ensure source files from main repo and external repos are available and up to date. But the SymlinkForest has accumulated many legacy behaviors that is currently not necessary and inefficient. This change tries to simplify the logic and make it much more faster. The main improvement is that instead of linking every file and dir under the top-level directory for every external repo, we only create a link to the top-level directory of the external repo. This will reduce a large amount of symlink create operations, which speeds up the preparing phase a lot on Windows. RELNOTES: None PiperOrigin-RevId: 246520821 --- .../build/lib/buildtool/ExecutionTool.java | 3 +- .../build/lib/buildtool/SymlinkForest.java | 253 ++++------------- .../lib/buildtool/SymlinkForestTest.java | 260 ++++++------------ 3 files changed, 142 insertions(+), 374 deletions(-) diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java index ca4e494cf85c83..187bead2246eca 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java @@ -453,8 +453,7 @@ private void prepare(PackageRoots packageRoots) // Plant the symlink forest. try (SilentCloseable c = Profiler.instance().profile("plantSymlinkForest")) { - new SymlinkForest( - packageRootMap.get(), getExecRoot(), runtime.getProductName(), env.getWorkspaceName()) + new SymlinkForest(packageRootMap.get(), getExecRoot(), runtime.getProductName()) .plantSymlinkForest(); } catch (IOException e) { throw new ExecutorInitException("Source forest creation failed", e); diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java b/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java index 59757857892d8d..0d70cb6b54748c 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java @@ -16,251 +16,116 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.LabelConstants; import com.google.devtools.build.lib.cmdline.PackageIdentifier; +import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.concurrent.ThreadSafety; -import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; import java.util.Map; import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Creates a symlink forest based on a package path map. */ class SymlinkForest { - - private static final Logger logger = Logger.getLogger(SymlinkForest.class.getName()); - private static final boolean LOG_FINER = logger.isLoggable(Level.FINER); - private final ImmutableMap packageRoots; private final Path execroot; - private final String workspaceName; - private final String productName; - private final String[] prefixes; + private final String prefix; SymlinkForest( - ImmutableMap packageRoots, - Path execroot, - String productName, - String workspaceName) { + ImmutableMap packageRoots, Path execroot, String productName) { this.packageRoots = packageRoots; this.execroot = execroot; - this.workspaceName = workspaceName; - this.productName = productName; - this.prefixes = new String[] { ".", "_", productName + "-"}; + this.prefix = productName + "-"; } /** - * Returns the longest prefix from a given set of 'prefixes' that are - * contained in 'path'. I.e the closest ancestor directory containing path. - * Returns null if none found. - * @param path - * @param prefixes - */ - @VisibleForTesting - static PackageIdentifier longestPathPrefix( - PackageIdentifier path, ImmutableSet prefixes) { - for (int i = path.getPackageFragment().segmentCount(); i >= 0; i--) { - PackageIdentifier prefix = createInRepo(path, path.getPackageFragment().subFragment(0, i)); - if (prefixes.contains(prefix)) { - return prefix; - } - } - return null; - } - - /** - * Delete all dir trees under a given 'dir' that don't start with one of a set - * of given 'prefixes'. Does not follow any symbolic links. + * Delete all dir trees under a given 'dir' that don't start with a given 'prefix'. Does not + * follow any symbolic links. */ @VisibleForTesting @ThreadSafety.ThreadSafe - static void deleteTreesBelowNotPrefixed(Path dir, String[] prefixes) throws IOException { - dirloop: + static void deleteTreesBelowNotPrefixed(Path dir, String prefix) throws IOException { for (Path p : dir.getDirectoryEntries()) { - String name = p.getBaseName(); - for (String prefix : prefixes) { - if (name.startsWith(prefix)) { - continue dirloop; - } + if (!p.getBaseName().startsWith(prefix)) { + p.deleteTree(); } - p.deleteTree(); } } + /** + * Plant a symlink forest under execution root to ensure sources file are available and up to + * date. For the main repo: If root package ("//:") is used, link every file and directory under + * the top-level directory of the main repo. Otherwise, we only link the directories that are used + * in presented main repo packages. For every external repo: make a such a directory link: + * //external/ --> /external/ + */ void plantSymlinkForest() throws IOException { - deleteTreesBelowNotPrefixed(execroot, prefixes); - // TODO(kchodorow): this can be removed once the execution root is rearranged. - // Current state: symlink tree was created under execroot/$(basename ws) and then - // execroot/wsname is symlinked to that. The execution root change creates (and cleans up) - // subtrees for each repository and has been rolled forward and back several times. Thus, if - // someone was using a with-execroot-change version of bazel and then switched to this one, - // their execution root would contain a subtree for execroot/wsname that would never be - // cleaned up by this version of Bazel. - Path realWorkspaceDir = execroot.getParentDirectory().getRelative(workspaceName); - if (!workspaceName.equals(execroot.getBaseName()) && realWorkspaceDir.exists() - && !realWorkspaceDir.isSymbolicLink()) { - realWorkspaceDir.deleteTree(); - } + deleteTreesBelowNotPrefixed(execroot, prefix); + + Path mainRepoRoot = null; + Map mainRepoLinks = Maps.newHashMap(); + Set externalRepoLinks = Sets.newHashSet(); - // Packages come from exactly one root, but their shared ancestors may come from more. - Map> dirRootsMap = Maps.newHashMap(); - // Elements in this list are added so that parents come before their children. - ArrayList dirsParentsFirst = new ArrayList<>(); for (Map.Entry entry : packageRoots.entrySet()) { PackageIdentifier pkgId = entry.getKey(); if (pkgId.equals(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER)) { // This isn't a "real" package, don't add it to the symlink tree. continue; } - Root pkgRoot = entry.getValue(); - ArrayList newDirs = new ArrayList<>(); - for (PathFragment fragment = pkgId.getPackageFragment(); - !fragment.isEmpty(); - fragment = fragment.getParentDirectory()) { - PackageIdentifier dirId = createInRepo(pkgId, fragment); - Set roots = dirRootsMap.get(dirId); - if (roots == null) { - roots = Sets.newHashSet(); - dirRootsMap.put(dirId, roots); - newDirs.add(dirId); + RepositoryName repository = pkgId.getRepository(); + if (repository.isMain() || repository.isDefault()) { + // If root package of the main repo is required, we record the main repo root so that + // we can later link everything under main repo's top-level directory. And in this case, + // we don't need to record other links for directories under the top-level directory any + // more. + if (pkgId.getPackageFragment().equals(PathFragment.EMPTY_FRAGMENT)) { + mainRepoRoot = entry.getValue().getRelative(pkgId.getSourceRoot()); } - roots.add(pkgRoot); - } - Collections.reverse(newDirs); - dirsParentsFirst.addAll(newDirs); - } - // Now add in roots for all non-pkg dirs that are in between two packages, and missed above. - for (PackageIdentifier dir : dirsParentsFirst) { - if (!packageRoots.containsKey(dir)) { - PackageIdentifier pkgId = longestPathPrefix(dir, packageRoots.keySet()); - if (pkgId != null) { - dirRootsMap.get(dir).add(packageRoots.get(pkgId)); + if (mainRepoRoot == null) { + Path execrootLink = execroot.getRelative(pkgId.getPackageFragment().getSegment(0)); + Path sourcePath = entry.getValue().getRelative(pkgId.getSourceRoot().getSegment(0)); + mainRepoLinks.putIfAbsent(execrootLink, sourcePath); } - } - } - // Create output dirs for all dirs that have more than one root and need to be split. - for (PackageIdentifier dir : dirsParentsFirst) { - if (!dir.getRepository().isMain()) { - FileSystemUtils.createDirectoryAndParents( - execroot.getRelative(dir.getRepository().getPathUnderExecRoot())); - } - if (dirRootsMap.get(dir).size() > 1) { - if (LOG_FINER) { - logger.finer("mkdir " + execroot.getRelative(dir.getPathUnderExecRoot())); + } else { + // For other external repositories, generate a symlink to the external repository + // directory itself. + // /execroot/
/external/ --> + // /external/ + Path execrootLink = execroot.getRelative(repository.getPathUnderExecRoot()); + Path sourcePath = entry.getValue().getRelative(repository.getSourceRoot()); + if (externalRepoLinks.contains(execrootLink)) { + continue; } - FileSystemUtils.createDirectoryAndParents( - execroot.getRelative(dir.getPathUnderExecRoot())); - } - } - - // Make dir links for single rooted dirs. - for (PackageIdentifier dir : dirsParentsFirst) { - Set roots = dirRootsMap.get(dir); - // Simple case of one root for this dir. - if (roots.size() == 1) { - PathFragment parent = dir.getPackageFragment().getParentDirectory(); - if (!parent.isEmpty() && dirRootsMap.get(createInRepo(dir, parent)).size() == 1) { - continue; // skip--an ancestor will link this one in from above - } - // This is the top-most dir that can be linked to a single root. Make it so. - Root root = roots.iterator().next(); // lone root in set - if (LOG_FINER) { - logger.finer( - "ln -s " - + root.getRelative(dir.getSourceRoot()) - + " " - + execroot.getRelative(dir.getPathUnderExecRoot())); - } - execroot.getRelative(dir.getPathUnderExecRoot()) - .createSymbolicLink(root.getRelative(dir.getSourceRoot())); - } - } - // Make links for dirs within packages, skip parent-only dirs. - for (PackageIdentifier dir : dirsParentsFirst) { - if (dirRootsMap.get(dir).size() > 1) { - // If this dir is at or below a package dir, link in its contents. - PackageIdentifier pkgId = longestPathPrefix(dir, packageRoots.keySet()); - if (pkgId != null) { - Root root = packageRoots.get(pkgId); - try { - Path absdir = root.getRelative(dir.getSourceRoot()); - if (absdir.isDirectory()) { - if (LOG_FINER) { - logger.finer( - "ln -s " + absdir + "/* " + execroot.getRelative(dir.getSourceRoot()) + "/"); - } - for (Path target : absdir.getDirectoryEntries()) { - PathFragment p = root.relativize(target); - if (!dirRootsMap.containsKey(createInRepo(pkgId, p))) { - //LOG.finest("ln -s " + target + " " + linkRoot.getRelative(p)); - execroot.getRelative(p).createSymbolicLink(target); - } - } - } else { - logger.fine("Symlink planting skipping dir '" + absdir + "'"); - } - } catch (IOException e) { - e.printStackTrace(); - } - // Otherwise its just an otherwise empty common parent dir. + if (externalRepoLinks.isEmpty()) { + execroot.getRelative(LabelConstants.EXTERNAL_PACKAGE_NAME).createDirectoryAndParents(); } + externalRepoLinks.add(execrootLink); + execrootLink.createSymbolicLink(sourcePath); } } - - for (Map.Entry entry : packageRoots.entrySet()) { - PackageIdentifier pkgId = entry.getKey(); - if (!pkgId.getPackageFragment().equals(PathFragment.EMPTY_FRAGMENT)) { - continue; - } - Path execrootDirectory = execroot.getRelative(pkgId.getPathUnderExecRoot()); - // If there were no subpackages, this directory might not exist yet. - if (!execrootDirectory.exists()) { - FileSystemUtils.createDirectoryAndParents(execrootDirectory); - } - // For the top-level directory, generate symlinks to everything in the directory instead of - // the directory itself. - Path sourceDirectory = entry.getValue().getRelative(pkgId.getSourceRoot()); - for (Path target : sourceDirectory.getDirectoryEntries()) { + if (mainRepoRoot != null) { + // For the main repo top-level directory, generate symlinks to everything in the directory + // instead of the directory itself. + for (Path target : mainRepoRoot.getDirectoryEntries()) { String baseName = target.getBaseName(); - Path execPath = execrootDirectory.getRelative(baseName); - // Create any links that don't exist yet and don't start with bazel-. - if (!baseName.startsWith(productName + "-") && !execPath.exists()) { + Path execPath = execroot.getRelative(baseName); + // Create any links that don't start with bazel-. + if (!baseName.startsWith(prefix)) { execPath.createSymbolicLink(target); } } + } else { + for (Map.Entry entry : mainRepoLinks.entrySet()) { + Path link = entry.getKey(); + Path target = entry.getValue(); + link.createSymbolicLink(target); + } } - - symlinkCorrectWorkspaceName(); - } - - /** - * Right now, the execution root is under the basename of the source directory, not the name - * defined in the WORKSPACE file. Thus, this adds a symlink with the WORKSPACE's workspace name - * to the old-style execution root. - * TODO(kchodorow): get rid of this once exec root is always under the WORKSPACE's workspace - * name. - * @throws IOException - */ - private void symlinkCorrectWorkspaceName() throws IOException { - Path correctDirectory = execroot.getParentDirectory().getRelative(workspaceName); - if (!correctDirectory.exists()) { - correctDirectory.createSymbolicLink(execroot); - } - } - - private static PackageIdentifier createInRepo( - PackageIdentifier repo, PathFragment packageFragment) { - return PackageIdentifier.create(repo.getRepository(), packageFragment); } } diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java b/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java index da35f94693d1e6..2c086db6188f33 100644 --- a/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java +++ b/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java @@ -15,10 +15,8 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static com.google.devtools.build.lib.vfs.FileSystemUtils.createDirectoryAndParents; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.cmdline.LabelConstants; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; @@ -30,7 +28,6 @@ import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; -import com.google.devtools.build.lib.vfs.Symlinks; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import java.io.IOException; import org.junit.Before; @@ -45,39 +42,30 @@ public class SymlinkForestTest { private FileSystem fileSystem; - private Path topDir; - private Path file1; - private Path file2; - private Path aDir; - - // The execution root. - private Path linkRoot; - @Before - public final void initializeFileSystem() throws Exception { + public final void initializeFileSystem() { ManualClock clock = new ManualClock(); fileSystem = new InMemoryFileSystem(clock); - linkRoot = fileSystem.getPath("/linkRoot"); - createDirectoryAndParents(linkRoot); } - /* - * Build a directory tree that looks like: - * top-dir/ - * file-1 - * file-2 - * a-dir/ - * file-3 - * inner-dir/ - * link-1 => file-4 - * dir-link => b-dir - * file-4 - */ - private void createTestDirectoryTree() throws IOException { - topDir = fileSystem.getPath("/top-dir"); - file1 = fileSystem.getPath("/top-dir/file-1"); - file2 = fileSystem.getPath("/top-dir/file-2"); - aDir = fileSystem.getPath("/top-dir/a-dir"); + @Test + public void testDeleteTreesBelowNotPrefixed() throws IOException { + /* + * Build a directory tree that looks like: + * top-dir/ + * file-1 + * file-2 + * a-dir/ + * file-3 + * inner-dir/ + * link-1 => file-4 + * dir-link => b-dir + * file-4 + */ + Path topDir = fileSystem.getPath("/top-dir"); + Path file1 = fileSystem.getPath("/top-dir/file-1"); + Path file2 = fileSystem.getPath("/top-dir/file-2"); + Path aDir = fileSystem.getPath("/top-dir/a-dir"); Path bDir = fileSystem.getPath("/top-dir/b-dir"); Path file3 = fileSystem.getPath("/top-dir/a-dir/file-3"); Path innerDir = fileSystem.getPath("/top-dir/a-dir/inner-dir"); @@ -97,63 +85,34 @@ private void createTestDirectoryTree() throws IOException { dirLink.createSymbolicLink(bDir); FileSystemUtils.createEmptyFile(file4); FileSystemUtils.createEmptyFile(file5); - } - private static PathFragment longestPathPrefix(String path, String... prefixStrs) { - ImmutableSet.Builder prefixes = ImmutableSet.builder(); - for (String prefix : prefixStrs) { - prefixes.add(PackageIdentifier.createInMainRepo(prefix)); - } - PackageIdentifier longest = SymlinkForest.longestPathPrefix( - PackageIdentifier.createInMainRepo(path), prefixes.build()); - return longest != null ? longest.getPackageFragment() : null; - } - - @Test - public void testLongestPathPrefix() { - PathFragment a = PathFragment.create("A"); - assertThat(longestPathPrefix("A/b", "A", "B")).isEqualTo(a); // simple parent - assertThat(longestPathPrefix("A", "A", "B")).isEqualTo(a); // self - assertThat(longestPathPrefix("A/B/c", "A", "A/B")) - .isEqualTo(a.getRelative("B")); // want longest - assertThat(longestPathPrefix("C/b", "A", "B")).isNull(); // not found in other parents - assertThat(longestPathPrefix("A", "A/B", "B")).isNull(); // not found in child - assertThat(longestPathPrefix("A/B/C/d/e/f.h", "A/B/C", "B/C/d")) - .isEqualTo(a.getRelative("B/C")); - assertThat(longestPathPrefix("A/f.h", "", "B/C/d")).isEqualTo(PathFragment.EMPTY_FRAGMENT); - } - - @Test - public void testDeleteTreesBelowNotPrefixed() throws IOException { - createTestDirectoryTree(); - SymlinkForest.deleteTreesBelowNotPrefixed(topDir, new String[]{"file-"}); + SymlinkForest.deleteTreesBelowNotPrefixed(topDir, "file-"); assertThat(file1.exists()).isTrue(); assertThat(file2.exists()).isTrue(); assertThat(aDir.exists()).isFalse(); } - private PackageIdentifier createPkg(Root rootA, Root rootB, String pkg) throws IOException { - if (rootA != null) { - createDirectoryAndParents(rootA.getRelative(pkg)); - FileSystemUtils.createEmptyFile(rootA.getRelative(pkg).getChild("file")); - } - if (rootB != null) { - createDirectoryAndParents(rootB.getRelative(pkg)); - FileSystemUtils.createEmptyFile(rootB.getRelative(pkg).getChild("file")); - } - return PackageIdentifier.createInMainRepo(pkg); - } - - private PackageIdentifier createPkg(Root root, String repo, String pkg) + // Create package for external repo + private PackageIdentifier createExternalPkg(Root root, String repo, String pkg) throws IOException, LabelSyntaxException { if (root != null) { Path repoRoot = root.getRelative(LabelConstants.EXTERNAL_PACKAGE_NAME).getRelative(repo); - createDirectoryAndParents(repoRoot.getRelative(pkg)); + repoRoot.getRelative(pkg).createDirectoryAndParents(); FileSystemUtils.createEmptyFile(repoRoot.getRelative(pkg).getChild("file")); } return PackageIdentifier.create(RepositoryName.create("@" + repo), PathFragment.create(pkg)); } + // Create package for main repo + private PackageIdentifier createMainPkg(Root repoRoot, String pkg) + throws IOException, LabelSyntaxException { + if (repoRoot != null) { + repoRoot.getRelative(pkg).createDirectoryAndParents(); + FileSystemUtils.createEmptyFile(repoRoot.getRelative(pkg).getChild("file")); + } + return PackageIdentifier.create(RepositoryName.create("@"), PathFragment.create(pkg)); + } + private void assertLinksTo(Path fromRoot, Root toRoot, String relpart) throws IOException { assertLinksTo(fromRoot.getRelative(relpart), toRoot.getRelative(relpart)); } @@ -163,105 +122,74 @@ private void assertLinksTo(Path fromRoot, Path toRoot) throws IOException { assertThat(fromRoot.readSymbolicLink()).isEqualTo(toRoot.asFragment()); } - private void assertIsDir(Path root, String relpart) { - assertThat(root.getRelative(relpart).isDirectory(Symlinks.NOFOLLOW)).isTrue(); - } - @Test - public void testPlantLinkForest() throws IOException { - Root rootA = Root.fromPath(fileSystem.getPath("/A")); - Root rootB = Root.fromPath(fileSystem.getPath("/B")); - - ImmutableMap packageRootMap = - ImmutableMap.builder() - .put(createPkg(rootA, rootB, "pkgA"), rootA) - .put(createPkg(rootA, rootB, "dir1/pkgA"), rootA) - .put(createPkg(rootA, rootB, "dir1/pkgB"), rootB) - .put(createPkg(rootA, rootB, "dir2/pkg"), rootA) - .put(createPkg(rootA, rootB, "dir2/pkg/pkg"), rootB) - .put(createPkg(rootA, rootB, "pkgB"), rootB) - .put(createPkg(rootA, rootB, "pkgB/dir/pkg"), rootA) - .put(createPkg(rootA, rootB, "pkgB/pkg"), rootA) - .put(createPkg(rootA, rootB, "pkgB/pkg/pkg"), rootA) - .build(); - createPkg(rootA, rootB, "pkgB/dir"); // create a file in there - - Path linkRoot = fileSystem.getPath("/linkRoot"); - createDirectoryAndParents(linkRoot); - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") - .plantSymlinkForest(); + public void testPlantSymlinkForest() throws Exception { + Root outputBase = Root.fromPath(fileSystem.getPath("/ob")); + Root mainRepo = Root.fromPath(fileSystem.getPath("/my_repo")); + Path linkRoot = outputBase.getRelative("execroot/ws_name"); - assertLinksTo(linkRoot, rootA, "pkgA"); - assertIsDir(linkRoot, "dir1"); - assertLinksTo(linkRoot, rootA, "dir1/pkgA"); - assertLinksTo(linkRoot, rootB, "dir1/pkgB"); - assertIsDir(linkRoot, "dir2"); - assertIsDir(linkRoot, "dir2/pkg"); - assertLinksTo(linkRoot, rootA, "dir2/pkg/file"); - assertLinksTo(linkRoot, rootB, "dir2/pkg/pkg"); - assertIsDir(linkRoot, "pkgB"); - assertIsDir(linkRoot, "pkgB/dir"); - assertLinksTo(linkRoot, rootB, "pkgB/dir/file"); - assertLinksTo(linkRoot, rootA, "pkgB/dir/pkg"); - assertLinksTo(linkRoot, rootA, "pkgB/pkg"); - } + mainRepo.asPath().createDirectoryAndParents(); + linkRoot.createDirectoryAndParents(); - @Test - public void testTopLevelPackage() throws Exception { - Root rootX = Root.fromPath(fileSystem.getPath("/X")); - Root rootY = Root.fromPath(fileSystem.getPath("/Y")); ImmutableMap packageRootMap = ImmutableMap.builder() - .put(createPkg(rootX, rootY, ""), rootX) - .put(createPkg(rootX, rootY, "foo"), rootX) + .put(createMainPkg(mainRepo, "dir_main"), mainRepo) + .put(createMainPkg(mainRepo, "dir_lib/pkg"), mainRepo) + .put(createMainPkg(mainRepo, ""), mainRepo) + .put(createExternalPkg(outputBase, "X", "dir_x/pkg"), outputBase) + .put(createExternalPkg(outputBase, "Y", "dir_y/pkg"), outputBase) + .put(createExternalPkg(outputBase, "Z", "dir_z/pkg"), outputBase) .build(); - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") - .plantSymlinkForest(); - assertLinksTo(linkRoot, rootX, "file"); + new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME).plantSymlinkForest(); + + assertLinksTo(linkRoot, mainRepo, "dir_main"); + assertLinksTo(linkRoot, mainRepo, "dir_lib"); + // This file will be a copy on Windows, so just check if it exists instead of if it's a symlink. + assertThat(linkRoot.getChild("file").exists()).isTrue(); + assertLinksTo(linkRoot, outputBase, LabelConstants.EXTERNAL_PATH_PREFIX + "/X"); + assertLinksTo(linkRoot, outputBase, LabelConstants.EXTERNAL_PATH_PREFIX + "/Y"); + assertLinksTo(linkRoot, outputBase, LabelConstants.EXTERNAL_PATH_PREFIX + "/Z"); } @Test - public void testRemotePackage() throws Exception { + public void testPlantSymlinkForestForMainRepo() throws Exception { + // For the main repo, plantSymlinkForest function should only link all files and dirs under + // main repo root that're presented in packageRootMap. Root outputBase = Root.fromPath(fileSystem.getPath("/ob")); - Root rootY = - Root.fromPath(outputBase.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX).getRelative("y")); - Root rootZ = - Root.fromPath(outputBase.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX).getRelative("z")); - Root rootW = - Root.fromPath(outputBase.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX).getRelative("w")); - createDirectoryAndParents(rootY.asPath()); - FileSystemUtils.createEmptyFile(rootY.getRelative("file")); + Root mainRepo = Root.fromPath(fileSystem.getPath("/my_repo")); + Path linkRoot = outputBase.getRelative("execroot/ws_name"); + + linkRoot.createDirectoryAndParents(); + mainRepo.asPath().createDirectoryAndParents(); + mainRepo.getRelative("dir4").createDirectoryAndParents(); + FileSystemUtils.createEmptyFile(mainRepo.getRelative("file")); ImmutableMap packageRootMap = ImmutableMap.builder() - // Remote repo without top-level package. - .put(createPkg(outputBase, "y", "w"), outputBase) - // Remote repo with and without top-level package. - .put(createPkg(outputBase, "z", ""), outputBase) - .put(createPkg(outputBase, "z", "a/b/c"), outputBase) - // Only top-level pkg. - .put(createPkg(outputBase, "w", ""), outputBase) + .put(createMainPkg(mainRepo, "dir1/pkg/foo"), mainRepo) + .put(createMainPkg(mainRepo, "dir2/pkg"), mainRepo) + .put(createMainPkg(mainRepo, "dir3"), mainRepo) + .put(createExternalPkg(outputBase, "X", "dir_x/pkg"), outputBase) .build(); - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") - .plantSymlinkForest(); - assertThat(linkRoot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX + "/y/file").exists()) - .isFalse(); - assertLinksTo( - linkRoot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX + "/y/w"), rootY.getRelative("w")); - assertLinksTo( - linkRoot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX + "/z/file"), - rootZ.getRelative("file")); - assertLinksTo( - linkRoot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX + "/z/a"), rootZ.getRelative("a")); - assertLinksTo( - linkRoot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX + "/w/file"), - rootW.getRelative("file")); + new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME).plantSymlinkForest(); + + assertLinksTo(linkRoot, mainRepo, "dir1"); + assertLinksTo(linkRoot, mainRepo, "dir2"); + assertLinksTo(linkRoot, mainRepo, "dir3"); + // dir4 and the file under main repo root should not be linked + // because they are not presented in packageRootMap. + assertThat(linkRoot.getChild("dir4").exists()).isFalse(); + assertThat(linkRoot.getChild("file").exists()).isFalse(); + assertLinksTo(linkRoot, outputBase, LabelConstants.EXTERNAL_PATH_PREFIX + "/X"); } @Test public void testExternalPackage() throws Exception { + Path linkRoot = fileSystem.getPath("/linkRoot"); + linkRoot.createDirectoryAndParents(); + Root root = Root.fromPath(fileSystem.getPath("/src")); ImmutableMap packageRootMap = ImmutableMap.builder() @@ -269,31 +197,7 @@ public void testExternalPackage() throws Exception { .put(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER, root) .build(); - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") - .plantSymlinkForest(); + new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME).plantSymlinkForest(); assertThat(linkRoot.getRelative(LabelConstants.EXTERNAL_PATH_PREFIX).exists()).isFalse(); } - - @Test - public void testWorkspaceName() throws Exception { - Root root = Root.fromPath(fileSystem.getPath("/src")); - ImmutableMap packageRootMap = - ImmutableMap.builder() - // Remote repo without top-level package. - .put(createPkg(root, "y", "w"), root) - .build(); - - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") - .plantSymlinkForest(); - assertThat(linkRoot.getRelative("../wsname").exists()).isTrue(); - } - - @Test - public void testExecrootVersionChanges() throws Exception { - ImmutableMap packageRootMap = ImmutableMap.of(); - linkRoot.getRelative("wsname").createDirectory(); - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") - .plantSymlinkForest(); - assertThat(linkRoot.getRelative("../wsname").isSymbolicLink()).isTrue(); - } }