Rollback of commit e0d7a540e3c615c628f63fcaaaba0c47fca2cb25.
*** Reason for rollback ***
Suspected root cause for Windows bootstrap on Bazel CI breakage:
java.lang.NullPointerException
at com.google.devtools.build.lib.vfs.Path$1.run(Path.java:123)
http://ci.bazel.io/view/Bazel%20bootstrap%20and%20maintenance/job/Bazel/922/JAVA_VERSION=1.8,PLATFORM_NAME=windows-x86_64/console
*** Original change description ***
VFS: implement a Windows-specific Path subclass
The new subclass WindowsFileSystem.WindowsPath is
aware of Windows drives.
This change:
- introduces a new factory for Path objects so
FileSystems can return a custom implementation
that instantiates filesystem-specific Paths
- implements the WindowsPath subclass of Path that
is aware of Windows drives
- introduces the bazel.windows_unix_root JVM
argument that defines the MSYS root, which
defines the absolute Windows path that is the...
***
--
MOS_MIGRATED_REVID=136583352
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
index 768792a..e7f1fa2 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -22,7 +22,6 @@
import com.google.common.io.CharStreams;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.vfs.Dirent.Type;
-import com.google.devtools.build.lib.vfs.Path.PathFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -38,20 +37,6 @@
@ThreadSafe
public abstract class FileSystem {
- private enum UnixPathFactory implements PathFactory {
- INSTANCE {
- @Override
- public Path createRootPath(FileSystem filesystem) {
- return new Path(filesystem, PathFragment.ROOT_DIR, null);
- }
-
- @Override
- public Path createChildPath(Path parent, String childName) {
- return new Path(parent.getFileSystem(), childName, parent);
- }
- };
- }
-
/**
* An exception thrown when attempting to resolve an ordinary file as a symlink.
*/
@@ -64,12 +49,19 @@
protected final Path rootPath;
protected FileSystem() {
- this.rootPath = getPathFactory().createRootPath(this);
+ this.rootPath = createRootPath();
}
- /** Returns filesystem-specific path factory. */
- protected PathFactory getPathFactory() {
- return UnixPathFactory.INSTANCE;
+ /**
+ * Creates the root of all paths used by this filesystem. This is a hook
+ * allowing subclasses to define their own root path class. All other paths
+ * are created via the root path's {@link Path#createChildPath(String)} method.
+ * <p>
+ * Beware: this is called during the FileSystem constructor which may occur
+ * before subclasses are completely initialized.
+ */
+ protected Path createRootPath() {
+ return new Path(this);
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
index be95e3b..dbec227 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.util.StringCanonicalizer;
import java.io.File;
@@ -30,6 +31,7 @@
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
+import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Objects;
@@ -51,28 +53,6 @@
@ThreadSafe
public class Path implements Comparable<Path>, Serializable {
- /** Filesystem-specific factory for {@link Path} objects. */
- public static interface PathFactory {
- /**
- * Creates the root of all paths used by a filesystem.
- *
- * <p>All other paths are instantiated via {@link Path#createChildPath(String)} which calls
- * {@link #createChildPath(Path, String)}.
- *
- * <p>Beware: this is called during the FileSystem constructor which may occur before subclasses
- * are completely initialized.
- */
- Path createRootPath(FileSystem filesystem);
-
- /**
- * Create a child path of the given parent.
- *
- * <p>All {@link Path} objects are instantiated via this method, with the sole exception of the
- * filesystem root, which is created by {@link #createRootPath(FileSystem)}.
- */
- Path createChildPath(Path parent, String childName);
- }
-
private static FileSystem fileSystemForSerialization;
/**
@@ -171,12 +151,10 @@
private volatile IdentityHashMap<String, Reference<Path>> children;
/**
- * Create a path instance.
- *
- * <p>Should only be called by {@link PathFactory#createChildPath(Path, String)}.
+ * Create a path instance. Should only be called by {@link #createChildPath}.
*
* @param name the name of this path; it must be canonicalized with {@link
- * StringCanonicalizer#intern}
+ * StringCanonicalizer#intern}
* @param parent this path's parent
*/
protected Path(FileSystem fileSystem, String name, Path parent) {
@@ -184,9 +162,6 @@
this.name = name;
this.parent = parent;
this.depth = parent == null ? 0 : parent.depth + 1;
-
- // No need to include the drive letter in the hash code, because it's derived from the parent
- // and/or the name.
if (fileSystem == null || fileSystem.isFilePathCaseSensitive()) {
this.hashCode = Objects.hash(parent, name);
} else {
@@ -195,9 +170,8 @@
}
/**
- * Create the root path.
- *
- * <p>Should only be called by {@link PathFactory#createRootPath(FileSystem)}.
+ * Create the root path. Should only be called by
+ * {@link FileSystem#createRootPath()}.
*/
protected Path(FileSystem fileSystem) {
this(fileSystem, StringCanonicalizer.intern("/"), null);
@@ -223,7 +197,6 @@
this.depth = this.parent.depth + 1;
}
this.hashCode = Objects.hash(parent, name);
- reinitializeAfterDeserialization();
}
/**
@@ -238,45 +211,7 @@
}
protected Path createChildPath(String childName) {
- return fileSystem.getPathFactory().createChildPath(this, childName);
- }
-
- /**
- * Reinitializes this object after deserialization.
- *
- * <p>Derived classes should use this hook to initialize additional state.
- */
- protected void reinitializeAfterDeserialization() {}
-
- /**
- * Returns true if {@code ancestorPath} may be an ancestor of {@code path}.
- *
- * <p>The return value may be a false positive, but it cannot be a false negative. This means that
- * a true return value doesn't mean the ancestor candidate is really an ancestor, however a false
- * return value means it's guaranteed that {@code ancestorCandidate} is not an ancestor of this
- * path.
- *
- * <p>Subclasses may override this method with filesystem-specific logic, e.g. a Windows
- * filesystem may return false if the ancestor path is on a different drive than this one, because
- * it is then guaranteed that the ancestor candidate cannot be an ancestor of this path.
- *
- * @param ancestorCandidate the path that may or may not be an ancestor of this one
- */
- protected boolean isMaybeRelativeTo(Path ancestorCandidate) {
- return true;
- }
-
- /**
- * Returns true if this directory is top-level, i.e. it is its own parent.
- *
- * <p>When canonicalizing paths the ".." segment of a top-level directory always resolves to the
- * directory itself.
- *
- * <p>On Unix, a top-level directory would be just the filesystem root ("/), on Windows it would
- * be the filesystem root and the volume roots.
- */
- protected boolean isTopLevelDirectory() {
- return isRootDirectory();
+ return new Path(fileSystem, childName, this);
}
/**
@@ -302,7 +237,7 @@
Reference<Path> childRef = children.get(childName);
Path child;
if (childRef == null || (child = childRef.get()) == null) {
- child = fileSystem.getPathFactory().createChildPath(this, childName);
+ child = createChildPath(childName);
children.put(childName, new PathWeakReferenceForCleanup(child, REFERENCE_QUEUE));
}
return child;
@@ -349,22 +284,37 @@
}
/**
- * Computes a string representation of this path, and writes it to the given string builder. Only
- * called locally with a new instance.
+ * Computes a string representation of this path, and writes it to the
+ * given string builder. Only called locally with a new instance.
*/
- protected void buildPathString(StringBuilder result) {
+ private void buildPathString(StringBuilder result) {
if (isRootDirectory()) {
- result.append(PathFragment.ROOT_DIR);
+ result.append('/');
} else {
- parent.buildPathString(result);
+ if (parent.isWindowsVolumeName()) {
+ result.append(parent.name);
+ } else {
+ parent.buildPathString(result);
+ }
if (!parent.isRootDirectory()) {
- result.append(PathFragment.SEPARATOR_CHAR);
+ result.append('/');
}
result.append(name);
}
}
/**
+ * Returns true if the current path represents a Windows volume name (such as "c:" or "d:").
+ *
+ * <p>Paths such as '\\\\vol\\foo' are not supported.
+ */
+ private boolean isWindowsVolumeName() {
+ return OS.getCurrent() == OS.WINDOWS
+ && parent != null && parent.isRootDirectory() && name.length() == 2
+ && PathFragment.getWindowsDriveLetter(name) != '\0';
+ }
+
+ /**
* Returns the path as a string.
*/
public String getPathString() {
@@ -647,8 +597,8 @@
if (segment.equals(".") || segment.isEmpty()) {
return this; // that's a noop
} else if (segment.equals("..")) {
- // top-level directory's parent is root, when canonicalising:
- return isTopLevelDirectory() ? this : parent;
+ // root's parent is root, when canonicalising:
+ return parent == null || isWindowsVolumeName() ? this : parent;
} else {
return getCachedChildPath(segment);
}
@@ -670,10 +620,6 @@
return getCachedChildPath(baseName);
}
- protected Path getRootForRelativePathComputation(PathFragment suffix) {
- return suffix.isAbsolute() ? fileSystem.getRootDirectory() : this;
- }
-
/**
* Returns the path formed by appending the relative or absolute path fragment
* {@code suffix} to this path.
@@ -684,7 +630,10 @@
* is canonical.
*/
public Path getRelative(PathFragment suffix) {
- Path result = getRootForRelativePathComputation(suffix);
+ Path result = suffix.isAbsolute() ? fileSystem.getRootDirectory() : this;
+ if (!suffix.windowsVolume().isEmpty()) {
+ result = result.getCanonicalPath(suffix.windowsVolume());
+ }
for (String segment : suffix.segments()) {
result = result.getCanonicalPath(segment);
}
@@ -707,7 +656,7 @@
if ((path.length() == 0) || (path.equals("."))) {
return this;
} else if (path.equals("..")) {
- return isTopLevelDirectory() ? this : parent;
+ return parent == null ? this : parent;
} else if (path.indexOf('/') != -1) {
return getRelative(new PathFragment(path));
} else if (path.indexOf(PathFragment.EXTRA_SEPARATOR_CHAR) != -1) {
@@ -717,20 +666,29 @@
}
}
- protected final String[] getSegments() {
+ /**
+ * Returns an absolute PathFragment representing this path.
+ */
+ public PathFragment asFragment() {
String[] resultSegments = new String[depth];
Path currentPath = this;
for (int pos = depth - 1; pos >= 0; pos--) {
resultSegments[pos] = currentPath.getBaseName();
currentPath = currentPath.getParentDirectory();
}
- return resultSegments;
+
+ char driveLetter = '\0';
+ if (resultSegments.length > 0) {
+ driveLetter = PathFragment.getWindowsDriveLetter(resultSegments[0]);
+ if (driveLetter != '\0') {
+ // Strip off the first segment that contains the volume name.
+ resultSegments = Arrays.copyOfRange(resultSegments, 1, resultSegments.length);
+ }
+ }
+
+ return new PathFragment(driveLetter, true, resultSegments);
}
- /** Returns an absolute PathFragment representing this path. */
- public PathFragment asFragment() {
- return new PathFragment('\0', true, getSegments());
- }
/**
* Returns a relative path fragment to this path, relative to {@code
@@ -750,19 +708,17 @@
public PathFragment relativeTo(Path ancestorPath) {
checkSameFilesystem(ancestorPath);
- if (isMaybeRelativeTo(ancestorPath)) {
- // Fast path: when otherPath is the ancestor of this path
- int resultSegmentCount = depth - ancestorPath.depth;
- if (resultSegmentCount >= 0) {
- String[] resultSegments = new String[resultSegmentCount];
- Path currentPath = this;
- for (int pos = resultSegmentCount - 1; pos >= 0; pos--) {
- resultSegments[pos] = currentPath.getBaseName();
- currentPath = currentPath.getParentDirectory();
- }
- if (ancestorPath.equals(currentPath)) {
- return new PathFragment('\0', false, resultSegments);
- }
+ // Fast path: when otherPath is the ancestor of this path
+ int resultSegmentCount = depth - ancestorPath.depth;
+ if (resultSegmentCount >= 0) {
+ String[] resultSegments = new String[resultSegmentCount];
+ Path currentPath = this;
+ for (int pos = resultSegmentCount - 1; pos >= 0; pos--) {
+ resultSegments[pos] = currentPath.getBaseName();
+ currentPath = currentPath.getParentDirectory();
+ }
+ if (ancestorPath.equals(currentPath)) {
+ return new PathFragment('\0', false, resultSegments);
}
}
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 c6a6ad6..1459379 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
@@ -14,7 +14,6 @@
package com.google.devtools.build.lib.vfs;
import com.google.common.base.Function;
-import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -107,14 +106,13 @@
// live PathFragments, so do not add further fields on a whim.
// The individual path components.
- // Does *not* include the Windows drive letter.
private final String[] segments;
// True both for UNIX-style absolute paths ("/foo") and Windows-style ("C:/foo").
- // False for a Windows-style volume label ("C:") which is actually a relative path.
private final boolean isAbsolute;
- // Upper case Windows drive letter, or '\0' if none or unknown.
+ // Upper case windows drive letter, or '\0' if none. While a volumeName string is more
+ // general, we create a lot of these objects, so space is at a premium.
private final char driveLetter;
// hashCode and path are lazily initialized but semantically immutable.
@@ -125,47 +123,13 @@
* Construct a PathFragment from a string, which is an absolute or relative UNIX or Windows path.
*/
public PathFragment(String path) {
- char drive = '\0';
- boolean abs = false;
- if (OS.getCurrent() == OS.WINDOWS) {
- if (path.length() == 2 && isSeparator(path.charAt(0)) && Character.isLetter(path.charAt(1))) {
- // Path is "/C" or other drive letter
- drive = Character.toUpperCase(path.charAt(1));
- abs = true;
- } else if (path.length() >= 2
- && Character.isLetter(path.charAt(0))
- && path.charAt(1) == ':') {
- // Path is like "C:", "C:/", "C:foo", or "C:/foo"
- drive = Character.toUpperCase(path.charAt(0));
- } else if (path.length() >= 3
- && isSeparator(path.charAt(0))
- && Character.isLetter(path.charAt(1))) {
- if (isSeparator(path.charAt(2))) {
- // Path is like "/C/" or "/C/foo"
- drive = Character.toUpperCase(path.charAt(1));
- abs = true;
- } else if (path.charAt(2) == ':') {
- // Path is like "/C:" or "/C:/" or "/C:/foo", neither of which is a valid path on MSYS.
- // They are also very confusing because they would be valid, absolute PathFragments with
- // no drive letters and the first segment being "C:".
- // We should not be constructing such PathFragments on Windows because it would allow
- // creating a valid Path object that would nevertheless be non-equal to "C:/" (because
- // the internal representation would be different).
- throw new IllegalArgumentException("Illegal path string \"" + path + "\"");
- }
- }
- }
-
- if (drive != '\0') {
+ this.driveLetter = getWindowsDriveLetter(path);
+ if (driveLetter != '\0') {
path = path.substring(2);
+ // TODO(bazel-team): Decide what to do about non-absolute paths with a volume name, e.g. C:x.
}
- this.isAbsolute = abs || (path.length() > 0 && isSeparator(path.charAt(0)));
+ this.isAbsolute = path.length() > 0 && isSeparator(path.charAt(0));
this.segments = segment(path, isAbsolute ? 1 : 0);
-
- // If the only difference between this object and EMPTY_FRAGMENT is the drive letter, then this
- // object is equivalent with the empty fragment. To make them compare equal we must use a null
- // drive letter.
- this.driveLetter = (this.isAbsolute || this.segments.length > 0) ? drive : '\0';
}
private static boolean isSeparator(char c) {
@@ -186,17 +150,6 @@
* here in PathFragment, and by Path.asFragment() and Path.relativeTo().
*/
PathFragment(char driveLetter, boolean isAbsolute, String[] segments) {
- driveLetter = Character.toUpperCase(driveLetter);
- if (OS.getCurrent() == OS.WINDOWS
- && segments.length > 0
- && segments[0].length() == 2
- && Character.toUpperCase(segments[0].charAt(0)) == driveLetter
- && segments[0].charAt(1) == ':') {
- throw new IllegalStateException(
- String.format(
- "the drive letter should not be a path segment; drive='%c', segments=[%s]",
- driveLetter, Joiner.on(", ").join(segments)));
- }
this.driveLetter = driveLetter;
this.isAbsolute = isAbsolute;
this.segments = segments;
@@ -385,11 +338,7 @@
((driveLetter != '\0') ? 2 : 0)
+ ((segments.length == 0) ? 0 : (segments.length + 1) * 20);
StringBuilder result = new StringBuilder(estimateSize);
- if (isAbsolute) {
- // Only print the Windows volume label if the PathFragment is absolute. Do not print relative
- // Windows paths like "C:foo/bar", it would break all kinds of things, e.g. glob().
- result.append(windowsVolume());
- }
+ result.append(windowsVolume());
boolean initialSegment = true;
for (String segment : segments) {
if (!initialSegment || isAbsolute) {
@@ -463,14 +412,9 @@
if (otherFragment == EMPTY_FRAGMENT) {
return this;
}
-
- if (otherFragment.isAbsolute()) {
- return this.driveLetter == '\0' || otherFragment.driveLetter != '\0'
- ? otherFragment
- : new PathFragment(this.driveLetter, true, otherFragment.segments);
- } else {
- return new PathFragment(this, otherFragment);
- }
+ return otherFragment.isAbsolute()
+ ? otherFragment
+ : new PathFragment(this, otherFragment);
}
/**
@@ -595,9 +539,9 @@
* order)
*/
public boolean startsWith(PathFragment prefix) {
- if (this.isAbsolute != prefix.isAbsolute
- || this.segments.length < prefix.segments.length
- || (isAbsolute && this.driveLetter != prefix.driveLetter)) {
+ if (this.isAbsolute != prefix.isAbsolute ||
+ this.segments.length < prefix.segments.length ||
+ this.driveLetter != prefix.driveLetter) {
return false;
}
for (int i = 0, len = prefix.segments.length; i < len; i++) {
@@ -683,6 +627,9 @@
}
public String windowsVolume() {
+ if (OS.getCurrent() != OS.WINDOWS) {
+ return "";
+ }
return (driveLetter != '\0') ? driveLetter + ":" : "";
}
@@ -740,8 +687,16 @@
return new PathFragment(driveLetter, false, segments);
}
- private boolean isEmpty() {
- return !isAbsolute && segments.length == 0;
+ /**
+ * Given a path, returns the Windows drive letter ('X'), or an null character if no volume
+ * name was specified.
+ */
+ static char getWindowsDriveLetter(String path) {
+ if (OS.getCurrent() == OS.WINDOWS
+ && path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) {
+ return Character.toUpperCase(path.charAt(0));
+ }
+ return '\0';
}
@Override
@@ -753,7 +708,7 @@
// Yes, this means that if the hash code is really 0 then we will "recompute" it each time. But
// this isn't a problem in practice since a hash code of 0 is rare.
//
- // (2) Since we have no synchronization, multiple threads can race here thinking they are the
+ // (2) Since we have no synchronization, multiple threads can race here thinking there are the
// first one to compute and cache the hash code.
//
// (3) Moreover, since 'hashCode' is non-volatile, the cached hash code value written from one
@@ -764,13 +719,10 @@
// once.
int h = hashCode;
if (h == 0) {
- h = Boolean.hashCode(isAbsolute);
+ h = isAbsolute ? 1 : 0;
for (String segment : segments) {
h = h * 31 + segment.hashCode();
}
- if (!isEmpty()) {
- h = h * 31 + Character.hashCode(driveLetter);
- }
hashCode = h;
}
return h;
@@ -785,13 +737,8 @@
return false;
}
PathFragment otherPath = (PathFragment) other;
- if (isEmpty() && otherPath.isEmpty()) {
- return true;
- } else {
- return isAbsolute == otherPath.isAbsolute
- && driveLetter == otherPath.driveLetter
- && Arrays.equals(otherPath.segments, segments);
- }
+ return isAbsolute == otherPath.isAbsolute &&
+ Arrays.equals(otherPath.segments, segments);
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java b/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java
index 23e7ad7..a982595 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.vfs;
import com.google.devtools.build.lib.util.Preconditions;
+
import java.io.Serializable;
import java.util.Objects;
@@ -48,21 +49,7 @@
* Returns a rooted path representing {@code relativePath} relative to {@code root}.
*/
public static RootedPath toRootedPath(Path root, PathFragment relativePath) {
- if (relativePath.isAbsolute()) {
- if (root.isRootDirectory()) {
- return new RootedPath(
- root.getRelative(relativePath.windowsVolume()), relativePath.toRelative());
- } else {
- Preconditions.checkArgument(
- relativePath.startsWith(root.asFragment()),
- "relativePath '%s' is absolute, but it's not under root '%s'",
- relativePath,
- root);
- return new RootedPath(root, relativePath.relativeTo(root.asFragment()));
- }
- } else {
- return new RootedPath(root, relativePath);
- }
+ return new RootedPath(root, relativePath);
}
/**
@@ -70,7 +57,7 @@
*/
public static RootedPath toRootedPath(Path root, Path path) {
Preconditions.checkState(path.startsWith(root), "path: %s root: %s", path, root);
- return toRootedPath(root, path.asFragment());
+ return new RootedPath(root, path.relativeTo(root));
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java
index bc9a10d..da04735 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java
@@ -15,205 +15,22 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
-import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.vfs.Path.PathFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
/** Jury-rigged file system for Windows. */
@ThreadSafe
public class WindowsFileSystem extends JavaIoFileSystem {
- @VisibleForTesting
- enum WindowsPathFactory implements PathFactory {
- INSTANCE {
- @Override
- public Path createRootPath(FileSystem filesystem) {
- return new WindowsPath(filesystem, PathFragment.ROOT_DIR, null);
- }
-
- @Override
- public Path createChildPath(Path parent, String childName) {
- Preconditions.checkState(parent instanceof WindowsPath);
- return new WindowsPath(parent.getFileSystem(), childName, (WindowsPath) parent);
- }
- };
- }
-
- private static final class WindowsPath extends Path {
-
- private static final String WINDOWS_UNIX_ROOT_JVM_ARG = "bazel.windows_unix_root";
- private static final String BAZEL_SH_ENV_VAR = "BAZEL_SH";
-
- // Absolute Windows path specifying the root of absolute Unix paths.
- // This is typically the MSYS installation root, e.g. C:\\tools\\msys64
- private static final PathFragment UNIX_ROOT =
- determineUnixRoot(WINDOWS_UNIX_ROOT_JVM_ARG, BAZEL_SH_ENV_VAR);
-
- // The drive letter is '\0' if and only if this Path is the filesystem root "/".
- private char driveLetter;
-
- private WindowsPath(FileSystem fileSystem) {
- super(fileSystem);
- this.driveLetter = '\0';
- }
-
- private WindowsPath(FileSystem fileSystem, String name, WindowsPath parent) {
- super(fileSystem, translateName(parent, name), translateParent(parent, name));
- this.driveLetter = getDriveLetter((WindowsPath) getParentDirectory(), getBaseName());
- }
-
- @Override
- protected void buildPathString(StringBuilder result) {
- if (isRootDirectory()) {
- result.append(PathFragment.ROOT_DIR);
- } else {
- if (isTopLevelDirectory()) {
- result.append(driveLetter).append(':').append(PathFragment.SEPARATOR_CHAR);
- } else {
- getParentDirectory().buildPathString(result);
- if (!getParentDirectory().isTopLevelDirectory()) {
- result.append(PathFragment.SEPARATOR_CHAR);
- }
- result.append(getBaseName());
- }
- }
- }
-
- @Override
- public void reinitializeAfterDeserialization() {
- Preconditions.checkState(
- getParentDirectory().isRootDirectory() || getParentDirectory() instanceof WindowsPath);
- this.driveLetter =
- (getParentDirectory() != null) ? ((WindowsPath) getParentDirectory()).driveLetter : '\0';
- }
-
- @Override
- public boolean isMaybeRelativeTo(Path ancestorCandidate) {
- Preconditions.checkState(ancestorCandidate instanceof WindowsPath);
- return ancestorCandidate.isRootDirectory()
- || driveLetter == ((WindowsPath) ancestorCandidate).driveLetter;
- }
-
- @Override
- public boolean isTopLevelDirectory() {
- return isRootDirectory() || getParentDirectory().isRootDirectory();
- }
-
- @Override
- public PathFragment asFragment() {
- String[] segments = getSegments();
- if (segments.length > 0) {
- // Strip off the first segment that contains the volume name.
- segments = Arrays.copyOfRange(segments, 1, segments.length);
- }
-
- return new PathFragment(driveLetter, true, segments);
- }
-
- @Override
- protected Path getRootForRelativePathComputation(PathFragment relative) {
- Path result = this;
- if (relative.isAbsolute()) {
- result = getFileSystem().getRootDirectory();
- if (!relative.windowsVolume().isEmpty()) {
- result = result.getRelative(relative.windowsVolume());
- }
- }
- return result;
- }
-
- private static boolean isWindowsVolumeName(String name) {
- return (name.length() == 1 || (name.length() == 2 && name.charAt(1) == ':'))
- && Character.isLetter(name.charAt(0));
- }
-
- private static String translateName(WindowsPath parent, String name) {
- if (parent != null && parent.isRootDirectory() && name.length() == 1) {
- // Path is /c (or similar), which is equivalent to C:/
- return name.toUpperCase() + ":";
- } else {
- return name;
- }
- }
-
- /**
- * Heuristic to translate absolute Unix paths to Windows paths.
- *
- * <p>Unix paths under MSYS should be resolved using /etc/mtab, but reading that every time we
- * create a Path object would be too expensive.
- *
- * <p>As a heuristic, we check if the paths looks like an absolute Unix path (e.g. "/usr/lib")
- * and make it relative to the MSYS root (yielding "c:/tools/msys64/usr/lib"), but only if the
- * path doesn't also look like an absolute path on a drive (e.g. /c/windows which means
- * C:/windows).
- *
- * <p>This is an imperfect workaround because the user may have other paths mounted as well, and
- * this heuristic won't handle those properly, but it's good enough.
- *
- * <p>The correct long-term solution is to update all tools on Windows to output Windows paths
- * and lock down WindowsPath creation to disallow absolute Unix paths.
- */
- private static WindowsPath translateParent(WindowsPath parent, String name) {
- if (parent != null && parent.isRootDirectory() && !isWindowsVolumeName(name)) {
- // This is a top-level directory which is not a drive name, e.g. "/usr".
- // Make it relative to UNIX_ROOT.
- Preconditions.checkNotNull(
- UNIX_ROOT,
- "Could not determine Unix path root or it is not an absolute Windows path. Set the "
- + "\"%s\" JVM argument, or export the \"%s\" environment variable for the MSYS bash"
- + " and have /usr/bin/cygpath installed",
- WINDOWS_UNIX_ROOT_JVM_ARG,
- BAZEL_SH_ENV_VAR);
-
- return (WindowsPath) parent.getRelative(UNIX_ROOT);
- } else {
- // This is not a top-level directory.
- return parent;
- }
- }
-
- private char getDriveLetter(WindowsPath parent, String name) {
- if (parent == null) {
- return '\0';
- } else {
- if (parent.isRootDirectory()) {
- Preconditions.checkState(
- isWindowsVolumeName(name),
- "top-level directory on Windows must be a drive (name = '%s')",
- name);
- return Character.toUpperCase(name.charAt(0));
- } else {
- return parent.driveLetter;
- }
- }
- }
- }
-
public static final LinkOption[] NO_OPTIONS = new LinkOption[0];
public static final LinkOption[] NO_FOLLOW = new LinkOption[] {LinkOption.NOFOLLOW_LINKS};
@Override
- protected PathFactory getPathFactory() {
- return WindowsPathFactory.INSTANCE;
- }
-
- @Override
- public String getFileSystemType(Path path) {
- // TODO(laszlocsomor): implement this properly, i.e. actually query this information from
- // somewhere (java.nio.Filesystem? System.getProperty? implement JNI method and use WinAPI?).
- return "ntfs";
- }
-
- @Override
protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException {
// TODO(lberki): Add some JNI to create hard links/junctions instead of calling out to
// cmd.exe
@@ -396,45 +213,4 @@
}
return false;
}
-
- private static PathFragment determineUnixRoot(String jvmArgName, String bazelShEnvVar) {
- // Get the path from a JVM argument, if specified.
- String path = System.getProperty(jvmArgName);
-
- if (path == null || path.isEmpty()) {
- path = "";
-
- // Fall back to executing cygpath.
- String bash = System.getenv(bazelShEnvVar);
- Process process = null;
- try {
- process = Runtime.getRuntime().exec(bash + "-c \"/usr/bin/cygpath -m /\"");
-
- // Wait 3 seconds max, that should be enough to run this command.
- process.waitFor(3, TimeUnit.SECONDS);
-
- if (process.exitValue() == 0) {
- char[] buf = new char[256];
- try (InputStreamReader r = new InputStreamReader(process.getInputStream())) {
- int len = 0;
- while ((len = r.read(buf)) > 0) {
- path = path + new String(buf, 0, len);
- }
- }
- }
- } catch (InterruptedException | IOException e) {
- // Silently ignore failure. Either MSYS is installed at a different location, or not
- // installed at all, or some error occurred. We can't do anything anymore but throw an
- // exception if someone tries to create a Path from an absolute Unix path.
- }
- }
-
- path = path.trim();
- PathFragment result = new PathFragment(path);
- if (path.isEmpty() || result.getDriveLetter() == '\0' || !result.isAbsolute()) {
- return null;
- } else {
- return result;
- }
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java
index 892fecb..4830d8f 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/ZipFileSystem.java
@@ -16,7 +16,7 @@
import com.google.common.base.Predicate;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.vfs.Path.PathFactory;
+
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
@@ -96,23 +96,6 @@
// #getDirectoryEntries}. Then this field becomes redundant.
@ThreadSafe
private static class ZipPath extends Path {
-
- private enum Factory implements PathFactory {
- INSTANCE {
- @Override
- public Path createRootPath(FileSystem filesystem) {
- Preconditions.checkArgument(filesystem instanceof ZipFileSystem);
- return new ZipPath((ZipFileSystem) filesystem);
- }
-
- @Override
- public Path createChildPath(Path parent, String childName) {
- Preconditions.checkState(parent instanceof ZipPath);
- return new ZipPath((ZipFileSystem) parent.getFileSystem(), childName, (ZipPath) parent);
- }
- };
- }
-
/**
* Non-null iff this file/directory exists. Set by setZipEntry for files
* explicitly mentioned in the zipfile's table of contents, or implicitly
@@ -121,12 +104,12 @@
ZipEntry entry = null;
// Root path.
- private ZipPath(ZipFileSystem fileSystem) {
+ ZipPath(ZipFileSystem fileSystem) {
super(fileSystem);
}
// Non-root paths.
- private ZipPath(ZipFileSystem fileSystem, String name, ZipPath parent) {
+ ZipPath(ZipFileSystem fileSystem, String name, ZipPath parent) {
super(fileSystem, name, parent);
}
@@ -145,6 +128,11 @@
path.setZipEntry(new ZipEntry(path + "/")); // trailing "/" => isDir
}
}
+
+ @Override
+ protected ZipPath createChildPath(String childName) {
+ return new ZipPath((ZipFileSystem) getFileSystem(), childName, this);
+ }
}
/**
@@ -169,8 +157,8 @@
}
@Override
- protected PathFactory getPathFactory() {
- return ZipPath.Factory.INSTANCE;
+ protected Path createRootPath() {
+ return new ZipPath(this);
}
/** Returns the ZipEntry associated with a given path name, if any. */
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 0556e41..942d9a3 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -169,10 +169,7 @@
java_test(
name = "windows_test",
srcs = CROSS_PLATFORM_WINDOWS_TESTS,
- jvm_flags = [
- "-Dblaze.os=Windows",
- "-Dbazel.windows_unix_root=C:/fake/msys",
- ],
+ jvm_flags = ["-Dblaze.os=Windows"],
test_class = "com.google.devtools.build.lib.AllTests",
deps = [
":foundations_testutil",
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
index c4ac7c1..2a41b10 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
@@ -26,13 +26,15 @@
import com.google.common.testing.EqualsTester;
import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
/**
* This class tests the functionality of the PathFragment.
*/
@@ -67,16 +69,15 @@
InMemoryFileSystem filesystem = new InMemoryFileSystem();
new EqualsTester()
- .addEqualityGroup(
- new PathFragment("../relative/path"),
- new PathFragment("..").getRelative("relative").getRelative("path"),
- new PathFragment('\0', false, new String[] {"..", "relative", "path"}),
- new PathFragment(new File("../relative/path")))
+ .addEqualityGroup(new PathFragment("../relative/path"),
+ new PathFragment("../relative/path"),
+ new PathFragment(new File("../relative/path")))
.addEqualityGroup(new PathFragment("something/else"))
.addEqualityGroup(new PathFragment("/something/else"))
- .addEqualityGroup(new PathFragment("/"), new PathFragment("//////"))
- .addEqualityGroup(new PathFragment(""), PathFragment.EMPTY_FRAGMENT)
- .addEqualityGroup(filesystem.getRootDirectory()) // A Path object.
+ .addEqualityGroup(new PathFragment("/"),
+ new PathFragment("//////"))
+ .addEqualityGroup(new PathFragment(""))
+ .addEqualityGroup(filesystem.getRootDirectory()) // A Path object.
.testEquals();
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
index df2c770..48a63e3 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
@@ -14,24 +14,23 @@
package com.google.devtools.build.lib.vfs;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
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 java.io.File;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
+
/**
* This class tests the functionality of the PathFragment.
*/
@RunWith(JUnit4.class)
public class PathFragmentWindowsTest {
-
+
@Test
public void testWindowsSeparator() {
assertEquals("bar/baz", new PathFragment("bar\\baz").toString());
@@ -53,26 +52,6 @@
}
@Test
- public void testAbsolutePathsWithDrive() {
- PathFragment p1 = new PathFragment("/c");
- assertThat(p1.isAbsolute()).isTrue();
- assertThat(p1.getDriveLetter()).isEqualTo('C');
-
- PathFragment p2 = new PathFragment("/c/");
- assertThat(p2.isAbsolute()).isTrue();
- assertThat(p2.getDriveLetter()).isEqualTo('C');
-
- assertThat(p1).isEqualTo(p2);
-
- try {
- new PathFragment("/c:");
- Assert.fail("expected failure");
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).contains("Illegal path string \"/c:\"");
- }
- }
-
- @Test
public void testIsAbsoluteWindowsBackslash() {
assertTrue(new PathFragment(new File("C:\\blah")).isAbsolute());
assertTrue(new PathFragment(new File("C:\\")).isAbsolute());
@@ -104,75 +83,10 @@
assertEquals("C:/c/d", new PathFragment("a/b").getRelative("C:/c/d").getPathString());
}
- private void assertGetRelative(String path, String relative, PathFragment expected)
- throws Exception {
- PathFragment actual = new PathFragment(path).getRelative(relative);
- assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
- assertThat(actual).isEqualTo(expected);
- assertThat(actual.getDriveLetter()).isEqualTo(expected.getDriveLetter());
- assertThat(actual.hashCode()).isEqualTo(expected.hashCode());
- }
-
- private void assertRelativeTo(String path, String relativeTo, String... expectedPathSegments)
- throws Exception {
- PathFragment expected = new PathFragment('\0', false, expectedPathSegments);
- PathFragment actual = new PathFragment(path).relativeTo(relativeTo);
- assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
- assertThat(actual).isEqualTo(expected);
- assertThat(actual.getDriveLetter()).isEqualTo(expected.getDriveLetter());
- assertThat(actual.hashCode()).isEqualTo(expected.hashCode());
- }
-
- private void assertCantComputeRelativeTo(String path, String relativeTo) throws Exception {
- try {
- new PathFragment(path).relativeTo(relativeTo);
- Assert.fail("expected failure");
- } catch (Exception e) {
- assertThat(e.getMessage()).contains("is not beneath");
- }
- }
-
- private static PathFragment makePath(char drive, boolean absolute, String... segments) {
- return new PathFragment(drive, absolute, segments);
- }
-
@Test
- public void testGetRelativeMixed() throws Exception {
- assertGetRelative("a", "b", makePath('\0', false, "a", "b"));
- assertGetRelative("a", "/b", makePath('B', true));
- assertGetRelative("a", "E:b", makePath('\0', false, "a", "b"));
- assertGetRelative("a", "E:/b", makePath('E', true, "b"));
-
- assertGetRelative("/a", "b", makePath('A', true, "b"));
- assertGetRelative("/a", "/b", makePath('B', true));
- assertGetRelative("/a", "E:b", makePath('A', true, "b"));
- assertGetRelative("/a", "E:/b", makePath('E', true, "b"));
-
- assertGetRelative("D:a", "b", makePath('D', false, "a", "b"));
- assertGetRelative("D:a", "/b", makePath('B', true));
- assertGetRelative("D:a", "E:b", makePath('D', false, "a", "b"));
- assertGetRelative("D:a", "E:/b", makePath('E', true, "b"));
-
- assertGetRelative("D:/a", "b", makePath('D', true, "a", "b"));
- assertGetRelative("D:/a", "/b", makePath('B', true));
- assertGetRelative("D:/a", "E:b", makePath('D', true, "a", "b"));
- assertGetRelative("D:/a", "E:/b", makePath('E', true, "b"));
- }
-
- @Test
- public void testRelativeTo() throws Exception {
- assertRelativeTo("", "");
- assertCantComputeRelativeTo("", "a");
-
- assertRelativeTo("a", "", "a");
- assertRelativeTo("a", "a");
- assertCantComputeRelativeTo("a", "b");
- assertRelativeTo("a/b", "a", "b");
-
- assertRelativeTo("C:", "");
- assertRelativeTo("C:", "C:");
- assertCantComputeRelativeTo("C:/", "");
- assertRelativeTo("C:/", "C:/");
+ public void testGetRelativeMixed() {
+ assertEquals("/b", new PathFragment("C:/a").getRelative("/b").getPathString());
+ assertEquals("C:/b", new PathFragment("/a").getRelative("C:/b").getPathString());
}
@Test
@@ -183,12 +97,8 @@
// Tests after here test the canonicalization
private void assertRegular(String expected, String actual) {
- PathFragment exp = new PathFragment(expected);
- PathFragment act = new PathFragment(actual);
- assertThat(exp.getPathString()).isEqualTo(expected);
- assertThat(act.getPathString()).isEqualTo(expected);
- assertThat(act).isEqualTo(exp);
- assertThat(act.hashCode()).isEqualTo(exp.hashCode());
+ assertEquals(expected, new PathFragment(actual).getPathString()); // compare string forms
+ assertEquals(new PathFragment(expected), new PathFragment(actual)); // compare fragment forms
}
@Test
@@ -196,38 +106,9 @@
assertRegular("C:/", "C:/");
}
- private void assertAllEqual(PathFragment... ps) {
- assertThat(ps.length).isGreaterThan(1);
- for (int i = 1; i < ps.length; i++) {
- String msg = "comparing items 0 and " + i;
- assertWithMessage(msg + " for getPathString")
- .that(ps[i].getPathString())
- .isEqualTo(ps[0].getPathString());
- assertWithMessage(msg + " for equals").that(ps[0]).isEqualTo(ps[i]);
- assertWithMessage(msg + " for hashCode").that(ps[0].hashCode()).isEqualTo(ps[i].hashCode());
- }
- }
-
@Test
public void testEmptyRelativePathToEmptyPathWindows() {
- // Surprising but correct behavior: a PathFragment made of just a drive identifier (and not the
- // absolute path "C:/") is equal not only to the empty fragment, but (therefore) also to other
- // drive identifiers.
- // This makes sense if you consider that these are still empty paths, the drive letter adds no
- // information to the path itself.
- assertAllEqual(
- PathFragment.EMPTY_FRAGMENT,
- new PathFragment("C:"),
- new PathFragment("D:"),
- new PathFragment('\0', false, new String[0]),
- new PathFragment('C', false, new String[0]),
- new PathFragment('D', false, new String[0]));
- assertAllEqual(new PathFragment("C:/"), new PathFragment("/c"), new PathFragment("/c/"));
- assertAllEqual(new PathFragment("C:/foo"), new PathFragment("/c/foo"));
-
- assertThat(new PathFragment("C:/")).isNotEqualTo(new PathFragment("C:"));
- assertThat(new PathFragment("C:/").getPathString())
- .isNotEqualTo(new PathFragment("C:").getPathString());
+ assertRegular("C:", "C:");
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java
index 864a483..cb8b23b 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java
@@ -18,10 +18,8 @@
import static org.junit.Assert.assertSame;
import com.google.devtools.build.lib.util.BlazeClock;
-import com.google.devtools.build.lib.vfs.Path.PathFactory;
-import com.google.devtools.build.lib.vfs.WindowsFileSystem.WindowsPathFactory;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-import org.junit.Assert;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,16 +35,8 @@
@Before
public final void initializeFileSystem() throws Exception {
- filesystem =
- new InMemoryFileSystem(BlazeClock.instance()) {
- @Override
- protected PathFactory getPathFactory() {
- return WindowsPathFactory.INSTANCE;
- }
- };
- root = filesystem.getRootDirectory().getRelative("C:/");
- root.createDirectory();
-
+ filesystem = new InMemoryFileSystem(BlazeClock.instance());
+ root = filesystem.getRootDirectory();
Path first = root.getChild("first");
first.createDirectory();
}
@@ -108,38 +98,10 @@
}
@Test
- public void testAbsoluteUnixPathIsRelativeToWindowsUnixRoot() {
- Path actual = root.getRelative("/foo/bar");
- Path expected = root.getRelative("C:/fake/msys/foo/bar");
- assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
- assertThat(actual).isEqualTo(expected);
- }
-
- @Test
- public void testAbsoluteUnixPathReferringToDriveIsRecognized() {
- Path actual = root.getRelative("/c/foo");
- Path expected = root.getRelative("C:/foo");
- assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
- assertThat(actual).isEqualTo(expected);
-
- // "unexpected" is not a valid MSYS path, we should not be able to create it.
- try {
- root.getRelative("/c:");
- Assert.fail("expected failure");
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).contains("Illegal path string \"/c:\"");
- }
- }
-
- @Test
public void testStartsWithWorksOnWindows() {
assertStartsWithReturnsOnWindows(true, "C:/first/x", "C:/first/x/y");
assertStartsWithReturnsOnWindows(true, "c:/first/x", "C:/FIRST/X/Y");
assertStartsWithReturnsOnWindows(true, "C:/FIRST/X", "c:/first/x/y");
- assertStartsWithReturnsOnWindows(true, "/", "C:/");
- assertStartsWithReturnsOnWindows(false, "C:/", "/");
- assertStartsWithReturnsOnWindows(false, "C:/", "D:/");
- assertStartsWithReturnsOnWindows(false, "C:/", "D:/foo");
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java
index e2317bf..2be5055 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java
@@ -24,6 +24,12 @@
import com.google.common.testing.EqualsTester;
import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.vfs.util.FileSystems;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@@ -33,10 +39,6 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
/**
* Tests for {@link Path}.
@@ -267,7 +269,7 @@
@Test
public void testDerivedSegmentEquality() {
- Path absoluteSegment = unixFs.getRootDirectory();
+ Path absoluteSegment = new Path(null);
Path derivedNode = absoluteSegment.getChild("derivedSegment");
Path otherDerivedNode = absoluteSegment.getChild("derivedSegment");