Make UnionFileSystem accept all paths Bazel can throw at it.
Instead of relying on a character-by-character StringTrie, segment paths based on PathFragments. This means UnionFS can accept any path that Bazel stores internally, removing the ASCII limitations.
This also means removing the ability to have a filesystem bound for sub-PathFragments, /foo/barbar, /foo/barqux could have the same filesystem bound at /foo/bar. This feature was tested for when a use case was envisioned, but it was never used, so removing it is safe.
RELNOTES: None.
PiperOrigin-RevId: 170054656
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 2f783f9..b8f6e9c 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -94,7 +94,6 @@
name = "base-util",
srcs = [
"util/StringCanonicalizer.java",
- "util/StringTrie.java",
"util/VarInt.java",
],
deps = [
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
index b904e1d..2bbdb48 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
@@ -808,7 +808,7 @@
}
}
- private static FileSystem fileSystemImplementation() {
+ private static FileSystem defaultFileSystemImplementation() {
if ("0".equals(System.getProperty("io.bazel.EnableJni"))) {
// Ignore UnixFileSystem, to be used for bootstrapping.
return OS.getCurrent() == OS.WINDOWS ? new WindowsFileSystem() : new JavaIoFileSystem();
@@ -943,7 +943,7 @@
}
if (fs == null) {
- fs = fileSystemImplementation();
+ fs = defaultFileSystemImplementation();
}
Path.setFileSystemForSerialization(fs);
diff --git a/src/main/java/com/google/devtools/build/lib/util/StringTrie.java b/src/main/java/com/google/devtools/build/lib/util/StringTrie.java
deleted file mode 100644
index 0b5d6e4..0000000
--- a/src/main/java/com/google/devtools/build/lib/util/StringTrie.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2014 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.util;
-
-/**
- * A trie that operates on path segments of an input string instead of individual characters.
- *
- * <p>Only accepts strings that contain only low-ASCII characters (0-127)
- *
- * @param <T> the type of the values
- */
-public class StringTrie<T> {
- private static final int RANGE = 128;
-
- @SuppressWarnings("unchecked")
- private static class Node<T> {
- private Node() {
- children = new Node[RANGE];
- }
-
- private T value;
- private Node<T> children[];
- }
-
- private final Node<T> root;
-
- public StringTrie() {
- root = new Node<T>();
- }
-
- /**
- * Puts a value in the trie.
- */
- public void put(CharSequence key, T value) {
- Node<T> current = root;
-
- for (int i = 0; i < key.length(); i++) {
- char ch = key.charAt(i);
- Preconditions.checkState(ch < RANGE);
- Node<T> next = current.children[ch];
- if (next == null) {
- next = new Node<T>();
- current.children[ch] = next;
- }
-
- current = next;
- }
-
- current.value = value;
- }
-
- /**
- * Gets a value from the trie. If there is an entry with the same key, that will be returned,
- * otherwise, the value corresponding to the key that matches the longest prefix of the input.
- */
- public T get(String key) {
- Node<T> current = root;
- T lastValue = current.value;
-
- for (int i = 0; i < key.length(); i++) {
- char ch = key.charAt(i);
- Preconditions.checkState(ch < RANGE);
-
- current = current.children[ch];
- if (current == null) {
- break;
- }
-
- if (current.value != null) {
- lastValue = current.value;
- }
- }
-
- return lastValue;
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java b/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java
index cc9aa19..c47a5f7 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java
@@ -58,6 +58,9 @@
/** An empty path fragment. */
public static final PathFragment EMPTY_FRAGMENT = create("");
+ /** The path fragment representing the root directory. */
+ public static final PathFragment ROOT_FRAGMENT = create(ROOT_DIR);
+
/**
* A helper object for manipulating the various internal {@link PathFragment} implementations.
*
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java b/src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java
new file mode 100644
index 0000000..86bc226
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java
@@ -0,0 +1,82 @@
+// Copyright 2014 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.vfs;
+
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.util.Preconditions;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A trie that operates on path segments.
+ *
+ * @param <T> the type of the values.
+ */
+@ThreadCompatible
+public class PathTrie<T> {
+ @SuppressWarnings("unchecked")
+ private static class Node<T> {
+ private Node() {
+ children = new HashMap<>();
+ }
+
+ private T value;
+ private Map<String, Node<T>> children;
+ }
+
+ private final Node<T> root;
+
+ public PathTrie() {
+ root = new Node<T>();
+ }
+
+ /**
+ * Puts a value in the trie.
+ *
+ * @param key must be an absolute path.
+ */
+ public void put(PathFragment key, T value) {
+ Preconditions.checkArgument(key.isAbsolute(), "PathTrie only accepts absolute paths as keys.");
+ Node<T> current = root;
+ for (String segment : key.getSegments()) {
+ current.children.putIfAbsent(segment, new Node<T>());
+ current = current.children.get(segment);
+ }
+ current.value = value;
+ }
+
+ /**
+ * Gets a value from the trie. If there is an entry with the same key, that will be returned,
+ * otherwise, the value corresponding to the key that matches the longest prefix of the input.
+ */
+ public T get(PathFragment key) {
+ Node<T> current = root;
+ T lastValue = current.value;
+
+ for (String segment : key.getSegments()) {
+ if (current.children.containsKey(segment)) {
+ current = current.children.get(segment);
+ // Track the values of increasing matching prefixes.
+ if (current.value != null) {
+ lastValue = current.value;
+ }
+ } else {
+ // We've reached the longest prefix, no further to go.
+ break;
+ }
+ }
+
+ return lastValue;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
index d1d82d9..de65cf7 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
@@ -17,8 +17,6 @@
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.util.StringTrie;
-import com.google.devtools.build.lib.vfs.FileSystem.HashFunction;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -28,21 +26,20 @@
/**
* Presents a unified view of multiple virtual {@link FileSystem} instances, to which requests are
- * delegated based on a {@link PathFragment} prefix mapping.
- * If multiple prefixes apply to a given path, the *longest* (i.e. most specific) match is used.
- * The order in which the delegates are specified does not influence the mapping.
+ * delegated based on a {@link PathFragment} prefix mapping. If multiple prefixes apply to a given
+ * path, the *longest* (i.e. most specific) match is used. The order in which the delegates are
+ * specified does not influence the mapping.
*
- * <p>Paths are preserved absolutely, contrary to how "mount" works, e.g.:
- * /foo/bar maps to /foo/bar on the delegate, even if it is mounted at /foo.
+ * <p>Paths are preserved absolutely, contrary to how "mount" works, e.g.: /foo/bar maps to /foo/bar
+ * on the delegate, even if it is mounted at /foo.
*
- * <p>For example:
- * "/in" maps to InFileSystem, "/" maps to OtherFileSystem.
- * Reading from "/in/base/BUILD" through the UnionFileSystem will delegate the read operation to
- * InFileSystem, which will read "/in/base/BUILD" relative to its root.
- * ("mount" behavior would remap it to "/base/BUILD" on the delegate).
+ * <p>For example: "/in" maps to InFileSystem, "/" maps to OtherFileSystem. Reading from
+ * "/in/base/BUILD" through the UnionFileSystem will delegate the read operation to InFileSystem,
+ * which will read "/in/base/BUILD" relative to its root. ("mount" behavior would remap it to
+ * "/base/BUILD" on the delegate).
*
- * <p>Intra-filesystem symbolic links are resolved to their ultimate targets.
- * Cross-filesystem links are not currently supported.
+ * <p>Intra-filesystem symbolic links are resolved to their ultimate targets. Cross-filesystem links
+ * are not currently supported.
*/
@ThreadSafety.ThreadSafe
public class UnionFileSystem extends FileSystem {
@@ -50,7 +47,7 @@
// Prefix trie index, allowing children to easily inherit prefix mappings
// of their parents.
// This does not currently handle unicode filenames.
- private StringTrie<FileSystem> pathDelegate;
+ private final PathTrie<FileSystem> pathDelegate;
// True iff the filesystem can be modified. If false, mutating operations
// will throw UnsupportedOperationExceptions.
@@ -61,37 +58,35 @@
private final boolean isCaseSensitive;
/**
- * Creates a new modifiable UnionFileSystem with prefix mappings
- * specified by a map.
+ * Creates a new modifiable UnionFileSystem with prefix mappings specified by a map.
*
* @param prefixMapping map of path prefixes to {@link FileSystem}s
*/
- public UnionFileSystem(Map<PathFragment, FileSystem> prefixMapping,
- FileSystem rootFileSystem) {
+ public UnionFileSystem(Map<PathFragment, FileSystem> prefixMapping, FileSystem rootFileSystem) {
this(prefixMapping, rootFileSystem, /* readOnly */ false);
}
/**
- * Creates a new modifiable or read-only UnionFileSystem with prefix mappings
- * specified by a map.
+ * Creates a new modifiable or read-only UnionFileSystem with prefix mappings specified by a map.
*
- * @param prefixMapping map of path prefixes to delegate {@link FileSystem}s
+ * @param prefixMapping map of path prefixes to delegate {@link FileSystem} instances to use for
+ * paths of that prefix. Note that all prefixes must be absolute paths.
* @param rootFileSystem root for default requests; i.e. mapping of "/"
* @param readOnly if true, mutating operations will throw
*/
- public UnionFileSystem(Map<PathFragment, FileSystem> prefixMapping,
- FileSystem rootFileSystem, boolean readOnly) {
+ public UnionFileSystem(
+ Map<PathFragment, FileSystem> prefixMapping, FileSystem rootFileSystem, boolean readOnly) {
super();
Preconditions.checkNotNull(prefixMapping);
Preconditions.checkNotNull(rootFileSystem);
Preconditions.checkArgument(rootFileSystem != this, "Circular root filesystem.");
Preconditions.checkArgument(
!prefixMapping.containsKey(PathFragment.EMPTY_FRAGMENT),
- "Attempted to specify an explicit root prefix mapping; " +
- "please use the rootFileSystem argument instead.");
+ "Attempted to specify an explicit root prefix mapping; "
+ + "please use the rootFileSystem argument instead.");
this.readOnly = readOnly;
- this.pathDelegate = new StringTrie<>();
+ this.pathDelegate = new PathTrie<>();
this.isCaseSensitive = rootFileSystem.isFilePathCaseSensitive();
for (Map.Entry<PathFragment, FileSystem> prefix : prefixMapping.entrySet()) {
@@ -102,29 +97,24 @@
PathFragment prefixPath = prefix.getKey();
// Extra slash prevents within-directory mappings, which Path can't handle.
- String path = prefixPath.getPathString();
- pathDelegate.put(path, delegate);
+ pathDelegate.put(prefixPath, delegate);
}
- pathDelegate.put(PathFragment.EMPTY_FRAGMENT.getPathString(), rootFileSystem);
+ pathDelegate.put(PathFragment.ROOT_FRAGMENT, rootFileSystem);
}
/**
- * Retrieves the filesystem delegate of a path mapping.
- * Does not follow symlinks (but you can call on a path preprocessed with
- * {@link #resolveSymbolicLinks} to support this use case).
+ * Retrieves the filesystem delegate of a path mapping. Does not follow symlinks (but you can call
+ * on a path preprocessed with {@link #resolveSymbolicLinks} to support this use case).
*
* @param path the {@link Path} to map to a filesystem
* @throws IllegalArgumentException if no delegate exists for the path
*/
protected FileSystem getDelegate(Path path) {
Preconditions.checkNotNull(path);
-
- String pathString = path.getPathString();
- FileSystem immediateDelegate = pathDelegate.get(pathString);
+ FileSystem immediateDelegate = pathDelegate.get(path.asFragment());
// Should never actually happen if the root delegate is present.
- Preconditions.checkArgument(immediateDelegate != null, "No delegate filesystem exists for %s",
- pathString);
+ Preconditions.checkNotNull(immediateDelegate, "No delegate filesystem exists for %s", path);
return immediateDelegate;
}
@@ -135,8 +125,8 @@
}
/**
- * Follow a symbolic link once using the appropriate delegate filesystem, also
- * resolving parent directory symlinks.
+ * Follow a symbolic link once using the appropriate delegate filesystem, also resolving parent
+ * directory symlinks.
*
* @param path {@link Path} to the symbolic link
*/
@@ -157,7 +147,7 @@
private void checkModifiable() {
if (!supportsModifications()) {
throw new UnsupportedOperationException(
- "Modifications to this " + getClass().getSimpleName() + " are disabled.");
+ String.format("Modifications to this %s are disabled.", getClass().getSimpleName()));
}
}
@@ -311,9 +301,8 @@
}
/**
- * Retrieves the directory entries for the specified path under the assumption
- * that {@code resolvedPath} is the resolved path of {@code path} in one of the
- * underlying file systems.
+ * Retrieves the directory entries for the specified path under the assumption that {@code
+ * resolvedPath} is the resolved path of {@code path} in one of the underlying file systems.
*
* @param path the {@link Path} whose children are to be retrieved
*/
@@ -409,16 +398,18 @@
FileSystem sourceDelegate = getDelegate(sourcePath);
if (!sourceDelegate.supportsModifications()) {
throw new UnsupportedOperationException(
- "The filesystem for the source path "
- + sourcePath.getPathString() + " does not support modifications.");
+ String.format(
+ "The filesystem for the source path %s does not support modifications.",
+ sourcePath.getPathString()));
}
sourcePath = adjustPath(sourcePath, sourceDelegate);
FileSystem targetDelegate = getDelegate(targetPath);
if (!targetDelegate.supportsModifications()) {
throw new UnsupportedOperationException(
- "The filesystem for the target path "
- + targetPath.getPathString() + " does not support modifications.");
+ String.format(
+ "The filesystem for the target path %s does not support modifications.",
+ targetPath.getPathString()));
}
targetPath = adjustPath(targetPath, targetDelegate);
@@ -435,8 +426,7 @@
}
@Override
- protected void createFSDependentHardLink(Path linkPath, Path originalPath)
- throws IOException {
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath) throws IOException {
checkModifiable();
FileSystem originalDelegate = getDelegate(originalPath);