From 9835cb4135503768cdf1161746b95d7969ccb938 Mon Sep 17 00:00:00 2001 From: janakr Date: Thu, 9 May 2019 08:22:34 -0700 Subject: [PATCH] Automated rollback of commit 844e4e297b404d6ff28b818d8150d4b9c47de887. *** Reason for rollback *** Breaks overlay builds (multiple package paths). PiperOrigin-RevId: 247429048 --- .../build/lib/buildtool/ExecutionTool.java | 3 +- .../build/lib/buildtool/SymlinkForest.java | 253 +++++++++++++---- .../lib/buildtool/SymlinkForestTest.java | 260 ++++++++++++------ 3 files changed, 374 insertions(+), 142 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 187bead2246eca..ca4e494cf85c83 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,7 +453,8 @@ private void prepare(PackageRoots packageRoots) // Plant the symlink forest. try (SilentCloseable c = Profiler.instance().profile("plantSymlinkForest")) { - new SymlinkForest(packageRootMap.get(), getExecRoot(), runtime.getProductName()) + new SymlinkForest( + packageRootMap.get(), getExecRoot(), runtime.getProductName(), env.getWorkspaceName()) .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 0d70cb6b54748c..59757857892d8d 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,116 +16,251 @@ 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 prefix; + private final String workspaceName; + private final String productName; + private final String[] prefixes; SymlinkForest( - ImmutableMap packageRoots, Path execroot, String productName) { + ImmutableMap packageRoots, + Path execroot, + String productName, + String workspaceName) { this.packageRoots = packageRoots; this.execroot = execroot; - this.prefix = productName + "-"; + this.workspaceName = workspaceName; + this.productName = productName; + this.prefixes = new String[] { ".", "_", productName + "-"}; } /** - * Delete all dir trees under a given 'dir' that don't start with a given 'prefix'. Does not - * follow any symbolic links. + * 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 - @ThreadSafety.ThreadSafe - static void deleteTreesBelowNotPrefixed(Path dir, String prefix) throws IOException { - for (Path p : dir.getDirectoryEntries()) { - if (!p.getBaseName().startsWith(prefix)) { - p.deleteTree(); + 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; } /** - * 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/ + * 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. */ - void plantSymlinkForest() throws IOException { - deleteTreesBelowNotPrefixed(execroot, prefix); + @VisibleForTesting + @ThreadSafety.ThreadSafe + static void deleteTreesBelowNotPrefixed(Path dir, String[] prefixes) throws IOException { + dirloop: + for (Path p : dir.getDirectoryEntries()) { + String name = p.getBaseName(); + for (String prefix : prefixes) { + if (name.startsWith(prefix)) { + continue dirloop; + } + } + p.deleteTree(); + } + } - Path mainRepoRoot = null; - Map mainRepoLinks = Maps.newHashMap(); - Set externalRepoLinks = Sets.newHashSet(); + 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(); + } + // 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; } - 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()); + 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); } - if (mainRepoRoot == null) { - Path execrootLink = execroot.getRelative(pkgId.getPackageFragment().getSegment(0)); - Path sourcePath = entry.getValue().getRelative(pkgId.getSourceRoot().getSegment(0)); - mainRepoLinks.putIfAbsent(execrootLink, sourcePath); + 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)); } - } 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; + } + } + // 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())); } - if (externalRepoLinks.isEmpty()) { - execroot.getRelative(LabelConstants.EXTERNAL_PACKAGE_NAME).createDirectoryAndParents(); + 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. } - externalRepoLinks.add(execrootLink); - execrootLink.createSymbolicLink(sourcePath); } } - 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()) { + + 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()) { String baseName = target.getBaseName(); - Path execPath = execroot.getRelative(baseName); - // Create any links that don't start with bazel-. - if (!baseName.startsWith(prefix)) { + 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()) { 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 2c086db6188f33..da35f94693d1e6 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,8 +15,10 @@ 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; @@ -28,6 +30,7 @@ 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; @@ -42,30 +45,39 @@ 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() { + public final void initializeFileSystem() throws Exception { ManualClock clock = new ManualClock(); fileSystem = new InMemoryFileSystem(clock); + linkRoot = fileSystem.getPath("/linkRoot"); + createDirectoryAndParents(linkRoot); } - @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"); + /* + * 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"); 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"); @@ -85,32 +97,61 @@ public void testDeleteTreesBelowNotPrefixed() throws IOException { dirLink.createSymbolicLink(bDir); FileSystemUtils.createEmptyFile(file4); FileSystemUtils.createEmptyFile(file5); + } - SymlinkForest.deleteTreesBelowNotPrefixed(topDir, "file-"); + 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-"}); assertThat(file1.exists()).isTrue(); assertThat(file2.exists()).isTrue(); assertThat(aDir.exists()).isFalse(); } - // 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); - repoRoot.getRelative(pkg).createDirectoryAndParents(); - FileSystemUtils.createEmptyFile(repoRoot.getRelative(pkg).getChild("file")); + 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")); } - return PackageIdentifier.create(RepositoryName.create("@" + repo), PathFragment.create(pkg)); + if (rootB != null) { + createDirectoryAndParents(rootB.getRelative(pkg)); + FileSystemUtils.createEmptyFile(rootB.getRelative(pkg).getChild("file")); + } + return PackageIdentifier.createInMainRepo(pkg); } - // Create package for main repo - private PackageIdentifier createMainPkg(Root repoRoot, String pkg) + private PackageIdentifier createPkg(Root root, String repo, String pkg) throws IOException, LabelSyntaxException { - if (repoRoot != null) { - repoRoot.getRelative(pkg).createDirectoryAndParents(); + if (root != null) { + Path repoRoot = root.getRelative(LabelConstants.EXTERNAL_PACKAGE_NAME).getRelative(repo); + createDirectoryAndParents(repoRoot.getRelative(pkg)); FileSystemUtils.createEmptyFile(repoRoot.getRelative(pkg).getChild("file")); } - return PackageIdentifier.create(RepositoryName.create("@"), PathFragment.create(pkg)); + return PackageIdentifier.create(RepositoryName.create("@" + repo), PathFragment.create(pkg)); } private void assertLinksTo(Path fromRoot, Root toRoot, String relpart) throws IOException { @@ -122,74 +163,105 @@ private void assertLinksTo(Path fromRoot, Path toRoot) throws IOException { assertThat(fromRoot.readSymbolicLink()).isEqualTo(toRoot.asFragment()); } - @Test - 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"); + private void assertIsDir(Path root, String relpart) { + assertThat(root.getRelative(relpart).isDirectory(Symlinks.NOFOLLOW)).isTrue(); + } - mainRepo.asPath().createDirectoryAndParents(); - linkRoot.createDirectoryAndParents(); + @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(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) + .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 - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME).plantSymlinkForest(); + Path linkRoot = fileSystem.getPath("/linkRoot"); + createDirectoryAndParents(linkRoot); + new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") + .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"); + 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"); } @Test - 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 mainRepo = Root.fromPath(fileSystem.getPath("/my_repo")); - Path linkRoot = outputBase.getRelative("execroot/ws_name"); + 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) + .build(); - linkRoot.createDirectoryAndParents(); - mainRepo.asPath().createDirectoryAndParents(); - mainRepo.getRelative("dir4").createDirectoryAndParents(); - FileSystemUtils.createEmptyFile(mainRepo.getRelative("file")); + new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") + .plantSymlinkForest(); + assertLinksTo(linkRoot, rootX, "file"); + } + + @Test + public void testRemotePackage() throws Exception { + 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")); ImmutableMap packageRootMap = ImmutableMap.builder() - .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) + // 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) .build(); - 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"); + 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")); } @Test public void testExternalPackage() throws Exception { - Path linkRoot = fileSystem.getPath("/linkRoot"); - linkRoot.createDirectoryAndParents(); - Root root = Root.fromPath(fileSystem.getPath("/src")); ImmutableMap packageRootMap = ImmutableMap.builder() @@ -197,7 +269,31 @@ public void testExternalPackage() throws Exception { .put(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER, root) .build(); - new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME).plantSymlinkForest(); + new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname") + .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(); + } }