aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/vfs/Path.java
diff options
context:
space:
mode:
authorGravatar Philipp Wollermann <philwo@google.com>2016-10-19 12:14:38 +0000
committerGravatar Philipp Wollermann <philwo@google.com>2016-10-19 13:19:18 +0000
commitd628b8005aa8e6489e407f105466e7c3df478776 (patch)
tree5ba50236351a163bba3a12f8ea138b1b9a287702 /src/main/java/com/google/devtools/build/lib/vfs/Path.java
parent4b4b9d33e50dfba8c4b2e798d3ede2c75fa3b256 (diff)
*** 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
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/vfs/Path.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/Path.java168
1 files changed, 62 insertions, 106 deletions
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 be95e3bc6e..dbec227a22 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.base.Predicate;
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.io.Serializable;
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 @@ import java.util.Objects;
@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 @@ public class Path implements Comparable<Path>, Serializable {
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 @@ public class Path implements Comparable<Path>, Serializable {
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 @@ public class Path implements Comparable<Path>, Serializable {
}
/**
- * 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 @@ public class Path implements Comparable<Path>, Serializable {
this.depth = this.parent.depth + 1;
}
this.hashCode = Objects.hash(parent, name);
- reinitializeAfterDeserialization();
}
/**
@@ -238,45 +211,7 @@ public class Path implements Comparable<Path>, Serializable {
}
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 @@ public class Path implements Comparable<Path>, Serializable {
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 @@ public class Path implements Comparable<Path>, Serializable {
}
/**
- * 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 @@ public class Path implements Comparable<Path>, Serializable {
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 @@ public class Path implements Comparable<Path>, Serializable {
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 @@ public class Path implements Comparable<Path>, Serializable {
* 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 @@ public class Path implements Comparable<Path>, Serializable {
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,21 +666,30 @@ public class Path implements Comparable<Path>, Serializable {
}
}
- 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;
- }
- /** Returns an absolute PathFragment representing this path. */
- public PathFragment asFragment() {
- return new PathFragment('\0', true, getSegments());
+ 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 a relative path fragment to this path, relative to {@code
* ancestorDirectory}. {@code ancestorDirectory} must be on the same
@@ -750,19 +708,17 @@ public class Path implements Comparable<Path>, Serializable {
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);
}
}