aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google
diff options
context:
space:
mode:
authorGravatar tomlu <tomlu@google.com>2018-02-08 15:32:00 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-08 15:34:11 -0800
commita729b9b4c3d7844a7d44934bf3365f92633c0a60 (patch)
tree6329f4baf5b0b83ea6e3bd577b78b8d49afea9f1 /src/main/java/com/google
parent0ab46f0dd95f735056add4dd8a90a76944b81d00 (diff)
Replace path implementation.
Path and PathFragment have been replaced with String-based implementations. They are pretty similar, but each method is dissimilar enough that I did not feel sharing code was appropriate. A summary of changes: PATH ==== * Subsumes LocalPath (deleted, its tests repurposed) * Use a simple string to back Path * Path instances are no longer interned; Reference equality will no longer work * Always normalized (same as before) * Some operations will now be slower, like instance compares (which were previously just a reference check) * Multiple identical paths will now consume more memory since they are not interned PATH FRAGMENT ============= * Use a simple string to back PathFragment * No more segment arrays with interned strings * Always normalized * Remove isNormalized * Replace some isNormalizied uses with containsUpLevelReferences() to check if path fragments try to escape their scope * To check if user input is normalized, supply static methods on PathFragment to validate the string before constructing a PathFragment * Because PathFragments are always normalized, we have to replace checks for literal "." from PathFragment#getPathString to PathFragment#getSafePathString. The latter returns "." for the empty string. * The previous implementation supported efficient segment semantics (segment count, iterating over segments). This is now expensive since we do longer have a segment array. ARTIFACT ======== * Remove Path instance. It is instead dynamically constructed on request. This is necessary to avoid this CL becoming a memory regression. RELNOTES: None PiperOrigin-RevId: 185062932
Diffstat (limited to 'src/main/java/com/google')
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/Actions.java55
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/Artifact.java64
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java44
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactAction.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java10
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/repository/StripPrefixedPath.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java12
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactory.java38
-rw-r--r--src/main/java/com/google/devtools/build/lib/cmdline/Label.java11
-rw-r--r--src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/Package.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/RelativePackageNameResolver.java1
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/TreeNodeRepository.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java34
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcIncLibrary.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java14
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppToolchainInfo.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/nativedeps/NativeDepsHelper.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/CompilationAttributes.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcLibrary.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/TargetMarkerFunction.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/BUILD10
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java74
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java39
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java715
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/OsPathPolicy.java144
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/Path.java792
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/PathCodec.java12
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java1097
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/PathFragmentSerializationProxy.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java82
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java64
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/UnixOsPathPolicy.java138
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/UnixPathFragment.java220
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/WindowsOsPathPolicy.java240
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java254
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java66
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java245
-rw-r--r--src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java4
58 files changed, 1628 insertions, 2993 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Actions.java b/src/main/java/com/google/devtools/build/lib/actions/Actions.java
index 91438261c7..9b49d5cc36 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Actions.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Actions.java
@@ -23,7 +23,9 @@ import com.google.common.escape.Escapers;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.vfs.OsPathPolicy;
import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -149,7 +151,7 @@ public final class Actions {
*/
public static Map<ActionAnalysisMetadata, ArtifactPrefixConflictException>
findArtifactPrefixConflicts(Map<Artifact, ActionAnalysisMetadata> generatingActions) {
- TreeMap<PathFragment, Artifact> artifactPathMap = new TreeMap();
+ TreeMap<PathFragment, Artifact> artifactPathMap = new TreeMap<>(comparatorForPrefixConflicts());
for (Artifact artifact : generatingActions.keySet()) {
artifactPathMap.put(artifact.getExecPath(), artifact);
}
@@ -159,18 +161,63 @@ public final class Actions {
}
/**
+ * Returns a comparator for use with {@link #findArtifactPrefixConflicts(ActionGraph, SortedMap)}.
+ */
+ public static Comparator<PathFragment> comparatorForPrefixConflicts() {
+ return PathFragmentPrefixComparator.INSTANCE;
+ }
+
+ private static class PathFragmentPrefixComparator implements Comparator<PathFragment> {
+ private static final PathFragmentPrefixComparator INSTANCE = new PathFragmentPrefixComparator();
+
+ @Override
+ public int compare(PathFragment lhs, PathFragment rhs) {
+ // We need to use the OS path policy in case the OS is case insensitive.
+ OsPathPolicy os = OsPathPolicy.getFilePathOs();
+ String str1 = lhs.getPathString();
+ String str2 = rhs.getPathString();
+ int len1 = str1.length();
+ int len2 = str2.length();
+ int n = Math.min(len1, len2);
+ for (int i = 0; i < n; ++i) {
+ char c1 = str1.charAt(i);
+ char c2 = str2.charAt(i);
+ int res = os.compare(c1, c2);
+ if (res != 0) {
+ if (c1 == PathFragment.SEPARATOR_CHAR) {
+ return -1;
+ } else if (c2 == PathFragment.SEPARATOR_CHAR) {
+ return 1;
+ }
+ return res;
+ }
+ }
+ return len1 - len2;
+ }
+ }
+
+ /**
* Finds Artifact prefix conflicts between generated artifacts. An artifact prefix conflict
* happens if one action generates an artifact whose path is a prefix of another artifact's path.
* Those two artifacts cannot exist simultaneously in the output tree.
*
* @param actionGraph the {@link ActionGraph} to query for artifact conflicts
- * @param artifactPathMap a map mapping generated artifacts to their exec paths
+ * @param artifactPathMap a map mapping generated artifacts to their exec paths. The map must be
+ * sorted using the comparator from {@link #comparatorForPrefixConflicts()}.
* @return A map between actions that generated the conflicting artifacts and their associated
* {@link ArtifactPrefixConflictException}.
*/
public static Map<ActionAnalysisMetadata, ArtifactPrefixConflictException>
- findArtifactPrefixConflicts(ActionGraph actionGraph,
- SortedMap<PathFragment, Artifact> artifactPathMap) {
+ findArtifactPrefixConflicts(
+ ActionGraph actionGraph, SortedMap<PathFragment, Artifact> artifactPathMap) {
+ // You must construct the sorted map using this comparator for the algorithm to work.
+ // The algorithm requires subdirectories to immediately follow parent directories,
+ // before any files in that directory.
+ // Example: "foo", "foo.obj", foo/bar" must be sorted
+ // "foo", "foo/bar", foo.obj"
+ Preconditions.checkArgument(
+ artifactPathMap.comparator() instanceof PathFragmentPrefixComparator,
+ "artifactPathMap must be sorted with PathFragmentPrefixComparator");
// No actions in graph -- currently happens only in tests. Special-cased because .next() call
// below is unconditional.
if (artifactPathMap.isEmpty()) {
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
index 395f5dfde8..964b05d5a6 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
@@ -176,7 +176,6 @@ public class Artifact
public static final Predicate<Artifact> MIDDLEMAN_FILTER = input -> !input.isMiddlemanArtifact();
private final int hashCode;
- private final Path path;
private final ArtifactRoot root;
private final PathFragment execPath;
private final PathFragment rootRelativePath;
@@ -203,35 +202,26 @@ public class Artifact
*/
@VisibleForTesting
@AutoCodec.Instantiator
- public Artifact(Path path, ArtifactRoot root, PathFragment execPath, ArtifactOwner owner) {
- if (root == null || !root.getRoot().contains(path)) {
- throw new IllegalArgumentException(root + ": illegal root for " + path
- + " (execPath: " + execPath + ")");
- }
- if (execPath == null
- || execPath.isAbsolute() != root.getRoot().isAbsolute()
- || !path.asFragment().endsWith(execPath)) {
- throw new IllegalArgumentException(execPath + ": illegal execPath for " + path
- + " (root: " + root + ")");
+ public Artifact(ArtifactRoot root, PathFragment execPath, ArtifactOwner owner) {
+ Preconditions.checkNotNull(root);
+ if (execPath == null || execPath.isAbsolute() != root.getRoot().isAbsolute()) {
+ throw new IllegalArgumentException(
+ execPath + ": illegal execPath for " + execPath + " (root: " + root + ")");
}
if (execPath.isEmpty()) {
throw new IllegalArgumentException(
"it is illegal to create an artifact with an empty execPath");
}
- this.hashCode = path.hashCode();
- this.path = path;
+ this.hashCode = execPath.hashCode();
this.root = root;
this.execPath = execPath;
- // These two lines establish the invariant that
- // execPath == rootRelativePath <=> execPath.equals(rootRelativePath)
- // This is important for isSourceArtifact.
- PathFragment rootRel = root.getRoot().relativize(path);
- if (!execPath.endsWith(rootRel)) {
- throw new IllegalArgumentException(execPath + ": illegal execPath doesn't end with "
- + rootRel + " at " + path + " with root " + root);
+ PathFragment rootExecPath = root.getExecPath();
+ if (rootExecPath.isEmpty()) {
+ this.rootRelativePath = execPath;
+ } else {
+ this.rootRelativePath = execPath.relativeTo(root.getExecPath());
}
- this.rootRelativePath = rootRel.equals(execPath) ? execPath : rootRel;
- this.owner = Preconditions.checkNotNull(owner, path);
+ this.owner = Preconditions.checkNotNull(owner);
}
/**
@@ -251,8 +241,8 @@ public class Artifact
* <pre>
*/
@VisibleForTesting
- public Artifact(Path path, ArtifactRoot root, PathFragment execPath) {
- this(path, root, execPath, ArtifactOwner.NullArtifactOwner.INSTANCE);
+ public Artifact(ArtifactRoot root, PathFragment execPath) {
+ this(root, execPath, ArtifactOwner.NullArtifactOwner.INSTANCE);
}
/**
@@ -262,7 +252,6 @@ public class Artifact
@VisibleForTesting // Only exists for testing.
public Artifact(Path path, ArtifactRoot root) {
this(
- path,
root,
root.getExecPath().getRelative(root.getRoot().relativize(path)),
ArtifactOwner.NullArtifactOwner.INSTANCE);
@@ -272,14 +261,13 @@ public class Artifact
@VisibleForTesting // Only exists for testing.
public Artifact(PathFragment rootRelativePath, ArtifactRoot root) {
this(
- root.getRoot().getRelative(rootRelativePath),
root,
root.getExecPath().getRelative(rootRelativePath),
ArtifactOwner.NullArtifactOwner.INSTANCE);
}
public final Path getPath() {
- return path;
+ return root.getRoot().getRelative(rootRelativePath);
}
public boolean hasParent() {
@@ -412,9 +400,8 @@ public class Artifact
structField = true,
doc = "Returns true if this is a source file, i.e. it is not generated."
)
- @SuppressWarnings("ReferenceEquality") // == is an optimization
public final boolean isSourceArtifact() {
- return execPath == rootRelativePath;
+ return root.isSourceRoot();
}
/**
@@ -479,12 +466,8 @@ public class Artifact
@VisibleForSerialization
public SpecialArtifact(
- Path path,
- ArtifactRoot root,
- PathFragment execPath,
- ArtifactOwner owner,
- SpecialArtifactType type) {
- super(path, root, execPath, owner);
+ ArtifactRoot root, PathFragment execPath, ArtifactOwner owner, SpecialArtifactType type) {
+ super(root, execPath, owner);
this.type = type;
}
@@ -559,17 +542,16 @@ public class Artifact
TreeFileArtifact(
SpecialArtifact parentTreeArtifact, PathFragment parentRelativePath, ArtifactOwner owner) {
super(
- parentTreeArtifact.getPath().getRelative(parentRelativePath),
parentTreeArtifact.getRoot(),
parentTreeArtifact.getExecPath().getRelative(parentRelativePath),
owner);
- Preconditions.checkState(
+ Preconditions.checkArgument(
parentTreeArtifact.isTreeArtifact(),
"The parent of TreeFileArtifact (parent-relative path: %s) is not a TreeArtifact: %s",
parentRelativePath,
parentTreeArtifact);
- Preconditions.checkState(
- parentRelativePath.isNormalized() && !parentRelativePath.isAbsolute(),
+ Preconditions.checkArgument(
+ !parentRelativePath.containsUplevelReferences() && !parentRelativePath.isAbsolute(),
"%s is not a proper normalized relative path",
parentRelativePath);
this.parentTreeArtifact = parentTreeArtifact;
@@ -668,7 +650,7 @@ public class Artifact
// We don't bother to check root in the equivalence relation, because we
// assume that no root is an ancestor of another one.
Artifact that = (Artifact) other;
- return Objects.equals(this.path, that.path);
+ return Objects.equals(this.execPath, that.execPath) && Objects.equals(this.root, that.root);
}
@Override
@@ -702,7 +684,7 @@ public class Artifact
return "[" + root + "]" + rootRelativePath;
} else {
// Derived Artifact: path and root are under execRoot
- PathFragment execRoot = trimTail(path.asFragment(), execPath);
+ PathFragment execRoot = trimTail(getPath().asFragment(), execPath);
return "[["
+ execRoot
+ "]"
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
index 7a0f88b7b4..d23637a0fb 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
@@ -149,8 +149,7 @@ public class ArtifactFactory implements ArtifactResolver {
Preconditions.checkArgument(
execPath.isAbsolute() == root.getRoot().isAbsolute(), "%s %s %s", execPath, root, owner);
Preconditions.checkNotNull(owner, "%s %s", execPath, root);
- execPath = execPath.normalize();
- return getArtifact(root.getRoot().getRelative(execPath), root, execPath, owner, null);
+ return getArtifact(root, execPath, owner, null);
}
@Override
@@ -162,7 +161,7 @@ public class ArtifactFactory implements ArtifactResolver {
Preconditions.checkArgument(!root.isSourceRoot());
Preconditions.checkArgument(
rootRelativePath.isAbsolute() == root.getRoot().isAbsolute(), rootRelativePath);
- Preconditions.checkArgument(rootRelativePath.isNormalized(), rootRelativePath);
+ Preconditions.checkArgument(!rootRelativePath.containsUplevelReferences(), rootRelativePath);
Preconditions.checkArgument(
root.getRoot().asPath().startsWith(execRootParent), "%s %s", root, execRootParent);
Preconditions.checkArgument(
@@ -182,8 +181,7 @@ public class ArtifactFactory implements ArtifactResolver {
public Artifact getDerivedArtifact(
PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
validatePath(rootRelativePath, root);
- Path path = root.getRoot().getRelative(rootRelativePath);
- return getArtifact(path, root, root.getExecPath().getRelative(rootRelativePath), owner, null);
+ return getArtifact(root, root.getExecPath().getRelative(rootRelativePath), owner, null);
}
/**
@@ -197,13 +195,8 @@ public class ArtifactFactory implements ArtifactResolver {
public Artifact getFilesetArtifact(
PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
validatePath(rootRelativePath, root);
- Path path = root.getRoot().getRelative(rootRelativePath);
return getArtifact(
- path,
- root,
- root.getExecPath().getRelative(rootRelativePath),
- owner,
- SpecialArtifactType.FILESET);
+ root, root.getExecPath().getRelative(rootRelativePath), owner, SpecialArtifactType.FILESET);
}
/**
@@ -216,21 +209,14 @@ public class ArtifactFactory implements ArtifactResolver {
public Artifact getTreeArtifact(
PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
validatePath(rootRelativePath, root);
- Path path = root.getRoot().getRelative(rootRelativePath);
return getArtifact(
- path,
- root,
- root.getExecPath().getRelative(rootRelativePath),
- owner,
- SpecialArtifactType.TREE);
+ root, root.getExecPath().getRelative(rootRelativePath), owner, SpecialArtifactType.TREE);
}
public Artifact getConstantMetadataArtifact(
PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
validatePath(rootRelativePath, root);
- Path path = root.getRoot().getRelative(rootRelativePath);
return getArtifact(
- path,
root,
root.getExecPath().getRelative(rootRelativePath),
owner,
@@ -242,7 +228,6 @@ public class ArtifactFactory implements ArtifactResolver {
* root</code> and <code>execPath</code> to the specified values.
*/
private synchronized Artifact getArtifact(
- Path path,
ArtifactRoot root,
PathFragment execPath,
ArtifactOwner owner,
@@ -251,7 +236,7 @@ public class ArtifactFactory implements ArtifactResolver {
Preconditions.checkNotNull(execPath);
if (!root.isSourceRoot()) {
- return createArtifact(path, root, execPath, owner, type);
+ return createArtifact(root, execPath, owner, type);
}
Artifact artifact = sourceArtifactCache.getArtifact(execPath);
@@ -260,23 +245,22 @@ public class ArtifactFactory implements ArtifactResolver {
// There really should be a safety net that makes it impossible to create two Artifacts
// with the same exec path but a different Owner, but we also need to reuse Artifacts from
// previous builds.
- artifact = createArtifact(path, root, execPath, owner, type);
+ artifact = createArtifact(root, execPath, owner, type);
sourceArtifactCache.putArtifact(execPath, artifact);
}
return artifact;
}
private Artifact createArtifact(
- Path path,
ArtifactRoot root,
PathFragment execPath,
ArtifactOwner owner,
@Nullable SpecialArtifactType type) {
- Preconditions.checkNotNull(owner, path);
+ Preconditions.checkNotNull(owner);
if (type == null) {
- return new Artifact(path, root, execPath, owner);
+ return new Artifact(root, execPath, owner);
} else {
- return new Artifact.SpecialArtifact(path, root, execPath, owner, type);
+ return new Artifact.SpecialArtifact(root, execPath, owner, type);
}
}
@@ -301,7 +285,6 @@ public class ArtifactFactory implements ArtifactResolver {
!relativePath.isEmpty(), "%s %s %s", relativePath, baseExecPath, baseRoot);
PathFragment execPath =
baseExecPath == null ? relativePath : baseExecPath.getRelative(relativePath);
- execPath = execPath.normalize();
if (execPath.containsUplevelReferences()) {
// Source exec paths cannot escape the source root.
return null;
@@ -371,17 +354,16 @@ public class ArtifactFactory implements ArtifactResolver {
ArrayList<PathFragment> unresolvedPaths = new ArrayList<>();
for (PathFragment execPath : execPaths) {
- PathFragment execPathNormalized = execPath.normalize();
- if (execPathNormalized.containsUplevelReferences()) {
+ if (execPath.containsUplevelReferences()) {
// Source exec paths cannot escape the source root.
result.put(execPath, null);
continue;
}
// First try a quick map lookup to see if the artifact already exists.
- Artifact a = sourceArtifactCache.getArtifactIfValid(execPathNormalized);
+ Artifact a = sourceArtifactCache.getArtifactIfValid(execPath);
if (a != null) {
result.put(execPath, a);
- } else if (isDerivedArtifact(execPathNormalized)) {
+ } else if (isDerivedArtifact(execPath)) {
// Don't create an artifact if it's derived.
result.put(execPath, null);
} else {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactAction.java
index fb5eafda48..fb9dc28823 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactAction.java
@@ -273,7 +273,7 @@ public final class PopulateTreeArtifactAction extends AbstractAction {
if (!line.isEmpty()) {
PathFragment path = PathFragment.create(line);
- if (!path.isNormalized() || path.isAbsolute()) {
+ if (!PathFragment.isNormalized(line) || path.isAbsolute()) {
throw new IllegalManifestFileException(
path + " is not a proper relative path");
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index d5aa190596..8652028507 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -1307,6 +1307,16 @@ public class BuildConfiguration implements BuildEvent {
return Objects.hash(isActionsEnabled(), fragments, buildOptions.getOptions());
}
+ public void describe(StringBuilder sb) {
+ sb.append(isActionsEnabled()).append('\n');
+ for (Fragment fragment : fragments.values()) {
+ sb.append(fragment.getClass().getName()).append('\n');
+ }
+ for (String s : buildOptions.toString().split(" ")) {
+ sb.append(s).append('\n');
+ }
+ }
+
@Override
public int hashCode() {
return hashCode;
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/StripPrefixedPath.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/StripPrefixedPath.java
index a957a00d02..ea3f887c21 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/StripPrefixedPath.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/StripPrefixedPath.java
@@ -64,10 +64,9 @@ public final class StripPrefixedPath {
* Normalize the path and, if it is absolute, make it relative (e.g., /foo/bar becomes foo/bar).
*/
private static PathFragment relativize(String path) {
- PathFragment entryPath = PathFragment.create(path).normalize();
+ PathFragment entryPath = PathFragment.create(path);
if (entryPath.isAbsolute()) {
- entryPath = PathFragment.create(entryPath.getSafePathString().substring(
- entryPath.windowsVolume().length() + 1));
+ entryPath = entryPath.toRelative();
}
return entryPath;
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java
index 71d26fcc55..0f32e752b0 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java
@@ -124,19 +124,17 @@ public class ZipDecompressor implements Decompressor {
// For symlinks, the "compressed data" is actually the target name.
int read = reader.getInputStream(entry).read(buffer);
Preconditions.checkState(read == buffer.length);
- PathFragment target = PathFragment.create(new String(buffer, Charset.defaultCharset()))
- .normalize();
- if (!target.isNormalized()) {
- PathFragment pointsTo =
- PathFragment.create(strippedRelativePath.getParentDirectory(), target).normalize();
- if (!pointsTo.isNormalized()) {
+ PathFragment target = PathFragment.create(new String(buffer, Charset.defaultCharset()));
+ if (target.containsUplevelReferences()) {
+ PathFragment pointsTo = strippedRelativePath.getParentDirectory().getRelative(target);
+ if (pointsTo.containsUplevelReferences()) {
throw new IOException("Zip entries cannot refer to files outside of their directory: "
+ reader.getFilename() + " has a symlink " + strippedRelativePath + " pointing to "
+ target);
}
}
if (target.isAbsolute()) {
- target = target.relativeTo(PathFragment.ROOT_DIR);
+ target = target.relativeTo("/");
target = destinationDirectory.getRelative(target).asFragment();
}
outputPath.createSymbolicLink(target);
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
index 50c65c1298..21f5ae5c55 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
@@ -236,8 +236,8 @@ public class BazelJavaSemantics implements JavaSemantics {
if (!isRunfilesEnabled) {
buffer.append("$(rlocation ");
PathFragment runfilePath =
- PathFragment.create(PathFragment.create(workspacePrefix), artifact.getRunfilesPath());
- buffer.append(runfilePath.normalize().getPathString());
+ PathFragment.create(workspacePrefix).getRelative(artifact.getRunfilesPath());
+ buffer.append(runfilePath.getPathString());
buffer.append(")");
} else {
buffer.append("${RUNPATH}");
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
index 0227f75ab2..8f9ede020b 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
@@ -107,8 +107,8 @@ public class BazelPythonSemantics implements PythonSemantics {
"ignoring invalid absolute path '" + importsAttr + "'");
continue;
}
- PathFragment importsPath = packageFragment.getRelative(importsAttr).normalize();
- if (!importsPath.isNormalized()) {
+ PathFragment importsPath = packageFragment.getRelative(importsAttr);
+ if (importsPath.containsUplevelReferences()) {
ruleContext.attributeError("imports",
"Path " + importsAttr + " references a path above the execution root");
}
@@ -239,10 +239,10 @@ public class BazelPythonSemantics implements PythonSemantics {
String zipRunfilesPath;
if (isUnderWorkspace(path)) {
// If the file is under workspace, add workspace name as prefix
- zipRunfilesPath = workspaceName.getRelative(path).normalize().toString();
+ zipRunfilesPath = workspaceName.getRelative(path).toString();
} else {
// If the file is in external package, strip "external"
- zipRunfilesPath = path.relativeTo(Label.EXTERNAL_PATH_PREFIX).normalize().toString();
+ zipRunfilesPath = path.relativeTo(Label.EXTERNAL_PATH_PREFIX).toString();
}
// We put the whole runfiles tree under the ZIP_RUNFILES_DIRECTORY_NAME directory, by doing this
// , we avoid the conflict between default workspace name "__main__" and __main__.py file.
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactory.java b/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactory.java
index d07f6a59ea..8c3921521e 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactory.java
@@ -22,6 +22,8 @@ import com.google.devtools.build.lib.buildeventstream.BuildEventTransport;
import com.google.devtools.build.lib.buildeventstream.PathConverter;
import com.google.devtools.build.lib.vfs.Path;
import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
/** Factory used to create a Set of BuildEventTransports from BuildEventStreamOptions. */
public enum BuildEventTransportFactory {
@@ -101,7 +103,41 @@ public enum BuildEventTransportFactory {
private static class NullPathConverter implements PathConverter {
@Override
public String apply(Path path) {
- return path.toURI().toString();
+ return pathToUriString(path.getPathString());
+ }
+ }
+
+ /**
+ * Returns the path encoded as an {@link URI}.
+ *
+ * <p>This concrete implementation returns URIs with "file" as the scheme. For Example: - On Unix
+ * the path "/tmp/foo bar.txt" will be encoded as "file:///tmp/foo%20bar.txt". - On Windows the
+ * path "C:\Temp\Foo Bar.txt" will be encoded as "file:///C:/Temp/Foo%20Bar.txt"
+ *
+ * <p>Implementors extending this class for special filesystems will likely need to override this
+ * method.
+ *
+ * @throws URISyntaxException if the URI cannot be constructed.
+ */
+ static String pathToUriString(String path) {
+ if (!path.startsWith("/")) {
+ // On Windows URI's need to start with a '/'. i.e. C:\Foo\Bar would be file:///C:/Foo/Bar
+ path = "/" + path;
+ }
+ try {
+ return new URI(
+ "file",
+ // Needs to be "" instead of null, so that toString() will append "//" after the
+ // scheme.
+ // We need this for backwards compatibility reasons as some consumers of the BEP are
+ // broken.
+ "",
+ path,
+ null,
+ null)
+ .toString();
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException(e);
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
index 2fbb2a4217..054648dd18 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
@@ -345,8 +345,17 @@ public final class Label
return packageIdentifier.getPackageFragment();
}
- /** Returns the label as a path fragment, using the package and the label name. */
+ /**
+ * Returns the label as a path fragment, using the package and the label name.
+ *
+ * <p>Make sure that the label refers to a file. Non-file labels do not necessarily have
+ * PathFragment representations.
+ */
public PathFragment toPathFragment() {
+ // PathFragments are normalized, so if we do this on a non-file target named '.'
+ // then the package would be returned. Detect this and throw.
+ // A target named '.' can never refer to a file.
+ Preconditions.checkArgument(!name.equals("."));
return packageIdentifier.getPackageFragment().getRelative(name);
}
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
index ac62a3ae96..2067b641f0 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/PackageIdentifier.java
@@ -87,7 +87,7 @@ public final class PackageIdentifier
// TODO(ulfjack): Remove this when kchodorow@'s exec root rearrangement has been rolled out.
RepositoryName repository = RepositoryName.create("@" + tofind.getSegment(1));
return PackageIdentifier.create(repository, tofind.subFragment(2));
- } else if (!tofind.normalize().isNormalized()) {
+ } else if (tofind.containsUplevelReferences()) {
RepositoryName repository = RepositoryName.create("@" + tofind.getSegment(1));
return PackageIdentifier.create(repository, tofind.subFragment(2));
} else {
@@ -112,9 +112,6 @@ public final class PackageIdentifier
private PackageIdentifier(RepositoryName repository, PathFragment pkgName) {
this.repository = Preconditions.checkNotNull(repository);
- if (!pkgName.isNormalized()) {
- pkgName = pkgName.normalize();
- }
this.pkgName = Canonicalizer.fragments().intern(Preconditions.checkNotNull(pkgName));
this.hashCode = Objects.hash(repository, pkgName);
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java
index 4bbd3bfe7c..8a1f02a63d 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Package.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java
@@ -536,7 +536,7 @@ public class Package {
// stat(2) is executed.
Path filename = getPackageDirectory().getRelative(targetName);
String suffix;
- if (!PathFragment.create(targetName).isNormalized()) {
+ if (!PathFragment.isNormalized(targetName)) {
// Don't check for file existence in this case because the error message
// would be confusing and wrong. If the targetName is "foo/bar/.", and
// there is a directory "foo/bar", it doesn't mean that "//pkg:foo/bar/."
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RelativePackageNameResolver.java b/src/main/java/com/google/devtools/build/lib/packages/RelativePackageNameResolver.java
index 871080702b..bc314a4f1a 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RelativePackageNameResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RelativePackageNameResolver.java
@@ -72,7 +72,6 @@ public class RelativePackageNameResolver {
}
PathFragment result = isAbsolute ? relative : offset.getRelative(relative);
- result = result.normalize();
if (result.containsUplevelReferences()) {
throw new InvalidPackageNameException(
PackageIdentifier.createInMainRepo(pkg),
diff --git a/src/main/java/com/google/devtools/build/lib/remote/TreeNodeRepository.java b/src/main/java/com/google/devtools/build/lib/remote/TreeNodeRepository.java
index 0e57b17e44..8a1dfe9fa4 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/TreeNodeRepository.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/TreeNodeRepository.java
@@ -110,8 +110,8 @@ public final class TreeNodeRepository extends TreeTraverser<TreeNodeRepository.T
return false;
}
ChildEntry other = (ChildEntry) o;
- // Pointer comparisons only, because both the Path segments and the TreeNodes are interned.
- return other.segment == segment && other.child == child;
+ // Pointer comparison for the TreeNode as it is interned
+ return other.segment.equals(segment) && other.child == child;
}
@Override
@@ -322,7 +322,7 @@ public final class TreeNodeRepository extends TreeTraverser<TreeNodeRepository.T
String segment = segments.get(inputsStart).get(segmentIndex);
for (int inputIndex = inputsStart; inputIndex < inputsEnd; ++inputIndex) {
if (inputIndex + 1 == inputsEnd
- || segment != segments.get(inputIndex + 1).get(segmentIndex)) {
+ || !segment.equals(segments.get(inputIndex + 1).get(segmentIndex))) {
entries.add(
new TreeNode.ChildEntry(
segment,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
index fa2c96e789..e9ae312128 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
@@ -545,8 +545,8 @@ public final class CcCommon {
"ignoring invalid absolute path '" + includesAttr + "'");
continue;
}
- PathFragment includesPath = packageFragment.getRelative(includesAttr).normalize();
- if (!includesPath.isNormalized()) {
+ PathFragment includesPath = packageFragment.getRelative(includesAttr);
+ if (includesPath.containsUplevelReferences()) {
ruleContext.attributeError("includes",
"Path references a path above the execution root.");
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
index d6464d1340..a7c1615d92 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
@@ -757,15 +757,25 @@ public final class CcCompilationHelper {
null);
}
- PathFragment prefix =
- ruleContext.attributes().isAttributeValueExplicitlySpecified("include_prefix")
- ? PathFragment.create(ruleContext.attributes().get("include_prefix", Type.STRING))
- : null;
+ PathFragment prefix = null;
+ if (ruleContext.attributes().isAttributeValueExplicitlySpecified("include_prefix")) {
+ String prefixAttr = ruleContext.attributes().get("include_prefix", Type.STRING);
+ prefix = PathFragment.create(prefixAttr);
+ if (PathFragment.containsUplevelReferences(prefixAttr)) {
+ ruleContext.attributeError("include_prefix", "should not contain uplevel references");
+ }
+ if (prefix.isAbsolute()) {
+ ruleContext.attributeError("include_prefix", "should be a relative path");
+ }
+ }
PathFragment stripPrefix;
if (ruleContext.attributes().isAttributeValueExplicitlySpecified("strip_include_prefix")) {
- stripPrefix =
- PathFragment.create(ruleContext.attributes().get("strip_include_prefix", Type.STRING));
+ String stripPrefixAttr = ruleContext.attributes().get("strip_include_prefix", Type.STRING);
+ if (PathFragment.containsUplevelReferences(stripPrefixAttr)) {
+ ruleContext.attributeError("strip_include_prefix", "should not contain uplevel references");
+ }
+ stripPrefix = PathFragment.create(stripPrefixAttr);
if (stripPrefix.isAbsolute()) {
stripPrefix =
ruleContext
@@ -791,18 +801,6 @@ public final class CcCompilationHelper {
null);
}
- if (stripPrefix.containsUplevelReferences()) {
- ruleContext.attributeError("strip_include_prefix", "should not contain uplevel references");
- }
-
- if (prefix != null && prefix.containsUplevelReferences()) {
- ruleContext.attributeError("include_prefix", "should not contain uplevel references");
- }
-
- if (prefix != null && prefix.isAbsolute()) {
- ruleContext.attributeError("include_prefix", "should be a relative path");
- }
-
if (ruleContext.hasErrors()) {
return new PublicHeaders(ImmutableList.<Artifact>of(), ImmutableList.<Artifact>of(), null);
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcIncLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcIncLibrary.java
index a844ef6375..9101e9b768 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcIncLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcIncLibrary.java
@@ -98,13 +98,12 @@ public abstract class CcIncLibrary implements RuleConfiguredTargetFactory {
// For every source artifact, we compute a virtual artifact that is below the include directory.
// These are used for include checking.
- PathFragment prefixFragment = packageFragment.getRelative(expandedIncSymlinkAttr);
- if (!prefixFragment.isNormalized()) {
+ if (!PathFragment.isNormalized(expandedIncSymlinkAttr)) {
ruleContext.attributeWarning("prefix", "should not contain '.' or '..' elements");
}
+ PathFragment prefixFragment = packageFragment.getRelative(expandedIncSymlinkAttr);
ImmutableSortedMap.Builder<Artifact, Artifact> virtualArtifactMapBuilder =
ImmutableSortedMap.orderedBy(Artifact.EXEC_PATH_COMPARATOR);
- prefixFragment = prefixFragment.normalize();
ImmutableList<Artifact> hdrs = ruleContext.getPrerequisiteArtifacts("hdrs", Mode.TARGET).list();
for (Artifact src : hdrs) {
// All declared header files must start with package/targetPrefix.
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
index 122fc14914..5f848b4a29 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchain.java
@@ -169,10 +169,10 @@ public class CcToolchain implements RuleConfiguredTargetFactory {
}
}
- PathFragment path = PathFragment.create(pathString);
- if (!path.isNormalized()) {
+ if (!PathFragment.isNormalized(pathString)) {
throw new InvalidConfigurationException("The include path '" + s + "' is not normalized.");
}
+ PathFragment path = PathFragment.create(pathString);
return pathPrefix.getRelative(path);
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java
index da8827d6ac..ea12fadece 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompilationContext.java
@@ -512,7 +512,7 @@ public final class CppCompilationContext implements TransitiveInfoProvider {
* absolute. Before it is stored, the include directory is normalized.
*/
public Builder addIncludeDir(PathFragment includeDir) {
- includeDirs.add(includeDir.normalize());
+ includeDirs.add(includeDir);
return this;
}
@@ -536,7 +536,7 @@ public final class CppCompilationContext implements TransitiveInfoProvider {
* is stored, the include directory is normalized.
*/
public Builder addQuoteIncludeDir(PathFragment quoteIncludeDir) {
- quoteIncludeDirs.add(quoteIncludeDir.normalize());
+ quoteIncludeDirs.add(quoteIncludeDir);
return this;
}
@@ -547,7 +547,7 @@ public final class CppCompilationContext implements TransitiveInfoProvider {
* is stored, the include directory is normalized.
*/
public Builder addSystemIncludeDir(PathFragment systemIncludeDir) {
- systemIncludeDirs.add(systemIncludeDir.normalize());
+ systemIncludeDirs.add(systemIncludeDir);
return this;
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
index 247d3ef91f..e462a03822 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
@@ -488,8 +488,7 @@ public class CppCompileActionBuilder {
if (includePath.startsWith(Label.EXTERNAL_PATH_PREFIX)) {
includePath = includePath.relativeTo(Label.EXTERNAL_PATH_PREFIX);
}
- if (includePath.isAbsolute()
- || !PathFragment.EMPTY_FRAGMENT.getRelative(includePath).normalize().isNormalized()) {
+ if (includePath.isAbsolute() || includePath.containsUplevelReferences()) {
errorReporter.accept(
String.format(
"The include path '%s' references a path outside of the execution root.",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
index 058f34db51..107a4cce77 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -1381,15 +1381,15 @@ public final class CppConfiguration extends BuildConfiguration.Fragment {
}
public static PathFragment computeDefaultSysroot(CToolchain toolchain) {
- PathFragment defaultSysroot =
- toolchain.getBuiltinSysroot().length() == 0
- ? null
- : PathFragment.create(toolchain.getBuiltinSysroot());
- if ((defaultSysroot != null) && !defaultSysroot.isNormalized()) {
+ String builtInSysroot = toolchain.getBuiltinSysroot();
+ if (builtInSysroot.isEmpty()) {
+ return null;
+ }
+ if (!PathFragment.isNormalized(builtInSysroot)) {
throw new IllegalArgumentException(
- "The built-in sysroot '" + defaultSysroot + "' is not normalized.");
+ "The built-in sysroot '" + builtInSysroot + "' is not normalized.");
}
- return defaultSysroot;
+ return PathFragment.create(builtInSysroot);
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppToolchainInfo.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppToolchainInfo.java
index 135b70be3b..91f95020b8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppToolchainInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppToolchainInfo.java
@@ -794,11 +794,11 @@ public final class CppToolchainInfo {
CToolchain toolchain, PathFragment crosstoolTopPathFragment) {
Map<String, PathFragment> toolPathsCollector = Maps.newHashMap();
for (CrosstoolConfig.ToolPath tool : toolchain.getToolPathList()) {
- PathFragment path = PathFragment.create(tool.getPath());
- if (!path.isNormalized()) {
- throw new IllegalArgumentException(
- "The include path '" + tool.getPath() + "' is not normalized.");
+ String pathStr = tool.getPath();
+ if (!PathFragment.isNormalized(pathStr)) {
+ throw new IllegalArgumentException("The include path '" + pathStr + "' is not normalized.");
}
+ PathFragment path = PathFragment.create(pathStr);
toolPathsCollector.put(tool.getName(), crosstoolTopPathFragment.getRelative(path));
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
index 458ac578d1..b2394ca51d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
@@ -505,9 +505,9 @@ public class JavaCommon {
if (!javaExecutable.isAbsolute()) {
javaExecutable =
- PathFragment.create(PathFragment.create(ruleContext.getWorkspaceName()), javaExecutable);
+ PathFragment.create(ruleContext.getWorkspaceName()).getRelative(javaExecutable);
}
- return javaExecutable.normalize().getPathString();
+ return javaExecutable.getPathString();
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/rules/nativedeps/NativeDepsHelper.java b/src/main/java/com/google/devtools/build/lib/rules/nativedeps/NativeDepsHelper.java
index 7f6437ed24..e15c7485a3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/nativedeps/NativeDepsHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/nativedeps/NativeDepsHelper.java
@@ -312,7 +312,7 @@ public abstract class NativeDepsHelper {
PathFragment libParentDir =
relativePath.replaceName(lib.getExecPath().getParentDirectory().getBaseName());
String libName = lib.getExecPath().getBaseName();
- return PathFragment.create(libParentDir, PathFragment.create(libName));
+ return libParentDir.getRelative(libName);
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java
index 2e84f6f491..e73c66572a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleStubBinary.java
@@ -188,8 +188,7 @@ public class AppleStubBinary implements RuleConfiguredTargetFactory {
makeVariableContext.validatePathRoot(pathString);
- PathFragment pathFragment = PathFragment.create(pathString);
- if (!pathFragment.isNormalized()) {
+ if (!PathFragment.isNormalized(pathString)) {
throw ruleContext.throwWithAttributeError(
AppleStubBinaryRule.XCENV_BASED_PATH_ATTR, PATH_NOT_NORMALIZED_ERROR);
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationAttributes.java b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationAttributes.java
index 39c28b750a..f3c79b270b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationAttributes.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/CompilationAttributes.java
@@ -403,7 +403,7 @@ final class CompilationAttributes {
Iterables.filter(includes(), Predicates.not(PathFragment::isAbsolute));
for (PathFragment include : relativeIncludes) {
for (PathFragment rootFragment : rootFragments) {
- paths.add(rootFragment.getRelative(include).normalize());
+ paths.add(rootFragment.getRelative(include));
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcLibrary.java
index 27c1f67b5e..61bbb37431 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcLibrary.java
@@ -130,7 +130,7 @@ public class J2ObjcLibrary implements RuleConfiguredTargetFactory {
// We add another header search path with gen root if we have generated sources to translate.
for (Artifact sourceToTranslate : sourcesToTranslate) {
if (!sourceToTranslate.isSourceArtifact()) {
- headerSearchPaths.add(PathFragment.create(objcFileRootExecPath, genRoot));
+ headerSearchPaths.add(objcFileRootExecPath.getRelative(genRoot));
return headerSearchPaths.build();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java
index 1a7e1e28f6..b3c6d7e93c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ProtobufSupport.java
@@ -515,8 +515,7 @@ final class ProtobufSupport {
// of dependers.
PathFragment rootRelativeOutputDir = ruleContext.getUniqueDirectory(UNIQUE_DIRECTORY_NAME);
- return PathFragment.create(
- buildConfiguration.getBinDirectory().getExecPath(), rootRelativeOutputDir);
+ return buildConfiguration.getBinDirectory().getExecPath().getRelative(rootRelativeOutputDir);
}
private Iterable<Artifact> getGeneratedProtoOutputs(
@@ -526,9 +525,8 @@ final class ProtobufSupport {
String protoFileName = FileSystemUtils.removeExtension(protoFile.getFilename());
String generatedOutputName = attributes.getGeneratedProtoFilename(protoFileName, true);
- PathFragment generatedFilePath = PathFragment.create(
- protoFile.getRootRelativePath().getParentDirectory(),
- PathFragment.create(generatedOutputName));
+ PathFragment generatedFilePath =
+ protoFile.getRootRelativePath().getParentDirectory().getRelative(generatedOutputName);
PathFragment outputFile = FileSystemUtils.appendExtension(generatedFilePath, extension);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
index c4b1e5f301..87cddd7d3b 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
@@ -33,7 +33,6 @@ import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor.RuleStat;
import com.google.devtools.build.lib.util.ExitCode;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.common.options.EnumConverter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
@@ -207,8 +206,9 @@ public class DumpCommand implements BlazeCommand {
}
if (dumpOptions.dumpVfs) {
+ // TODO(b/72498697): Remove this flag
out.println("Filesystem cache");
- FileSystemUtils.dump(env.getOutputBase().getFileSystem(), out);
+ out.println("dump --vfs is no longer meaningful");
out.println();
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java
index 6d6d2eb3f7..4c699ab635 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/LocalRepositoryLookupFunction.java
@@ -217,8 +217,7 @@ public class LocalRepositoryLookupFunction implements SkyFunction {
String path = (String) rule.getAttributeContainer().getAttr("path");
return Optional.of(
LocalRepositoryLookupValue.success(
- RepositoryName.create("@" + rule.getName()),
- PathFragment.create(path).normalize()));
+ RepositoryName.create("@" + rule.getName()), PathFragment.create(path)));
} catch (LabelSyntaxException e) {
// This shouldn't be possible if the rule name is valid, and it should already have been
// validated.
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index 6da94fd90b..6114f1d933 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -814,7 +814,13 @@ public class PackageFunction implements SkyFunction {
Set<SkyKey> containingPkgLookupKeys = Sets.newHashSet();
Map<Target, SkyKey> targetToKey = new HashMap<>();
for (Target target : pkgBuilder.getTargets()) {
- PathFragment dir = target.getLabel().toPathFragment().getParentDirectory();
+ PathFragment dir = getContainingDirectory(target.getLabel());
+ if (dir == null) {
+ throw new IllegalStateException(
+ String.format(
+ "Null pkg for label %s as path fragment %s in pkg %s",
+ target.getLabel(), target.getLabel().getPackageFragment(), pkgId));
+ }
PackageIdentifier dirId = PackageIdentifier.create(pkgId.getRepository(), dir);
if (dir.equals(pkgId.getPackageFragment())) {
continue;
@@ -825,7 +831,7 @@ public class PackageFunction implements SkyFunction {
}
Map<Label, SkyKey> subincludeToKey = new HashMap<>();
for (Label subincludeLabel : pkgBuilder.getSubincludeLabels()) {
- PathFragment dir = subincludeLabel.toPathFragment().getParentDirectory();
+ PathFragment dir = getContainingDirectory(subincludeLabel);
PackageIdentifier dirId = PackageIdentifier.create(pkgId.getRepository(), dir);
if (dir.equals(pkgId.getPackageFragment())) {
continue;
@@ -870,6 +876,12 @@ public class PackageFunction implements SkyFunction {
}
}
+ private static PathFragment getContainingDirectory(Label label) {
+ PathFragment pkg = label.getPackageFragment();
+ String name = label.getName();
+ return name.equals(".") ? pkg : pkg.getRelative(name).getParentDirectory();
+ }
+
@Nullable
private static ContainingPackageLookupValue
getContainingPkgLookupValueAndPropagateInconsistentFilesystemExceptions(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
index 97d257c84a..ff12e07091 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
@@ -266,7 +266,8 @@ public final class SkyframeActionExecutor implements ActionExecutionContextFacto
ConcurrentMap<ActionAnalysisMetadata, ConflictException> badActionMap)
throws InterruptedException {
MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
- ConcurrentNavigableMap<PathFragment, Artifact> artifactPathMap = new ConcurrentSkipListMap<>();
+ ConcurrentNavigableMap<PathFragment, Artifact> artifactPathMap =
+ new ConcurrentSkipListMap<>(Actions.comparatorForPrefixConflicts());
// Action graph construction is CPU-bound.
int numJobs = Runtime.getRuntime().availableProcessors();
// No great reason for expecting 5000 action lookup values, but not worth counting size of
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetMarkerFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetMarkerFunction.java
index 2aeba41d09..1758fb3f1f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetMarkerFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetMarkerFunction.java
@@ -57,7 +57,7 @@ public final class TargetMarkerFunction implements SkyFunction {
if (label.getName().contains("/")) {
// This target is in a subdirectory, therefore it could potentially be invalidated by
// a new BUILD file appearing in the hierarchy.
- PathFragment containingDirectory = label.toPathFragment().getParentDirectory();
+ PathFragment containingDirectory = getContainingDirectory(label);
ContainingPackageLookupValue containingPackageLookupValue;
try {
PackageIdentifier newPkgId = PackageIdentifier.create(
@@ -109,6 +109,12 @@ public final class TargetMarkerFunction implements SkyFunction {
return TargetMarkerValue.TARGET_MARKER_INSTANCE;
}
+ private static PathFragment getContainingDirectory(Label label) {
+ PathFragment pkg = label.getPackageFragment();
+ String name = label.getName();
+ return name.equals(".") ? pkg : pkg.getRelative(name).getParentDirectory();
+ }
+
@Override
public String extractTag(SkyKey skyKey) {
return Label.print((Label) skyKey.argument());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
index 783899ab45..f54522f8f1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
@@ -182,8 +182,7 @@ class TreeArtifactValue implements SkyValue {
PathFragment pathToExplode, ImmutableSet.Builder<PathFragment> valuesBuilder)
throws IOException {
for (Path subpath : treeArtifact.getPath().getRelative(pathToExplode).getDirectoryEntries()) {
- PathFragment canonicalSubpathFragment =
- pathToExplode.getChild(subpath.getBaseName()).normalize();
+ PathFragment canonicalSubpathFragment = pathToExplode.getChild(subpath.getBaseName());
if (subpath.isDirectory()) {
explodeDirectory(treeArtifact,
pathToExplode.getChild(subpath.getBaseName()), valuesBuilder);
@@ -202,7 +201,7 @@ class TreeArtifactValue implements SkyValue {
// TreeArtifact into a/b/outside_dir.
PathFragment intermediatePath = canonicalSubpathFragment.getParentDirectory();
for (String pathSegment : linkTarget.getSegments()) {
- intermediatePath = intermediatePath.getRelative(pathSegment).normalize();
+ intermediatePath = intermediatePath.getRelative(pathSegment);
if (intermediatePath.containsUplevelReferences()) {
String errorMessage = String.format(
"A TreeArtifact may not contain relative symlinks whose target paths traverse "
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java
index e8c9ad2b54..26fc565930 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkImports.java
@@ -173,7 +173,7 @@ public class SkylarkImports {
@Override
public PathFragment asPathFragment() {
- return PathFragment.create(PathFragment.ROOT_DIR).getRelative(importLabel.toPathFragment());
+ return PathFragment.create("/").getRelative(importLabel.toPathFragment());
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java
index 033a6a692d..d5a74ebaf6 100644
--- a/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java
@@ -326,7 +326,7 @@ public class UnixFileSystem extends AbstractFileSystemWithCustomStat {
@Override
protected void createSymbolicLink(Path linkPath, PathFragment targetFragment)
throws IOException {
- NativePosixFiles.symlink(targetFragment.toString(), linkPath.toString());
+ NativePosixFiles.symlink(targetFragment.getSafePathString(), linkPath.toString());
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/BUILD b/src/main/java/com/google/devtools/build/lib/vfs/BUILD
index f7a57bee03..b829858847 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/vfs/BUILD
@@ -10,8 +10,9 @@ PATH_FRAGMENT_SOURCES = [
"Canonicalizer.java",
"PathFragment.java",
"PathFragmentSerializationProxy.java",
- "UnixPathFragment.java",
- "WindowsPathFragment.java",
+ "OsPathPolicy.java",
+ "UnixOsPathPolicy.java",
+ "WindowsOsPathPolicy.java",
]
java_library(
@@ -25,6 +26,8 @@ java_library(
"//src/main/java/com/google/devtools/build/lib:skylarkinterface",
"//src/main/java/com/google/devtools/build/lib/concurrent",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
+ "//src/main/java/com/google/devtools/build/lib/windows:windows_short_path",
+ "//src/main/java/com/google/devtools/build/lib/windows/jni",
"//third_party:guava",
"//third_party/protobuf:protobuf_java",
],
@@ -47,7 +50,6 @@ java_library(
":pathfragment",
"//src/main/java/com/google/devtools/build/lib:base-util",
"//src/main/java/com/google/devtools/build/lib:filetype",
- "//src/main/java/com/google/devtools/build/lib:os_util",
"//src/main/java/com/google/devtools/build/lib:skylarkinterface",
"//src/main/java/com/google/devtools/build/lib/clock",
"//src/main/java/com/google/devtools/build/lib/concurrent",
@@ -55,8 +57,6 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/shell",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
- "//src/main/java/com/google/devtools/build/lib/windows:windows_short_path",
- "//src/main/java/com/google/devtools/build/lib/windows/jni",
"//src/main/java/com/google/devtools/common/options",
"//third_party:guava",
"//third_party:jsr305",
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 aaaacd93d2..38da81c6d9 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
@@ -23,7 +23,6 @@ import com.google.common.io.ByteSource;
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 com.google.devtools.common.options.EnumConverter;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -84,25 +83,6 @@ public abstract class FileSystem {
return digestFunction;
}
- 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);
- }
-
- @Override
- public Path getCachedChildPathInternal(Path path, String childName) {
- return Path.getCachedChildPathInternal(path, childName, /*cacheable=*/ true);
- }
- };
- }
-
/**
* An exception thrown when attempting to resolve an ordinary file as a symlink.
*/
@@ -112,53 +92,23 @@ public abstract class FileSystem {
}
}
- /** Lazy-initialized on first access, always use {@link FileSystem#getRootDirectory} */
- private volatile Path rootPath;
-
private final Root absoluteRoot = new Root.AbsoluteRoot(this);
- /** Returns filesystem-specific path factory. */
- protected PathFactory getPathFactory() {
- return UnixPathFactory.INSTANCE;
- }
-
- /**
- * Returns an absolute path instance, given an absolute path name, without
- * double slashes, .., or . segments. While this method will normalize the
- * path representation by creating a structured/parsed representation, it will
- * not cause any IO. (e.g., it will not resolve symbolic links if it's a Unix
- * file system.
- */
- public Path getPath(String pathName) {
- return getPath(PathFragment.create(pathName));
- }
-
/**
- * Returns an absolute path instance, given an absolute path name, without
- * double slashes, .., or . segments. While this method will normalize the
- * path representation by creating a structured/parsed representation, it will
- * not cause any IO. (e.g., it will not resolve symbolic links if it's a Unix
- * file system.
+ * Returns an absolute path instance, given an absolute path name, without double slashes, .., or
+ * . segments. While this method will normalize the path representation by creating a
+ * structured/parsed representation, it will not cause any IO. (e.g., it will not resolve symbolic
+ * links if it's a Unix file system.
*/
- public Path getPath(PathFragment pathName) {
- if (!pathName.isAbsolute()) {
- throw new IllegalArgumentException(pathName.getPathString() + " (not an absolute path)");
- }
- return getRootDirectory().getRelative(pathName);
+ public Path getPath(String path) {
+ return Path.create(path, this);
}
- /**
- * Returns a path representing the root directory of the current file system.
- */
- public final Path getRootDirectory() {
- if (rootPath == null) {
- synchronized (this) {
- if (rootPath == null) {
- rootPath = getPathFactory().createRootPath(this);
- }
- }
- }
- return rootPath;
+ /** Returns an absolute path instance, given an absolute path fragment. */
+ public Path getPath(PathFragment pathFragment) {
+ Preconditions.checkArgument(pathFragment.isAbsolute());
+ return Path.createAlreadyNormalized(
+ pathFragment.getPathString(), pathFragment.getDriveStrLength(), this);
}
final Root getAbsoluteRoot() {
@@ -401,7 +351,7 @@ public abstract class FileSystem {
throw new IOException(naive + " (Too many levels of symbolic links)");
}
if (linkTarget.isAbsolute()) {
- dir = getRootDirectory();
+ dir = getPath(linkTarget.getDriveStr());
}
for (String name : linkTarget.segments()) {
if (name.equals(".") || name.isEmpty()) {
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 adeb9c847c..b7fd8d2a43 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
@@ -25,7 +25,6 @@ import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -90,25 +89,13 @@ public class FileSystemUtils {
return a;
}
- /**
- * Returns a path fragment from a given from-dir to a given to-path. May be
- * either a short relative path "foo/bar", an up'n'over relative path
- * "../../foo/bar" or an absolute path.
- */
- public static PathFragment relativePath(Path fromDir, Path to) {
- if (to.getFileSystem() != fromDir.getFileSystem()) {
- throw new IllegalArgumentException("fromDir and to must be on the same FileSystem");
- }
-
- return relativePath(fromDir.asFragment(), to.asFragment());
- }
/**
* Returns a path fragment from a given from-dir to a given to-path.
*/
public static PathFragment relativePath(PathFragment fromDir, PathFragment to) {
if (to.equals(fromDir)) {
- return PathFragment.create("."); // same dir, just return '.'
+ return PathFragment.EMPTY_FRAGMENT;
}
if (to.startsWith(fromDir)) {
return to.relativeTo(fromDir); // easy case--it's a descendant
@@ -883,30 +870,6 @@ public class FileSystemUtils {
}
/**
- * Dumps diagnostic information about the specified filesystem to {@code out}.
- * This is the implementation of the filesystem part of the 'blaze dump'
- * command. It lives here, rather than in DumpCommand, because it requires
- * privileged access to members of this package.
- *
- * <p>Its results are unspecified and MUST NOT be interpreted programmatically.
- */
- public static void dump(FileSystem fs, final PrintStream out) {
- // Unfortunately there's no "letrec" for anonymous functions so we have to
- // (a) name the function, (b) put it in a box and (c) use List not array
- // because of the generic type. *sigh*.
- final List<Predicate<Path>> dumpFunction = new ArrayList<>();
- dumpFunction.add(
- child -> {
- Path path = child;
- out.println(" " + path + " (" + path.toDebugString() + ")");
- path.applyToChildren(dumpFunction.get(0));
- return false;
- });
-
- fs.getRootDirectory().applyToChildren(dumpFunction.get(0));
- }
-
- /**
* Returns the type of the file system path belongs to.
*/
public static String getFileSystem(Path path) {
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
index 5bba53533f..f5b0220e32 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
@@ -281,7 +281,7 @@ public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat {
throws IOException {
java.nio.file.Path nioPath = getNioPath(linkPath);
try {
- Files.createSymbolicLink(nioPath, Paths.get(targetFragment.getPathString()));
+ Files.createSymbolicLink(nioPath, Paths.get(targetFragment.getSafePathString()));
} catch (java.nio.file.FileAlreadyExistsException e) {
throw new IOException(linkPath + ERR_FILE_EXISTS);
} catch (java.nio.file.AccessDeniedException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java b/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java
deleted file mode 100644
index a32a4e356c..0000000000
--- a/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java
+++ /dev/null
@@ -1,715 +0,0 @@
-// Copyright 2017 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.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.devtools.build.lib.util.OS;
-import com.google.devtools.build.lib.windows.WindowsShortPath;
-import com.google.devtools.build.lib.windows.jni.WindowsFileOperations;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicReference;
-import javax.annotation.Nullable;
-
-/**
- * A local file path representing a file on the host machine. You should use this when you want to
- * access local files via the file system.
- *
- * <p>Paths are either absolute or relative.
- *
- * <p>Strings are normalized with '.' and '..' removed and resolved (if possible), any multiple
- * slashes ('/') removed, and any trailing slash also removed. The current implementation does not
- * touch the incoming path string unless the string actually needs to be normalized.
- *
- * <p>There is some limited support for Windows-style paths. Most importantly, drive identifiers in
- * front of a path (c:/abc) are supported and such paths are correctly recognized as absolute, as
- * are paths with backslash separators (C:\\foo\\bar). However, advanced Windows-style features like
- * \\\\network\\paths and \\\\?\\unc\\paths are not supported. We are currently using forward
- * slashes ('/') even on Windows, so backslashes '\' get converted to forward slashes during
- * normalization.
- *
- * <p>Mac and Windows file paths are case insensitive. Case is preserved.
- *
- * <p>This class is replaces {@link Path} as the way to access the host machine's file system.
- * Developers should use this class instead of {@link Path}.
- */
-public final class LocalPath implements Comparable<LocalPath> {
- private static final OsPathPolicy DEFAULT_OS = createFilePathOs();
-
- public static final LocalPath EMPTY = create("");
-
- private final String path;
- private final int driveStrLength; // 0 for relative paths, 1 on Unix, 3 on Windows
- private final OsPathPolicy os;
-
- /** Creates a local path that is specific to the host OS. */
- public static LocalPath create(String path) {
- return createWithOs(path, DEFAULT_OS);
- }
-
- @VisibleForTesting
- static LocalPath createWithOs(String path, OsPathPolicy os) {
- Preconditions.checkNotNull(path);
- int normalizationLevel = os.needsToNormalize(path);
- String normalizedPath = os.normalize(path, normalizationLevel);
- int driveStrLength = os.getDriveStrLength(normalizedPath);
- return new LocalPath(normalizedPath, driveStrLength, os);
- }
-
- /** This method expects path to already be normalized. */
- private LocalPath(String path, int driveStrLength, OsPathPolicy os) {
- this.path = Preconditions.checkNotNull(path);
- this.driveStrLength = driveStrLength;
- this.os = Preconditions.checkNotNull(os);
- }
-
- public String getPathString() {
- return path;
- }
-
- /**
- * If called on a {@link LocalPath} instance for a mount name (eg. '/' or 'C:/'), the empty string
- * is returned.
- */
- public String getBaseName() {
- int lastSeparator = path.lastIndexOf(os.getSeparator());
- return lastSeparator < driveStrLength
- ? path.substring(driveStrLength)
- : path.substring(lastSeparator + 1);
- }
-
- /**
- * Returns a {@link LocalPath} instance representing the relative path between this {@link
- * LocalPath} and the given {@link LocalPath}.
- *
- * <pre>
- * Example:
- *
- * LocalPath.create("/foo").getRelative(LocalPath.create("bar/baz"))
- * -> "/foo/bar/baz"
- * </pre>
- *
- * <p>If the passed path is absolute it is returned untouched. This can be useful to resolve
- * symlinks.
- */
- public LocalPath getRelative(LocalPath other) {
- Preconditions.checkNotNull(other);
- Preconditions.checkArgument(os == other.os);
- return getRelative(other.getPathString(), other.driveStrLength);
- }
-
- /**
- * Returns a {@link LocalPath} instance representing the relative path between this {@link
- * LocalPath} and the given path.
- *
- * <p>See {@link #getRelative(LocalPath)} for details.
- */
- public LocalPath getRelative(String other) {
- Preconditions.checkNotNull(other);
- return getRelative(other, os.getDriveStrLength(other));
- }
-
- private LocalPath getRelative(String other, int otherDriveStrLength) {
- if (path.isEmpty()) {
- return create(other);
- }
- if (other.isEmpty()) {
- return this;
- }
- // Note that even if other came from a LocalPath instance we still might
- // need to normalize the result if (for instance) other is a path that
- // starts with '..'
- int normalizationLevel = os.needsToNormalize(other);
- // This is an absolute path, simply return it
- if (otherDriveStrLength > 0) {
- String normalizedPath = os.normalize(other, normalizationLevel);
- return new LocalPath(normalizedPath, otherDriveStrLength, os);
- }
- String newPath;
- if (path.length() == driveStrLength) {
- newPath = path + other;
- } else {
- newPath = path + '/' + other;
- }
- newPath = os.normalize(newPath, normalizationLevel);
- return new LocalPath(newPath, driveStrLength, os);
- }
-
- /**
- * Returns the parent directory of this {@link LocalPath}.
- *
- * <p>If this is called on an single directory for a relative path, this returns an empty relative
- * path. If it's called on a root (like '/') or the empty string, it returns null.
- */
- @Nullable
- public LocalPath getParentDirectory() {
- int lastSeparator = path.lastIndexOf(os.getSeparator());
-
- // For absolute paths we need to specially handle when we hit root
- // Relative paths can't hit this path as driveStrLength == 0
- if (driveStrLength > 0) {
- if (lastSeparator < driveStrLength) {
- if (path.length() > driveStrLength) {
- String newPath = path.substring(0, driveStrLength);
- return new LocalPath(newPath, driveStrLength, os);
- } else {
- return null;
- }
- }
- } else {
- if (lastSeparator == -1) {
- if (!path.isEmpty()) {
- return EMPTY;
- } else {
- return null;
- }
- }
- }
- String newPath = path.substring(0, lastSeparator);
- return new LocalPath(newPath, driveStrLength, os);
- }
-
- /**
- * Returns the {@link LocalPath} relative to the base {@link LocalPath}.
- *
- * <p>For example, <code>LocalPath.create("foo/bar/wiz").relativeTo(LocalPath.create("foo"))
- * </code> returns <code>LocalPath.create("bar/wiz")</code>.
- *
- * <p>If the {@link LocalPath} is not a child of the passed {@link LocalPath} an {@link
- * IllegalArgumentException} is thrown. In particular, this will happen whenever the two {@link
- * LocalPath} instances aren't both absolute or both relative.
- */
- public LocalPath relativeTo(LocalPath base) {
- Preconditions.checkNotNull(base);
- Preconditions.checkArgument(os == base.os);
- if (isAbsolute() != base.isAbsolute()) {
- throw new IllegalArgumentException(
- "Cannot relativize an absolute and a non-absolute path pair");
- }
- String basePath = base.path;
- if (!os.startsWith(path, basePath)) {
- throw new IllegalArgumentException(
- String.format("Path '%s' is not under '%s', cannot relativize", this, base));
- }
- int bn = basePath.length();
- if (bn == 0) {
- return this;
- }
- if (path.length() == bn) {
- return EMPTY;
- }
- final int lastSlashIndex;
- if (basePath.charAt(bn - 1) == '/') {
- lastSlashIndex = bn - 1;
- } else {
- lastSlashIndex = bn;
- }
- if (path.charAt(lastSlashIndex) != '/') {
- throw new IllegalArgumentException(
- String.format("Path '%s' is not under '%s', cannot relativize", this, base));
- }
- String newPath = path.substring(lastSlashIndex + 1);
- return new LocalPath(newPath, 0 /* Always a relative path */, os);
- }
-
- /**
- * Splits a path into its constituent parts. The root is not included. This is an inefficient
- * operation and should be avoided.
- */
- public String[] split() {
- String[] segments = path.split("/");
- if (driveStrLength > 0) {
- // String#split("/") for some reason returns a zero-length array
- // String#split("/hello") returns a 2-length array, so this makes little sense
- if (segments.length == 0) {
- return segments;
- }
- return Arrays.copyOfRange(segments, 1, segments.length);
- }
- return segments;
- }
-
- /**
- * Returns whether this path is an ancestor of another path.
- *
- * <p>A path is considered an ancestor of itself.
- *
- * <p>An absolute path can never be an ancestor of a relative path, and vice versa.
- */
- public boolean startsWith(LocalPath other) {
- Preconditions.checkNotNull(other);
- Preconditions.checkArgument(os == other.os);
- if (other.path.length() > path.length()) {
- return false;
- }
- if (driveStrLength != other.driveStrLength) {
- return false;
- }
- if (!os.startsWith(path, other.path)) {
- return false;
- }
- return path.length() == other.path.length()
- || other.path.length() == driveStrLength
- || path.charAt(other.path.length()) == os.getSeparator();
- }
-
- public boolean isAbsolute() {
- return driveStrLength > 0;
- }
-
- @Override
- public String toString() {
- return path;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- return os.compare(this.path, ((LocalPath) o).path) == 0;
- }
-
- @Override
- public int hashCode() {
- return os.hashPath(this.path);
- }
-
- @Override
- public int compareTo(LocalPath o) {
- return os.compare(this.path, o.path);
- }
-
- /**
- * An interface class representing the differences in path style between different OSs.
- *
- * <p>Eg. case sensitivity, '/' mounts vs. 'C:/', etc.
- */
- @VisibleForTesting
- interface OsPathPolicy {
- int NORMALIZED = 0; // Path is normalized
- int NEEDS_NORMALIZE = 1; // Path requires normalization
-
- /** Returns required normalization level, passed to {@link #normalize}. */
- int needsToNormalize(String path);
-
- /**
- * Normalizes the passed string according to the passed normalization level.
- *
- * @param normalizationLevel The normalizationLevel from {@link #needsToNormalize}
- */
- String normalize(String path, int normalizationLevel);
-
- /**
- * Returns the length of the mount, eg. 1 for unix '/', 3 for Windows 'C:/'.
- *
- * <p>If the path is relative, 0 is returned
- */
- int getDriveStrLength(String path);
-
- /** Compares two path strings, using the given OS case sensitivity. */
- int compare(String s1, String s2);
-
- /** Computes the hash code for a path string. */
- int hashPath(String s);
-
- /**
- * Returns whether the passed string starts with the given prefix, given the OS case
- * sensitivity.
- *
- * <p>This is a pure string operation and doesn't need to worry about matching path segments.
- */
- boolean startsWith(String path, String prefix);
-
- char getSeparator();
-
- boolean isCaseSensitive();
- }
-
- @VisibleForTesting
- static class UnixOsPathPolicy implements OsPathPolicy {
-
- @Override
- public int needsToNormalize(String path) {
- int n = path.length();
- int dotCount = 0;
- char prevChar = 0;
- for (int i = 0; i < n; i++) {
- char c = path.charAt(i);
- if (c == '/') {
- if (prevChar == '/') {
- return NEEDS_NORMALIZE;
- }
- if (dotCount == 1 || dotCount == 2) {
- return NEEDS_NORMALIZE;
- }
- }
- dotCount = c == '.' ? dotCount + 1 : 0;
- prevChar = c;
- }
- if (prevChar == '/' || dotCount == 1 || dotCount == 2) {
- return NEEDS_NORMALIZE;
- }
- return NORMALIZED;
- }
-
- @Override
- public String normalize(String path, int normalizationLevel) {
- if (normalizationLevel == NORMALIZED) {
- return path;
- }
- if (path.isEmpty()) {
- return path;
- }
- boolean isAbsolute = path.charAt(0) == '/';
- String[] segments = path.split("/+");
- int segmentCount = removeRelativePaths(segments, isAbsolute ? 1 : 0);
- StringBuilder sb = new StringBuilder(path.length());
- if (isAbsolute) {
- sb.append('/');
- }
- for (int i = 0; i < segmentCount; ++i) {
- sb.append(segments[i]);
- sb.append('/');
- }
- if (segmentCount > 0) {
- sb.deleteCharAt(sb.length() - 1);
- }
- return sb.toString();
- }
-
- @Override
- public int getDriveStrLength(String path) {
- if (path.length() == 0) {
- return 0;
- }
- return (path.charAt(0) == '/') ? 1 : 0;
- }
-
- @Override
- public int compare(String s1, String s2) {
- return s1.compareTo(s2);
- }
-
- @Override
- public int hashPath(String s) {
- return s.hashCode();
- }
-
- @Override
- public boolean startsWith(String path, String prefix) {
- return path.startsWith(prefix);
- }
-
- @Override
- public char getSeparator() {
- return '/';
- }
-
- @Override
- public boolean isCaseSensitive() {
- return true;
- }
- }
-
- /** Mac is a unix file system that is case insensitive. */
- @VisibleForTesting
- static class MacOsPathPolicy extends UnixOsPathPolicy {
- @Override
- public int compare(String s1, String s2) {
- return s1.compareToIgnoreCase(s2);
- }
-
- @Override
- public int hashPath(String s) {
- return s.toLowerCase().hashCode();
- }
-
- @Override
- public boolean isCaseSensitive() {
- return false;
- }
- }
-
- @VisibleForTesting
- static class WindowsOsPathPolicy implements OsPathPolicy {
-
- private static final int NEEDS_SHORT_PATH_NORMALIZATION = NEEDS_NORMALIZE + 1;
-
- // msys root, used to resolve paths from msys starting with "/"
- private static final AtomicReference<String> UNIX_ROOT = new AtomicReference<>(null);
- private final ShortPathResolver shortPathResolver;
-
- interface ShortPathResolver {
- String resolveShortPath(String path);
- }
-
- static class DefaultShortPathResolver implements ShortPathResolver {
- @Override
- public String resolveShortPath(String path) {
- try {
- return WindowsFileOperations.getLongPath(path);
- } catch (IOException e) {
- return path;
- }
- }
- }
-
- WindowsOsPathPolicy() {
- this(new DefaultShortPathResolver());
- }
-
- WindowsOsPathPolicy(ShortPathResolver shortPathResolver) {
- this.shortPathResolver = shortPathResolver;
- }
-
- @Override
- public int needsToNormalize(String path) {
- int n = path.length();
- int normalizationLevel = 0;
- // Check for unix path
- if (n > 0 && path.charAt(0) == '/') {
- normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
- }
- int dotCount = 0;
- char prevChar = 0;
- int segmentBeginIndex = 0; // The start index of the current path index
- boolean segmentHasShortPathChar = false; // Triggers more expensive short path regex test
- for (int i = 0; i < n; i++) {
- char c = path.charAt(i);
- if (c == '/' || c == '\\') {
- if (c == '\\') {
- normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
- }
- // No need to check for '\' here because that already causes normalization
- if (prevChar == '/') {
- normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
- }
- if (dotCount == 1 || dotCount == 2) {
- normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
- }
- if (segmentHasShortPathChar) {
- if (WindowsShortPath.isShortPath(path.substring(segmentBeginIndex, i))) {
- normalizationLevel = Math.max(normalizationLevel, NEEDS_SHORT_PATH_NORMALIZATION);
- }
- }
- segmentBeginIndex = i + 1;
- segmentHasShortPathChar = false;
- } else if (c == '~') {
- // This path segment might be a Windows short path segment
- segmentHasShortPathChar = true;
- }
- dotCount = c == '.' ? dotCount + 1 : 0;
- prevChar = c;
- }
- if (prevChar == '/' || dotCount == 1 || dotCount == 2) {
- normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
- }
- return normalizationLevel;
- }
-
- @Override
- public String normalize(String path, int normalizationLevel) {
- if (normalizationLevel == NORMALIZED) {
- return path;
- }
- if (normalizationLevel == NEEDS_SHORT_PATH_NORMALIZATION) {
- String resolvedPath = shortPathResolver.resolveShortPath(path);
- if (resolvedPath != null) {
- path = resolvedPath;
- }
- }
- String[] segments = path.split("[\\\\/]+");
- int driveStrLength = getDriveStrLength(path);
- boolean isAbsolute = driveStrLength > 0;
- int segmentSkipCount = isAbsolute ? 1 : 0;
-
- StringBuilder sb = new StringBuilder(path.length());
- if (isAbsolute) {
- char driveLetter = path.charAt(0);
- sb.append(Character.toUpperCase(driveLetter));
- sb.append(":/");
- }
- // unix path support
- if (!path.isEmpty() && path.charAt(0) == '/') {
- if (path.length() == 2 || (path.length() > 2 && path.charAt(2) == '/')) {
- sb.append(Character.toUpperCase(path.charAt(1)));
- sb.append(":/");
- segmentSkipCount = 2;
- } else {
- String unixRoot = getUnixRoot();
- sb.append(unixRoot);
- }
- }
- int segmentCount = removeRelativePaths(segments, segmentSkipCount);
- for (int i = 0; i < segmentCount; ++i) {
- sb.append(segments[i]);
- sb.append('/');
- }
- if (segmentCount > 0) {
- sb.deleteCharAt(sb.length() - 1);
- }
- return sb.toString();
- }
-
- @Override
- public int getDriveStrLength(String path) {
- int n = path.length();
- if (n < 3) {
- return 0;
- }
- if (isDriveLetter(path.charAt(0))
- && path.charAt(1) == ':'
- && (path.charAt(2) == '/' || path.charAt(2) == '\\')) {
- return 3;
- }
- return 0;
- }
-
- private static boolean isDriveLetter(char c) {
- return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
- }
-
- @Override
- public int compare(String s1, String s2) {
- // Windows is case-insensitive
- return s1.compareToIgnoreCase(s2);
- }
-
- @Override
- public int hashPath(String s) {
- // Windows is case-insensitive
- return s.toLowerCase().hashCode();
- }
-
- @Override
- public boolean startsWith(String path, String prefix) {
- int pathn = path.length();
- int prefixn = prefix.length();
- if (pathn < prefixn) {
- return false;
- }
- for (int i = 0; i < prefixn; ++i) {
- if (Character.toLowerCase(path.charAt(i)) != Character.toLowerCase(prefix.charAt(i))) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public char getSeparator() {
- return '/';
- }
-
- @Override
- public boolean isCaseSensitive() {
- return false;
- }
-
- private String getUnixRoot() {
- String value = UNIX_ROOT.get();
- if (value == null) {
- String jvmFlag = "bazel.windows_unix_root";
- value = determineUnixRoot(jvmFlag);
- if (value == null) {
- throw new IllegalStateException(
- String.format(
- "\"%1$s\" JVM flag is not set. Use the --host_jvm_args flag or export the "
- + "BAZEL_SH environment variable. For example "
- + "\"--host_jvm_args=-D%1$s=c:/tools/msys64\" or "
- + "\"set BAZEL_SH=c:/tools/msys64/usr/bin/bash.exe\".",
- jvmFlag));
- }
- if (getDriveStrLength(value) != 3) {
- throw new IllegalStateException(
- String.format("\"%s\" must be an absolute path, got: \"%s\"", jvmFlag, value));
- }
- value = value.replace('\\', '/');
- if (value.length() > 3 && value.endsWith("/")) {
- value = value.substring(0, value.length() - 1);
- }
- UNIX_ROOT.set(value);
- }
- return value;
- }
-
- private String determineUnixRoot(String jvmArgName) {
- // Get the path from a JVM flag, if specified.
- String path = System.getProperty(jvmArgName);
- if (path == null) {
- return null;
- }
- path = path.trim();
- if (path.isEmpty()) {
- return null;
- }
- return path;
- }
- }
-
- private static OsPathPolicy createFilePathOs() {
- switch (OS.getCurrent()) {
- case LINUX:
- case FREEBSD:
- case UNKNOWN:
- return new UnixOsPathPolicy();
- case DARWIN:
- return new MacOsPathPolicy();
- case WINDOWS:
- return new WindowsOsPathPolicy();
- default:
- throw new AssertionError("Not covering all OSs");
- }
- }
-
- /**
- * Normalizes any '.' and '..' in-place in the segment array by shifting other segments to the
- * front. Returns the remaining number of items.
- */
- private static int removeRelativePaths(String[] segments, int starti) {
- int segmentCount = 0;
- int shift = starti;
- for (int i = starti; i < segments.length; ++i) {
- String segment = segments[i];
- switch (segment) {
- case ".":
- // Just discard it
- ++shift;
- break;
- case "..":
- if (segmentCount > 0 && !segments[segmentCount - 1].equals("..")) {
- // Remove the last segment, if there is one and it is not "..". This
- // means that the resulting path can still contain ".."
- // segments at the beginning.
- segmentCount--;
- shift += 2;
- break;
- }
- // Fall through
- default:
- ++segmentCount;
- if (shift > 0) {
- segments[i - shift] = segments[i];
- }
- break;
- }
- }
- return segmentCount;
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/OsPathPolicy.java b/src/main/java/com/google/devtools/build/lib/vfs/OsPathPolicy.java
new file mode 100644
index 0000000000..6b0380f8c8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/vfs/OsPathPolicy.java
@@ -0,0 +1,144 @@
+// Copyright 2017 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.util.OS;
+
+/**
+ * An interface class representing the differences in path style between different OSs.
+ *
+ * <p>Eg. case sensitivity, '/' mounts vs. 'C:/', etc.
+ */
+public interface OsPathPolicy {
+ int NORMALIZED = 0; // Path is normalized
+ int NEEDS_NORMALIZE = 1; // Path requires normalization
+
+ /** Returns required normalization level, passed to {@link #normalize}. */
+ int needsToNormalize(String path);
+
+ /**
+ * Returns the required normalization level if an already normalized string is concatenated with
+ * another normalized path fragment.
+ *
+ * <p>This method may be faster than {@link #needsToNormalize(String)}.
+ */
+ int needsToNormalizeSuffix(String normalizedSuffix);
+
+ /**
+ * Normalizes the passed string according to the passed normalization level.
+ *
+ * @param normalizationLevel The normalizationLevel from {@link #needsToNormalize}
+ */
+ String normalize(String path, int normalizationLevel);
+
+ /**
+ * Returns the length of the mount, eg. 1 for unix '/', 3 for Windows 'C:/'.
+ *
+ * <p>If the path is relative, 0 is returned
+ */
+ int getDriveStrLength(String path);
+
+ /** Compares two path strings, using the given OS case sensitivity. */
+ int compare(String s1, String s2);
+
+ /** Compares two characters, using the given OS case sensitivity. */
+ int compare(char c1, char c2);
+
+ /** Tests two path strings for equality, using the given OS case sensitivity. */
+ boolean equals(String s1, String s2);
+
+ /** Computes the hash code for a path string. */
+ int hash(String s);
+
+ /**
+ * Returns whether the passed string starts with the given prefix, given the OS case sensitivity.
+ *
+ * <p>This is a pure string operation and doesn't need to worry about matching path segments.
+ */
+ boolean startsWith(String path, String prefix);
+
+ /**
+ * Returns whether the passed string ends with the given prefix, given the OS case sensitivity.
+ *
+ * <p>This is a pure string operation and doesn't need to worry about matching path segments.
+ */
+ boolean endsWith(String path, String suffix);
+
+ /** Returns the separator used for normalized paths. */
+ char getSeparator();
+
+ /** Returns whether the unnormalized character c is a separator. */
+ boolean isSeparator(char c);
+
+ boolean isCaseSensitive();
+
+ static OsPathPolicy getFilePathOs() {
+ switch (OS.getCurrent()) {
+ case LINUX:
+ case FREEBSD:
+ case UNKNOWN:
+ return UnixOsPathPolicy.INSTANCE;
+ case DARWIN:
+ // NOTE: We *should* return a path policy that is case insensitive,
+ // but we currently don't handle this
+ return UnixOsPathPolicy.INSTANCE;
+ case WINDOWS:
+ return WindowsOsPathPolicy.INSTANCE;
+ default:
+ throw new AssertionError("Not covering all OSs");
+ }
+ }
+
+ /** Utilities for implementations of {@link OsPathPolicy}. */
+ class Utils {
+ /**
+ * Normalizes any '.' and '..' in-place in the segment array by shifting other segments to the
+ * front. Returns the remaining number of items.
+ */
+ static int removeRelativePaths(String[] segments, int starti, boolean isAbsolute) {
+ int segmentCount = 0;
+ int shift = starti;
+ int n = segments.length;
+ for (int i = starti; i < n; ++i) {
+ String segment = segments[i];
+ switch (segment) {
+ case ".":
+ ++shift;
+ break;
+ case "..":
+ if (segmentCount > 0 && !segments[segmentCount - 1].equals("..")) {
+ // Remove the last segment, if there is one and it is not "..". This
+ // means that the resulting path can still contain ".."
+ // segments at the beginning.
+ segmentCount--;
+ shift += 2;
+ break;
+ } else if (isAbsolute) {
+ // If this is absolute, then just pop it the ".." off and remain at root
+ ++shift;
+ break;
+ }
+ // Fall through
+ default:
+ ++segmentCount;
+ if (shift > 0) {
+ segments[i - shift] = segments[i];
+ }
+ break;
+ }
+ }
+ return segmentCount;
+ }
+ }
+}
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 9549d25bab..075c47bd07 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
@@ -14,14 +14,14 @@
package com.google.devtools.build.lib.vfs;
import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.skyframe.serialization.InjectingObjectCodec;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
+import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.util.FileType;
-import com.google.devtools.build.lib.util.StringCanonicalizer;
import com.google.devtools.build.lib.vfs.FileSystem.HashFunction;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
@@ -33,28 +33,29 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.IdentityHashMap;
-import java.util.Objects;
+import javax.annotation.Nullable;
/**
- * Instances of this class represent pathnames, forming a tree structure to implement sharing of
- * common prefixes (parent directory names). A node in these trees is something like foo, bar, ..,
- * ., or /. If the instance is not a root path, it will have a parent path. A path can also have
- * children, which are indexed by name in a map.
+ * A local file path representing a file on the host machine. You should use this when you want to
+ * access local files via the file system.
+ *
+ * <p>Paths are always absolute.
+ *
+ * <p>Strings are normalized with '.' and '..' removed and resolved (if possible), any multiple
+ * slashes ('/') removed, and any trailing slash also removed. Windows drive letters are uppercased.
+ * The current implementation does not touch the incoming path string unless the string actually
+ * needs to be normalized.
*
* <p>There is some limited support for Windows-style paths. Most importantly, drive identifiers in
- * front of a path (c:/abc) are supported. However, Windows-style backslash separators
- * (C:\\foo\\bar) and drive-relative paths ("C:foo") are explicitly not supported, same with
- * advanced features like \\\\network\\paths and \\\\?\\unc\\paths.
+ * front of a path (c:/abc) are supported and such paths are correctly recognized as absolute, as
+ * are paths with backslash separators (C:\\foo\\bar). However, advanced Windows-style features like
+ * \\\\network\\paths and \\\\?\\unc\\paths are not supported. We are currently using forward
+ * slashes ('/') even on Windows, so backslashes '\' get converted to forward slashes during
+ * normalization.
*
- * <p>{@link FileSystem} implementations maintain pointers into this graph.
+ * <p>Mac and Windows file paths are case insensitive. Case is preserved.
*/
@ThreadSafe
public class Path
@@ -62,34 +63,6 @@ public class Path
public static final InjectingObjectCodec<Path, FileSystemProvider> CODEC =
new PathCodecWithInjectedFileSystem();
- /** 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);
-
- /**
- * Makes the proper invocation of {@link FileSystem#getCachedChildPathInternal}, doing
- * filesystem-specific logic if necessary.
- */
- Path getCachedChildPathInternal(Path path, String childName);
- }
-
private static FileSystem fileSystemForSerialization;
/**
@@ -106,398 +79,279 @@ public class Path
return fileSystemForSerialization;
}
- // These are basically final, but can't be marked as such in order to support serialization.
- private FileSystem fileSystem;
- private String name;
- private Path parent;
- private int depth;
- private int hashCode;
-
- private static final ReferenceQueue<Path> REFERENCE_QUEUE = new ReferenceQueue<>();
+ private static final OsPathPolicy OS = OsPathPolicy.getFilePathOs();
+ private static final char SEPARATOR = OS.getSeparator();
- private static class PathWeakReferenceForCleanup extends WeakReference<Path> {
- final Path parent;
- final String baseName;
+ private String path;
+ private int driveStrLength; // 1 on Unix, 3 on Windows
+ private FileSystem fileSystem;
- PathWeakReferenceForCleanup(Path referent, ReferenceQueue<Path> referenceQueue) {
- super(referent, referenceQueue);
- parent = referent.getParentDirectory();
- baseName = referent.getBaseName();
- }
+ /** Creates a local path that is specific to the host OS. */
+ static Path create(String path, FileSystem fileSystem) {
+ Preconditions.checkNotNull(path);
+ int normalizationLevel = OS.needsToNormalize(path);
+ String normalizedPath = OS.normalize(path, normalizationLevel);
+ return createAlreadyNormalized(normalizedPath, fileSystem);
}
- private static final Thread PATH_CHILD_CACHE_CLEANUP_THREAD = new Thread("Path cache cleanup") {
- @Override
- public void run() {
- while (true) {
- try {
- PathWeakReferenceForCleanup ref = (PathWeakReferenceForCleanup) REFERENCE_QUEUE.remove();
- Path parent = ref.parent;
- synchronized (parent) {
- // It's possible that since this reference was enqueued for deletion, the Path was
- // recreated with a new entry in the map. We definitely shouldn't delete that entry, so
- // check to make sure they're the same.
- Reference<Path> currentRef = ref.parent.children.get(ref.baseName);
- if (currentRef == ref) {
- ref.parent.children.remove(ref.baseName);
- }
- }
- } catch (InterruptedException e) {
- // Ignored.
- }
- }
- }
- };
-
- static {
- PATH_CHILD_CACHE_CLEANUP_THREAD.setDaemon(true);
- PATH_CHILD_CACHE_CLEANUP_THREAD.start();
+ static Path createAlreadyNormalized(String path, FileSystem fileSystem) {
+ int driveStrLength = OS.getDriveStrLength(path);
+ return createAlreadyNormalized(path, driveStrLength, fileSystem);
}
- /**
- * A mapping from a child file name to the {@link Path} representing it.
- *
- * <p>File names must be a single path segment. The strings must be
- * canonical. We use IdentityHashMap instead of HashMap for reasons of space
- * efficiency: instances are smaller by a single word. Also, since all path
- * segments are interned, the universe of Paths holds a minimal number of
- * references to strings. (It's doubtful that there's any time gain from use
- * of an IdentityHashMap, since the time saved by avoiding string equality
- * tests during hash lookups is probably equal to the time spent eagerly
- * interning strings, unless the collision rate is high.)
- *
- * <p>The Paths are stored as weak references to ensure that a live
- * Path for a directory does not hold a strong reference to all of its
- * descendants, which would prevent collection of paths we never intend to
- * use again. Stale references in the map must be treated as absent.
- *
- * <p>A Path may be recycled once there is no Path that refers to it or
- * to one of its descendants. This means that any data stored in the
- * Path instance by one of its subclasses must be recoverable: it's ok to
- * store data in Paths as an optimization, but there must be another
- * source for that data in case the Path is recycled.
- *
- * <p>We intentionally avoid using the existing library classes for reasons of
- * space efficiency: while ConcurrentHashMap would reduce our locking
- * overhead, and ReferenceMap would simplify the code a little, both of those
- * classes have much higher per-instance overheads than IdentityHashMap.
- *
- * <p>The Path object must be synchronized while children is being
- * accessed.
- */
- private volatile IdentityHashMap<String, Reference<Path>> children;
-
- /**
- * Create a path instance.
- *
- * <p>Should only be called by {@link PathFactory#createChildPath(Path, String)}.
- *
- * @param name the name of this path; it must be canonicalized with {@link
- * StringCanonicalizer#intern}
- * @param parent this path's parent
- */
- protected Path(FileSystem fileSystem, String name, Path parent) {
- this.fileSystem = fileSystem;
- 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 {
- this.hashCode = Objects.hash(parent, name.toLowerCase());
- }
+ static Path createAlreadyNormalized(String path, int driveStrLength, FileSystem fileSystem) {
+ return new Path(path, driveStrLength, fileSystem);
}
- /**
- * Create the root path.
- *
- * <p>Should only be called by {@link PathFactory#createRootPath(FileSystem)}.
- */
- protected Path(FileSystem fileSystem) {
- this(fileSystem, StringCanonicalizer.intern("/"), null);
+ /** This method expects path to already be normalized. */
+ private Path(String path, int driveStrLength, FileSystem fileSystem) {
+ Preconditions.checkArgument(driveStrLength > 0, "Paths must be absolute: '%s'", path);
+ this.path = Preconditions.checkNotNull(path);
+ this.driveStrLength = driveStrLength;
+ this.fileSystem = fileSystem;
}
- private void writeObject(ObjectOutputStream out) throws IOException {
- Preconditions.checkState(
- fileSystem == fileSystemForSerialization, "%s %s", fileSystem, fileSystemForSerialization);
- out.writeUTF(getPathString());
+ public String getPathString() {
+ return path;
}
- private void readObject(ObjectInputStream in) throws IOException {
- fileSystem = fileSystemForSerialization;
- String p = in.readUTF();
- PathFragment pf = PathFragment.create(p);
- PathFragment parentDir = pf.getParentDirectory();
- if (parentDir == null) {
- this.name = "/";
- this.parent = null;
- this.depth = 0;
- } else {
- this.name = pf.getBaseName();
- this.parent = fileSystem.getPath(parentDir);
- this.depth = this.parent.depth + 1;
- }
- this.hashCode = Objects.hash(parent, name);
- reinitializeAfterDeserialization();
+ @Override
+ public String filePathForFileTypeMatcher() {
+ return path;
}
/**
- * Returns the filesystem instance to which this path belongs.
+ * Returns the name of the leaf file or directory.
+ *
+ * <p>If called on a {@link Path} instance for a mount name (eg. '/' or 'C:/'), the empty string
+ * is returned.
*/
- public FileSystem getFileSystem() {
- return fileSystem;
- }
-
- public boolean isRootDirectory() {
- return parent == null;
+ public String getBaseName() {
+ int lastSeparator = path.lastIndexOf(SEPARATOR);
+ return lastSeparator < driveStrLength
+ ? path.substring(driveStrLength)
+ : path.substring(lastSeparator + 1);
}
- protected Path createChildPath(String childName) {
- return fileSystem.getPathFactory().createChildPath(this, childName);
+ /** Synonymous with {@link Path#getRelative(String)}. */
+ public Path getChild(String child) {
+ FileSystemUtils.checkBaseName(child);
+ return getRelative(child);
}
/**
- * Reinitializes this object after deserialization.
- *
- * <p>Derived classes should use this hook to initialize additional state.
+ * Returns a {@link Path} instance representing the relative path between this {@link Path} and
+ * the given path.
*/
- 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;
+ public Path getRelative(PathFragment other) {
+ Preconditions.checkNotNull(other);
+ String otherStr = other.getPathString();
+ // Fast-path: The path fragment is already normal, use cheaper normalization check
+ return getRelative(otherStr, other.getDriveStrLength(), OS.needsToNormalizeSuffix(otherStr));
}
/**
- * 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.
+ * Returns a {@link Path} instance representing the relative path between this {@link Path} and
+ * the given path.
*/
- protected boolean isTopLevelDirectory() {
- return isRootDirectory();
+ public Path getRelative(String other) {
+ Preconditions.checkNotNull(other);
+ return getRelative(other, OS.getDriveStrLength(other), OS.needsToNormalize(other));
}
- /**
- * Returns the child path named name, or creates such a path (and caches it)
- * if it doesn't already exist.
- */
- private Path getCachedChildPath(String childName) {
- return fileSystem.getPathFactory().getCachedChildPathInternal(this, childName);
+ private Path getRelative(String other, int otherDriveStrLength, int normalizationLevel) {
+ if (other.isEmpty()) {
+ return this;
+ }
+ // This is an absolute path, simply return it
+ if (otherDriveStrLength > 0) {
+ String normalizedPath = OS.normalize(other, normalizationLevel);
+ return new Path(normalizedPath, otherDriveStrLength, fileSystem);
+ }
+ String newPath;
+ if (path.length() == driveStrLength) {
+ newPath = path + other;
+ } else {
+ newPath = path + '/' + other;
+ }
+ // Note that even if other came from a PathFragment instance we still might
+ // need to normalize the result if (for instance) other is a path that
+ // starts with '..'
+ newPath = OS.normalize(newPath, normalizationLevel);
+ return new Path(newPath, driveStrLength, fileSystem);
}
/**
- * Internal method only intended to be called by {@link PathFactory#getCachedChildPathInternal}.
+ * Returns the parent directory of this {@link Path}.
+ *
+ * <p>If called on a root (like '/'), it returns null.
*/
- public static Path getCachedChildPathInternal(Path parent, String childName, boolean cacheable) {
- // We get a canonical instance since 'children' is an IdentityHashMap.
- childName = StringCanonicalizer.intern(childName);
- if (!cacheable) {
- // Non-cacheable children won't show up in `children` so applyToChildren won't run for these.
- return parent.createChildPath(childName);
- }
-
- synchronized (parent) {
- if (parent.children == null) {
- // 66% of Paths have size == 1, 80% <= 2
- parent.children = new IdentityHashMap<>(1);
- }
- Reference<Path> childRef = parent.children.get(childName);
- Path child;
- if (childRef == null || (child = childRef.get()) == null) {
- child = parent.createChildPath(childName);
- parent.children.put(childName, new PathWeakReferenceForCleanup(child, REFERENCE_QUEUE));
+ @Nullable
+ public Path getParentDirectory() {
+ int lastSeparator = path.lastIndexOf(SEPARATOR);
+ if (lastSeparator < driveStrLength) {
+ if (path.length() > driveStrLength) {
+ String newPath = path.substring(0, driveStrLength);
+ return new Path(newPath, driveStrLength, fileSystem);
+ } else {
+ return null;
}
- return child;
}
+ String newPath = path.substring(0, lastSeparator);
+ return new Path(newPath, driveStrLength, fileSystem);
}
/**
- * Applies the specified function to each {@link Path} that is an existing direct
- * descendant of this one. The Predicate is evaluated only for its
- * side-effects.
+ * Returns the drive.
*
- * <p>This function exists to hide the "children" field, whose complex
- * synchronization and identity requirements are too unsafe to be exposed to
- * subclasses. For example, the "children" field must be synchronized for
- * the duration of any iteration over it; it may be null; and references
- * within it may be stale, and must be ignored.
+ * <p>On unix, this will return "/". On Windows it will return the drive letter, like "C:/".
*/
- protected synchronized void applyToChildren(Predicate<Path> function) {
- if (children != null) {
- for (Reference<Path> childRef : children.values()) {
- Path child = childRef.get();
- if (child != null) {
- function.apply(child);
- }
- }
- }
+ public String getDriveStr() {
+ return path.substring(0, driveStrLength);
}
/**
- * Returns whether this path is recursively "under" {@code prefix} - that is,
- * whether {@code path} is a prefix of this path.
+ * Returns the {@link Path} relative to the base {@link Path}.
*
- * <p>This method returns {@code true} when called with this path itself. This
- * method acts independently of the existence of files or folders.
+ * <p>For example, <code>Path.create("foo/bar/wiz").relativeTo(Path.create("foo"))
+ * </code> returns <code>Path.create("bar/wiz")</code>.
*
- * @param prefix a path which may or may not be a prefix of this path
+ * <p>If the {@link Path} is not a child of the passed {@link Path} an {@link
+ * IllegalArgumentException} is thrown. In particular, this will happen whenever the two {@link
+ * Path} instances aren't both absolute or both relative.
*/
- public boolean startsWith(Path prefix) {
- Path n = this;
- for (int i = 0, len = depth - prefix.depth; i < len; i++) {
- n = n.getParentDirectory();
+ public PathFragment relativeTo(Path base) {
+ Preconditions.checkNotNull(base);
+ checkSameFileSystem(base);
+ String basePath = base.path;
+ if (!OS.startsWith(path, basePath)) {
+ throw new IllegalArgumentException(
+ String.format("Path '%s' is not under '%s', cannot relativize", this, base));
+ }
+ int bn = basePath.length();
+ if (bn == 0) {
+ return PathFragment.createAlreadyNormalized(path, driveStrLength);
+ }
+ if (path.length() == bn) {
+ return PathFragment.EMPTY_FRAGMENT;
+ }
+ final int lastSlashIndex;
+ if (basePath.charAt(bn - 1) == '/') {
+ lastSlashIndex = bn - 1;
+ } else {
+ lastSlashIndex = bn;
+ }
+ if (path.charAt(lastSlashIndex) != '/') {
+ throw new IllegalArgumentException(
+ String.format("Path '%s' is not under '%s', cannot relativize", this, base));
}
- return prefix.equals(n);
+ String newPath = path.substring(lastSlashIndex + 1);
+ return PathFragment.createAlreadyNormalized(newPath, 0);
}
/**
- * Computes a string representation of this path, and writes it to the given string builder. Only
- * called locally with a new instance.
+ * Returns whether this path is an ancestor of another path.
+ *
+ * <p>A path is considered an ancestor of itself.
*/
- protected void buildPathString(StringBuilder result) {
- if (isRootDirectory()) {
- result.append(PathFragment.ROOT_DIR);
- } else {
- parent.buildPathString(result);
- if (!parent.isRootDirectory()) {
- result.append(PathFragment.SEPARATOR_CHAR);
- }
- result.append(name);
+ public boolean startsWith(Path other) {
+ if (fileSystem != other.fileSystem) {
+ return false;
}
+ return startsWith(other.path, other.driveStrLength);
}
/**
- * Returns the path encoded as an {@link URI}.
+ * Returns whether this path is an ancestor of another path.
*
- * <p>This concrete implementation returns URIs with "file" as the scheme.
- * For Example:
- * - On Unix the path "/tmp/foo bar.txt" will be encoded as
- * "file:///tmp/foo%20bar.txt".
- * - On Windows the path "C:\Temp\Foo Bar.txt" will be encoded as
- * "file:///C:/Temp/Foo%20Bar.txt"
+ * <p>A path is considered an ancestor of itself.
*
- * <p>Implementors extending this class for special filesystems will likely need to override
- * this method.
- *
- * @throws URISyntaxException if the URI cannot be constructed.
+ * <p>An absolute path can never be an ancestor of a relative path fragment.
*/
- public URI toURI() {
- String ps = getPathString();
- if (!ps.startsWith("/")) {
- // On Windows URI's need to start with a '/'. i.e. C:\Foo\Bar would be file:///C:/Foo/Bar
- ps = "/" + ps;
- }
- try {
- return new URI("file",
- // Needs to be "" instead of null, so that toString() will append "//" after the scheme.
- // We need this for backwards compatibility reasons as some consumers of the BEP are
- // broken.
- "",
- ps, null, null);
- } catch (URISyntaxException e) {
- throw new IllegalStateException(e);
+ public boolean startsWith(PathFragment other) {
+ if (!other.isAbsolute()) {
+ return false;
}
+ String otherPath = other.getPathString();
+ return startsWith(otherPath, OS.getDriveStrLength(otherPath));
}
- /**
- * Returns the path as a string.
- */
- public String getPathString() {
- // Profile driven optimization:
- // Preallocate a size determined by the depth, so that
- // we do not have to expand the capacity of the StringBuilder
- StringBuilder builder = new StringBuilder(depth * 20);
- buildPathString(builder);
- return builder.toString();
+ private boolean startsWith(String otherPath, int otherDriveStrLength) {
+ Preconditions.checkNotNull(otherPath);
+ if (otherPath.length() > path.length()) {
+ return false;
+ }
+ if (driveStrLength != otherDriveStrLength) {
+ return false;
+ }
+ if (!OS.startsWith(path, otherPath)) {
+ return false;
+ }
+ return path.length() == otherPath.length() // Handle equal paths
+ || otherPath.length() == driveStrLength // Handle (eg.) 'C:/foo' starts with 'C:/'
+ // Handle 'true' ancestors, eg. "foo/bar" starts with "foo", but does not start with "fo"
+ || path.charAt(otherPath.length()) == SEPARATOR;
}
- @Override
- public void repr(SkylarkPrinter printer) {
- printer.append(getPathString());
+ public FileSystem getFileSystem() {
+ return fileSystem;
}
- @Override
- public String filePathForFileTypeMatcher() {
- return name;
+ public PathFragment asFragment() {
+ return PathFragment.createAlreadyNormalized(path, driveStrLength);
}
- /**
- * Returns the path as a string.
- */
@Override
public String toString() {
- return getPathString();
+ return path;
}
@Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) {
+ public boolean equals(Object o) {
+ if (this == o) {
return true;
}
- if (!(other instanceof Path)) {
+ if (o == null || getClass() != o.getClass()) {
return false;
}
-
- Path otherPath = (Path) other;
- if (hashCode != otherPath.hashCode) {
+ Path other = (Path) o;
+ if (fileSystem != other.fileSystem) {
return false;
}
+ return OS.equals(this.path, other.path);
+ }
- if (!fileSystem.equals(otherPath.fileSystem)) {
- return false;
- }
+ @Override
+ public int hashCode() {
+ // Do not include file system for efficiency.
+ // In practice we never construct paths from different file systems.
+ return OS.hash(this.path);
+ }
- if (fileSystem.isFilePathCaseSensitive()) {
- return name.equals(otherPath.name)
- && Objects.equals(parent, otherPath.parent);
- } else {
- return name.toLowerCase().equals(otherPath.name.toLowerCase())
- && Objects.equals(parent, otherPath.parent);
+ @Override
+ public int compareTo(Path o) {
+ // If they are on different file systems, the file system decides the ordering.
+ FileSystem otherFs = o.getFileSystem();
+ if (!fileSystem.equals(otherFs)) {
+ int thisFileSystemHash = System.identityHashCode(fileSystem);
+ int otherFileSystemHash = System.identityHashCode(otherFs);
+ if (thisFileSystemHash < otherFileSystemHash) {
+ return -1;
+ } else if (thisFileSystemHash > otherFileSystemHash) {
+ return 1;
+ }
}
+ return OS.compare(this.path, o.path);
}
- /**
- * Returns a string of debugging information associated with this path.
- * The results are unspecified and MUST NOT be interpreted programmatically.
- */
- protected String toDebugString() {
- return "";
+ @Override
+ public void repr(SkylarkPrinter printer) {
+ printer.append(path);
}
- /**
- * Returns a path representing the parent directory of this path,
- * or null iff this Path represents the root of the filesystem.
- *
- * <p>Note: This method normalises ".." and "." path segments by string
- * processing, not by directory lookups.
- */
- public Path getParentDirectory() {
- return parent;
+ @Override
+ public void str(SkylarkPrinter printer) {
+ repr(printer);
}
/** Returns true iff this path denotes an existing file of any kind. Follows symbolic links. */
@@ -662,157 +516,6 @@ public class Path
}
/**
- * Returns the last segment of this path, or "/" for the root directory.
- */
- public String getBaseName() {
- return name;
- }
-
- /**
- * Interprets the name of a path segment relative to the current path and
- * returns the result.
- *
- * <p>This is a purely syntactic operation, i.e. it does no I/O, it does not
- * validate the existence of any path, nor resolve symbolic links. If 'prefix'
- * is not canonical, then a 'name' of '..' will be interpreted incorrectly.
- *
- * @precondition segment contains no slashes.
- */
- private Path getCanonicalPath(String segment) {
- 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;
- } else {
- return getCachedChildPath(segment);
- }
- }
-
- /**
- * Returns the path formed by appending the single non-special segment
- * "baseName" to this path.
- *
- * <p>You should almost always use {@link #getRelative} instead, which has
- * the same performance characteristics if the given name is a valid base
- * name, and which also works for '.', '..', and strings containing '/'.
- *
- * @throws IllegalArgumentException if {@code baseName} is not a valid base
- * name according to {@link FileSystemUtils#checkBaseName}
- */
- public Path getChild(String baseName) {
- FileSystemUtils.checkBaseName(baseName);
- 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.
- *
- * <p>If suffix is absolute, the current path will be ignored; otherwise, they
- * will be combined. Up-level references ("..") cause the preceding path
- * segment to be elided; this interpretation is only correct if the base path
- * is canonical.
- */
- public Path getRelative(PathFragment suffix) {
- Path result = getRootForRelativePathComputation(suffix);
- for (String segment : suffix.segments()) {
- result = result.getCanonicalPath(segment);
- }
- return result;
- }
-
- /**
- * Returns the path formed by appending the relative or absolute string
- * {@code path} to this path.
- *
- * <p>If the given path string is absolute, the current path will be ignored;
- * otherwise, they will be combined. Up-level references ("..") cause the
- * preceding path segment to be elided.
- *
- * <p>This is a purely syntactic operation, i.e. it does no I/O, it does not
- * validate the existence of any path, nor resolve symbolic links.
- */
- public Path getRelative(String path) {
- // Fast path for valid base names.
- if ((path.length() == 0) || (path.equals("."))) {
- return this;
- } else if (path.equals("..")) {
- return isTopLevelDirectory() ? this : parent;
- } else if (PathFragment.containsSeparator(path)) {
- return getRelative(PathFragment.create(path));
- } else {
- return getCachedChildPath(path);
- }
- }
-
- protected final String[] getSegments() {
- 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 PathFragment.createAlreadyInterned('\0', true, getSegments());
- }
-
- /**
- * Returns a relative path fragment to this path, relative to {@code
- * ancestorDirectory}. {@code ancestorDirectory} must be on the same
- * filesystem as this path. (Currently, both this path and "ancestorDirectory"
- * must be absolute, though this restriction could be loosened.)
- * <p>
- * <code>x.relativeTo(z) == y</code> implies
- * <code>z.getRelative(y.getPathString()) == x</code>.
- * <p>
- * For example, <code>"/foo/bar/wiz".relativeTo("/foo")</code> returns
- * <code>"bar/wiz"</code>.
- *
- * @throws IllegalArgumentException if this path is not beneath {@code
- * ancestorDirectory} or if they are not part of the same filesystem
- */
- 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 PathFragment.createAlreadyInterned('\0', false, resultSegments);
- }
- }
- }
-
- throw new IllegalArgumentException("Path " + this + " is not beneath " + ancestorPath);
- }
-
- /**
- * Checks that "this" and "that" are paths on the same filesystem.
- */
- protected void checkSameFilesystem(Path that) {
- if (this.fileSystem != that.fileSystem) {
- throw new IllegalArgumentException("Files are on different filesystems: "
- + this + ", " + that);
- }
- }
-
- /**
* Returns an output stream to the file denoted by the current path, creating it and truncating it
* if necessary. The stream is opened for writing.
*
@@ -868,7 +571,7 @@ public class Path
* @throws IOException if the creation of the symbolic link was unsuccessful for any reason
*/
public void createSymbolicLink(Path target) throws IOException {
- checkSameFilesystem(target);
+ checkSameFileSystem(target);
fileSystem.createSymbolicLink(this, target.asFragment());
}
@@ -943,7 +646,7 @@ public class Path
* @throws IOException if the rename failed for any reason
*/
public void renameTo(Path target) throws IOException {
- checkSameFilesystem(target);
+ checkSameFileSystem(target);
fileSystem.renameTo(this, target);
}
@@ -1183,69 +886,28 @@ public class Path
fileSystem.prefetchPackageAsync(this, maxDirs);
}
- /**
- * Compare Paths of the same file system using their PathFragments.
- *
- * <p>Paths from different filesystems will be compared using the identity
- * hash code of their respective filesystems.
- */
- @Override
- public int compareTo(Path o) {
- // Fast-path.
- if (equals(o)) {
- return 0;
+ private void checkSameFileSystem(Path that) {
+ if (this.fileSystem != that.fileSystem) {
+ throw new IllegalArgumentException(
+ "Files are on different filesystems: " + this + ", " + that);
}
+ }
- // If they are on different file systems, the file system decides the ordering.
- FileSystem otherFs = o.getFileSystem();
- if (!fileSystem.equals(otherFs)) {
- int thisFileSystemHash = System.identityHashCode(fileSystem);
- int otherFileSystemHash = System.identityHashCode(otherFs);
- if (thisFileSystemHash < otherFileSystemHash) {
- return -1;
- } else if (thisFileSystemHash > otherFileSystemHash) {
- return 1;
- } else {
- // TODO(bazel-team): Add a name to every file system to be used here.
- return 0;
- }
- }
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ Preconditions.checkState(
+ fileSystem == fileSystemForSerialization, "%s %s", fileSystem, fileSystemForSerialization);
+ out.writeUTF(path);
+ }
- // Equal file system, but different paths, because of the canonicalization.
- // We expect to often compare Paths that are very similar, for example for files in the same
- // directory. This can be done efficiently by going up segment by segment until we get the
- // identical path (canonicalization again), and then just compare the immediate child segments.
- // Overall this is much faster than creating PathFragment instances, and comparing those, which
- // requires us to always go up to the top-level directory and copy all segments into a new
- // string array.
- // This was previously showing up as a hotspot in a profile of globbing a large directory.
- Path a = this;
- Path b = o;
- int maxDepth = Math.min(a.depth, b.depth);
- while (a.depth > maxDepth) {
- a = a.getParentDirectory();
- }
- while (b.depth > maxDepth) {
- b = b.getParentDirectory();
- }
- // One is the child of the other.
- if (a.equals(b)) {
- // If a is the same as this, this.depth must be less than o.depth.
- return equals(a) ? -1 : 1;
- }
- Path previousa;
- Path previousb;
- do {
- previousa = a;
- previousb = b;
- a = a.getParentDirectory();
- b = b.getParentDirectory();
- } while (!a.equals(b)); // This has to happen eventually.
- return previousa.name.compareTo(previousb.name);
+ private void readObject(ObjectInputStream in) throws IOException {
+ path = in.readUTF();
+ fileSystem = fileSystemForSerialization;
+ driveStrLength = OS.getDriveStrLength(path);
}
private static class PathCodecWithInjectedFileSystem
implements InjectingObjectCodec<Path, FileSystemProvider> {
+ private final ObjectCodec<String> stringCodec = StringCodecs.asciiOptimized();
@Override
public Class<Path> getEncodedClass() {
@@ -1256,14 +918,14 @@ public class Path
public void serialize(FileSystemProvider fsProvider, Path path, CodedOutputStream codedOut)
throws IOException, SerializationException {
Preconditions.checkArgument(path.getFileSystem() == fsProvider.getFileSystem());
- PathFragment.CODEC.serialize(path.asFragment(), codedOut);
+ stringCodec.serialize(path.getPathString(), codedOut);
}
@Override
public Path deserialize(FileSystemProvider fsProvider, CodedInputStream codedIn)
throws IOException, SerializationException {
- PathFragment pathFragment = PathFragment.CODEC.deserialize(codedIn);
- return fsProvider.getFileSystem().getPath(pathFragment);
+ return Path.createAlreadyNormalized(
+ stringCodec.deserialize(codedIn), fsProvider.getFileSystem());
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathCodec.java b/src/main/java/com/google/devtools/build/lib/vfs/PathCodec.java
index ee0ef364e3..c7644ed960 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/PathCodec.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/PathCodec.java
@@ -17,6 +17,7 @@ package com.google.devtools.build.lib.vfs;
import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
+import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
@@ -24,6 +25,7 @@ import java.io.IOException;
/** Custom serialization for {@link Path}s. */
public class PathCodec implements ObjectCodec<Path> {
+ private final ObjectCodec<String> stringCodec = StringCodecs.asciiOptimized();
private final FileSystem fileSystem;
/** Create an instance for serializing and deserializing {@link Path}s on {@code fileSystem}. */
@@ -41,15 +43,15 @@ public class PathCodec implements ObjectCodec<Path> {
throws IOException, SerializationException {
Preconditions.checkState(
path.getFileSystem() == fileSystem,
- "Path's FileSystem (%s) did not match the configured FileSystem (%s)",
+ "Path's FileSystem (%s) did not match the configured FileSystem (%s) for path (%s)",
path.getFileSystem(),
- fileSystem);
- PathFragment.CODEC.serialize(path.asFragment(), codedOut);
+ fileSystem,
+ path);
+ stringCodec.serialize(path.getPathString(), codedOut);
}
@Override
public Path deserialize(CodedInputStream codedIn) throws IOException, SerializationException {
- PathFragment pathFragment = PathFragment.CODEC.deserialize(codedIn);
- return fileSystem.getPath(pathFragment);
+ return fileSystem.getPath(stringCodec.deserialize(codedIn));
}
}
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 572042a7d7..88ae732590 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
@@ -1,4 +1,4 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
+// Copyright 2017 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.
@@ -16,630 +16,470 @@ package com.google.devtools.build.lib.vfs;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.actions.CommandLineItem;
-import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
-import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.util.FileType;
-import com.google.devtools.build.lib.util.OS;
-import com.google.devtools.build.lib.util.StringCanonicalizer;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
-import java.io.File;
import java.io.IOException;
-import java.io.InvalidObjectException;
-import java.io.ObjectInputStream;
import java.io.Serializable;
-import java.util.Arrays;
-import java.util.List;
import java.util.Set;
+import javax.annotation.Nullable;
/**
- * This class represents an immutable filesystem path, which may be absolute or relative. The path
- * is maintained as a simple ordered list of path segment strings.
+ * A path segment representing a path fragment using the host machine's path style. That is; If you
+ * are running on a Unix machine, the path style will be unix, on Windows it is the windows path
+ * style.
*
- * <p>This class is independent from other VFS classes, especially anything requiring native code.
- * It is safe to use in places that need simple segmented string path functionality.
+ * <p>Path fragments are either absolute or relative.
+ *
+ * <p>Strings are normalized with '.' and '..' removed and resolved (if possible), any multiple
+ * slashes ('/') removed, and any trailing slash also removed. Windows drive letters are uppercased.
+ * The current implementation does not touch the incoming path string unless the string actually
+ * needs to be normalized.
*
* <p>There is some limited support for Windows-style paths. Most importantly, drive identifiers in
* front of a path (c:/abc) are supported and such paths are correctly recognized as absolute, as
* are paths with backslash separators (C:\\foo\\bar). However, advanced Windows-style features like
- * \\\\network\\paths and \\\\?\\unc\\paths are not supported.
+ * \\\\network\\paths and \\\\?\\unc\\paths are not supported. We are currently using forward
+ * slashes ('/') even on Windows.
+ *
+ * <p>Mac and Windows path fragments are case insensitive.
*/
-@Immutable
-@javax.annotation.concurrent.Immutable
-@ThreadSafe
-public abstract class PathFragment
+public final class PathFragment
implements Comparable<PathFragment>,
Serializable,
- SkylarkPrintable,
FileType.HasFileType,
+ SkylarkPrintable,
CommandLineItem {
- private static final Helper HELPER =
- OS.getCurrent() == OS.WINDOWS ? WindowsPathFragment.HELPER : UnixPathFragment.HELPER;
-
- public static final char SEPARATOR_CHAR = HELPER.getPrimarySeparatorChar();
+ private static final OsPathPolicy OS = OsPathPolicy.getFilePathOs();
- public static final int INVALID_SEGMENT = -1;
-
- public static final String ROOT_DIR = "/";
-
- /** An empty path fragment. */
public static final PathFragment EMPTY_FRAGMENT = create("");
+ public static final char SEPARATOR_CHAR = OS.getSeparator();
+ public static final int INVALID_SEGMENT = -1;
- /** The path fragment representing the root directory. */
- public static final PathFragment ROOT_FRAGMENT = create(ROOT_DIR);
+ private final String path;
+ private final int driveStrLength; // 0 for relative paths, 1 on Unix, 3 on Windows
public static final ObjectCodec<PathFragment> CODEC = new PathFragmentCodec();
- /**
- * A helper object for manipulating the various internal {@link PathFragment} implementations.
- *
- * <p>There will be exactly one {@link Helper} instance used to manipulate all the {@link
- * PathFragment} instances (see {@link PathFragment#HELPER}). All of the various {@link Helper}
- * and {@link PathFragment} implementations may assume this property.
- */
- protected abstract static class Helper {
- /**
- * Returns whether the two given arrays of segments have the same length and should be
- * considered have logically equal contents.
- */
- protected final boolean segmentsEqual(String[] segments1, String[] segments2) {
- return segments1.length == segments2.length
- && segmentsEqual(segments1.length, segments1, 0, segments2);
- }
-
- /**
- * Returns whether the {@code length} segments in {@code segments1}, starting at {@code offset1}
- * should be considered to be logically equal to the first {@code length} segments in {@code
- * segments2}.
- */
- abstract boolean segmentsEqual(int length, String[] segments1, int offset1, String[] segments2);
-
- /** Returns the comparison result of two {@link PathFragment} instances. */
- protected abstract int compare(PathFragment pathFragment1, PathFragment pathFragment2);
-
- /** Returns a fresh {@link PathFragment} instance from the given path string. */
- abstract PathFragment create(String path);
- /**
- * Returns a fresh {@link PathFragment} instance from the given information, taking ownership of
- * {@code segments} and assuming the {@link String}s within have already been interned.
- */
- abstract PathFragment createAlreadyInterned(
- char driveLetter, boolean isAbsolute, String[] segments);
-
- /** Returns whether {@code c} is a path separator. */
- abstract boolean isSeparator(char c);
- /** Returns the primary path separator. */
- abstract char getPrimarySeparatorChar();
- /** Return whether the given {@code path} contains a path separator. */
- abstract boolean containsSeparatorChar(String path);
-
- /**
- * Splits the given {@code toSegment} into path segments, starting at the given {@code offset}.
- */
- protected final String[] segment(String toSegment, int offset) {
- int length = toSegment.length();
-
- // We make two passes through the array of characters: count & alloc,
- // because simply using ArrayList was a bottleneck showing up during profiling.
- int seg = 0;
- int start = offset;
- for (int i = offset; i < length; i++) {
- if (isSeparator(toSegment.charAt(i))) {
- if (i > start) { // to skip repeated separators
- seg++;
- }
- start = i + 1;
- }
- }
- if (start < length) {
- seg++;
- }
- String[] result = new String[seg];
- seg = 0;
- start = offset;
- for (int i = offset; i < length; i++) {
- if (isSeparator(toSegment.charAt(i))) {
- if (i > start) { // to skip repeated separators
- result[seg] = StringCanonicalizer.intern(toSegment.substring(start, i));
- seg++;
- }
- start = i + 1;
- }
- }
- if (start < length) {
- result[seg] = StringCanonicalizer.intern(toSegment.substring(start, length));
- }
- return result;
- }
- }
-
- /** Lower-level API. Create a PathFragment, interning segments. */
- public static PathFragment create(char driveLetter, boolean isAbsolute, String[] segments) {
- String[] internedSegments = new String[segments.length];
- for (int i = 0; i < segments.length; i++) {
- internedSegments[i] = StringCanonicalizer.intern(segments[i]);
- }
- return createAlreadyInterned(driveLetter, isAbsolute, internedSegments);
- }
-
- /** Same as {@link #create(char, boolean, String[])}, except for {@link List}s of segments. */
- public static PathFragment create(char driveLetter, boolean isAbsolute, List<String> segments) {
- String[] internedSegments = new String[segments.size()];
- for (int i = 0; i < segments.size(); i++) {
- internedSegments[i] = StringCanonicalizer.intern(segments.get(i));
- }
- return createAlreadyInterned(driveLetter, isAbsolute, internedSegments);
- }
-
- /**
- * Construct a PathFragment from a java.io.File, which is an absolute or
- * relative UNIX path. Does not support Windows-style Files.
- */
- public static PathFragment create(File path) {
- return HELPER.create(path.getPath());
- }
-
- /**
- * Construct a PathFragment from a string, which is an absolute or relative UNIX or Windows path.
- */
+ /** Creates a new normalized path fragment. */
public static PathFragment create(String path) {
- return HELPER.create(path);
+ int normalizationLevel = OS.needsToNormalize(path);
+ String normalizedPath =
+ normalizationLevel != OsPathPolicy.NORMALIZED
+ ? OS.normalize(path, normalizationLevel)
+ : path;
+ int driveStrLength = OS.getDriveStrLength(normalizedPath);
+ return new PathFragment(normalizedPath, driveStrLength);
}
/**
- * Constructs a PathFragment, taking ownership of {@code segments} and assuming the {@link
- * String}s within have already been interned.
+ * Creates a new path fragment, where the caller promises that the path is normalized.
*
- * <p>Package-private because it does not perform a defensive copy of the segments array. Used
- * here in PathFragment, and by Path.asFragment() and Path.relativeTo().
+ * <p>WARNING! Make sure the path fragment is in fact already normalized. The rest of the code
+ * assumes this is the case.
*/
- static PathFragment createAlreadyInterned(
- char driveLetter, boolean isAbsolute, String[] segments) {
- return HELPER.createAlreadyInterned(driveLetter, isAbsolute, segments);
- }
-
- /** Returns whether the current {@code path} contains a path separator. */
- static boolean containsSeparator(String path) {
- return HELPER.containsSeparatorChar(path);
+ public static PathFragment createAlreadyNormalized(String normalizedPath) {
+ int driveStrLength = OS.getDriveStrLength(normalizedPath);
+ return createAlreadyNormalized(normalizedPath, driveStrLength);
}
/**
- * Construct a PathFragment from a sequence of other PathFragments. The new fragment will be
- * absolute iff the first fragment was absolute.
+ * Creates a new path fragment, where the caller promises that the path is normalized.
+ *
+ * <p>Should only be used internally.
*/
- // TODO(bazel-team): Most usages of this method are wasteful from a garbage perspective. Refactor
- // to something better.
- public static PathFragment create(PathFragment first, PathFragment second, PathFragment... more) {
- String[] segments = new String[sumLengths(first, second, more)];
- int offset = 0;
- offset += addSegmentsTo(segments, offset, first);
- offset += addSegmentsTo(segments, offset, second);
- for (PathFragment fragment : more) {
- offset += addSegmentsTo(segments, offset, fragment);
- }
- boolean isAbsolute = first.isAbsolute();
- char driveLetter = first.getDriveLetter();
- return HELPER.createAlreadyInterned(driveLetter, isAbsolute, segments);
+ static PathFragment createAlreadyNormalized(String normalizedPath, int driveStrLength) {
+ return new PathFragment(normalizedPath, driveStrLength);
}
- // Medium sized builds can easily hold millions of live PathFragments, so the per-instance size of
- // PathFragment is a concern.
- //
- // We have two oop-sized fields (segments, path), and one 4-byte-sized one (hashCode).
- //
- // If Blaze is run on a jvm with -XX:+UseCompressedOops, each PathFragment instance is 24 bytes
- // and so adding any additional field will increase the per-instance size to at least 32 bytes.
- //
- // If Blaze is run on a jvm with -XX:-UseCompressedOops, each PathFragment instance is 32 bytes
- // and so adding any additional field will increase the per-instance size to at least 40 bytes.
- //
- // Therefore, do not add any additional fields unless you have considered the memory implications.
-
- // The individual path components.
- // Does *not* include the Windows drive letter.
- protected final String[] segments;
-
- // hashCode and path are lazily initialized but semantically immutable.
- private int hashCode;
- private String path;
-
- protected PathFragment(String[] segments) {
- this.segments = segments;
- }
-
- private static int addSegmentsTo(String[] segments, int offset, PathFragment fragment) {
- int count = fragment.segmentCount();
- System.arraycopy(fragment.segments, 0, segments, offset, count);
- return count;
+ /** This method expects path to already be normalized. */
+ private PathFragment(String path, int driveStrLength) {
+ this.path = Preconditions.checkNotNull(path);
+ this.driveStrLength = driveStrLength;
}
- private static int sumLengths(PathFragment first, PathFragment second, PathFragment[] more) {
- int total = first.segmentCount() + second.segmentCount();
- for (PathFragment fragment : more) {
- total += fragment.segmentCount();
- }
- return total;
+ public String getPathString() {
+ return path;
}
- protected Object writeReplace() {
- return new PathFragmentSerializationProxy(toString());
+ public boolean isEmpty() {
+ return path.isEmpty();
}
- protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
- throw new InvalidObjectException("Serialization is allowed only by proxy");
+ int getDriveStrLength() {
+ return driveStrLength;
}
/**
- * Returns the path string using '/' as the name-separator character. Returns "" if the path
- * is both relative and empty.
+ * If called on a {@link PathFragment} instance for a mount name (eg. '/' or 'C:/'), the empty
+ * string is returned.
+ *
+ * <p>This operation allocates a new string.
*/
- public String getPathString() {
- // Double-checked locking works, even without volatile, because path is a String, according to:
- // http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
- if (path == null) {
- synchronized (this) {
- if (path == null) {
- path = StringCanonicalizer.intern(joinSegments(HELPER.getPrimarySeparatorChar()));
- }
- }
- }
- return path;
+ public String getBaseName() {
+ int lastSeparator = path.lastIndexOf(SEPARATOR_CHAR);
+ return lastSeparator < driveStrLength
+ ? path.substring(driveStrLength)
+ : path.substring(lastSeparator + 1);
}
/**
- * Returns "." if the path fragment is both relative and empty, or {@link
- * #getPathString} otherwise.
+ * Returns a {@link PathFragment} instance representing the relative path between this {@link
+ * PathFragment} and the given {@link PathFragment}.
+ *
+ * <p>If the passed path is absolute it is returned untouched. This can be useful to resolve
+ * symlinks.
*/
- // TODO(bazel-team): Change getPathString to do this - this behavior makes more sense.
- public String getSafePathString() {
- return (!isAbsolute() && (segmentCount() == 0)) ? "." : getPathString();
+ public PathFragment getRelative(PathFragment other) {
+ Preconditions.checkNotNull(other);
+ // Fast-path: The path fragment is already normal, use cheaper normalization check
+ String otherStr = other.path;
+ return getRelative(otherStr, other.getDriveStrLength(), OS.needsToNormalizeSuffix(otherStr));
}
/**
- * Returns the path string using '/' as the name-separator character, but do so in a way
- * unambiguously recognizable as path. In other words, return "." for relative and empty paths,
- * and prefix relative paths with one segment by "./".
+ * Returns a {@link PathFragment} instance representing the relative path between this {@link
+ * PathFragment} and the given path.
*
- * <p>In this way, a shell will always interpret such a string as path (absolute or relative to
- * the working directory) and not as command to be searched for in the search path.
+ * <p>See {@link #getRelative(PathFragment)} for details.
*/
- public String getCallablePathString() {
- if (isAbsolute()) {
- return getPathString();
- } else if (segmentCount() == 0) {
- return ".";
- } else if (segmentCount() == 1) {
- return "." + HELPER.getPrimarySeparatorChar() + getPathString();
- } else {
- return getPathString();
- }
+ public PathFragment getRelative(String other) {
+ Preconditions.checkNotNull(other);
+ return getRelative(other, OS.getDriveStrLength(other), OS.needsToNormalize(other));
}
- /**
- * Throws {@link IllegalArgumentException} if {@code paths} contains any paths that
- * are equal to {@code startingWithPath} or that are not beneath {@code startingWithPath}.
- */
- public static void checkAllPathsAreUnder(Iterable<PathFragment> paths,
- PathFragment startingWithPath) {
- for (PathFragment path : paths) {
- Preconditions.checkArgument(
- !path.equals(startingWithPath) && path.startsWith(startingWithPath),
- "%s is not beneath %s", path, startingWithPath);
+ private PathFragment getRelative(String other, int otherDriveStrLength, int normalizationLevel) {
+ if (path.isEmpty()) {
+ return create(other);
}
- }
-
- private String joinSegments(char separatorChar) {
- if (segments.length == 0 && isAbsolute()) {
- return windowsVolume() + ROOT_DIR;
+ if (other.isEmpty()) {
+ return this;
}
-
- // Profile driven optimization:
- // Preallocate a size determined by the number of segments, so that
- // we do not have to expand the capacity of the StringBuilder.
- // Heuristically, this estimate is right for about 99% of the time.
- int estimateSize =
- ((getDriveLetter() != '\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());
- }
- boolean initialSegment = true;
- for (String segment : segments) {
- if (!initialSegment || isAbsolute()) {
- result.append(separatorChar);
- }
- initialSegment = false;
- result.append(segment);
+ // This is an absolute path, simply return it
+ if (otherDriveStrLength > 0) {
+ String normalizedPath =
+ normalizationLevel != OsPathPolicy.NORMALIZED
+ ? OS.normalize(other, normalizationLevel)
+ : other;
+ return new PathFragment(normalizedPath, otherDriveStrLength);
+ }
+ String newPath;
+ if (path.length() == driveStrLength) {
+ newPath = path + other;
+ } else {
+ newPath = path + '/' + other;
}
- return result.toString();
+ newPath =
+ normalizationLevel != OsPathPolicy.NORMALIZED
+ ? OS.normalize(newPath, normalizationLevel)
+ : newPath;
+ return new PathFragment(newPath, driveStrLength);
}
- /**
- * Return true iff none of the segments are either "." or "..".
- */
- public boolean isNormalized() {
- for (String segment : segments) {
- if (segment.equals(".") || segment.equals("..")) {
- return false;
- }
+ public PathFragment getChild(String baseName) {
+ checkBaseName(baseName);
+ String newPath;
+ if (path.length() == driveStrLength) {
+ newPath = path + baseName;
+ } else {
+ newPath = path + '/' + baseName;
}
- return true;
- }
-
- public static boolean isNormalized(String path) {
- return PathFragment.create(path).isNormalized();
+ return new PathFragment(newPath, driveStrLength);
}
/**
- * Normalizes the path fragment: removes "." and ".." segments if possible
- * (if there are too many ".." segments, the resulting PathFragment will still
- * start with "..").
+ * Returns the parent directory of this {@link PathFragment}.
+ *
+ * <p>If this is called on an single directory for a relative path, this returns an empty relative
+ * path. If it's called on a root (like '/') or the empty string, it returns null.
*/
- public PathFragment normalize() {
- String[] scratchSegments = new String[segments.length];
- int segmentCount = 0;
-
- for (String segment : segments) {
- switch (segment) {
- case ".":
- // Just discard it
- break;
- case "..":
- if (segmentCount > 0 && !scratchSegments[segmentCount - 1].equals("..")) {
- // Remove the last segment, if there is one and it is not "..". This
- // means that the resulting PathFragment can still contain ".."
- // segments at the beginning.
- segmentCount--;
- } else {
- scratchSegments[segmentCount++] = segment;
- }
- break;
- default:
- scratchSegments[segmentCount++] = segment;
+ @Nullable
+ public PathFragment getParentDirectory() {
+ int lastSeparator = path.lastIndexOf(SEPARATOR_CHAR);
+
+ // For absolute paths we need to specially handle when we hit root
+ // Relative paths can't hit this path as driveStrLength == 0
+ if (driveStrLength > 0) {
+ if (lastSeparator < driveStrLength) {
+ if (path.length() > driveStrLength) {
+ String newPath = path.substring(0, driveStrLength);
+ return new PathFragment(newPath, driveStrLength);
+ } else {
+ return null;
+ }
+ }
+ } else {
+ if (lastSeparator == -1) {
+ if (!path.isEmpty()) {
+ return EMPTY_FRAGMENT;
+ } else {
+ return null;
+ }
}
}
-
- if (segmentCount == segments.length) {
- // Optimization, no new PathFragment needs to be created.
- return this;
- }
-
- return HELPER.createAlreadyInterned(
- getDriveLetter(), isAbsolute(), subarray(scratchSegments, 0, segmentCount));
+ String newPath = path.substring(0, lastSeparator);
+ return new PathFragment(newPath, driveStrLength);
}
/**
- * Returns the path formed by appending the relative or absolute path fragment
- * {@code otherFragment} to this path.
+ * Returns the {@link PathFragment} relative to the base {@link PathFragment}.
*
- * <p>If {@code otherFragment} is absolute, the current path will be ignored;
- * otherwise, they will be concatenated. This is a purely syntactic operation,
- * with no path normalization or I/O performed.
+ * <p>For example, <code>FilePath.create("foo/bar/wiz").relativeTo(FilePath.create("foo"))</code>
+ * returns <code>"bar/wiz"</code>.
+ *
+ * <p>If the {@link PathFragment} is not a child of the passed {@link PathFragment} an {@link
+ * IllegalArgumentException} is thrown. In particular, this will happen whenever the two {@link
+ * PathFragment} instances aren't both absolute or both relative.
*/
- public PathFragment getRelative(PathFragment otherFragment) {
- if (otherFragment == EMPTY_FRAGMENT) {
+ public PathFragment relativeTo(PathFragment base) {
+ Preconditions.checkNotNull(base);
+ if (isAbsolute() != base.isAbsolute()) {
+ throw new IllegalArgumentException(
+ "Cannot relativize an absolute and a non-absolute path pair");
+ }
+ String basePath = base.path;
+ if (!OS.startsWith(path, basePath)) {
+ throw new IllegalArgumentException(
+ String.format("Path '%s' is not under '%s', cannot relativize", this, base));
+ }
+ int bn = basePath.length();
+ if (bn == 0) {
return this;
}
-
- if (otherFragment.isAbsolute()) {
- char driveLetter = getDriveLetter();
- return driveLetter == '\0' || otherFragment.getDriveLetter() != '\0'
- ? otherFragment
- : createAlreadyInterned(driveLetter, true, otherFragment.segments);
+ if (path.length() == bn) {
+ return EMPTY_FRAGMENT;
+ }
+ final int lastSlashIndex;
+ if (basePath.charAt(bn - 1) == '/') {
+ lastSlashIndex = bn - 1;
} else {
- return create(this, otherFragment);
+ lastSlashIndex = bn;
+ }
+ if (path.charAt(lastSlashIndex) != '/') {
+ throw new IllegalArgumentException(
+ String.format("Path '%s' is not under '%s', cannot relativize", this, base));
}
+ String newPath = path.substring(lastSlashIndex + 1);
+ return new PathFragment(newPath, 0 /* Always a relative path */);
}
- /**
- * Returns the path formed by appending the relative or absolute string
- * {@code path} to this path.
- *
- * <p>If the given path string is absolute, the current path will be ignored;
- * otherwise, they will be concatenated. This is a purely syntactic operation,
- * with no path normalization or I/O performed.
- */
- public PathFragment getRelative(String path) {
- return getRelative(create(path));
+ public PathFragment relativeTo(String base) {
+ return relativeTo(PathFragment.create(base));
}
/**
- * Returns the path formed by appending the single non-special segment "baseName" to this path.
+ * Returns whether this path is an ancestor of another path.
*
- * <p>You should almost always use {@link #getRelative} instead, which has the same performance
- * characteristics if the given name is a valid base name, and which also works for '.', '..', and
- * strings containing '/'.
+ * <p>If this == other, true is returned.
*
- * @throws IllegalArgumentException if {@code baseName} is not a valid base name according to
- * {@link #checkBaseName}
+ * <p>An absolute path can never be an ancestor of a relative path, and vice versa.
*/
- public PathFragment getChild(String baseName) {
- checkBaseName(baseName);
- baseName = StringCanonicalizer.intern(baseName);
- String[] newSegments = Arrays.copyOf(segments, segments.length + 1);
- newSegments[newSegments.length - 1] = baseName;
- return createAlreadyInterned(getDriveLetter(), isAbsolute(), newSegments);
+ public boolean startsWith(PathFragment other) {
+ Preconditions.checkNotNull(other);
+ if (other.path.length() > path.length()) {
+ return false;
+ }
+ if (driveStrLength != other.driveStrLength) {
+ return false;
+ }
+ if (!OS.startsWith(path, other.path)) {
+ return false;
+ }
+ return path.length() == other.path.length()
+ || other.path.length() == driveStrLength
+ || path.charAt(other.path.length()) == SEPARATOR_CHAR;
}
/**
- * Returns the last segment of this path, or "" for the empty fragment.
+ * Returns true iff {@code suffix}, considered as a list of path segments, is relative and a
+ * suffix of {@code this}, or both are absolute and equal.
+ *
+ * <p>This is a reflexive, transitive, anti-symmetric relation (i.e. a partial order)
*/
- public String getBaseName() {
- return (segments.length == 0) ? "" : segments[segments.length - 1];
+ public boolean endsWith(PathFragment other) {
+ Preconditions.checkNotNull(other);
+ if (other.path.length() > path.length()) {
+ return false;
+ }
+ if (other.isAbsolute()) {
+ return this.equals(other);
+ }
+ if (!OS.endsWith(path, other.path)) {
+ return false;
+ }
+ return path.length() == other.path.length()
+ || other.path.length() == 0
+ || path.charAt(path.length() - other.path.length() - 1) == SEPARATOR_CHAR;
}
- /**
- * Returns the file extension of this path, excluding the period, or "" if there is no extension.
- */
- public String getFileExtension() {
- String baseName = getBaseName();
+ public boolean isAbsolute() {
+ return driveStrLength > 0;
+ }
- int lastIndex = baseName.lastIndexOf('.');
- if (lastIndex != -1) {
- return baseName.substring(lastIndex + 1);
- }
+ public static boolean isAbsolute(String path) {
+ return OS.getDriveStrLength(path) > 0;
+ }
- return "";
+ @Override
+ public String toString() {
+ return path;
}
- /**
- * Returns a relative path fragment to this path, relative to
- * {@code ancestorDirectory}.
- * <p>
- * <code>x.relativeTo(z) == y</code> implies
- * <code>z.getRelative(y) == x</code>.
- * <p>
- * For example, <code>"foo/bar/wiz".relativeTo("foo")</code>
- * returns <code>"bar/wiz"</code>.
- */
- public PathFragment relativeTo(PathFragment ancestorDirectory) {
- String[] ancestorSegments = ancestorDirectory.segments();
- int ancestorLength = ancestorSegments.length;
+ @Override
+ public void repr(SkylarkPrinter printer) {
+ printer.append(path);
+ }
- if (isAbsolute() != ancestorDirectory.isAbsolute() || segments.length < ancestorLength) {
- throw new IllegalArgumentException("PathFragment " + this
- + " is not beneath " + ancestorDirectory);
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
}
-
- if (!HELPER.segmentsEqual(ancestorLength, segments, 0, ancestorSegments)) {
- throw new IllegalArgumentException(
- "PathFragment " + this + " is not beneath " + ancestorDirectory);
+ if (o == null || getClass() != o.getClass()) {
+ return false;
}
-
- int length = segments.length - ancestorLength;
- String[] resultSegments = subarray(segments, ancestorLength, length);
- return createAlreadyInterned('\0', false, resultSegments);
+ return OS.equals(this.path, ((PathFragment) o).path);
}
- /**
- * Returns a relative path fragment to this path, relative to {@code path}.
- */
- public PathFragment relativeTo(String path) {
- return relativeTo(create(path));
+ @Override
+ public int hashCode() {
+ return OS.hash(this.path);
}
- /**
- * Returns a new PathFragment formed by appending {@code newName} to the
- * parent directory. Null is returned iff this method is called on a
- * PathFragment with zero segments. If {@code newName} designates an absolute path,
- * the value of {@code this} will be ignored and a PathFragment corresponding to
- * {@code newName} will be returned. This behavior is consistent with the behavior of
- * {@link #getRelative(String)}.
- */
- public PathFragment replaceName(String newName) {
- return segments.length == 0 ? null : getParentDirectory().getRelative(newName);
+ @Override
+ public int compareTo(PathFragment o) {
+ return OS.compare(this.path, o.path);
}
- /**
- * Returns a path representing the parent directory of this path,
- * or null iff this Path represents the root of the filesystem.
- *
- * <p>Note: This method DOES NOT normalize ".." and "." path segments.
- */
- public PathFragment getParentDirectory() {
- return segments.length == 0 ? null : subFragment(0, segments.length - 1);
- }
+ ////////////////////////////////////////////////////////////////////////
/**
- * Returns true iff {@code prefix}, considered as a list of path segments, is
- * a prefix of {@code this}, and that they are both relative or both
- * absolute.
+ * Returns the number of segments in this path.
*
- * <p>This is a reflexive, transitive, anti-symmetric relation (i.e. a partial
- * order)
+ * <p>This operation is O(N) on the length of the string.
*/
- public boolean startsWith(PathFragment prefix) {
- if (isAbsolute() != prefix.isAbsolute()
- || this.segments.length < prefix.segments.length
- || (isAbsolute() && getDriveLetter() != prefix.getDriveLetter())) {
- return false;
+ public int segmentCount() {
+ int n = path.length();
+ int segmentCount = 0;
+ int i;
+ for (i = driveStrLength; i < n; ++i) {
+ if (path.charAt(i) == SEPARATOR_CHAR) {
+ ++segmentCount;
+ }
}
- return HELPER.segmentsEqual(prefix.segments.length, segments, 0, prefix.segments);
- }
-
- /**
- * Returns true iff {@code suffix}, considered as a list of path segments, is
- * relative and a suffix of {@code this}, or both are absolute and equal.
- *
- * <p>This is a reflexive, transitive, anti-symmetric relation (i.e. a partial
- * order)
- */
- public boolean endsWith(PathFragment suffix) {
- if ((suffix.isAbsolute() && !suffix.equals(this))
- || this.segments.length < suffix.segments.length) {
- return false;
+ // Add last segment if one exists.
+ if (i > driveStrLength) {
+ ++segmentCount;
}
- int offset = this.segments.length - suffix.segments.length;
- return HELPER.segmentsEqual(suffix.segments.length, segments, offset, suffix.segments);
- }
-
- private static String[] subarray(String[] array, int start, int length) {
- String[] subarray = new String[length];
- System.arraycopy(array, start, subarray, 0, length);
- return subarray;
+ return segmentCount;
}
/**
- * Returns a new path fragment that is a sub fragment of this one.
- * The sub fragment begins at the specified <code>beginIndex</code> segment
- * and ends at the segment at index <code>endIndex - 1</code>. Thus the number
- * of segments in the new PathFragment is <code>endIndex - beginIndex</code>.
+ * Returns the specified segment of this path; index must be positive and less than numSegments().
*
- * @param beginIndex the beginning index, inclusive.
- * @param endIndex the ending index, exclusive.
- * @return the specified sub fragment, never null.
- * @exception IndexOutOfBoundsException if the
- * <code>beginIndex</code> is negative, or
- * <code>endIndex</code> is larger than the length of
- * this <code>String</code> object, or
- * <code>beginIndex</code> is larger than
- * <code>endIndex</code>.
+ * <p>This operation is O(N) on the length of the string.
*/
- public PathFragment subFragment(int beginIndex, int endIndex) {
- int count = segments.length;
- if ((beginIndex < 0) || (beginIndex > endIndex) || (endIndex > count)) {
- throw new IndexOutOfBoundsException(String.format("path: %s, beginIndex: %d endIndex: %d",
- toString(), beginIndex, endIndex));
+ public String getSegment(int index) {
+ int n = path.length();
+ int segmentCount = 0;
+ int i;
+ for (i = driveStrLength; i < n && segmentCount < index; ++i) {
+ if (path.charAt(i) == SEPARATOR_CHAR) {
+ ++segmentCount;
+ }
+ }
+ int starti = i;
+ for (; i < n; ++i) {
+ if (path.charAt(i) == SEPARATOR_CHAR) {
+ break;
+ }
}
- boolean isAbsolute = (beginIndex == 0) && isAbsolute();
- return ((beginIndex == 0) && (endIndex == count))
- ? this
- : createAlreadyInterned(
- getDriveLetter(), isAbsolute, subarray(segments, beginIndex, endIndex - beginIndex));
+ // Add last segment if one exists.
+ if (i > driveStrLength) {
+ ++segmentCount;
+ }
+ int endi = i;
+ if (index < 0 || index >= segmentCount) {
+ throw new IllegalArgumentException("Illegal segment index: " + index);
+ }
+ return path.substring(starti, endi);
}
/**
* Returns a new path fragment that is a sub fragment of this one. The sub fragment begins at the
- * specified <code>beginIndex</code> segment and contains the rest of the original path fragment.
+ * specified <code>beginIndex</code> segment and ends at the segment at index <code>endIndex - 1
+ * </code>. Thus the number of segments in the new PathFragment is <code>endIndex - beginIndex
+ * </code>.
+ *
+ * <p>This operation is O(N) on the length of the string.
*
* @param beginIndex the beginning index, inclusive.
+ * @param endIndex the ending index, exclusive.
* @return the specified sub fragment, never null.
* @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>
* endIndex</code> is larger than the length of this <code>String</code> object, or <code>
* beginIndex</code> is larger than <code>endIndex</code>.
*/
- public PathFragment subFragment(int beginIndex) {
- return subFragment(beginIndex, segments.length);
+ public PathFragment subFragment(int beginIndex, int endIndex) {
+ if (beginIndex < 0 || beginIndex > endIndex) {
+ throw new IndexOutOfBoundsException(
+ String.format("path: %s, beginIndex: %d endIndex: %d", toString(), beginIndex, endIndex));
+ }
+ return subFragmentImpl(beginIndex, endIndex);
}
- /**
- * Returns true iff the path represented by this object is absolute.
- *
- * <p>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.
- */
- public abstract boolean isAbsolute();
+ public PathFragment subFragment(int beginIndex) {
+ if (beginIndex < 0) {
+ throw new IndexOutOfBoundsException(
+ String.format("path: %s, beginIndex: %d", toString(), beginIndex));
+ }
+ return subFragmentImpl(beginIndex, -1);
+ }
- public static boolean isAbsolute(String path) {
- return PathFragment.create(path).isAbsolute();
+ private PathFragment subFragmentImpl(int beginIndex, int endIndex) {
+ int n = path.length();
+ int segmentIndex = 0;
+ int i;
+ for (i = driveStrLength; i < n && segmentIndex < beginIndex; ++i) {
+ if (path.charAt(i) == SEPARATOR_CHAR) {
+ ++segmentIndex;
+ }
+ }
+ int starti = i;
+ if (segmentIndex < endIndex) {
+ for (; i < n; ++i) {
+ if (path.charAt(i) == SEPARATOR_CHAR) {
+ ++segmentIndex;
+ if (segmentIndex == endIndex) {
+ break;
+ }
+ }
+ }
+ } else if (endIndex == -1) {
+ i = path.length();
+ }
+ int endi = i;
+ // Add last segment if one exists for verification
+ if (i == n && i > driveStrLength) {
+ ++segmentIndex;
+ }
+ if (beginIndex > segmentIndex || endIndex > segmentIndex) {
+ throw new IndexOutOfBoundsException(
+ String.format("path: %s, beginIndex: %d endIndex: %d", toString(), beginIndex, endIndex));
+ }
+ // If beginIndex is 0 we include the drive. Very odd semantics.
+ int driveStrLength = 0;
+ if (beginIndex == 0) {
+ starti = 0;
+ driveStrLength = this.driveStrLength;
+ endi = Math.max(endi, driveStrLength);
+ }
+ return new PathFragment(path.substring(starti, endi), driveStrLength);
}
/**
@@ -647,62 +487,104 @@ public abstract class PathFragment
* modified.
*/
String[] segments() {
+ int segmentCount = segmentCount();
+ String[] segments = new String[segmentCount];
+ int segmentIndex = 0;
+ int nexti = driveStrLength;
+ int n = path.length();
+ for (int i = driveStrLength; i < n; ++i) {
+ if (path.charAt(i) == SEPARATOR_CHAR) {
+ segments[segmentIndex++] = path.substring(nexti, i);
+ nexti = i + 1;
+ }
+ }
+ // Add last segment if one exists.
+ if (nexti < n) {
+ segments[segmentIndex] = path.substring(nexti);
+ }
return segments;
}
+ /**
+ * Returns a list of the segments.
+ *
+ * <p>This operation is O(N) on the length of the string.
+ */
public ImmutableList<String> getSegments() {
- return ImmutableList.copyOf(segments);
+ return ImmutableList.copyOf(segments());
}
- public abstract String windowsVolume();
-
- /** Return the drive letter or '\0' if not applicable. */
- // TODO(bazel-team): This doesn't need to pollute the PathFragment interface (ditto for
- // windowsVolume).
- public abstract char getDriveLetter();
+ public int getFirstSegment(Set<String> values) {
+ String[] segments = segments();
+ for (int i = 0; i < segments.length; ++i) {
+ if (values.contains(segments[i])) {
+ return i;
+ }
+ }
+ return INVALID_SEGMENT;
+ }
- public boolean isEmpty() {
- return segments.length == 0;
+ /** Returns the path string, or '.' if the path is empty. */
+ public String getSafePathString() {
+ return !path.isEmpty() ? path : ".";
}
/**
- * Returns the number of segments in this path.
+ * Returns the path string using '/' as the name-separator character, but do so in a way
+ * unambiguously recognizable as path. In other words, return "." for relative and empty paths,
+ * and prefix relative paths with one segment by "./".
+ *
+ * <p>In this way, a shell will always interpret such a string as path (absolute or relative to
+ * the working directory) and not as command to be searched for in the search path.
*/
- public int segmentCount() {
- return segments.length;
+ public String getCallablePathString() {
+ if (isAbsolute()) {
+ return path;
+ } else if (path.isEmpty()) {
+ return ".";
+ } else if (path.indexOf(SEPARATOR_CHAR) == -1) {
+ return "." + SEPARATOR_CHAR + path;
+ } else {
+ return path;
+ }
}
/**
- * Returns the specified segment of this path; index must be positive and
- * less than numSegments().
+ * Returns the file extension of this path, excluding the period, or "" if there is no extension.
*/
- public String getSegment(int index) {
- return segments[index];
+ public String getFileExtension() {
+ int n = path.length();
+ for (int i = n - 1; i > driveStrLength; --i) {
+ char c = path.charAt(i);
+ if (c == '.') {
+ return path.substring(i + 1, n);
+ } else if (c == SEPARATOR_CHAR) {
+ break;
+ }
+ }
+ return "";
}
/**
- * Returns the index of the first segment which equals one of the input values
- * or {@link PathFragment#INVALID_SEGMENT} if none of the segments match.
+ * Returns a new PathFragment formed by appending {@code newName} to the parent directory. Null is
+ * returned iff this method is called on a PathFragment with zero segments. If {@code newName}
+ * designates an absolute path, the value of {@code this} will be ignored and a PathFragment
+ * corresponding to {@code newName} will be returned. This behavior is consistent with the
+ * behavior of {@link #getRelative(String)}.
*/
- public int getFirstSegment(Set<String> values) {
- for (int i = 0; i < segments.length; i++) {
- if (values.contains(segments[i])) {
- return i;
- }
- }
- return INVALID_SEGMENT;
+ public PathFragment replaceName(String newName) {
+ PathFragment parent = getParentDirectory();
+ return parent != null ? parent.getRelative(newName) : null;
}
/**
- * Returns true iff this path contains uplevel references "..".
+ * Returns the drive for an absolute path fragment.
+ *
+ * <p>On unix, this will return "/". On Windows it will return the drive letter, like "C:/".
*/
- public boolean containsUplevelReferences() {
- for (String segment : segments) {
- if (segment.equals("..")) {
- return true;
- }
- }
- return false;
+ public String getDriveStr() {
+ Preconditions.checkArgument(isAbsolute());
+ return path.substring(0, driveStrLength);
}
/**
@@ -711,60 +593,129 @@ public abstract class PathFragment
*/
public PathFragment toRelative() {
Preconditions.checkArgument(isAbsolute());
- return HELPER.createAlreadyInterned(getDriveLetter(), false, segments);
+ return new PathFragment(path.substring(driveStrLength), 0);
}
- @Override
- public final int hashCode() {
- // We use the hash code caching strategy employed by java.lang.String. There are three subtle
- // things going on here:
- //
- // (1) We use a value of 0 to indicate that the hash code hasn't been computed and cached yet.
- // 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
- // first one to compute and cache the hash code.
- //
- // (3) Moreover, since 'hashCode' is non-volatile, the cached hash code value written from one
- // thread may not be visible by another. Note that we don't need to worry about multiple
- // inefficient reads of 'hashCode' on the same thread since it's non-volatile.
- //
- // All three of these issues are benign from a correctness perspective; in the end we have no
- // overhead from synchronization, at the cost of potentially computing the hash code more than
- // once.
- if (hashCode == 0) {
- hashCode = computeHashCode();
- }
- return hashCode;
- }
-
- protected abstract int computeHashCode();
-
- @Override
- public abstract boolean equals(Object other);
+ /**
+ * Returns true if this path contains uplevel references "..".
+ *
+ * <p>Since path fragments are normalized, this implies that the uplevel reference is at the start
+ * of the path fragment.
+ */
+ public boolean containsUplevelReferences() {
+ // Path is normalized, so any ".." would have to be at the very start
+ return path.startsWith("..");
+ }
/**
- * Compares two PathFragments using the lexicographical order.
+ * Returns true if the passed path contains uplevel references ".." or single-dot references "."
+ *
+ * <p>This is useful to check a string for normalization before constructing a PathFragment, since
+ * these are always normalized and will throw uplevel references away.
*/
- @Override
- public int compareTo(PathFragment p2) {
- return HELPER.compare(this, p2);
+ public static boolean isNormalized(String path) {
+ return isNormalizedImpl(path, true /* lookForSameLevelReferences */);
}
- @Override
- public String toString() {
- return getPathString();
+ /**
+ * Returns true if the passed path contains uplevel references "..".
+ *
+ * <p>This is useful to check a string for '..' segments before constructing a PathFragment, since
+ * these are always normalized and will throw uplevel references away.
+ */
+ public static boolean containsUplevelReferences(String path) {
+ return !isNormalizedImpl(path, false /* lookForSameLevelReferences */);
+ }
+
+ private enum NormalizedImplState {
+ Base, /* No particular state, eg. an 'a' or 'L' character */
+ Separator, /* We just saw a separator */
+ Dot, /* We just saw a dot after a separator */
+ DotDot, /* We just saw two dots after a separator */
+ }
+
+ private static boolean isNormalizedImpl(String path, boolean lookForSameLevelReferences) {
+ // Starting state is equivalent to having just seen a separator
+ NormalizedImplState state = NormalizedImplState.Separator;
+ int n = path.length();
+ for (int i = 0; i < n; ++i) {
+ char c = path.charAt(i);
+ boolean isSeparator = OS.isSeparator(c);
+ switch (state) {
+ case Base:
+ if (isSeparator) {
+ state = NormalizedImplState.Separator;
+ } else {
+ state = NormalizedImplState.Base;
+ }
+ break;
+ case Separator:
+ if (isSeparator) {
+ state = NormalizedImplState.Separator;
+ } else if (c == '.') {
+ state = NormalizedImplState.Dot;
+ } else {
+ state = NormalizedImplState.Base;
+ }
+ break;
+ case Dot:
+ if (isSeparator) {
+ if (lookForSameLevelReferences) {
+ // "." segment found
+ return false;
+ }
+ state = NormalizedImplState.Separator;
+ } else if (c == '.') {
+ state = NormalizedImplState.DotDot;
+ } else {
+ state = NormalizedImplState.Base;
+ }
+ break;
+ case DotDot:
+ if (isSeparator) {
+ // ".." segment found
+ return false;
+ } else {
+ state = NormalizedImplState.Base;
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unhandled state: " + state);
+ }
+ }
+ // The character just after the string is equivalent to a separator
+ switch (state) {
+ case Dot:
+ if (lookForSameLevelReferences) {
+ // "." segment found
+ return false;
+ }
+ break;
+ case DotDot:
+ return false;
+ default:
+ }
+ return true;
}
- @Override
- public void repr(SkylarkPrinter printer) {
- printer.append(getPathString());
+ /**
+ * Throws {@link IllegalArgumentException} if {@code paths} contains any paths that are equal to
+ * {@code startingWithPath} or that are not beneath {@code startingWithPath}.
+ */
+ public static void checkAllPathsAreUnder(
+ Iterable<PathFragment> paths, PathFragment startingWithPath) {
+ for (PathFragment path : paths) {
+ Preconditions.checkArgument(
+ !path.equals(startingWithPath) && path.startsWith(startingWithPath),
+ "%s is not beneath %s",
+ path,
+ startingWithPath);
+ }
}
@Override
public String filePathForFileTypeMatcher() {
- return getBaseName();
+ return path;
}
@Override
@@ -783,25 +734,13 @@ public abstract class PathFragment
@Override
public void serialize(PathFragment pathFragment, CodedOutputStream codedOut)
throws IOException, SerializationException {
- codedOut.writeInt32NoTag(pathFragment.getDriveLetter());
- codedOut.writeBoolNoTag(pathFragment.isAbsolute());
- codedOut.writeInt32NoTag(pathFragment.segmentCount());
- for (int i = 0; i < pathFragment.segmentCount(); i++) {
- stringCodec.serialize(pathFragment.getSegment(i), codedOut);
- }
+ stringCodec.serialize(pathFragment.getPathString(), codedOut);
}
@Override
public PathFragment deserialize(CodedInputStream codedIn)
throws IOException, SerializationException {
- char driveLetter = (char) codedIn.readInt32();
- boolean isAbsolute = codedIn.readBool();
- int segmentCount = codedIn.readInt32();
- String[] segments = new String[segmentCount];
- for (int i = 0; i < segmentCount; i++) {
- segments[i] = stringCodec.deserialize(codedIn);
- }
- return PathFragment.create(driveLetter, isAbsolute, segments);
+ return PathFragment.createAlreadyNormalized(stringCodec.deserialize(codedIn));
}
}
@@ -816,4 +755,8 @@ public abstract class PathFragment
throw new IllegalArgumentException("baseName must not contain a slash: '" + baseName + "'");
}
}
+
+ private Object writeReplace() {
+ return new PathFragmentSerializationProxy(path);
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathFragmentSerializationProxy.java b/src/main/java/com/google/devtools/build/lib/vfs/PathFragmentSerializationProxy.java
index 39aefd1318..4ba66be7fc 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/PathFragmentSerializationProxy.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/PathFragmentSerializationProxy.java
@@ -17,7 +17,6 @@ import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutput;
-
/**
* A helper proxy for serializing immutable {@link PathFragment} objects.
*/
@@ -44,7 +43,7 @@ public final class PathFragmentSerializationProxy implements Externalizable {
}
private Object readResolve() {
- return PathFragment.create(pathFragmentString);
+ return PathFragment.createAlreadyNormalized(pathFragmentString);
}
}
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
deleted file mode 100644
index fd783c5929..0000000000
--- a/src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java
+++ /dev/null
@@ -1,82 +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.vfs;
-
-import com.google.common.base.Preconditions;
-import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
-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/RootedPath.java b/src/main/java/com/google/devtools/build/lib/vfs/RootedPath.java
index 9182f10c0f..09ab3139a4 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
@@ -30,9 +30,8 @@ import java.util.Objects;
* <p>Two {@link RootedPath}s are considered equal iff they have equal roots and equal relative
* paths.
*
- * <p>TODO(bazel-team): refactor Artifact to use this instead of Root. TODO(bazel-team): use an
- * opaque root representation so as to not expose the absolute path to clients via #asPath or
- * #getRoot.
+ * <p>TODO(bazel-team): use an opaque root representation so as to not expose the absolute path to
+ * clients via #asPath or #getRoot.
*/
public class RootedPath implements Serializable {
@@ -48,7 +47,7 @@ public class RootedPath implements Serializable {
rootRelativePath,
root);
this.root = root;
- this.rootRelativePath = rootRelativePath.normalize();
+ this.rootRelativePath = rootRelativePath;
this.path = root.getRelative(this.rootRelativePath);
}
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 d924e19d45..a929f1435d 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
@@ -15,12 +15,14 @@
package com.google.devtools.build.lib.vfs;
import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -42,12 +44,21 @@ import javax.annotation.Nullable;
* are not currently supported.
*/
@ThreadSafety.ThreadSafe
-public class UnionFileSystem extends FileSystem {
+public final class UnionFileSystem extends FileSystem {
- // Prefix trie index, allowing children to easily inherit prefix mappings
- // of their parents.
- // This does not currently handle unicode filenames.
- private final PathTrie<FileSystem> pathDelegate;
+ private static class FileSystemAndPrefix {
+ final PathFragment prefix;
+ final FileSystem fileSystem;
+
+ public FileSystemAndPrefix(PathFragment prefix, FileSystem fileSystem) {
+ this.prefix = prefix;
+ this.fileSystem = fileSystem;
+ }
+ }
+
+ // List of file systems and their mappings, sorted by prefix length descending.
+ private final List<FileSystemAndPrefix> fileSystems;
+ private final FileSystem rootFileSystem;
// True if the file path is case-sensitive on all the FileSystem
// or False if they are all case-insensitive, otherwise error.
@@ -65,11 +76,12 @@ public class UnionFileSystem extends FileSystem {
Preconditions.checkNotNull(rootFileSystem);
Preconditions.checkArgument(rootFileSystem != this, "Circular root filesystem.");
Preconditions.checkArgument(
- !prefixMapping.containsKey(PathFragment.EMPTY_FRAGMENT),
+ prefixMapping.keySet().stream().noneMatch(p -> p.getPathString().equals("/")),
"Attempted to specify an explicit root prefix mapping; "
+ "please use the rootFileSystem argument instead.");
- this.pathDelegate = new PathTrie<>();
+ this.fileSystems = new ArrayList<>();
+ this.rootFileSystem = rootFileSystem;
this.isCaseSensitive = rootFileSystem.isFilePathCaseSensitive();
for (Map.Entry<PathFragment, FileSystem> prefix : prefixMapping.entrySet()) {
@@ -80,9 +92,13 @@ public class UnionFileSystem extends FileSystem {
PathFragment prefixPath = prefix.getKey();
// Extra slash prevents within-directory mappings, which Path can't handle.
- pathDelegate.put(prefixPath, delegate);
+ fileSystems.add(new FileSystemAndPrefix(prefixPath, delegate));
}
- pathDelegate.put(PathFragment.ROOT_FRAGMENT, rootFileSystem);
+ // Order by length descending. This ensures that more specific mapping takes precedence
+ // when we try to find the file system of a given path.
+ Comparator<FileSystemAndPrefix> comparator =
+ Comparator.comparing(f -> f.prefix.getPathString().length());
+ fileSystems.sort(comparator.reversed());
}
/**
@@ -92,19 +108,24 @@ public class UnionFileSystem extends FileSystem {
* @param path the {@link Path} to map to a filesystem
* @throws IllegalArgumentException if no delegate exists for the path
*/
- protected FileSystem getDelegate(Path path) {
+ FileSystem getDelegate(Path path) {
Preconditions.checkNotNull(path);
- FileSystem immediateDelegate = pathDelegate.get(path.asFragment());
-
- // Should never actually happen if the root delegate is present.
- Preconditions.checkNotNull(immediateDelegate, "No delegate filesystem exists for %s", path);
- return immediateDelegate;
+ FileSystem delegate = null;
+ // Linearly iterate over each mapped file system and find the one that handles this path.
+ // For small number of mappings, this will be more efficient than using a trie
+ for (FileSystemAndPrefix fileSystemAndPrefix : this.fileSystems) {
+ if (path.startsWith(fileSystemAndPrefix.prefix)) {
+ delegate = fileSystemAndPrefix.fileSystem;
+ break;
+ }
+ }
+ return delegate != null ? delegate : rootFileSystem;
}
// Associates the path with the root of the given delegate filesystem.
// Necessary to avoid null pointer problems inside of the delegates.
- protected Path adjustPath(Path path, FileSystem delegate) {
- return delegate.getPath(path.asFragment());
+ Path adjustPath(Path path, FileSystem delegate) {
+ return delegate.getPath(path.getPathString());
}
/**
@@ -344,12 +365,7 @@ public class UnionFileSystem extends FileSystem {
path = internalResolveSymlink(path);
FileSystem delegate = getDelegate(path);
Path resolvedPath = adjustPath(path, delegate);
- Collection<Path> entries = resolvedPath.getDirectoryEntries();
- Collection<String> result = Lists.newArrayListWithCapacity(entries.size());
- for (Path entry : entries) {
- result.add(entry.getBaseName());
- }
- return result;
+ return delegate.getDirectoryEntries(resolvedPath);
}
// No need for the more complex logic of getDirectoryEntries; it calls it implicitly.
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixOsPathPolicy.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixOsPathPolicy.java
new file mode 100644
index 0000000000..8576643267
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixOsPathPolicy.java
@@ -0,0 +1,138 @@
+// Copyright 2017 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.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+
+@VisibleForTesting
+class UnixOsPathPolicy implements OsPathPolicy {
+
+ static final UnixOsPathPolicy INSTANCE = new UnixOsPathPolicy();
+ private static final Splitter PATH_SPLITTER = Splitter.onPattern("/+").omitEmptyStrings();
+
+ @Override
+ public int needsToNormalize(String path) {
+ int n = path.length();
+ int dotCount = 0;
+ char prevChar = 0;
+ for (int i = 0; i < n; i++) {
+ char c = path.charAt(i);
+ if (c == '\\') {
+ return NEEDS_NORMALIZE;
+ }
+ if (c == '/') {
+ if (prevChar == '/') {
+ return NEEDS_NORMALIZE;
+ }
+ if (dotCount == 1 || dotCount == 2) {
+ return NEEDS_NORMALIZE;
+ }
+ }
+ dotCount = c == '.' ? dotCount + 1 : 0;
+ prevChar = c;
+ }
+ if (prevChar == '/' || dotCount == 1 || dotCount == 2) {
+ return NEEDS_NORMALIZE;
+ }
+ return NORMALIZED;
+ }
+
+ @Override
+ public int needsToNormalizeSuffix(String normalizedSuffix) {
+ // We know that the string is normalized
+ // In this case only suffixes starting with ".." may cause
+ // normalization once concatenated with other strings
+ return normalizedSuffix.startsWith("..") ? NEEDS_NORMALIZE : NORMALIZED;
+ }
+
+ @Override
+ public String normalize(String path, int normalizationLevel) {
+ if (normalizationLevel == NORMALIZED) {
+ return path;
+ }
+ if (path.isEmpty()) {
+ return path;
+ }
+ boolean isAbsolute = path.charAt(0) == '/';
+ StringBuilder sb = new StringBuilder(path.length());
+ if (isAbsolute) {
+ sb.append('/');
+ }
+ String[] segments = Iterables.toArray(PATH_SPLITTER.splitToList(path), String.class);
+ int segmentCount = Utils.removeRelativePaths(segments, 0, isAbsolute);
+ for (int i = 0; i < segmentCount; ++i) {
+ sb.append(segments[i]);
+ sb.append('/');
+ }
+ if (segmentCount > 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int getDriveStrLength(String path) {
+ if (path.length() == 0) {
+ return 0;
+ }
+ return (path.charAt(0) == '/') ? 1 : 0;
+ }
+
+ @Override
+ public int compare(String s1, String s2) {
+ return s1.compareTo(s2);
+ }
+
+ @Override
+ public int compare(char c1, char c2) {
+ return Character.compare(c1, c2);
+ }
+
+ @Override
+ public boolean equals(String s1, String s2) {
+ return s1.equals(s2);
+ }
+
+ @Override
+ public int hash(String s) {
+ return s.hashCode();
+ }
+
+ @Override
+ public boolean startsWith(String path, String prefix) {
+ return path.startsWith(prefix);
+ }
+
+ @Override
+ public boolean endsWith(String path, String suffix) {
+ return path.endsWith(suffix);
+ }
+
+ @Override
+ public char getSeparator() {
+ return '/';
+ }
+
+ @Override
+ public boolean isSeparator(char c) {
+ return c == '/';
+ }
+
+ @Override
+ public boolean isCaseSensitive() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixPathFragment.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixPathFragment.java
deleted file mode 100644
index b581d4375a..0000000000
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixPathFragment.java
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2017 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.common.base.Preconditions;
-import java.io.InvalidObjectException;
-import java.io.ObjectInputStream;
-
-/**
- * Abstract base class for {@link PathFragment} instances that will be allocated when Blaze is run
- * on a non-Windows platform.
- */
-abstract class UnixPathFragment extends PathFragment {
- static final Helper HELPER = new Helper();
- /**
- * We have two concrete subclasses with zero per-instance additional memory overhead. Do not add
- * any fields. See the comment on memory use in PathFragment for details on the current
- * per-instance memory usage.
- */
-
- protected UnixPathFragment(String[] segments) {
- super(segments);
- }
-
- @Override
- protected int computeHashCode() {
- int h = 0;
- for (String segment : segments) {
- int segmentHash = segment.hashCode();
- h = h * 31 + segmentHash;
- }
- return h;
- }
-
- @Override
- public String windowsVolume() {
- return "";
- }
-
- @Override
- public char getDriveLetter() {
- return '\0';
- }
-
- private static class Helper extends PathFragment.Helper {
- private static final char SEPARATOR_CHAR = '/';
-
- @Override
- PathFragment create(String path) {
- boolean isAbsolute = path.length() > 0 && isSeparator(path.charAt(0));
- return isAbsolute
- ? new AbsoluteUnixPathFragment(segment(path, 1))
- : new RelativeUnixPathFragment(segment(path, 0));
- }
-
- @Override
- PathFragment createAlreadyInterned(char driveLetter, boolean isAbsolute, String[] segments) {
- Preconditions.checkState(driveLetter == '\0', driveLetter);
- return isAbsolute
- ? new AbsoluteUnixPathFragment(segments)
- : new RelativeUnixPathFragment(segments);
- }
-
- @Override
- char getPrimarySeparatorChar() {
- return SEPARATOR_CHAR;
- }
-
- @Override
- boolean isSeparator(char c) {
- return c == SEPARATOR_CHAR;
- }
-
- @Override
- boolean containsSeparatorChar(String path) {
- return path.indexOf(SEPARATOR_CHAR) != -1;
- }
-
- @Override
- boolean segmentsEqual(int length, String[] segments1, int offset1, String[] segments2) {
- if ((segments1.length - offset1) < length || segments2.length < length) {
- return false;
- }
- for (int i = 0; i < length; ++i) {
- String seg1 = segments1[i + offset1];
- String seg2 = segments2[i];
- if ((seg1 == null) != (seg2 == null)) {
- return false;
- }
- if (seg1 == null) {
- continue;
- }
- if (!seg1.equals(seg2)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- protected int compare(PathFragment pathFragment1, PathFragment pathFragment2) {
- if (pathFragment1.isAbsolute() != pathFragment2.isAbsolute()) {
- return pathFragment1.isAbsolute() ? -1 : 1;
- }
- String[] segments1 = pathFragment1.segments();
- String[] segments2 = pathFragment2.segments();
- int len1 = segments1.length;
- int len2 = segments2.length;
- int n = Math.min(len1, len2);
- for (int i = 0; i < n; i++) {
- String seg1 = segments1[i];
- String seg2 = segments2[i];
- int cmp = seg1.compareTo(seg2);
- if (cmp != 0) {
- return cmp;
- }
- }
- return len1 - len2;
- }
- }
-
- private static final class AbsoluteUnixPathFragment extends UnixPathFragment {
- private AbsoluteUnixPathFragment(String[] segments) {
- super(segments);
- }
-
- @Override
- public boolean isAbsolute() {
- return true;
- }
-
- @Override
- protected int computeHashCode() {
- int h = Boolean.TRUE.hashCode();
- h = h * 31 + super.computeHashCode();
- return h;
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof AbsoluteUnixPathFragment)) {
- return false;
- }
- if (this == other) {
- return true;
- }
- AbsoluteUnixPathFragment otherAbsoluteUnixPathFragment = (AbsoluteUnixPathFragment) other;
- return HELPER.segmentsEqual(this.segments, otherAbsoluteUnixPathFragment.segments);
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected Object writeReplace() {
- return super.writeReplace();
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
- super.readObject(stream);
- }
- }
-
- private static final class RelativeUnixPathFragment extends UnixPathFragment {
- private RelativeUnixPathFragment(String[] segments) {
- super(segments);
- }
-
- @Override
- public boolean isAbsolute() {
- return false;
- }
-
- @Override
- protected int computeHashCode() {
- int h = Boolean.FALSE.hashCode();
- h = h * 31 + super.computeHashCode();
- return h;
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof RelativeUnixPathFragment)) {
- return false;
- }
- if (this == other) {
- return true;
- }
- RelativeUnixPathFragment otherRelativeUnixPathFragment = (RelativeUnixPathFragment) other;
- return HELPER.segmentsEqual(this.segments, otherRelativeUnixPathFragment.segments);
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected Object writeReplace() {
- return super.writeReplace();
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
- super.readObject(stream);
- }
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/WindowsOsPathPolicy.java b/src/main/java/com/google/devtools/build/lib/vfs/WindowsOsPathPolicy.java
new file mode 100644
index 0000000000..f420683b75
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/vfs/WindowsOsPathPolicy.java
@@ -0,0 +1,240 @@
+// Copyright 2017 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.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.windows.WindowsShortPath;
+import com.google.devtools.build.lib.windows.jni.WindowsFileOperations;
+import java.io.IOException;
+
+@VisibleForTesting
+class WindowsOsPathPolicy implements OsPathPolicy {
+
+ static final WindowsOsPathPolicy INSTANCE = new WindowsOsPathPolicy();
+
+ static final int NEEDS_SHORT_PATH_NORMALIZATION = NEEDS_NORMALIZE + 1;
+
+ private static final Splitter WINDOWS_PATH_SPLITTER =
+ Splitter.onPattern("[\\\\/]+").omitEmptyStrings();
+
+ private final ShortPathResolver shortPathResolver;
+
+ interface ShortPathResolver {
+ String resolveShortPath(String path);
+ }
+
+ static class DefaultShortPathResolver implements ShortPathResolver {
+ @Override
+ public String resolveShortPath(String path) {
+ try {
+ return WindowsFileOperations.getLongPath(path);
+ } catch (IOException e) {
+ return path;
+ }
+ }
+ }
+
+ WindowsOsPathPolicy() {
+ this(new DefaultShortPathResolver());
+ }
+
+ WindowsOsPathPolicy(ShortPathResolver shortPathResolver) {
+ this.shortPathResolver = shortPathResolver;
+ }
+
+ @Override
+ public int needsToNormalize(String path) {
+ int n = path.length();
+ int normalizationLevel = NORMALIZED;
+ int dotCount = 0;
+ char prevChar = 0;
+ int segmentBeginIndex = 0; // The start index of the current path index
+ boolean segmentHasShortPathChar = false; // Triggers more expensive short path regex test
+ for (int i = 0; i < n; i++) {
+ char c = path.charAt(i);
+ if (isSeparator(c)) {
+ if (c == '\\') {
+ normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
+ }
+ // No need to check for '\\' here because that already causes normalization
+ if (prevChar == '/') {
+ normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
+ }
+ if (dotCount == 1 || dotCount == 2) {
+ normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
+ }
+ if (segmentHasShortPathChar) {
+ if (WindowsShortPath.isShortPath(path.substring(segmentBeginIndex, i))) {
+ normalizationLevel = Math.max(normalizationLevel, NEEDS_SHORT_PATH_NORMALIZATION);
+ }
+ }
+ segmentBeginIndex = i + 1;
+ segmentHasShortPathChar = false;
+ } else if (c == '~') {
+ // This path segment might be a Windows short path segment
+ segmentHasShortPathChar = true;
+ }
+ dotCount = c == '.' ? dotCount + 1 : 0;
+ prevChar = c;
+ }
+ if (segmentHasShortPathChar) {
+ if (WindowsShortPath.isShortPath(path.substring(segmentBeginIndex))) {
+ normalizationLevel = Math.max(normalizationLevel, NEEDS_SHORT_PATH_NORMALIZATION);
+ }
+ }
+ if ((n > 1 && isSeparator(prevChar)) || dotCount == 1 || dotCount == 2) {
+ normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
+ }
+ return normalizationLevel;
+ }
+
+ @Override
+ public int needsToNormalizeSuffix(String normalizedSuffix) {
+ // On Windows, all bets are off because of short paths, so we have to check the entire string
+ return needsToNormalize(normalizedSuffix);
+ }
+
+ @Override
+ public String normalize(String path, int normalizationLevel) {
+ if (normalizationLevel == NORMALIZED) {
+ return path;
+ }
+ if (normalizationLevel == NEEDS_SHORT_PATH_NORMALIZATION) {
+ String resolvedPath = shortPathResolver.resolveShortPath(path);
+ if (resolvedPath != null) {
+ path = resolvedPath;
+ }
+ }
+ String[] segments = Iterables.toArray(WINDOWS_PATH_SPLITTER.splitToList(path), String.class);
+ int driveStrLength = getDriveStrLength(path);
+ boolean isAbsolute = driveStrLength > 0;
+ int segmentSkipCount = isAbsolute && driveStrLength > 1 ? 1 : 0;
+
+ StringBuilder sb = new StringBuilder(path.length());
+ if (isAbsolute) {
+ char c = path.charAt(0);
+ if (isSeparator(c)) {
+ sb.append('/');
+ } else {
+ sb.append(Character.toUpperCase(c));
+ sb.append(":/");
+ }
+ }
+ int segmentCount = Utils.removeRelativePaths(segments, segmentSkipCount, isAbsolute);
+ for (int i = 0; i < segmentCount; ++i) {
+ sb.append(segments[i]);
+ sb.append('/');
+ }
+ if (segmentCount > 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int getDriveStrLength(String path) {
+ int n = path.length();
+ if (n == 0) {
+ return 0;
+ }
+ char c0 = path.charAt(0);
+ if (isSeparator(c0)) {
+ return 1;
+ }
+ if (n < 3) {
+ return 0;
+ }
+ char c1 = path.charAt(1);
+ char c2 = path.charAt(2);
+ if (isDriveLetter(c0) && c1 == ':' && isSeparator(c2)) {
+ return 3;
+ }
+ return 0;
+ }
+
+ private static boolean isDriveLetter(char c) {
+ return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
+ }
+
+ @Override
+ public int compare(String s1, String s2) {
+ // Windows is case-insensitive
+ return s1.compareToIgnoreCase(s2);
+ }
+
+ @Override
+ public int compare(char c1, char c2) {
+ return Character.compare(Character.toLowerCase(c1), Character.toLowerCase(c2));
+ }
+
+ @Override
+ public boolean equals(String s1, String s2) {
+ return s1.equalsIgnoreCase(s2);
+ }
+
+ @Override
+ public int hash(String s) {
+ // Windows is case-insensitive
+ return s.toLowerCase().hashCode();
+ }
+
+ @Override
+ public boolean startsWith(String path, String prefix) {
+ int pathn = path.length();
+ int prefixn = prefix.length();
+ if (pathn < prefixn) {
+ return false;
+ }
+ for (int i = 0; i < prefixn; ++i) {
+ if (Character.toLowerCase(path.charAt(i)) != Character.toLowerCase(prefix.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean endsWith(String path, String suffix) {
+ int pathn = path.length();
+ int suffixLength = suffix.length();
+ if (pathn < suffixLength) {
+ return false;
+ }
+ int offset = pathn - suffixLength;
+ for (int i = 0; i < suffixLength; ++i) {
+ if (Character.toLowerCase(path.charAt(i + offset))
+ != Character.toLowerCase(suffix.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public char getSeparator() {
+ return '/';
+ }
+
+ @Override
+ public boolean isSeparator(char c) {
+ return c == '/' || c == '\\';
+ }
+
+ @Override
+ public boolean isCaseSensitive() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java b/src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java
deleted file mode 100644
index ac3be0f524..0000000000
--- a/src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2017 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 java.io.InvalidObjectException;
-import java.io.ObjectInputStream;
-
-/**
- * Abstract base class for {@link PathFragment} instances that will be allocated when Blaze is run
- * on a Windows platform.
- */
-abstract class WindowsPathFragment extends PathFragment {
- static final Helper HELPER = new Helper();
-
- // The drive letter of an absolute path, eg. 'C' for 'C:/foo'.
- // We deliberately ignore "C:foo" style paths and treat them like a literal "C:foo" path segment.
- protected final char driveLetter;
-
- protected WindowsPathFragment(char driveLetter, String[] segments) {
- super(segments);
- this.driveLetter = driveLetter;
- }
-
- @Override
- public String windowsVolume() {
- return (driveLetter != '\0') ? driveLetter + ":" : "";
- }
-
- @Override
- public char getDriveLetter() {
- return driveLetter;
- }
-
- @Override
- protected int computeHashCode() {
- int h = 0;
- for (String segment : segments) {
- int segmentHash = segment.toLowerCase().hashCode();
- h = h * 31 + segmentHash;
- }
- return h;
- }
-
- private static class Helper extends PathFragment.Helper {
- private static final char SEPARATOR_CHAR = '/';
- // TODO(laszlocsomor): Lots of internal PathFragment operations, e.g. getPathString, use the
- // primary separator char and do not use this.
- private static final char EXTRA_SEPARATOR_CHAR = '\\';
-
- private static boolean isDriveLetter(char c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
- }
-
- @Override
- PathFragment create(String path) {
- char driveLetter =
- path.length() >= 3
- && path.charAt(1) == ':'
- && isSeparator(path.charAt(2))
- && isDriveLetter(path.charAt(0))
- ? Character.toUpperCase(path.charAt(0))
- : '\0';
- if (driveLetter != '\0') {
- path = path.substring(2);
- }
- boolean isAbsolute = path.length() > 0 && isSeparator(path.charAt(0));
- return isAbsolute
- ? new AbsoluteWindowsPathFragment(driveLetter, segment(path, 1))
- : new RelativeWindowsPathFragment(driveLetter, segment(path, 0));
- }
-
- @Override
- PathFragment createAlreadyInterned(char driveLetter, boolean isAbsolute, String[] segments) {
- return isAbsolute
- ? new AbsoluteWindowsPathFragment(driveLetter, segments)
- : new RelativeWindowsPathFragment(driveLetter, segments);
- }
-
- @Override
- char getPrimarySeparatorChar() {
- return SEPARATOR_CHAR;
- }
-
- @Override
- boolean isSeparator(char c) {
- return c == SEPARATOR_CHAR || c == EXTRA_SEPARATOR_CHAR;
- }
-
- @Override
- boolean containsSeparatorChar(String path) {
- // TODO(laszlocsomor): This is inefficient.
- return path.indexOf(SEPARATOR_CHAR) != -1 || path.indexOf(EXTRA_SEPARATOR_CHAR) != -1;
- }
-
- @Override
- boolean segmentsEqual(int length, String[] segments1, int offset1, String[] segments2) {
- if ((segments1.length - offset1) < length || segments2.length < length) {
- return false;
- }
- for (int i = 0; i < length; ++i) {
- String seg1 = segments1[i + offset1];
- String seg2 = segments2[i];
- if ((seg1 == null) != (seg2 == null)) {
- return false;
- }
- if (seg1 == null) {
- continue;
- }
- // TODO(laszlocsomor): The calls to String#toLowerCase are inefficient and potentially
- // repeated too. Also, why not use String#equalsIgnoreCase.
- seg1 = seg1.toLowerCase();
- seg2 = seg2.toLowerCase();
- if (!seg1.equals(seg2)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- protected int compare(PathFragment pathFragment1, PathFragment pathFragment2) {
- if (pathFragment1.isAbsolute() != pathFragment2.isAbsolute()) {
- return pathFragment1.isAbsolute() ? -1 : 1;
- }
- int cmp = Character.compare(pathFragment1.getDriveLetter(), pathFragment2.getDriveLetter());
- if (cmp != 0) {
- return cmp;
- }
- String[] segments1 = pathFragment1.segments();
- String[] segments2 = pathFragment2.segments();
- int len1 = segments1.length;
- int len2 = segments2.length;
- int n = Math.min(len1, len2);
- for (int i = 0; i < n; i++) {
- String seg1 = segments1[i].toLowerCase();
- String seg2 = segments2[i].toLowerCase();
- cmp = seg1.compareTo(seg2);
- if (cmp != 0) {
- return cmp;
- }
- }
- return len1 - len2;
- }
- }
-
- private static final class AbsoluteWindowsPathFragment extends WindowsPathFragment {
- private AbsoluteWindowsPathFragment(char driveLetter, String[] segments) {
- super(driveLetter, segments);
- }
-
- @Override
- public boolean isAbsolute() {
- return true;
- }
-
- @Override
- protected int computeHashCode() {
- int h = Boolean.TRUE.hashCode();
- h = h * 31 + super.computeHashCode();
- h = h * 31 + Character.valueOf(getDriveLetter()).hashCode();
- return h;
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof AbsoluteWindowsPathFragment)) {
- return false;
- }
- if (this == other) {
- return true;
- }
- AbsoluteWindowsPathFragment otherAbsoluteWindowsPathFragment =
- (AbsoluteWindowsPathFragment) other;
- return this.driveLetter == otherAbsoluteWindowsPathFragment.driveLetter
- && HELPER.segmentsEqual(this.segments, otherAbsoluteWindowsPathFragment.segments);
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected Object writeReplace() {
- return super.writeReplace();
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
- super.readObject(stream);
- }
- }
-
- private static final class RelativeWindowsPathFragment extends WindowsPathFragment {
- private RelativeWindowsPathFragment(char driveLetter, String[] segments) {
- super(driveLetter, segments);
- }
-
- @Override
- public boolean isAbsolute() {
- return false;
- }
-
- @Override
- protected int computeHashCode() {
- int h = Boolean.FALSE.hashCode();
- h = h * 31 + super.computeHashCode();
- if (!isEmpty()) {
- h = h * 31 + Character.valueOf(getDriveLetter()).hashCode();
- }
- return h;
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof RelativeWindowsPathFragment)) {
- return false;
- }
- if (this == other) {
- return true;
- }
- RelativeWindowsPathFragment otherRelativeWindowsPathFragment =
- (RelativeWindowsPathFragment) other;
- return isEmpty() && otherRelativeWindowsPathFragment.isEmpty()
- ? true
- : this.driveLetter == otherRelativeWindowsPathFragment.driveLetter
- && HELPER.segmentsEqual(this.segments, otherRelativeWindowsPathFragment.segments);
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected Object writeReplace() {
- return super.writeReplace();
- }
-
- // Java serialization looks for the presence of this method in the concrete class. It is not
- // inherited from the parent class.
- @Override
- protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
- super.readObject(stream);
- }
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
index 8fd21180b4..c2a52a5b3d 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
@@ -350,7 +350,8 @@ public class InMemoryFileSystem extends FileSystem {
Stack<String> stack = new Stack<>();
for (Path p = path; !isRootDirectory(p); p = p.getParentDirectory()) {
- stack.push(p.getBaseName());
+ String name = baseNameOrWindowsDrive(p);
+ stack.push(name);
}
InMemoryContentInfo inode = rootInode;
@@ -381,6 +382,13 @@ public class InMemoryFileSystem extends FileSystem {
for (int ii = segments.size() - 1; ii >= 0; --ii) {
stack.push(segments.get(ii)); // Note this may include ".." segments.
}
+ // Push Windows drive if there is one
+ if (linkTarget.isAbsolute()) {
+ String driveStr = linkTarget.getDriveStr();
+ if (driveStr.length() > 1) {
+ stack.push(driveStr);
+ }
+ }
} else {
inode = child;
}
@@ -412,7 +420,7 @@ public class InMemoryFileSystem extends FileSystem {
private synchronized InMemoryContentInfo getNoFollowStatOrOutOfScopeParent(Path path)
throws IOException {
InMemoryDirectoryInfo dirInfo = getDirectory(path.getParentDirectory());
- return directoryLookup(dirInfo, path.getBaseName(), /*create=*/ false, path);
+ return directoryLookup(dirInfo, baseNameOrWindowsDrive(path), /*create=*/ false, path);
}
/**
@@ -606,7 +614,7 @@ public class InMemoryFileSystem extends FileSystem {
InMemoryDirectoryInfo parent;
synchronized (this) {
parent = getDirectory(path.getParentDirectory());
- InMemoryContentInfo child = parent.getChild(path.getBaseName());
+ InMemoryContentInfo child = parent.getChild(baseNameOrWindowsDrive(path));
if (child != null) { // already exists
if (child.isDirectory()) {
return false;
@@ -618,7 +626,7 @@ public class InMemoryFileSystem extends FileSystem {
InMemoryDirectoryInfo newDir = new InMemoryDirectoryInfo(clock);
newDir.addChild(".", newDir);
newDir.addChild("..", parent);
- insert(parent, path.getBaseName(), newDir, path);
+ insert(parent, baseNameOrWindowsDrive(path), newDir, path);
return true;
}
@@ -649,10 +657,11 @@ public class InMemoryFileSystem extends FileSystem {
synchronized (this) {
InMemoryDirectoryInfo parent = getDirectory(path.getParentDirectory());
- if (parent.getChild(path.getBaseName()) != null) {
+ if (parent.getChild(baseNameOrWindowsDrive(path)) != null) {
throw Error.EEXIST.exception(path);
}
- insert(parent, path.getBaseName(), new InMemoryLinkInfo(clock, targetFragment), path);
+ insert(
+ parent, baseNameOrWindowsDrive(path), new InMemoryLinkInfo(clock, targetFragment), path);
}
}
@@ -703,11 +712,11 @@ public class InMemoryFileSystem extends FileSystem {
synchronized (this) {
InMemoryDirectoryInfo parent = getDirectory(path.getParentDirectory());
- InMemoryContentInfo child = parent.getChild(path.getBaseName());
+ InMemoryContentInfo child = parent.getChild(baseNameOrWindowsDrive(path));
if (child.isDirectory() && child.getSize() > 2) {
throw Error.ENOTEMPTY.exception(path);
}
- unlink(parent, path.getBaseName(), path);
+ unlink(parent, baseNameOrWindowsDrive(path), path);
return true;
}
}
@@ -780,13 +789,13 @@ public class InMemoryFileSystem extends FileSystem {
InMemoryDirectoryInfo sourceParent = getDirectory(sourcePath.getParentDirectory());
InMemoryDirectoryInfo targetParent = getDirectory(targetPath.getParentDirectory());
- InMemoryContentInfo sourceInode = sourceParent.getChild(sourcePath.getBaseName());
+ InMemoryContentInfo sourceInode = sourceParent.getChild(baseNameOrWindowsDrive(sourcePath));
if (sourceInode == null) {
throw Error.ENOENT.exception(sourcePath);
}
- InMemoryContentInfo targetInode = targetParent.getChild(targetPath.getBaseName());
+ InMemoryContentInfo targetInode = targetParent.getChild(baseNameOrWindowsDrive(targetPath));
- unlink(sourceParent, sourcePath.getBaseName(), sourcePath);
+ unlink(sourceParent, baseNameOrWindowsDrive(sourcePath), sourcePath);
try {
// TODO(bazel-team): (2009) test with symbolic links.
@@ -802,15 +811,19 @@ public class InMemoryFileSystem extends FileSystem {
} else if (sourceInode.isDirectory()) {
throw new IOException(sourcePath + " -> " + targetPath + " (" + Error.ENOTDIR + ")");
}
- unlink(targetParent, targetPath.getBaseName(), targetPath);
+ unlink(targetParent, baseNameOrWindowsDrive(targetPath), targetPath);
}
sourceInode.movedTo(targetPath);
- insert(targetParent, targetPath.getBaseName(), sourceInode, targetPath);
+ insert(targetParent, baseNameOrWindowsDrive(targetPath), sourceInode, targetPath);
return;
} catch (IOException e) {
sourceInode.movedTo(sourcePath);
- insert(sourceParent, sourcePath.getBaseName(), sourceInode, sourcePath); // restore source
+ insert(
+ sourceParent,
+ baseNameOrWindowsDrive(sourcePath),
+ sourceInode,
+ sourcePath); // restore source
throw e;
}
}
@@ -828,18 +841,33 @@ public class InMemoryFileSystem extends FileSystem {
synchronized (this) {
InMemoryDirectoryInfo linkParent = getDirectory(linkPath.getParentDirectory());
// Same check used when creating a symbolic link
- if (linkParent.getChild(linkPath.getBaseName()) != null) {
+ if (linkParent.getChild(baseNameOrWindowsDrive(linkPath)) != null) {
throw Error.EEXIST.exception(linkPath);
}
insert(
linkParent,
- linkPath.getBaseName(),
- getDirectory(originalPath.getParentDirectory()).getChild(originalPath.getBaseName()),
+ baseNameOrWindowsDrive(linkPath),
+ getDirectory(originalPath.getParentDirectory())
+ .getChild(baseNameOrWindowsDrive(originalPath)),
linkPath);
}
}
- private boolean isRootDirectory(Path path) {
- return path.isRootDirectory();
+ /**
+ * On Unix the root directory is "/". On Windows there isn't one, so we reach null from
+ * getParentDirectory.
+ */
+ private boolean isRootDirectory(@Nullable Path path) {
+ return path == null || path.getPathString().equals("/");
+ }
+
+ /**
+ * Returns either the base name of the path, or the drive (if referring to a Windows drive).
+ *
+ * <p>This allows the file system to treat windows drives much like directories.
+ */
+ private static String baseNameOrWindowsDrive(Path path) {
+ String name = path.getBaseName();
+ return !name.isEmpty() ? name : path.getDriveStr();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java
index 107f319115..dabbbcefeb 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java
@@ -31,7 +31,7 @@ class InMemoryLinkInfo extends InMemoryContentInfo {
InMemoryLinkInfo(Clock clock, PathFragment linkContent) {
super(clock);
this.linkContent = linkContent;
- this.normalizedLinkContent = linkContent.normalize();
+ this.normalizedLinkContent = linkContent;
}
@Override
@@ -56,7 +56,7 @@ class InMemoryLinkInfo extends InMemoryContentInfo {
@Override
public long getSize() {
- return linkContent.toString().length();
+ return linkContent.getSafePathString().length();
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
index af97e27d3d..961465ec6d 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
@@ -14,15 +14,10 @@
package com.google.devtools.build.lib.windows;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.vfs.FileStatus;
-import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.JavaIoFileSystem;
import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.Path.PathFactory;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.windows.jni.WindowsFileOperations;
import java.io.File;
@@ -31,246 +26,11 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.attribute.DosFileAttributes;
-import java.util.Arrays;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import javax.annotation.Nullable;
/** File system implementation for Windows. */
@ThreadSafe
public class WindowsFileSystem extends JavaIoFileSystem {
- // Properties of 8dot3 (DOS-style) short file names:
- // - they are at most 11 characters long
- // - they have a prefix (before "~") that is {1..6} characters long, may contain numbers, letters,
- // "_", even "~", and maybe even more
- // - they have a "~" after the prefix
- // - have {1..6} numbers after "~" (according to [1] this is only one digit, but MSDN doesn't
- // clarify this), the combined length up till this point is at most 8
- // - they have an optional "." afterwards, and another {0..3} more characters
- // - just because a path looks like a short name it isn't necessarily one; the user may create
- // such names and they'd resolve to themselves
- // [1] https://en.wikipedia.org/wiki/8.3_filename#VFAT_and_Computer-generated_8.3_filenames
- // bullet point (3) (on 2016-12-05)
- @VisibleForTesting
- static final Predicate<String> SHORT_NAME_MATCHER =
- new Predicate<String>() {
- private final Pattern pattern = Pattern.compile("^(.{1,6})~([0-9]{1,6})(\\..{0,3}){0,1}");
-
- @Override
- public boolean apply(@Nullable String input) {
- Matcher m = pattern.matcher(input);
- return input.length() <= 12
- && m.matches()
- && m.groupCount() >= 2
- && (m.group(1).length() + m.group(2).length()) < 8; // the "~" makes it at most 8
- }
- };
-
- /** Resolves DOS-style, shortened path names, returning the last segment's long form. */
- private static final Function<String, String> WINDOWS_SHORT_PATH_RESOLVER =
- path -> {
- try {
- // Since Path objects are created hierarchically, we know for sure that every segment of
- // the path, except the last one, is already canonicalized, so we can return just that.
- // Plus the returned value is passed to Path.getChild so we must not return a full
- // path here.
- return PathFragment.create(WindowsFileOperations.getLongPath(path)).getBaseName();
- } catch (IOException e) {
- return null;
- }
- };
-
- @VisibleForTesting
- private 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);
- }
-
- @Override
- public Path getCachedChildPathInternal(Path path, String childName) {
- return WindowsPathFactory.getCachedChildPathInternalImpl(
- path, childName, WINDOWS_SHORT_PATH_RESOLVER);
- }
- };
-
- private static Path getCachedChildPathInternalImpl(
- Path parent, String child, Function<String, String> resolver) {
- if (parent != null && parent.isRootDirectory()) {
- // This is a top-level directory. It must be a drive name ("C:" or "c").
- if (WindowsPath.isWindowsVolumeName(child)) {
- child = WindowsPath.getDriveLetter((WindowsPath) parent, child) + ":";
- } else {
- throw new IllegalArgumentException("Cannot create Unix-style paths on Windows.");
- }
- }
-
- String resolvedChild = child;
- if (parent != null && !parent.isRootDirectory() && SHORT_NAME_MATCHER.apply(child)) {
- String pathString = parent.getPathString();
- if (!pathString.endsWith("/")) {
- pathString += "/";
- }
- pathString += child;
- resolvedChild = resolver.apply(pathString);
- }
- return Path.getCachedChildPathInternal(
- parent,
- // If resolution succeeded, or we didn't attempt to resolve, then `resolvedChild` has the
- // child name. If it's null, then resolution failed; use the unresolved child name in that
- // case.
- resolvedChild != null ? resolvedChild : child,
- // If resolution failed, likely because the path doesn't exist, then do not cache the
- // child. If we did, then in case the path later came into existence, we'd have a stale
- // cache entry.
- /* cacheable */ resolvedChild != null);
- }
-
- /**
- * Creates a {@link PathFactory} with a mock shortname resolver.
- *
- * <p>The factory works exactly like the actual one ({@link WindowsPathFactory#INSTANCE}) except
- * it's using the mock resolver.
- */
- public static PathFactory createForTesting(final Function<String, String> mockResolver) {
- return new PathFactory() {
- @Override
- public Path createRootPath(FileSystem filesystem) {
- return INSTANCE.createRootPath(filesystem);
- }
-
- @Override
- public Path createChildPath(Path parent, String childName) {
- return INSTANCE.createChildPath(parent, childName);
- }
-
- @Override
- public Path getCachedChildPathInternal(Path path, String childName) {
- return WindowsPathFactory.getCachedChildPathInternalImpl(path, childName, mockResolver);
- }
- };
- }
- }
-
- /** A windows-specific subclass of Path. */
- @VisibleForTesting
- protected static final class WindowsPath extends Path {
-
- // 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, name, parent);
- this.driveLetter = getDriveLetter(parent, name);
- }
-
- @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 {
- WindowsPath parent = (WindowsPath) getParentDirectory();
- parent.buildPathString(result);
- if (!parent.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 PathFragment.create(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 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;
- }
- }
- }
-
- @VisibleForTesting
- @Override
- protected synchronized void applyToChildren(Predicate<Path> function) {
- super.applyToChildren(function);
- }
- }
-
- @VisibleForTesting
- static PathFactory getPathFactoryForTesting(Function<String, String> mockResolver) {
- return WindowsPathFactory.createForTesting(mockResolver);
- }
-
public static final LinkOption[] NO_OPTIONS = new LinkOption[0];
public static final LinkOption[] NO_FOLLOW = new LinkOption[] {LinkOption.NOFOLLOW_LINKS};
@@ -281,11 +41,6 @@ public class WindowsFileSystem extends JavaIoFileSystem {
}
@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?).
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java
index a69884066c..af591cc7e8 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java
@@ -83,9 +83,7 @@ public class WindowsSubprocessFactory implements SubprocessFactory {
// If it's not absolute, then it cannot be longer than MAX_PATH, since MAX_PATH also limits the
// length of file names.
PathFragment argv0fragment = PathFragment.create(argv0);
- return (argv0fragment.isAbsolute())
- ? argv0fragment.normalize().getPathString().replace('/', '\\')
- : argv0;
+ return (argv0fragment.isAbsolute()) ? argv0fragment.getPathString().replace('/', '\\') : argv0;
}
private String getRedirectPath(StreamAction action, File file) {