From 52bc57cd26af622bbf8366d56fcdbad49e6886df Mon Sep 17 00:00:00 2001 From: Kristina Chodorow Date: Mon, 20 Jun 2016 14:27:56 +0000 Subject: Move execroot tree creation to its own class Part 1 of many for #1262, rolling forward. -- MOS_MIGRATED_REVID=125334954 --- .../build/lib/buildtool/ExecutionTool.java | 18 +- .../build/lib/buildtool/SymlinkForest.java | 194 +++++++++++++++++++++ .../devtools/build/lib/vfs/FileSystemUtils.java | 157 ----------------- .../com/google/devtools/build/lib/buildtool/BUILD | 27 +++ .../build/lib/buildtool/SymlinkForestTest.java | 184 +++++++++++++++++++ .../build/lib/vfs/FileSystemUtilsTest.java | 113 ------------ 6 files changed, 411 insertions(+), 282 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java create mode 100644 src/test/java/com/google/devtools/build/lib/buildtool/BUILD create mode 100644 src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java 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 a07fb9de73..76706bc538 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 @@ -504,7 +504,12 @@ public class ExecutionTool { createActionLogDirectory(); // Plant the symlink forest. - plantSymlinkForest(packageRoots); + try { + new SymlinkForest( + packageRoots, getExecRoot(), runtime.getProductName()).plantSymlinkForest(); + } catch (IOException e) { + throw new ExecutorInitException("Source forest creation failed", e); + } } private void createToolsSymlinks() throws ExecutorInitException { @@ -515,17 +520,6 @@ public class ExecutionTool { } } - private void plantSymlinkForest(ImmutableMap packageRoots) - throws ExecutorInitException { - try { - FileSystemUtils.deleteTreesBelowNotPrefixed(getExecRoot(), - new String[] { ".", "_", runtime.getProductName() + "-"}); - FileSystemUtils.plantLinkForest(packageRoots, getExecRoot(), runtime.getProductName()); - } catch (IOException e) { - throw new ExecutorInitException("Source forest creation failed", e); - } - } - private void createActionLogDirectory() throws ExecutorInitException { Path directory = env.getDirectories().getActionConsoleOutputDirectory(); try { 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 new file mode 100644 index 0000000000..ceeb77a726 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java @@ -0,0 +1,194 @@ +// Copyright 2016 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.buildtool; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +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 java.io.IOException; +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 LOG = Logger.getLogger(SymlinkForest.class.getName()); + private static final boolean LOG_FINER = LOG.isLoggable(Level.FINER); + + private final ImmutableMap packageRoots; + private final Path workspace; + private final String productName; + private final String[] prefixes; + + SymlinkForest( + ImmutableMap packageRoots, Path workspace, String productName) { + this.packageRoots = packageRoots; + this.workspace = workspace; + this.productName = productName; + this.prefixes = new String[] { ".", "_", 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. + */ + @VisibleForTesting + static PathFragment longestPathPrefix(PathFragment path, Set prefixes) { + for (int i = path.segmentCount(); i >= 0; i--) { + PathFragment prefix = path.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. + */ + @VisibleForTesting + @ThreadSafety.ThreadSafe + static void deleteTreesBelowNotPrefixed(Path dir, String[] prefixes) throws IOException { + dirloop: + for (Path p : dir.getDirectoryEntries()) { + String name = p.getBaseName(); + for (int i = 0; i < prefixes.length; i++) { + if (name.startsWith(prefixes[i])) { + continue dirloop; + } + } + FileSystemUtils.deleteTree(p); + } + } + + void plantSymlinkForest() throws IOException { + deleteTreesBelowNotPrefixed(workspace, prefixes); + Path emptyPackagePath = null; + + // Create a sorted map of all dirs (packages and their ancestors) to sets of their roots. + // Packages come from exactly one root, but their shared ancestors may come from more. + // The map is maintained sorted lexicographically, so parents are before their children. + Map> dirRootsMap = Maps.newTreeMap(); + for (Map.Entry entry : packageRoots.entrySet()) { + PathFragment pkgDir = entry.getKey(); + Path pkgRoot = entry.getValue(); + if (pkgDir.segmentCount() == 0) { + emptyPackagePath = entry.getValue(); + } + for (int i = 1; i <= pkgDir.segmentCount(); i++) { + PathFragment dir = pkgDir.subFragment(0, i); + Set roots = dirRootsMap.get(dir); + if (roots == null) { + roots = Sets.newHashSet(); + dirRootsMap.put(dir, roots); + } + roots.add(pkgRoot); + } + } + // Now add in roots for all non-pkg dirs that are in between two packages, and missed above. + for (Map.Entry> entry : dirRootsMap.entrySet()) { + PathFragment dir = entry.getKey(); + if (!packageRoots.containsKey(dir)) { + PathFragment pkgDir = longestPathPrefix(dir, packageRoots.keySet()); + if (pkgDir != null) { + entry.getValue().add(packageRoots.get(pkgDir)); + } + } + } + // Create output dirs for all dirs that have more than one root and need to be split. + for (Map.Entry> entry : dirRootsMap.entrySet()) { + PathFragment dir = entry.getKey(); + if (entry.getValue().size() > 1) { + if (LOG_FINER) { + LOG.finer("mkdir " + workspace.getRelative(dir)); + } + FileSystemUtils.createDirectoryAndParents(workspace.getRelative(dir)); + } + } + // Make dir links for single rooted dirs. + for (Map.Entry> entry : dirRootsMap.entrySet()) { + PathFragment dir = entry.getKey(); + Set roots = entry.getValue(); + // Simple case of one root for this dir. + if (roots.size() == 1) { + if (dir.segmentCount() > 1 && dirRootsMap.get(dir.getParentDirectory()).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. + Path root = roots.iterator().next(); // lone root in set + if (LOG_FINER) { + LOG.finer("ln -s " + root.getRelative(dir) + " " + workspace.getRelative(dir)); + } + workspace.getRelative(dir).createSymbolicLink(root.getRelative(dir)); + } + } + // Make links for dirs within packages, skip parent-only dirs. + for (Map.Entry> entry : dirRootsMap.entrySet()) { + PathFragment dir = entry.getKey(); + if (entry.getValue().size() > 1) { + // If this dir is at or below a package dir, link in its contents. + PathFragment pkgDir = longestPathPrefix(dir, packageRoots.keySet()); + if (pkgDir != null) { + Path root = packageRoots.get(pkgDir); + try { + Path absdir = root.getRelative(dir); + if (absdir.isDirectory()) { + if (LOG_FINER) { + LOG.finer("ln -s " + absdir + "/* " + workspace.getRelative(dir) + "/"); + } + for (Path target : absdir.getDirectoryEntries()) { + PathFragment p = target.relativeTo(root); + if (!dirRootsMap.containsKey(p)) { + //LOG.finest("ln -s " + target + " " + linkRoot.getRelative(p)); + workspace.getRelative(p).createSymbolicLink(target); + } + } + } else { + LOG.fine("Symlink planting skipping dir '" + absdir + "'"); + } + } catch (IOException e) { + e.printStackTrace(); + } + // Otherwise its just an otherwise empty common parent dir. + } + } + } + + if (emptyPackagePath != null) { + // For the top-level directory, generate symlinks to everything in the directory instead of + // the directory itself. + for (Path target : emptyPackagePath.getDirectoryEntries()) { + String baseName = target.getBaseName(); + // Create any links that don't exist yet and don't start with bazel-. + if (!baseName.startsWith(productName + "-") + && !workspace.getRelative(baseName).exists()) { + workspace.getRelative(baseName).createSymbolicLink(target); + } + } + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java index 0facfefde1..8c864547ff 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java @@ -16,9 +16,6 @@ package com.google.devtools.build.lib.vfs; import static java.nio.charset.StandardCharsets.ISO_8859_1; import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.google.common.io.ByteSink; import com.google.common.io.ByteSource; import com.google.common.io.ByteStreams; @@ -35,10 +32,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Helper functions that implement often-used complex operations on file @@ -47,9 +40,6 @@ import java.util.logging.Logger; @ConditionallyThreadSafe // ThreadSafe except for deleteTree. public class FileSystemUtils { - static final Logger LOG = Logger.getLogger(FileSystemUtils.class.getName()); - static final boolean LOG_FINER = LOG.isLoggable(Level.FINER); - private FileSystemUtils() {} /**************************************************************************** @@ -136,21 +126,6 @@ public class FileSystemUtils { return new PathFragment(dotdots.toString()).getRelative(to.relativeTo(ancestor)); } - /** - * 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. - */ - static PathFragment longestPathPrefix(PathFragment path, Set prefixes) { - for (int i = path.segmentCount(); i >= 0; i--) { - PathFragment prefix = path.subFragment(0, i); - if (prefixes.contains(prefix)) { - return prefix; - } - } - return null; - } - /** * Removes the shortest suffix beginning with '.' from the basename of the * filename string. If the basename contains no '.', the filename is returned @@ -562,24 +537,6 @@ public class FileSystemUtils { } } - /** - * 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. - */ - @ThreadSafe - public static void deleteTreesBelowNotPrefixed(Path dir, String[] prefixes) throws IOException { - dirloop: - for (Path p : dir.getDirectoryEntries()) { - String name = p.getBaseName(); - for (int i = 0; i < prefixes.length; i++) { - if (name.startsWith(prefixes[i])) { - continue dirloop; - } - } - deleteTree(p); - } - } - /** * Copies all dir trees under a given 'from' dir to location 'to', while overwriting * all files in the potentially existing 'to'. Resolves symbolic links. @@ -676,120 +633,6 @@ public class FileSystemUtils { return true; } - /** - * Takes a map of directory fragments to root paths, and creates a symlink - * forest under an existing linkRoot to the corresponding source dirs or - * files. Symlink are made at the highest dir possible, linking files directly - * only when needed with nested packages. - */ - public static void plantLinkForest(ImmutableMap packageRootMap, - Path linkRoot, String productName) - throws IOException { - Path emptyPackagePath = null; - - // Create a sorted map of all dirs (packages and their ancestors) to sets of their roots. - // Packages come from exactly one root, but their shared ancestors may come from more. - // The map is maintained sorted lexicographically, so parents are before their children. - Map> dirRootsMap = Maps.newTreeMap(); - for (Map.Entry entry : packageRootMap.entrySet()) { - PathFragment pkgDir = entry.getKey(); - Path pkgRoot = entry.getValue(); - if (pkgDir.segmentCount() == 0) { - emptyPackagePath = entry.getValue(); - } - for (int i = 1; i <= pkgDir.segmentCount(); i++) { - PathFragment dir = pkgDir.subFragment(0, i); - Set roots = dirRootsMap.get(dir); - if (roots == null) { - roots = Sets.newHashSet(); - dirRootsMap.put(dir, roots); - } - roots.add(pkgRoot); - } - } - // Now add in roots for all non-pkg dirs that are in between two packages, and missed above. - for (Map.Entry> entry : dirRootsMap.entrySet()) { - PathFragment dir = entry.getKey(); - if (!packageRootMap.containsKey(dir)) { - PathFragment pkgDir = longestPathPrefix(dir, packageRootMap.keySet()); - if (pkgDir != null) { - entry.getValue().add(packageRootMap.get(pkgDir)); - } - } - } - // Create output dirs for all dirs that have more than one root and need to be split. - for (Map.Entry> entry : dirRootsMap.entrySet()) { - PathFragment dir = entry.getKey(); - if (entry.getValue().size() > 1) { - if (LOG_FINER) { - LOG.finer("mkdir " + linkRoot.getRelative(dir)); - } - createDirectoryAndParents(linkRoot.getRelative(dir)); - } - } - // Make dir links for single rooted dirs. - for (Map.Entry> entry : dirRootsMap.entrySet()) { - PathFragment dir = entry.getKey(); - Set roots = entry.getValue(); - // Simple case of one root for this dir. - if (roots.size() == 1) { - if (dir.segmentCount() > 1 && dirRootsMap.get(dir.getParentDirectory()).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. - Path root = roots.iterator().next(); // lone root in set - if (LOG_FINER) { - LOG.finer("ln -s " + root.getRelative(dir) + " " + linkRoot.getRelative(dir)); - } - linkRoot.getRelative(dir).createSymbolicLink(root.getRelative(dir)); - } - } - // Make links for dirs within packages, skip parent-only dirs. - for (Map.Entry> entry : dirRootsMap.entrySet()) { - PathFragment dir = entry.getKey(); - if (entry.getValue().size() > 1) { - // If this dir is at or below a package dir, link in its contents. - PathFragment pkgDir = longestPathPrefix(dir, packageRootMap.keySet()); - if (pkgDir != null) { - Path root = packageRootMap.get(pkgDir); - try { - Path absdir = root.getRelative(dir); - if (absdir.isDirectory()) { - if (LOG_FINER) { - LOG.finer("ln -s " + absdir + "/* " + linkRoot.getRelative(dir) + "/"); - } - for (Path target : absdir.getDirectoryEntries()) { - PathFragment p = target.relativeTo(root); - if (!dirRootsMap.containsKey(p)) { - //LOG.finest("ln -s " + target + " " + linkRoot.getRelative(p)); - linkRoot.getRelative(p).createSymbolicLink(target); - } - } - } else { - LOG.fine("Symlink planting skipping dir '" + absdir + "'"); - } - } catch (IOException e) { - e.printStackTrace(); - } - // Otherwise its just an otherwise empty common parent dir. - } - } - } - - if (emptyPackagePath != null) { - // For the top-level directory, generate symlinks to everything in the directory instead of - // the directory itself. - for (Path target : emptyPackagePath.getDirectoryEntries()) { - String baseName = target.getBaseName(); - // Create any links that don't exist yet and don't start with bazel-. - if (!baseName.startsWith(productName + "-") - && !linkRoot.getRelative(baseName).exists()) { - linkRoot.getRelative(baseName).createSymbolicLink(target); - } - } - } - } - /**************************************************************************** * Whole-file I/O utilities for characters and bytes. These convenience * methods are not efficient and should not be used for large amounts of data! diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD new file mode 100644 index 0000000000..3b4268ade6 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD @@ -0,0 +1,27 @@ +java_library( + name = "testutil", + srcs = glob(["util/*.java"]), + deps = [ + "//src/main/java/com/google/devtools/build/lib:bazel-main", + "//src/main/java/com/google/devtools/build/lib:runtime", + "//src/test/java/com/google/devtools/build/lib:packages_testutil", + "//src/test/java/com/google/devtools/build/lib:testutil", + ], +) + +java_test( + name = "BuildtoolTests", + srcs = glob(["*.java"]), + test_class = "com.google.devtools.build.lib.AllTests", + runtime_deps = ["//src/test/java/com/google/devtools/build/lib:test_runner"], + deps = [ + ":testutil", + "//src/main/java/com/google/devtools/build/lib:inmemoryfs", + "//src/main/java/com/google/devtools/build/lib:packages-internal", + "//src/main/java/com/google/devtools/build/lib:runtime", + "//src/main/java/com/google/devtools/build/lib:vfs", + "//src/test/java/com/google/devtools/build/lib:packages_testutil", + "//src/test/java/com/google/devtools/build/lib:testutil", + "//third_party:junit4", + ], +) 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 new file mode 100644 index 0000000000..7ea95a4c3e --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java @@ -0,0 +1,184 @@ +// Copyright 2016 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.buildtool; + +import static com.google.devtools.build.lib.vfs.FileSystemUtils.createDirectoryAndParents; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.testutil.ManualClock; +import com.google.devtools.build.lib.testutil.TestConstants; +import com.google.devtools.build.lib.vfs.FileSystem; +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.Symlinks; +import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * Tests {@link SymlinkForest}. + */ +@RunWith(JUnit4.class) +public class SymlinkForestTest { + private FileSystem fileSystem; + + private Path topDir; + private Path file1; + private Path file2; + private Path aDir; + + @Before + public final void initializeFileSystem() throws Exception { + ManualClock clock = new ManualClock(); + fileSystem = new InMemoryFileSystem(clock); + } + + /* + * 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"); + Path link1 = fileSystem.getPath("/top-dir/a-dir/inner-dir/link-1"); + Path dirLink = fileSystem.getPath("/top-dir/a-dir/inner-dir/dir-link"); + Path file4 = fileSystem.getPath("/file-4"); + Path file5 = fileSystem.getPath("/top-dir/b-dir/file-5"); + + topDir.createDirectory(); + FileSystemUtils.createEmptyFile(file1); + FileSystemUtils.createEmptyFile(file2); + aDir.createDirectory(); + bDir.createDirectory(); + FileSystemUtils.createEmptyFile(file3); + innerDir.createDirectory(); + link1.createSymbolicLink(file4); // simple symlink + dirLink.createSymbolicLink(bDir); + FileSystemUtils.createEmptyFile(file4); + FileSystemUtils.createEmptyFile(file5); + } + + private static String longestPathPrefixStr(String path, String... prefixStrs) { + Set prefixes = new HashSet<>(); + for (String prefix : prefixStrs) { + prefixes.add(new PathFragment(prefix)); + } + PathFragment longest = SymlinkForest.longestPathPrefix(new PathFragment(path), prefixes); + return longest != null ? longest.getPathString() : null; + } + + @Test + public void testLongestPathPrefix() { + assertEquals("A", longestPathPrefixStr("A/b", "A", "B")); // simple parent + assertEquals("A", longestPathPrefixStr("A", "A", "B")); // self + assertEquals("A/B", longestPathPrefixStr("A/B/c", "A", "A/B")); // want longest + assertNull(longestPathPrefixStr("C/b", "A", "B")); // not found in other parents + assertNull(longestPathPrefixStr("A", "A/B", "B")); // not found in child + assertEquals("A/B/C", longestPathPrefixStr("A/B/C/d/e/f.h", "A/B/C", "B/C/d")); + assertEquals("", longestPathPrefixStr("A/f.h", "", "B/C/d")); + } + + @Test + public void testDeleteTreesBelowNotPrefixed() throws IOException { + createTestDirectoryTree(); + SymlinkForest.deleteTreesBelowNotPrefixed(topDir, new String[] { "file-"}); + assertTrue(file1.exists()); + assertTrue(file2.exists()); + assertFalse(aDir.exists()); + } + + private PathFragment createPkg(Path rootA, Path 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 new PathFragment(pkg); + } + + private void assertLinksTo(Path fromRoot, Path toRoot, String relpart) throws IOException { + assertTrue(fromRoot.getRelative(relpart).isSymbolicLink()); + assertEquals(toRoot.getRelative(relpart).asFragment(), + fromRoot.getRelative(relpart).readSymbolicLink()); + } + + private void assertIsDir(Path root, String relpart) { + assertTrue(root.getRelative(relpart).isDirectory(Symlinks.NOFOLLOW)); + } + + @Test + public void testPlantLinkForest() throws IOException { + Path rootA = fileSystem.getPath("/A"); + Path rootB = 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).plantSymlinkForest(); + + 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"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java index 23e05102dd..5c4aaa32d7 100644 --- a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java +++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java @@ -19,9 +19,6 @@ import static com.google.devtools.build.lib.vfs.FileSystemUtils.copyFile; import static com.google.devtools.build.lib.vfs.FileSystemUtils.copyTool; import static com.google.devtools.build.lib.vfs.FileSystemUtils.createDirectoryAndParents; import static com.google.devtools.build.lib.vfs.FileSystemUtils.deleteTree; -import static com.google.devtools.build.lib.vfs.FileSystemUtils.deleteTreesBelowNotPrefixed; -import static com.google.devtools.build.lib.vfs.FileSystemUtils.longestPathPrefix; -import static com.google.devtools.build.lib.vfs.FileSystemUtils.plantLinkForest; import static com.google.devtools.build.lib.vfs.FileSystemUtils.relativePath; import static com.google.devtools.build.lib.vfs.FileSystemUtils.removeExtension; import static com.google.devtools.build.lib.vfs.FileSystemUtils.touchFile; @@ -37,11 +34,9 @@ import static org.junit.Assert.fail; import com.google.common.base.Predicate; import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.devtools.build.lib.testutil.BlazeTestUtils; import com.google.devtools.build.lib.testutil.ManualClock; -import com.google.devtools.build.lib.testutil.TestConstants; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import org.junit.Before; @@ -51,12 +46,9 @@ import org.junit.runners.JUnit4; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; /** * This class tests the file system utilities. @@ -191,26 +183,6 @@ public class FileSystemUtilsTest { assertEquals("../../../file-4", relativePath(innerDir, file4).getPathString()); } - private static String longestPathPrefixStr(String path, String... prefixStrs) { - Set prefixes = new HashSet<>(); - for (String prefix : prefixStrs) { - prefixes.add(new PathFragment(prefix)); - } - PathFragment longest = longestPathPrefix(new PathFragment(path), prefixes); - return longest != null ? longest.getPathString() : null; - } - - @Test - public void testLongestPathPrefix() { - assertEquals("A", longestPathPrefixStr("A/b", "A", "B")); // simple parent - assertEquals("A", longestPathPrefixStr("A", "A", "B")); // self - assertEquals("A/B", longestPathPrefixStr("A/B/c", "A", "A/B")); // want longest - assertNull(longestPathPrefixStr("C/b", "A", "B")); // not found in other parents - assertNull(longestPathPrefixStr("A", "A/B", "B")); // not found in child - assertEquals("A/B/C", longestPathPrefixStr("A/B/C/d/e/f.h", "A/B/C", "B/C/d")); - assertEquals("", longestPathPrefixStr("A/f.h", "", "B/C/d")); - } - @Test public void testRemoveExtension_Strings() throws Exception { assertEquals("foo", removeExtension("foo.c")); @@ -641,15 +613,6 @@ public class FileSystemUtilsTest { assertFalse(file3.exists()); } - @Test - public void testDeleteTreesBelowNotPrefixed() throws IOException { - createTestDirectoryTree(); - deleteTreesBelowNotPrefixed(topDir, new String[] { "file-"}); - assertTrue(file1.exists()); - assertTrue(file2.exists()); - assertFalse(aDir.exists()); - } - @Test public void testCreateDirectories() throws IOException { Path mainPath = fileSystem.getPath("/some/where/deep/in/the/hierarchy"); @@ -699,82 +662,6 @@ public class FileSystemUtilsTest { assertTrue(createDirectoryAndParents(theHierarchy)); } - PathFragment createPkg(Path rootA, Path 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 new PathFragment(pkg); - } - - void assertLinksTo(Path fromRoot, Path toRoot, String relpart) throws IOException { - assertTrue(fromRoot.getRelative(relpart).isSymbolicLink()); - assertEquals(toRoot.getRelative(relpart).asFragment(), - fromRoot.getRelative(relpart).readSymbolicLink()); - } - - void assertIsDir(Path root, String relpart) { - assertTrue(root.getRelative(relpart).isDirectory(Symlinks.NOFOLLOW)); - } - - void dumpTree(Path root, PrintStream out) throws IOException { - out.println("\n" + root); - for (Path p : FileSystemUtils.traverseTree(root, Predicates.alwaysTrue())) { - if (p.isDirectory(Symlinks.NOFOLLOW)) { - out.println(" " + p + "/"); - } else if (p.isSymbolicLink()) { - out.println(" " + p + " => " + p.readSymbolicLink()); - } else { - out.println(" " + p + " [" + p.resolveSymbolicLinks() + "]"); - } - } - } - - @Test - public void testPlantLinkForest() throws IOException { - Path rootA = fileSystem.getPath("/A"); - Path rootB = 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 - - //dumpTree(rootA, System.err); - //dumpTree(rootB, System.err); - - Path linkRoot = fileSystem.getPath("/linkRoot"); - createDirectoryAndParents(linkRoot); - plantLinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME); - - //dumpTree(linkRoot, System.err); - - 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 testWriteIsoLatin1() throws Exception { -- cgit v1.2.3