aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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
-rw-r--r--src/test/java/com/google/devtools/build/lib/BUILD7
-rw-r--r--src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java54
-rw-r--r--src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactActionTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactoryTest.java7
-rw-r--r--src/test/java/com/google/devtools/build/lib/pkgcache/PathPackageLocatorTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java6
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoaderTest.java14
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java3
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java8
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java1
-rw-r--r--src/test/java/com/google/devtools/build/lib/unix/UnixPathEqualityTest.java25
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java33
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java28
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/LocalPathAbstractTest.java180
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/MacOsLocalPathTest.java48
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/NativePathTest.java17
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathAbstractTest.java141
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java583
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java315
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathTest.java341
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathTrieTest.java78
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java8
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java (renamed from src/test/java/com/google/devtools/build/lib/vfs/UnixLocalPathTest.java)60
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/WindowsPathTest.java (renamed from src/test/java/com/google/devtools/build/lib/vfs/WindowsLocalPathTest.java)77
-rw-r--r--src/test/java/com/google/devtools/build/lib/windows/PathWindowsTest.java298
-rw-r--r--src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java49
96 files changed, 2326 insertions, 4698 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) {
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 7cbf304f16..538cb8fd2c 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -9,8 +9,7 @@ package(
CROSS_PLATFORM_WINDOWS_TESTS = [
"util/DependencySetWindowsTest.java",
"vfs/PathFragmentWindowsTest.java",
- "vfs/WindowsLocalPathTest.java",
- "windows/PathWindowsTest.java",
+ "vfs/WindowsPathTest.java",
]
# Tests for Windows-specific functionality that run on Windows.
@@ -18,7 +17,6 @@ WINDOWS_ON_WINDOWS_TESTS = glob(
["windows/*.java"],
exclude = [
"windows/MockSubprocess.java",
- "windows/PathWindowsTest.java",
],
)
@@ -374,6 +372,7 @@ java_library(
deps = [
":guava_junit_truth",
":vfs_filesystem_test",
+ "//src/main/java/com/google/devtools/build/lib:os_util",
"//src/main/java/com/google/devtools/build/lib/vfs",
],
)
@@ -401,7 +400,7 @@ java_test(
# systems
java_test(
name = "windows_test",
- srcs = CROSS_PLATFORM_WINDOWS_TESTS + ["vfs/LocalPathAbstractTest.java"],
+ srcs = CROSS_PLATFORM_WINDOWS_TESTS + ["vfs/PathAbstractTest.java"],
jvm_flags = [
"-Dblaze.os=Windows",
"-Dbazel.windows_unix_root=C:/fake/msys",
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
index 363bae9df3..f26d00eaa5 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
@@ -27,6 +27,7 @@ import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
import com.google.devtools.build.lib.skyframe.serialization.InjectingObjectCodecAdapter;
import com.google.devtools.build.lib.skyframe.serialization.testutils.ObjectCodecTester;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -58,7 +59,7 @@ public class ArtifactTest {
Path f1 = scratch.file("/exec/dir/file.ext");
Path bogusDir = scratch.file("/exec/dir/bogus");
try {
- new Artifact(f1, ArtifactRoot.asDerivedRoot(execDir, bogusDir), f1.relativeTo(execDir));
+ new Artifact(ArtifactRoot.asDerivedRoot(execDir, bogusDir), f1.relativeTo(execDir));
fail("Expected IllegalArgumentException constructing artifact with a bad root dir");
} catch (IllegalArgumentException expected) {}
}
@@ -93,7 +94,7 @@ public class ArtifactTest {
@Test
public void testRootPrefixedExecPath_normal() throws IOException {
Path f1 = scratch.file("/exec/root/dir/file.ext");
- Artifact a1 = new Artifact(f1, rootDir, f1.relativeTo(execDir));
+ Artifact a1 = new Artifact(rootDir, f1.relativeTo(execDir));
assertThat(Artifact.asRootPrefixedExecPath(a1)).isEqualTo("root:dir/file.ext");
}
@@ -109,9 +110,10 @@ public class ArtifactTest {
public void testRootPrefixedExecPath_nullRootDir() throws IOException {
Path f1 = scratch.file("/exec/dir/file.ext");
try {
- new Artifact(f1, null, f1.relativeTo(execDir));
- fail("Expected IllegalArgumentException creating artifact with null root");
- } catch (IllegalArgumentException expected) {}
+ new Artifact(null, f1.relativeTo(execDir));
+ fail("Expected NullPointerException creating artifact with null root");
+ } catch (NullPointerException expected) {
+ }
}
@Test
@@ -119,9 +121,9 @@ public class ArtifactTest {
Path f1 = scratch.file("/exec/root/dir/file1.ext");
Path f2 = scratch.file("/exec/root/dir/dir/file2.ext");
Path f3 = scratch.file("/exec/root/dir/dir/dir/file3.ext");
- Artifact a1 = new Artifact(f1, rootDir, f1.relativeTo(execDir));
- Artifact a2 = new Artifact(f2, rootDir, f2.relativeTo(execDir));
- Artifact a3 = new Artifact(f3, rootDir, f3.relativeTo(execDir));
+ Artifact a1 = new Artifact(rootDir, f1.relativeTo(execDir));
+ Artifact a2 = new Artifact(rootDir, f2.relativeTo(execDir));
+ Artifact a3 = new Artifact(rootDir, f3.relativeTo(execDir));
List<String> strings = new ArrayList<>();
Artifact.addRootPrefixedExecPaths(Lists.newArrayList(a1, a2, a3), strings);
assertThat(strings).containsExactly(
@@ -283,10 +285,9 @@ public class ArtifactTest {
@Test
public void testToDetailString() throws Exception {
- Path execRoot = scratch.getFileSystem().getPath("/");
+ Path execRoot = scratch.getFileSystem().getPath("/a");
Artifact a =
new Artifact(
- scratch.file("/a/b/c"),
ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/a/b")),
PathFragment.create("b/c"));
assertThat(a.toDetailString()).isEqualTo("[[/a]b]c");
@@ -294,17 +295,12 @@ public class ArtifactTest {
@Test
public void testWeirdArtifact() throws Exception {
- try {
- Path execRoot = scratch.getFileSystem().getPath("/");
- new Artifact(
- scratch.file("/a/b/c"),
- ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/a")),
- PathFragment.create("c"));
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage(
- "c: illegal execPath doesn't end with b/c at /a/b/c with root /a[derived]");
- }
+ Path execRoot = scratch.getFileSystem().getPath("/");
+ MoreAsserts.expectThrows(
+ IllegalArgumentException.class,
+ () ->
+ new Artifact(
+ ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/a")), PathFragment.create("c")));
}
@Test
@@ -319,25 +315,23 @@ public class ArtifactTest {
@Test
public void testSerializeToStringWithExecPath() throws Exception {
- Path execRoot = scratch.getFileSystem().getPath("/");
- Path path = scratch.file("/aaa/bbb/ccc");
+ Path execRoot = scratch.getFileSystem().getPath("/aaa");
ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/aaa/bbb"));
PathFragment execPath = PathFragment.create("bbb/ccc");
- assertThat(new Artifact(path, root, execPath).serializeToString()).isEqualTo("bbb/ccc /3");
+ assertThat(new Artifact(root, execPath).serializeToString()).isEqualTo("bbb/ccc /3");
}
@Test
public void testSerializeToStringWithOwner() throws Exception {
- Path execRoot = scratch.getFileSystem().getPath("/");
+ Path execRoot = scratch.getFileSystem().getPath("/aa");
assertThat(
new Artifact(
- scratch.file("/aa/b/c"),
- ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/aa")),
+ ArtifactRoot.asDerivedRoot(execRoot, scratch.dir("/aa/b")),
PathFragment.create("b/c"),
new LabelArtifactOwner(Label.parseAbsoluteUnchecked("//foo:bar")))
.serializeToString())
- .isEqualTo("b/c /3 //foo:bar");
+ .isEqualTo("b/c /1 //foo:bar");
}
@Test
@@ -349,10 +343,9 @@ public class ArtifactTest {
new Artifact(
PathFragment.create("src/b"), ArtifactRoot.asSourceRoot(Root.fromPath(execDir))),
new Artifact(
- scratch.file("/src/c"),
ArtifactRoot.asDerivedRoot(
scratch.getFileSystem().getPath("/"), scratch.dir("/src")),
- PathFragment.create("c"),
+ PathFragment.create("src/c"),
new LabelArtifactOwner(Label.parseAbsoluteUnchecked("//foo:bar"))))
.buildAndRunTests();
}
@@ -387,7 +380,6 @@ public class ArtifactTest {
public void testIsSourceArtifact() throws Exception {
assertThat(
new Artifact(
- scratch.file("/src/foo.cc"),
ArtifactRoot.asSourceRoot(Root.fromPath(scratch.dir("/"))),
PathFragment.create("src/foo.cc"))
.isSourceArtifact())
diff --git a/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java
index 085415cf34..b9f341a588 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/CustomCommandLineTest.java
@@ -984,7 +984,6 @@ public class CustomCommandLineTest {
private SpecialArtifact createTreeArtifact(String rootRelativePath) {
PathFragment relpath = PathFragment.create(rootRelativePath);
return new SpecialArtifact(
- rootDir.getRoot().getRelative(relpath),
rootDir,
rootDir.getExecPath().getRelative(relpath),
ArtifactOwner.NullArtifactOwner.INSTANCE,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java
index 4c9d2dda5c..8678d50a30 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/ParamFileWriteActionTest.java
@@ -111,7 +111,6 @@ public class ParamFileWriteActionTest extends BuildViewTestCase {
private SpecialArtifact createTreeArtifact(String rootRelativePath) {
PathFragment relpath = PathFragment.create(rootRelativePath);
return new SpecialArtifact(
- rootDir.getRoot().getRelative(relpath),
rootDir,
rootDir.getExecPath().getRelative(relpath),
ArtifactOwner.NullArtifactOwner.INSTANCE,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactActionTest.java
index 345aa97ab8..1ce4fd5b43 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/PopulateTreeArtifactActionTest.java
@@ -346,7 +346,6 @@ public class PopulateTreeArtifactActionTest extends BuildViewTestCase {
private SpecialArtifact createTreeArtifact(String rootRelativePath) {
PathFragment relpath = PathFragment.create(rootRelativePath);
return new SpecialArtifact(
- root.getRoot().getRelative(relpath),
root,
root.getExecPath().getRelative(relpath),
ArtifactOwner.NullArtifactOwner.INSTANCE,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java
index ad03fe7ef9..17588a6504 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SpawnActionTemplateTest.java
@@ -325,7 +325,6 @@ public class SpawnActionTemplateTest {
private SpecialArtifact createTreeArtifact(String rootRelativePath) {
PathFragment relpath = PathFragment.create(rootRelativePath);
return new SpecialArtifact(
- root.getRoot().getRelative(relpath),
root,
root.getExecPath().getRelative(relpath),
ArtifactOwner.NullArtifactOwner.INSTANCE,
diff --git a/src/test/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactoryTest.java b/src/test/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactoryTest.java
index ca8bef91a2..5e22269292 100644
--- a/src/test/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventTransportFactoryTest.java
@@ -133,6 +133,13 @@ public class BuildEventTransportFactoryTest {
assertThat(transports).isEmpty();
}
+ @Test
+ public void testPathToUriString() {
+ // See https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/
+ assertThat(BuildEventTransportFactory.pathToUriString("C:/Temp/Foo Bar.txt"))
+ .isEqualTo("file:///C:/Temp/Foo%20Bar.txt");
+ }
+
private void sendEventsAndClose(BuildEvent event, Iterable<BuildEventTransport> transports)
throws IOException{
for (BuildEventTransport transport : transports) {
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/PathPackageLocatorTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/PathPackageLocatorTest.java
index 1cac35349c..a9b224f026 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/PathPackageLocatorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/PathPackageLocatorTest.java
@@ -338,7 +338,7 @@ public class PathPackageLocatorTest extends FoundationTestCase {
workspace.getRelative("foo"),
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY);
assertThat(eventCollector.count()).isSameAs(1);
- assertContainsEvent("The package path element './foo' will be taken relative");
+ assertContainsEvent("The package path element 'foo' will be taken relative");
}
/** Regression test for bug: "IllegalArgumentException in PathPackageLocator.create()" */
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java b/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java
index 1bd5bed2d3..9ea174a8ad 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java
@@ -183,6 +183,6 @@ public abstract class ResourceTestBase {
public Artifact getResource(String pathString) {
Path path = fileSystem.getPath("/" + RESOURCE_ROOT + "/" + pathString);
return new Artifact(
- path, root, root.getExecPath().getRelative(root.getRoot().relativize(path)), OWNER);
+ root, root.getExecPath().getRelative(root.getRoot().relativize(path)), OWNER);
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java
index 4d7c3fc697..36fcadf028 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java
@@ -1066,7 +1066,7 @@ public class CcLibraryConfiguredTargetTest extends BuildViewTestCase {
checkError(
"root",
"a",
- "The include path 'd/../../somewhere' references a path outside of the execution root.",
+ "The include path '../somewhere' references a path outside of the execution root.",
"cc_library(name='a', srcs=['a.cc'], copts=['-Id/../../somewhere'])");
}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
index db56bd53d6..f7bf8ebfbd 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
@@ -524,10 +524,6 @@ public class CppLinkActionTest extends BuildViewTestCase {
public Artifact getOutputArtifact(String relpath) {
return new Artifact(
- getTargetConfiguration()
- .getBinDirectory(RepositoryName.MAIN)
- .getRoot()
- .getRelative(relpath),
getTargetConfiguration().getBinDirectory(RepositoryName.MAIN),
getTargetConfiguration().getBinFragment().getRelative(relpath));
}
@@ -672,9 +668,7 @@ public class CppLinkActionTest extends BuildViewTestCase {
FileSystem fs = scratch.getFileSystem();
Path execRoot = fs.getPath(TestUtils.tmpDir());
PathFragment execPath = PathFragment.create("out").getRelative(name);
- Path path = execRoot.getRelative(execPath);
return new SpecialArtifact(
- path,
ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
execPath,
ArtifactOwner.NullArtifactOwner.INSTANCE,
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoaderTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoaderTest.java
index 4a3714b890..e45e32fe56 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoaderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CrosstoolConfigurationLoaderTest.java
@@ -186,7 +186,7 @@ public class CrosstoolConfigurationLoaderTest extends AnalysisTestCase {
assertThat(ccProvider.getTargetCpu()).isEqualTo("piii");
assertThat(ccProvider.getTargetGnuSystemName()).isEqualTo("target-system-name");
- assertThat(toolchain.getToolPathFragment(Tool.AR)).isEqualTo(getToolPath("/path-to-ar"));
+ assertThat(toolchain.getToolPathFragment(Tool.AR)).isEqualTo(getToolPath("path-to-ar"));
assertThat(ccProvider.getAbi()).isEqualTo("abi-version");
assertThat(ccProvider.getAbiGlibcVersion()).isEqualTo("abi-libc-version");
@@ -199,7 +199,7 @@ public class CrosstoolConfigurationLoaderTest extends AnalysisTestCase {
assertThat(ccProvider.supportsFission()).isTrue();
assertThat(ccProvider.getBuiltInIncludeDirectories())
- .containsExactly(getToolPath("/system-include-dir"));
+ .containsExactly(getToolPath("system-include-dir"));
assertThat(ccProvider.getSysroot()).isNull();
assertThat(CppHelper.getCompilerOptions(toolchain, ccProvider, NO_FEATURES))
@@ -238,8 +238,8 @@ public class CrosstoolConfigurationLoaderTest extends AnalysisTestCase {
"CC_FLAGS", "")
.entrySet());
- assertThat(toolchain.getToolPathFragment(Tool.LD)).isEqualTo(getToolPath("/path-to-ld"));
- assertThat(toolchain.getToolPathFragment(Tool.DWP)).isEqualTo(getToolPath("/path-to-dwp"));
+ assertThat(toolchain.getToolPathFragment(Tool.LD)).isEqualTo(getToolPath("path-to-ld"));
+ assertThat(toolchain.getToolPathFragment(Tool.DWP)).isEqualTo(getToolPath("path-to-dwp"));
}
/**
@@ -605,7 +605,7 @@ public class CrosstoolConfigurationLoaderTest extends AnalysisTestCase {
.entrySet());
assertThat(ccProviderA.getBuiltInIncludeDirectories())
.containsExactly(
- getToolPath("/system-include-dir-A-1"), getToolPath("/system-include-dir-A-2"))
+ getToolPath("system-include-dir-A-1"), getToolPath("system-include-dir-A-2"))
.inOrder();
assertThat(ccProviderA.getSysroot()).isEqualTo(PathFragment.create("some"));
@@ -682,9 +682,7 @@ public class CrosstoolConfigurationLoaderTest extends AnalysisTestCase {
PackageIdentifier packageIdentifier =
PackageIdentifier.create(
TestConstants.TOOLS_REPOSITORY,
- PathFragment.create(
- PathFragment.create(TestConstants.MOCK_CC_CROSSTOOL_PATH),
- PathFragment.create(path)));
+ PathFragment.create(TestConstants.MOCK_CC_CROSSTOOL_PATH).getRelative(path));
return packageIdentifier.getPathUnderExecRoot();
}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java
index cdd9d72764..ee9b9fb6d8 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/objc/HeaderThinningTest.java
@@ -189,7 +189,6 @@ public class HeaderThinningTest extends ObjcRuleTestCase {
private Artifact getTreeArtifact(String name) {
Artifact treeArtifactBase = getSourceArtifact(name);
return new SpecialArtifact(
- treeArtifactBase.getPath(),
treeArtifactBase.getRoot(),
treeArtifactBase.getExecPath(),
treeArtifactBase.getArtifactOwner(),
diff --git a/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java b/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java
index d21acf1839..bc0ebaabe7 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/proto/ProtoCompileActionBuilderTest.java
@@ -347,7 +347,6 @@ public class ProtoCompileActionBuilderTest {
private Artifact artifact(String ownerLabel, String path) {
return new Artifact(
- root.getRoot().getRelative(path),
root,
root.getExecPath().getRelative(path),
new LabelArtifactOwner(Label.parseAbsoluteUnchecked(ownerLabel)));
@@ -356,7 +355,6 @@ public class ProtoCompileActionBuilderTest {
/** Creates a dummy artifact with the given path, that actually resides in /out/<path>. */
private Artifact derivedArtifact(String ownerLabel, String path) {
return new Artifact(
- derivedRoot.getRoot().getRelative(path),
derivedRoot,
derivedRoot.getExecPath().getRelative(path),
new LabelArtifactOwner(Label.parseAbsoluteUnchecked(ownerLabel)));
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java
index 7c864789ec..f8b6a9db3d 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunctionTest.java
@@ -39,7 +39,6 @@ import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate.Output
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
-import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.skyframe.EvaluationResult;
@@ -205,9 +204,7 @@ public final class ActionTemplateExpansionFunctionTest extends FoundationTestCas
private SpecialArtifact createTreeArtifact(String path) {
PathFragment execPath = PathFragment.create("out").getRelative(path);
- Path fullPath = rootDirectory.getRelative(execPath);
return new SpecialArtifact(
- fullPath,
ArtifactRoot.asDerivedRoot(rootDirectory, rootDirectory.getRelative("out")),
execPath,
ArtifactOwner.NullArtifactOwner.INSTANCE,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
index 1699fc25fc..21b0a10274 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
@@ -250,10 +250,8 @@ public class ArtifactFunctionTest extends ArtifactFunctionTestCase {
private Artifact createDerivedArtifact(String path) {
PathFragment execPath = PathFragment.create("out").getRelative(path);
- Path fullPath = root.getRelative(execPath);
Artifact output =
new Artifact(
- fullPath,
ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
execPath,
ALL_OWNER);
@@ -264,9 +262,7 @@ public class ArtifactFunctionTest extends ArtifactFunctionTestCase {
private Artifact createMiddlemanArtifact(String path) {
ArtifactRoot middlemanRoot =
ArtifactRoot.middlemanRoot(middlemanPath, middlemanPath.getRelative("out"));
- Path fullPath = middlemanRoot.getRoot().getRelative(path);
- return new Artifact(
- fullPath, middlemanRoot, middlemanRoot.getExecPath().getRelative(path), ALL_OWNER);
+ return new Artifact(middlemanRoot, middlemanRoot.getExecPath().getRelative(path), ALL_OWNER);
}
private SpecialArtifact createDerivedTreeArtifactWithAction(String path) {
@@ -277,9 +273,7 @@ public class ArtifactFunctionTest extends ArtifactFunctionTestCase {
private SpecialArtifact createDerivedTreeArtifactOnly(String path) {
PathFragment execPath = PathFragment.create("out").getRelative(path);
- Path fullPath = root.getRelative(execPath);
return new SpecialArtifact(
- fullPath,
ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
execPath,
ALL_OWNER,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
index f7a989a4ae..48ccd7e341 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
@@ -620,7 +620,6 @@ public class FilesystemValueCheckerTest {
outputDir.createDirectory();
ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(fs.getPath("/"), outputDir);
return new SpecialArtifact(
- outputPath,
derivedRoot,
derivedRoot.getExecPath().getRelative(derivedRoot.getRoot().relativize(outputPath)),
ArtifactOwner.NullArtifactOwner.INSTANCE,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
index 872de47ad0..25b510e18e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
@@ -171,10 +171,8 @@ public final class RecursiveFilesystemTraversalFunctionTest extends FoundationTe
private Artifact derivedArtifact(String path) {
PathFragment execPath = PathFragment.create("out").getRelative(path);
- Path fullPath = rootDirectory.getRelative(execPath);
Artifact output =
new Artifact(
- fullPath,
ArtifactRoot.asDerivedRoot(rootDirectory, rootDirectory.getRelative("out")),
execPath);
return output;
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
index 3b39d03441..e63d1ea06e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
@@ -336,9 +336,7 @@ public abstract class TimestampBuilderTestCase extends FoundationTestCase {
Artifact createDerivedArtifact(FileSystem fs, String name) {
Path execRoot = fs.getPath(TestUtils.tmpDir());
PathFragment execPath = PathFragment.create("out").getRelative(name);
- Path path = execRoot.getRelative(execPath);
return new Artifact(
- path,
ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
execPath,
ACTION_LOOKUP_KEY);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
index 30b6c4caf8..5d24b9ef28 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
@@ -1192,9 +1192,7 @@ public class TreeArtifactBuildTest extends TimestampBuilderTestCase {
FileSystem fs = scratch.getFileSystem();
Path execRoot = fs.getPath(TestUtils.tmpDir());
PathFragment execPath = PathFragment.create("out").getRelative(name);
- Path path = execRoot.getRelative(execPath);
return new SpecialArtifact(
- path,
ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
execPath,
ACTION_LOOKUP_KEY,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
index 96bc4bed65..9e3fd39ddd 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
@@ -204,7 +204,6 @@ public class TreeArtifactMetadataTest extends ArtifactFunctionTestCase {
Path fullPath = root.getRelative(execPath);
SpecialArtifact output =
new SpecialArtifact(
- fullPath,
ArtifactRoot.asDerivedRoot(root, root.getRelative("out")),
execPath,
ALL_OWNER,
diff --git a/src/test/java/com/google/devtools/build/lib/unix/UnixPathEqualityTest.java b/src/test/java/com/google/devtools/build/lib/unix/UnixPathEqualityTest.java
index d77b10fd86..2d81e13965 100644
--- a/src/test/java/com/google/devtools/build/lib/unix/UnixPathEqualityTest.java
+++ b/src/test/java/com/google/devtools/build/lib/unix/UnixPathEqualityTest.java
@@ -14,9 +14,9 @@
package com.google.devtools.build.lib.unix;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
import com.google.common.testing.EqualsTester;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import org.junit.Before;
@@ -93,25 +93,8 @@ public class UnixPathEqualityTest {
Path a = unixFs.getPath("/a");
Path b = otherUnixFs.getPath("/b");
- try {
- a.renameTo(b);
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().contains("different filesystems");
- }
-
- try {
- a.relativeTo(b);
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().contains("different filesystems");
- }
-
- try {
- a.createSymbolicLink(b);
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().contains("different filesystems");
- }
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> a.renameTo(b));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> a.relativeTo(b));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> a.createSymbolicLink(b));
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
index e8896bbc0d..47ae76aa8b 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
@@ -14,7 +14,6 @@
package com.google.devtools.build.lib.vfs;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.fail;
@@ -789,23 +788,6 @@ public abstract class FileSystemTest {
assertThat(xNonEmptyDirectoryFoo.isFile()).isTrue();
}
- @Test
- public void testCannotRemoveRoot() {
- Path rootDirectory = testFS.getRootDirectory();
- try {
- rootDirectory.delete();
- fail();
- } catch (IOException e) {
- String msg = e.getMessage();
- assertWithMessage(String.format("got %s want EBUSY or ENOTEMPTY", msg))
- .that(
- msg.endsWith(" (Directory not empty)")
- || msg.endsWith(" (Device or resource busy)")
- || msg.endsWith(" (Is a directory)"))
- .isTrue(); // Happens on OS X.
- }
- }
-
// Test the date functions
@Test
public void testCreateFileChangesTimeOfDirectory() throws Exception {
@@ -1105,22 +1087,13 @@ public abstract class FileSystemTest {
// Test the Paths
@Test
public void testGetPathOnlyAcceptsAbsolutePath() {
- try {
- testFS.getPath("not-absolute");
- fail("The expected Exception was not thrown.");
- } catch (IllegalArgumentException ex) {
- assertThat(ex).hasMessage("not-absolute (not an absolute path)");
- }
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> testFS.getPath("not-absolute"));
}
@Test
public void testGetPathOnlyAcceptsAbsolutePathFragment() {
- try {
- testFS.getPath(PathFragment.create("not-absolute"));
- fail("The expected Exception was not thrown.");
- } catch (IllegalArgumentException ex) {
- assertThat(ex).hasMessage("not-absolute (not an absolute path)");
- }
+ MoreAsserts.expectThrows(
+ IllegalArgumentException.class, () -> testFS.getPath(PathFragment.create("not-absolute")));
}
// Test the access permissions
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
index 656363333e..f9cc1a9031 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
@@ -175,11 +175,29 @@ public class FileSystemUtilsTest {
@Test
public void testRelativePath() throws IOException {
createTestDirectoryTree();
- assertThat(relativePath(topDir, file1).getPathString()).isEqualTo("file-1");
- assertThat(relativePath(topDir, topDir).getPathString()).isEqualTo(".");
- assertThat(relativePath(topDir, dirLink).getPathString()).isEqualTo("a-dir/inner-dir/dir-link");
- assertThat(relativePath(topDir, file4).getPathString()).isEqualTo("../file-4");
- assertThat(relativePath(innerDir, file4).getPathString()).isEqualTo("../../../file-4");
+ assertThat(
+ relativePath(PathFragment.create("/top-dir"), PathFragment.create("/top-dir/file-1"))
+ .getPathString())
+ .isEqualTo("file-1");
+ assertThat(
+ relativePath(PathFragment.create("/top-dir"), PathFragment.create("/top-dir"))
+ .getPathString())
+ .isEqualTo("");
+ assertThat(
+ relativePath(
+ PathFragment.create("/top-dir"),
+ PathFragment.create("/top-dir/a-dir/inner-dir/dir-link"))
+ .getPathString())
+ .isEqualTo("a-dir/inner-dir/dir-link");
+ assertThat(
+ relativePath(PathFragment.create("/top-dir"), PathFragment.create("/file-4"))
+ .getPathString())
+ .isEqualTo("../file-4");
+ assertThat(
+ relativePath(
+ PathFragment.create("/top-dir/a-dir/inner-dir"), PathFragment.create("/file-4"))
+ .getPathString())
+ .isEqualTo("../../../file-4");
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/LocalPathAbstractTest.java b/src/test/java/com/google/devtools/build/lib/vfs/LocalPathAbstractTest.java
deleted file mode 100644
index 93869521ad..0000000000
--- a/src/test/java/com/google/devtools/build/lib/vfs/LocalPathAbstractTest.java
+++ /dev/null
@@ -1,180 +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 static com.google.common.truth.Truth.assertThat;
-import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
-import static java.util.stream.Collectors.toList;
-
-import com.google.common.collect.Lists;
-import com.google.common.testing.EqualsTester;
-import com.google.devtools.build.lib.vfs.LocalPath.OsPathPolicy;
-import java.util.Collections;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-
-/** Tests for {@link LocalPath}. */
-public abstract class LocalPathAbstractTest {
-
- private OsPathPolicy os;
-
- @Before
- public void setup() {
- os = getFilePathOs();
- }
-
- @Test
- public void testEqualsAndHashCode() {
- new EqualsTester()
- .addEqualityGroup(
- create("../relative/path"), create("..").getRelative("relative").getRelative("path"))
- .addEqualityGroup(create("something/else"))
- .addEqualityGroup(create(""), LocalPath.EMPTY)
- .testEquals();
- }
-
- @Test
- public void testRelativeTo() {
- assertThat(create("").relativeTo(create("")).getPathString()).isEmpty();
- assertThat(create("foo").relativeTo(create("foo")).getPathString()).isEmpty();
- assertThat(create("foo/bar/baz").relativeTo(create("foo")).getPathString())
- .isEqualTo("bar/baz");
- assertThat(create("foo/bar/baz").relativeTo(create("foo/bar")).getPathString())
- .isEqualTo("baz");
- assertThat(create("foo").relativeTo(create("")).getPathString()).isEqualTo("foo");
-
- // Cannot relativize non-ancestors
- assertThrows(IllegalArgumentException.class, () -> create("foo/bar").relativeTo(create("fo")));
-
- // Make sure partial directory matches aren't reported
- assertThrows(
- IllegalArgumentException.class, () -> create("foo/bar").relativeTo(create("foo/ba")));
- }
-
- @Test
- public void testGetRelative() {
- assertThat(create("a").getRelative("b").getPathString()).isEqualTo("a/b");
- assertThat(create("a/b").getRelative("c/d").getPathString()).isEqualTo("a/b/c/d");
- assertThat(create("a").getRelative("").getPathString()).isEqualTo("a");
- assertThat(create("a/b").getRelative("../c").getPathString()).isEqualTo("a/c");
- assertThat(create("a/b").getRelative("..").getPathString()).isEqualTo("a");
- }
-
- @Test
- public void testEmptyPathToEmptyPath() {
- // compare string forms
- assertThat(create("").getPathString()).isEmpty();
- // compare fragment forms
- assertThat(create("")).isEqualTo(create(""));
- }
-
- @Test
- public void testSimpleNameToSimpleName() {
- // compare string forms
- assertThat(create("foo").getPathString()).isEqualTo("foo");
- // compare fragment forms
- assertThat(create("foo")).isEqualTo(create("foo"));
- }
-
- @Test
- public void testSimplePathToSimplePath() {
- // compare string forms
- assertThat(create("foo/bar").getPathString()).isEqualTo("foo/bar");
- // compare fragment forms
- assertThat(create("foo/bar")).isEqualTo(create("foo/bar"));
- }
-
- @Test
- public void testStripsTrailingSlash() {
- // compare string forms
- assertThat(create("foo/bar/").getPathString()).isEqualTo("foo/bar");
- // compare fragment forms
- assertThat(create("foo/bar/")).isEqualTo(create("foo/bar"));
- }
-
- @Test
- public void testGetParentDirectory() {
- LocalPath fooBarWiz = create("foo/bar/wiz");
- LocalPath fooBar = create("foo/bar");
- LocalPath foo = create("foo");
- LocalPath empty = create("");
- assertThat(fooBarWiz.getParentDirectory()).isEqualTo(fooBar);
- assertThat(fooBar.getParentDirectory()).isEqualTo(foo);
- assertThat(foo.getParentDirectory()).isEqualTo(empty);
- assertThat(empty.getParentDirectory()).isNull();
- }
-
- @Test
- public void testBasename() throws Exception {
- assertThat(create("foo/bar").getBaseName()).isEqualTo("bar");
- assertThat(create("foo/").getBaseName()).isEqualTo("foo");
- assertThat(create("foo").getBaseName()).isEqualTo("foo");
- assertThat(create("").getBaseName()).isEmpty();
- }
-
- @Test
- public void testStartsWith() {
- // (relative path, relative prefix) => true
- assertThat(create("foo/bar").startsWith(create("foo/bar"))).isTrue();
- assertThat(create("foo/bar").startsWith(create("foo"))).isTrue();
- assertThat(create("foot/bar").startsWith(create("foo"))).isFalse();
- }
-
- @Test
- public void testNormalize() {
- assertThat(create("a/b")).isEqualTo(create("a/b"));
- assertThat(create("a/../../b")).isEqualTo(create("../b"));
- assertThat(create("a/../..")).isEqualTo(create(".."));
- assertThat(create("a/../b")).isEqualTo(create("b"));
- assertThat(create("a/b/../b")).isEqualTo(create("a/b"));
- }
-
- @Test
- public void testNormalStringsDoNotAllocate() {
- String normal1 = "a/b/hello.txt";
- assertThat(create(normal1).getPathString()).isSameAs(normal1);
-
- // Sanity check our testing strategy
- String notNormal = "a/../b";
- assertThat(create(notNormal).getPathString()).isNotSameAs(notNormal);
- }
-
- @Test
- public void testComparableSortOrder() {
- List<LocalPath> list =
- Lists.newArrayList(
- create("zzz"),
- create("ZZZ"),
- create("ABC"),
- create("aBc"),
- create("AbC"),
- create("abc"));
- Collections.sort(list);
- List<String> result = list.stream().map(LocalPath::getPathString).collect(toList());
-
- if (os.isCaseSensitive()) {
- assertThat(result).containsExactly("ABC", "AbC", "ZZZ", "aBc", "abc", "zzz").inOrder();
- } else {
- // Partial ordering among case-insensitive items guaranteed by Collections.sort stability
- assertThat(result).containsExactly("ABC", "aBc", "AbC", "abc", "zzz", "ZZZ").inOrder();
- }
- }
-
- protected abstract OsPathPolicy getFilePathOs();
-
- protected LocalPath create(String path) {
- return LocalPath.createWithOs(path, os);
- }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/MacOsLocalPathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/MacOsLocalPathTest.java
deleted file mode 100644
index c99ad487ee..0000000000
--- a/src/test/java/com/google/devtools/build/lib/vfs/MacOsLocalPathTest.java
+++ /dev/null
@@ -1,48 +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 static com.google.common.truth.Truth.assertThat;
-
-import com.google.common.testing.EqualsTester;
-import com.google.devtools.build.lib.vfs.LocalPath.MacOsPathPolicy;
-import com.google.devtools.build.lib.vfs.LocalPath.OsPathPolicy;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests Mac-specific parts of {@link LocalPath}. */
-@RunWith(JUnit4.class)
-public class MacOsLocalPathTest extends UnixLocalPathTest {
-
- @Override
- protected OsPathPolicy getFilePathOs() {
- return new MacOsPathPolicy();
- }
-
- @Test
- public void testMacEqualsAndHashCode() {
- new EqualsTester()
- .addEqualityGroup(create("a/b"), create("A/B"))
- .addEqualityGroup(create("/a/b"), create("/A/B"))
- .addEqualityGroup(create("something/else"))
- .addEqualityGroup(create("/something/else"))
- .testEquals();
- }
-
- @Test
- public void testCaseIsPreserved() {
- assertThat(create("a/B").getPathString()).isEqualTo("a/B");
- }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/NativePathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/NativePathTest.java
index 6d112853fd..5dc43a2567 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/NativePathTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/NativePathTest.java
@@ -106,13 +106,6 @@ public class NativePathTest {
}
@Test
- public void testParentOfRootIsRoot() {
- assertThat(fs.getPath("/..")).isEqualTo(fs.getPath("/"));
- assertThat(fs.getPath("/../../../../../..")).isEqualTo(fs.getPath("/"));
- assertThat(fs.getPath("/../../../foo")).isEqualTo(fs.getPath("/foo"));
- }
-
- @Test
public void testIsDirectory() {
assertThat(fs.getPath(aDirectory.getPath()).isDirectory()).isTrue();
assertThat(fs.getPath(aFile.getPath()).isDirectory()).isFalse();
@@ -241,14 +234,4 @@ public class NativePathTest {
assertThat(in.read()).isEqualTo(-1);
in.close();
}
-
- @Test
- public void testDerivedSegmentEquality() {
- Path absoluteSegment = fs.getRootDirectory();
-
- Path derivedNode = absoluteSegment.getChild("derivedSegment");
- Path otherDerivedNode = absoluteSegment.getChild("derivedSegment");
-
- assertThat(otherDerivedNode).isSameAs(derivedNode);
- }
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathAbstractTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathAbstractTest.java
new file mode 100644
index 0000000000..7494683391
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathAbstractTest.java
@@ -0,0 +1,141 @@
+// 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 static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+import com.google.devtools.build.lib.clock.BlazeClock;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Tests for {@link Path}. */
+public abstract class PathAbstractTest {
+
+ private FileSystem fileSystem;
+ private boolean isCaseSensitive;
+
+ @Before
+ public void setup() {
+ fileSystem = new InMemoryFileSystem(BlazeClock.instance());
+ isCaseSensitive = OsPathPolicy.getFilePathOs().isCaseSensitive();
+ }
+
+ @Test
+ public void testStripsTrailingSlash() {
+ // compare string forms
+ assertThat(create("/foo/bar/").getPathString()).isEqualTo("/foo/bar");
+ // compare fragment forms
+ assertThat(create("/foo/bar/")).isEqualTo(create("/foo/bar"));
+ }
+
+ @Test
+ public void testBasename() throws Exception {
+ assertThat(create("/foo/bar").getBaseName()).isEqualTo("bar");
+ assertThat(create("/foo/").getBaseName()).isEqualTo("foo");
+ assertThat(create("/foo").getBaseName()).isEqualTo("foo");
+ assertThat(create("/").getBaseName()).isEmpty();
+ }
+
+ @Test
+ public void testNormalStringsDoNotAllocate() {
+ String normal1 = "/a/b/hello.txt";
+ assertThat(create(normal1).getPathString()).isSameAs(normal1);
+
+ // Sanity check our testing strategy
+ String notNormal = "/a/../b";
+ assertThat(create(notNormal).getPathString()).isNotSameAs(notNormal);
+ }
+
+ @Test
+ public void testComparableSortOrder() {
+ List<Path> list =
+ Lists.newArrayList(
+ create("/zzz"),
+ create("/ZZZ"),
+ create("/ABC"),
+ create("/aBc"),
+ create("/AbC"),
+ create("/abc"));
+ Collections.sort(list);
+ List<String> result = list.stream().map(Path::getPathString).collect(toList());
+
+ if (isCaseSensitive) {
+ assertThat(result).containsExactly("/ABC", "/AbC", "/ZZZ", "/aBc", "/abc", "/zzz").inOrder();
+ } else {
+ // Partial ordering among case-insensitive items guaranteed by Collections.sort stability
+ assertThat(result).containsExactly("/ABC", "/aBc", "/AbC", "/abc", "/zzz", "/ZZZ").inOrder();
+ }
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ FileSystem oldFileSystem = Path.getFileSystemForSerialization();
+ try {
+ Path.setFileSystemForSerialization(fileSystem);
+ Path root = fileSystem.getPath("/");
+ Path p1 = fileSystem.getPath("/foo");
+ Path p2 = fileSystem.getPath("/foo/bar");
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos);
+
+ oos.writeObject(root);
+ oos.writeObject(p1);
+ oos.writeObject(p2);
+
+ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bis);
+
+ Path dsRoot = (Path) ois.readObject();
+ Path dsP1 = (Path) ois.readObject();
+ Path dsP2 = (Path) ois.readObject();
+
+ new EqualsTester()
+ .addEqualityGroup(root, dsRoot)
+ .addEqualityGroup(p1, dsP1)
+ .addEqualityGroup(p2, dsP2)
+ .testEquals();
+
+ assertThat(p2.startsWith(p1)).isTrue();
+ assertThat(p2.startsWith(dsP1)).isTrue();
+ assertThat(dsP2.startsWith(p1)).isTrue();
+ assertThat(dsP2.startsWith(dsP1)).isTrue();
+
+ // Regression test for a very specific bug in compareTo involving our incorrect usage of
+ // reference equality rather than logical equality.
+ String relativePathStringA = "child/grandchildA";
+ String relativePathStringB = "child/grandchildB";
+ assertThat(
+ p1.getRelative(relativePathStringA).compareTo(dsP1.getRelative(relativePathStringB)))
+ .isEqualTo(
+ p1.getRelative(relativePathStringA).compareTo(p1.getRelative(relativePathStringB)));
+ } finally {
+ Path.setFileSystemForSerialization(oldFileSystem);
+ }
+ }
+
+ protected Path create(String path) {
+ return Path.create(path, fileSystem);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
index 7fa3c73d89..02446c0337 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
@@ -15,13 +15,14 @@ package com.google.devtools.build.lib.vfs;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static com.google.devtools.build.lib.vfs.PathFragment.create;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.testing.EqualsTester;
import com.google.devtools.build.lib.skyframe.serialization.testutils.ObjectCodecTester;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import java.io.File;
@@ -36,32 +37,6 @@ import org.junit.runners.JUnit4;
*/
@RunWith(JUnit4.class)
public class PathFragmentTest {
- @Test
- public void testMergeFourPathsWithAbsolute() {
- assertThat(
- PathFragment.create(
- PathFragment.create("x/y"),
- PathFragment.create("z/a"),
- PathFragment.create("/b/c"),
- PathFragment.create("d/e")))
- .isEqualTo(PathFragment.create("x/y/z/a/b/c/d/e"));
- }
-
- @Test
- public void testCreateInternsPathFragments() {
- String[] firstSegments = new String[] {"hello", "world"};
- PathFragment first = PathFragment.create(
- /*driveLetter=*/ '\0', /*isAbsolute=*/ false, firstSegments);
-
- String[] secondSegments = new String[] {new String("hello"), new String("world")};
- PathFragment second = PathFragment.create(
- /*driveLetter=*/ '\0', /*isAbsolute=*/ false, secondSegments);
-
- assertThat(first.segmentCount()).isEqualTo(second.segmentCount());
- for (int i = 0; i < first.segmentCount(); i++) {
- assertThat(first.getSegment(i)).isSameAs(second.getSegment(i));
- }
- }
@Test
public void testEqualsAndHashCode() {
@@ -69,23 +44,21 @@ public class PathFragmentTest {
new EqualsTester()
.addEqualityGroup(
- PathFragment.create("../relative/path"),
- PathFragment.create("..").getRelative("relative").getRelative("path"),
- PathFragment.createAlreadyInterned(
- '\0', false, new String[] {"..", "relative", "path"}),
- PathFragment.create(new File("../relative/path")))
- .addEqualityGroup(PathFragment.create("something/else"))
- .addEqualityGroup(PathFragment.create("/something/else"))
- .addEqualityGroup(PathFragment.create("/"), PathFragment.create("//////"))
- .addEqualityGroup(PathFragment.create(""), PathFragment.EMPTY_FRAGMENT)
+ create("../relative/path"),
+ create("..").getRelative("relative").getRelative("path"),
+ create(new File("../relative/path").getPath()))
+ .addEqualityGroup(create("something/else"))
+ .addEqualityGroup(create("/something/else"))
+ .addEqualityGroup(create("/"), create("//////"))
+ .addEqualityGroup(create(""), PathFragment.EMPTY_FRAGMENT)
.addEqualityGroup(filesystem.getPath("/")) // A Path object.
.testEquals();
}
@Test
public void testHashCodeCache() {
- PathFragment relativePath = PathFragment.create("../relative/path");
- PathFragment rootPath = PathFragment.create("/");
+ PathFragment relativePath = create("../relative/path");
+ PathFragment rootPath = create("/");
int oldResult = relativePath.hashCode();
int rootResult = rootPath.hashCode();
@@ -93,279 +66,272 @@ public class PathFragmentTest {
assertThat(rootPath.hashCode()).isEqualTo(rootResult);
}
- private void checkRelativeTo(String path, String base) {
- PathFragment relative = PathFragment.create(path).relativeTo(base);
- assertThat(PathFragment.create(base).getRelative(relative).normalize())
- .isEqualTo(PathFragment.create(path));
- }
-
@Test
public void testRelativeTo() {
- assertPath("bar/baz", PathFragment.create("foo/bar/baz").relativeTo("foo"));
- assertPath("bar/baz", PathFragment.create("/foo/bar/baz").relativeTo("/foo"));
- assertPath("baz", PathFragment.create("foo/bar/baz").relativeTo("foo/bar"));
- assertPath("baz", PathFragment.create("/foo/bar/baz").relativeTo("/foo/bar"));
- assertPath("foo", PathFragment.create("/foo").relativeTo("/"));
- assertPath("foo", PathFragment.create("foo").relativeTo(""));
- assertPath("foo/bar", PathFragment.create("foo/bar").relativeTo(""));
-
- checkRelativeTo("foo/bar/baz", "foo");
- checkRelativeTo("/foo/bar/baz", "/foo");
- checkRelativeTo("foo/bar/baz", "foo/bar");
- checkRelativeTo("/foo/bar/baz", "/foo/bar");
- checkRelativeTo("/foo", "/");
- checkRelativeTo("foo", "");
- checkRelativeTo("foo/bar", "");
+ assertThat(create("foo/bar/baz").relativeTo("foo").getPathString()).isEqualTo("bar/baz");
+ assertThat(create("/foo/bar/baz").relativeTo("/foo").getPathString()).isEqualTo("bar/baz");
+ assertThat(create("foo/bar/baz").relativeTo("foo/bar").getPathString()).isEqualTo("baz");
+ assertThat(create("/foo/bar/baz").relativeTo("/foo/bar").getPathString()).isEqualTo("baz");
+ assertThat(create("/foo").relativeTo("/").getPathString()).isEqualTo("foo");
+ assertThat(create("foo").relativeTo("").getPathString()).isEqualTo("foo");
+ assertThat(create("foo/bar").relativeTo("").getPathString()).isEqualTo("foo/bar");
}
@Test
public void testIsAbsolute() {
- assertThat(PathFragment.create("/absolute/test").isAbsolute()).isTrue();
- assertThat(PathFragment.create("relative/test").isAbsolute()).isFalse();
- assertThat(PathFragment.create(new File("/absolute/test")).isAbsolute()).isTrue();
- assertThat(PathFragment.create(new File("relative/test")).isAbsolute()).isFalse();
+ assertThat(create("/absolute/test").isAbsolute()).isTrue();
+ assertThat(create("relative/test").isAbsolute()).isFalse();
+ assertThat(create(new File("/absolute/test").getPath()).isAbsolute()).isTrue();
+ assertThat(create(new File("relative/test").getPath()).isAbsolute()).isFalse();
}
@Test
public void testIsNormalized() {
- assertThat(PathFragment.create("/absolute/path").isNormalized()).isTrue();
- assertThat(PathFragment.create("some//path").isNormalized()).isTrue();
- assertThat(PathFragment.create("some/./path").isNormalized()).isFalse();
- assertThat(PathFragment.create("../some/path").isNormalized()).isFalse();
- assertThat(PathFragment.create("some/other/../path").isNormalized()).isFalse();
- assertThat(PathFragment.create("some/other//tricky..path..").isNormalized()).isTrue();
- assertThat(PathFragment.create("/some/other//tricky..path..").isNormalized()).isTrue();
+ assertThat(PathFragment.isNormalized("/absolute/path")).isTrue();
+ assertThat(PathFragment.isNormalized("some//path")).isTrue();
+ assertThat(PathFragment.isNormalized("some/./path")).isFalse();
+ assertThat(PathFragment.isNormalized("../some/path")).isFalse();
+ assertThat(PathFragment.isNormalized("./some/path")).isFalse();
+ assertThat(PathFragment.isNormalized("some/path/..")).isFalse();
+ assertThat(PathFragment.isNormalized("some/path/.")).isFalse();
+ assertThat(PathFragment.isNormalized("some/other/../path")).isFalse();
+ assertThat(PathFragment.isNormalized("some/other//tricky..path..")).isTrue();
+ assertThat(PathFragment.isNormalized("/some/other//tricky..path..")).isTrue();
+ }
+
+ @Test
+ public void testContainsUpLevelReferences() {
+ assertThat(PathFragment.containsUplevelReferences("/absolute/path")).isFalse();
+ assertThat(PathFragment.containsUplevelReferences("some//path")).isFalse();
+ assertThat(PathFragment.containsUplevelReferences("some/./path")).isFalse();
+ assertThat(PathFragment.containsUplevelReferences("../some/path")).isTrue();
+ assertThat(PathFragment.containsUplevelReferences("./some/path")).isFalse();
+ assertThat(PathFragment.containsUplevelReferences("some/path/..")).isTrue();
+ assertThat(PathFragment.containsUplevelReferences("some/path/.")).isFalse();
+ assertThat(PathFragment.containsUplevelReferences("some/other/../path")).isTrue();
+ assertThat(PathFragment.containsUplevelReferences("some/other//tricky..path..")).isFalse();
+ assertThat(PathFragment.containsUplevelReferences("/some/other//tricky..path..")).isFalse();
+
+ // Normalization cannot remove leading uplevel references, so this will be true
+ assertThat(create("../some/path").containsUplevelReferences()).isTrue();
+ // Normalization will remove these, so no uplevel references left
+ assertThat(create("some/path/..").containsUplevelReferences()).isFalse();
}
@Test
public void testRootNodeReturnsRootString() {
- PathFragment rootFragment = PathFragment.create("/");
+ PathFragment rootFragment = create("/");
assertThat(rootFragment.getPathString()).isEqualTo("/");
}
@Test
- public void testGetPathFragmentDoesNotNormalize() {
- String nonCanonicalPath = "/a/weird/noncanonical/../path/.";
- assertThat(PathFragment.create(nonCanonicalPath).getPathString()).isEqualTo(nonCanonicalPath);
- }
-
- @Test
public void testGetRelative() {
- assertThat(PathFragment.create("a").getRelative("b").getPathString()).isEqualTo("a/b");
- assertThat(PathFragment.create("a/b").getRelative("c/d").getPathString()).isEqualTo("a/b/c/d");
- assertThat(PathFragment.create("c/d").getRelative("/a/b").getPathString()).isEqualTo("/a/b");
- assertThat(PathFragment.create("a").getRelative("").getPathString()).isEqualTo("a");
- assertThat(PathFragment.create("/").getRelative("").getPathString()).isEqualTo("/");
+ assertThat(create("a").getRelative("b").getPathString()).isEqualTo("a/b");
+ assertThat(create("a/b").getRelative("c/d").getPathString()).isEqualTo("a/b/c/d");
+ assertThat(create("c/d").getRelative("/a/b").getPathString()).isEqualTo("/a/b");
+ assertThat(create("a").getRelative("").getPathString()).isEqualTo("a");
+ assertThat(create("/").getRelative("").getPathString()).isEqualTo("/");
+ assertThat(create("a/b").getRelative("../foo").getPathString()).isEqualTo("a/foo");
+ assertThat(create("/a/b").getRelative("../foo").getPathString()).isEqualTo("/a/foo");
+
+ // Make sure any fast path of PathFragment#getRelative(PathFragment) works
+ assertThat(create("a/b").getRelative(create("../foo")).getPathString()).isEqualTo("a/foo");
+ assertThat(create("/a/b").getRelative(create("../foo")).getPathString()).isEqualTo("/a/foo");
+
+ // Make sure any fast path of PathFragment#getRelative(PathFragment) works
+ assertThat(create("c/d").getRelative(create("/a/b")).getPathString()).isEqualTo("/a/b");
+
+ // Test normalization
+ assertThat(create("a").getRelative(".").getPathString()).isEqualTo("a");
}
@Test
public void testGetChildWorks() {
- PathFragment pf = PathFragment.create("../some/path");
- assertThat(pf.getChild("hi")).isEqualTo(PathFragment.create("../some/path/hi"));
+ PathFragment pf = create("../some/path");
+ assertThat(pf.getChild("hi")).isEqualTo(create("../some/path/hi"));
}
@Test
public void testGetChildRejectsInvalidBaseNames() {
- PathFragment pf = PathFragment.create("../some/path");
- assertGetChildFails(pf, ".");
- assertGetChildFails(pf, "..");
- assertGetChildFails(pf, "x/y");
- assertGetChildFails(pf, "/y");
- assertGetChildFails(pf, "y/");
- assertGetChildFails(pf, "");
- }
-
- private void assertGetChildFails(PathFragment pf, String baseName) {
- try {
- pf.getChild(baseName);
- fail();
- } catch (Exception e) { /* Expected. */ }
- }
-
- // Tests after here test the canonicalization
- private void assertRegular(String expected, String actual) {
- // compare string forms
- assertThat(PathFragment.create(actual).getPathString()).isEqualTo(expected);
- // compare fragment forms
- assertThat(PathFragment.create(actual)).isEqualTo(PathFragment.create(expected));
+ PathFragment pf = create("../some/path");
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> pf.getChild("."));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> pf.getChild(".."));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> pf.getChild("x/y"));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> pf.getChild("/y"));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> pf.getChild("y/"));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> pf.getChild(""));
}
@Test
public void testEmptyPathToEmptyPath() {
- assertRegular("/", "/");
- assertRegular("", "");
+ assertThat(create("/").getPathString()).isEqualTo("/");
+ assertThat(create("").getPathString()).isEqualTo("");
}
@Test
public void testRedundantSlashes() {
- assertRegular("/", "///");
- assertRegular("/foo/bar", "/foo///bar");
- assertRegular("/foo/bar", "////foo//bar");
+ assertThat(create("///").getPathString()).isEqualTo("/");
+ assertThat(create("/foo///bar").getPathString()).isEqualTo("/foo/bar");
+ assertThat(create("////foo//bar").getPathString()).isEqualTo("/foo/bar");
}
@Test
public void testSimpleNameToSimpleName() {
- assertRegular("/foo", "/foo");
- assertRegular("foo", "foo");
+ assertThat(create("/foo").getPathString()).isEqualTo("/foo");
+ assertThat(create("foo").getPathString()).isEqualTo("foo");
}
@Test
public void testSimplePathToSimplePath() {
- assertRegular("/foo/bar", "/foo/bar");
- assertRegular("foo/bar", "foo/bar");
+ assertThat(create("/foo/bar").getPathString()).isEqualTo("/foo/bar");
+ assertThat(create("foo/bar").getPathString()).isEqualTo("foo/bar");
}
@Test
public void testStripsTrailingSlash() {
- assertRegular("/foo/bar", "/foo/bar/");
+ assertThat(create("/foo/bar/").getPathString()).isEqualTo("/foo/bar");
}
@Test
public void testGetParentDirectory() {
- PathFragment fooBarWiz = PathFragment.create("foo/bar/wiz");
- PathFragment fooBar = PathFragment.create("foo/bar");
- PathFragment foo = PathFragment.create("foo");
- PathFragment empty = PathFragment.create("");
+ PathFragment fooBarWiz = create("foo/bar/wiz");
+ PathFragment fooBar = create("foo/bar");
+ PathFragment foo = create("foo");
+ PathFragment empty = create("");
assertThat(fooBarWiz.getParentDirectory()).isEqualTo(fooBar);
assertThat(fooBar.getParentDirectory()).isEqualTo(foo);
assertThat(foo.getParentDirectory()).isEqualTo(empty);
assertThat(empty.getParentDirectory()).isNull();
- PathFragment fooBarWizAbs = PathFragment.create("/foo/bar/wiz");
- PathFragment fooBarAbs = PathFragment.create("/foo/bar");
- PathFragment fooAbs = PathFragment.create("/foo");
- PathFragment rootAbs = PathFragment.create("/");
+ PathFragment fooBarWizAbs = create("/foo/bar/wiz");
+ PathFragment fooBarAbs = create("/foo/bar");
+ PathFragment fooAbs = create("/foo");
+ PathFragment rootAbs = create("/");
assertThat(fooBarWizAbs.getParentDirectory()).isEqualTo(fooBarAbs);
assertThat(fooBarAbs.getParentDirectory()).isEqualTo(fooAbs);
assertThat(fooAbs.getParentDirectory()).isEqualTo(rootAbs);
assertThat(rootAbs.getParentDirectory()).isNull();
-
- // Note, this is surprising but correct behavior:
- assertThat(PathFragment.create("/foo/bar/..").getParentDirectory()).isEqualTo(fooBarAbs);
}
@Test
public void testSegmentsCount() {
- assertThat(PathFragment.create("foo/bar").segmentCount()).isEqualTo(2);
- assertThat(PathFragment.create("/foo/bar").segmentCount()).isEqualTo(2);
- assertThat(PathFragment.create("foo//bar").segmentCount()).isEqualTo(2);
- assertThat(PathFragment.create("/foo//bar").segmentCount()).isEqualTo(2);
- assertThat(PathFragment.create("foo/").segmentCount()).isEqualTo(1);
- assertThat(PathFragment.create("/foo/").segmentCount()).isEqualTo(1);
- assertThat(PathFragment.create("foo").segmentCount()).isEqualTo(1);
- assertThat(PathFragment.create("/foo").segmentCount()).isEqualTo(1);
- assertThat(PathFragment.create("/").segmentCount()).isEqualTo(0);
- assertThat(PathFragment.create("").segmentCount()).isEqualTo(0);
+ assertThat(create("foo/bar").segmentCount()).isEqualTo(2);
+ assertThat(create("/foo/bar").segmentCount()).isEqualTo(2);
+ assertThat(create("foo//bar").segmentCount()).isEqualTo(2);
+ assertThat(create("/foo//bar").segmentCount()).isEqualTo(2);
+ assertThat(create("foo/").segmentCount()).isEqualTo(1);
+ assertThat(create("/foo/").segmentCount()).isEqualTo(1);
+ assertThat(create("foo").segmentCount()).isEqualTo(1);
+ assertThat(create("/foo").segmentCount()).isEqualTo(1);
+ assertThat(create("/").segmentCount()).isEqualTo(0);
+ assertThat(create("").segmentCount()).isEqualTo(0);
}
@Test
public void testGetSegment() {
- assertThat(PathFragment.create("foo/bar").getSegment(0)).isEqualTo("foo");
- assertThat(PathFragment.create("foo/bar").getSegment(1)).isEqualTo("bar");
- assertThat(PathFragment.create("/foo/bar").getSegment(0)).isEqualTo("foo");
- assertThat(PathFragment.create("/foo/bar").getSegment(1)).isEqualTo("bar");
- assertThat(PathFragment.create("foo/").getSegment(0)).isEqualTo("foo");
- assertThat(PathFragment.create("/foo/").getSegment(0)).isEqualTo("foo");
- assertThat(PathFragment.create("foo").getSegment(0)).isEqualTo("foo");
- assertThat(PathFragment.create("/foo").getSegment(0)).isEqualTo("foo");
+ assertThat(create("foo/bar").getSegment(0)).isEqualTo("foo");
+ assertThat(create("foo/bar").getSegment(1)).isEqualTo("bar");
+ assertThat(create("/foo/bar").getSegment(0)).isEqualTo("foo");
+ assertThat(create("/foo/bar").getSegment(1)).isEqualTo("bar");
+ assertThat(create("foo/").getSegment(0)).isEqualTo("foo");
+ assertThat(create("/foo/").getSegment(0)).isEqualTo("foo");
+ assertThat(create("foo").getSegment(0)).isEqualTo("foo");
+ assertThat(create("/foo").getSegment(0)).isEqualTo("foo");
}
@Test
public void testBasename() throws Exception {
- assertThat(PathFragment.create("foo/bar").getBaseName()).isEqualTo("bar");
- assertThat(PathFragment.create("/foo/bar").getBaseName()).isEqualTo("bar");
- assertThat(PathFragment.create("foo/").getBaseName()).isEqualTo("foo");
- assertThat(PathFragment.create("/foo/").getBaseName()).isEqualTo("foo");
- assertThat(PathFragment.create("foo").getBaseName()).isEqualTo("foo");
- assertThat(PathFragment.create("/foo").getBaseName()).isEqualTo("foo");
- assertThat(PathFragment.create("/").getBaseName()).isEmpty();
- assertThat(PathFragment.create("").getBaseName()).isEmpty();
+ assertThat(create("foo/bar").getBaseName()).isEqualTo("bar");
+ assertThat(create("/foo/bar").getBaseName()).isEqualTo("bar");
+ assertThat(create("foo/").getBaseName()).isEqualTo("foo");
+ assertThat(create("/foo/").getBaseName()).isEqualTo("foo");
+ assertThat(create("foo").getBaseName()).isEqualTo("foo");
+ assertThat(create("/foo").getBaseName()).isEqualTo("foo");
+ assertThat(create("/").getBaseName()).isEmpty();
+ assertThat(create("").getBaseName()).isEmpty();
}
@Test
public void testFileExtension() throws Exception {
- assertThat(PathFragment.create("foo.bar").getFileExtension()).isEqualTo("bar");
- assertThat(PathFragment.create("foo.barr").getFileExtension()).isEqualTo("barr");
- assertThat(PathFragment.create("foo.b").getFileExtension()).isEqualTo("b");
- assertThat(PathFragment.create("foo.").getFileExtension()).isEmpty();
- assertThat(PathFragment.create("foo").getFileExtension()).isEmpty();
- assertThat(PathFragment.create(".").getFileExtension()).isEmpty();
- assertThat(PathFragment.create("").getFileExtension()).isEmpty();
- assertThat(PathFragment.create("foo/bar.baz").getFileExtension()).isEqualTo("baz");
- assertThat(PathFragment.create("foo.bar.baz").getFileExtension()).isEqualTo("baz");
- assertThat(PathFragment.create("foo.bar/baz").getFileExtension()).isEmpty();
- }
-
- private static void assertPath(String expected, PathFragment actual) {
- assertThat(actual.getPathString()).isEqualTo(expected);
+ assertThat(create("foo.bar").getFileExtension()).isEqualTo("bar");
+ assertThat(create("foo.barr").getFileExtension()).isEqualTo("barr");
+ assertThat(create("foo.b").getFileExtension()).isEqualTo("b");
+ assertThat(create("foo.").getFileExtension()).isEmpty();
+ assertThat(create("foo").getFileExtension()).isEmpty();
+ assertThat(create(".").getFileExtension()).isEmpty();
+ assertThat(create("").getFileExtension()).isEmpty();
+ assertThat(create("foo/bar.baz").getFileExtension()).isEqualTo("baz");
+ assertThat(create("foo.bar.baz").getFileExtension()).isEqualTo("baz");
+ assertThat(create("foo.bar/baz").getFileExtension()).isEmpty();
}
@Test
public void testReplaceName() throws Exception {
- assertPath("foo/baz", PathFragment.create("foo/bar").replaceName("baz"));
- assertPath("/foo/baz", PathFragment.create("/foo/bar").replaceName("baz"));
- assertPath("foo", PathFragment.create("foo/bar").replaceName(""));
- assertPath("baz", PathFragment.create("foo/").replaceName("baz"));
- assertPath("/baz", PathFragment.create("/foo/").replaceName("baz"));
- assertPath("baz", PathFragment.create("foo").replaceName("baz"));
- assertPath("/baz", PathFragment.create("/foo").replaceName("baz"));
- assertThat(PathFragment.create("/").replaceName("baz")).isNull();
- assertThat(PathFragment.create("/").replaceName("")).isNull();
- assertThat(PathFragment.create("").replaceName("baz")).isNull();
- assertThat(PathFragment.create("").replaceName("")).isNull();
-
- assertPath("foo/bar/baz", PathFragment.create("foo/bar").replaceName("bar/baz"));
- assertPath("foo/bar/baz", PathFragment.create("foo/bar").replaceName("bar/baz/"));
+ assertThat(create("foo/bar").replaceName("baz").getPathString()).isEqualTo("foo/baz");
+ assertThat(create("/foo/bar").replaceName("baz").getPathString()).isEqualTo("/foo/baz");
+ assertThat(create("foo/bar").replaceName("").getPathString()).isEqualTo("foo");
+ assertThat(create("foo/").replaceName("baz").getPathString()).isEqualTo("baz");
+ assertThat(create("/foo/").replaceName("baz").getPathString()).isEqualTo("/baz");
+ assertThat(create("foo").replaceName("baz").getPathString()).isEqualTo("baz");
+ assertThat(create("/foo").replaceName("baz").getPathString()).isEqualTo("/baz");
+ assertThat(create("/").replaceName("baz")).isNull();
+ assertThat(create("/").replaceName("")).isNull();
+ assertThat(create("").replaceName("baz")).isNull();
+ assertThat(create("").replaceName("")).isNull();
+
+ assertThat(create("foo/bar").replaceName("bar/baz").getPathString()).isEqualTo("foo/bar/baz");
+ assertThat(create("foo/bar").replaceName("bar/baz/").getPathString()).isEqualTo("foo/bar/baz");
// Absolute path arguments will clobber the original path.
- assertPath("/absolute", PathFragment.create("foo/bar").replaceName("/absolute"));
- assertPath("/", PathFragment.create("foo/bar").replaceName("/"));
+ assertThat(create("foo/bar").replaceName("/absolute").getPathString()).isEqualTo("/absolute");
+ assertThat(create("foo/bar").replaceName("/").getPathString()).isEqualTo("/");
}
@Test
public void testSubFragment() throws Exception {
- assertPath("/foo/bar/baz",
- PathFragment.create("/foo/bar/baz").subFragment(0, 3));
- assertPath("foo/bar/baz",
- PathFragment.create("foo/bar/baz").subFragment(0, 3));
- assertPath("/foo/bar",
- PathFragment.create("/foo/bar/baz").subFragment(0, 2));
- assertPath("bar/baz",
- PathFragment.create("/foo/bar/baz").subFragment(1, 3));
- assertPath("/foo",
- PathFragment.create("/foo/bar/baz").subFragment(0, 1));
- assertPath("bar",
- PathFragment.create("/foo/bar/baz").subFragment(1, 2));
- assertPath("baz", PathFragment.create("/foo/bar/baz").subFragment(2, 3));
- assertPath("/", PathFragment.create("/foo/bar/baz").subFragment(0, 0));
- assertPath("", PathFragment.create("foo/bar/baz").subFragment(0, 0));
- assertPath("", PathFragment.create("foo/bar/baz").subFragment(1, 1));
- assertPath("/foo/bar/baz", PathFragment.create("/foo/bar/baz").subFragment(0));
- assertPath("bar/baz", PathFragment.create("/foo/bar/baz").subFragment(1));
- try {
- fail("unexpectedly succeeded: " + PathFragment.create("foo/bar/baz").subFragment(3, 2));
- } catch (IndexOutOfBoundsException e) { /* Expected. */ }
- try {
- fail("unexpectedly succeeded: " + PathFragment.create("foo/bar/baz").subFragment(4, 4));
- } catch (IndexOutOfBoundsException e) { /* Expected. */ }
+ assertThat(create("/foo/bar/baz").subFragment(0, 3).getPathString()).isEqualTo("/foo/bar/baz");
+ assertThat(create("foo/bar/baz").subFragment(0, 3).getPathString()).isEqualTo("foo/bar/baz");
+ assertThat(create("/foo/bar/baz").subFragment(0, 2).getPathString()).isEqualTo("/foo/bar");
+ assertThat(create("/foo/bar/baz").subFragment(1, 3).getPathString()).isEqualTo("bar/baz");
+ assertThat(create("/foo/bar/baz").subFragment(0, 1).getPathString()).isEqualTo("/foo");
+ assertThat(create("/foo/bar/baz").subFragment(1, 2).getPathString()).isEqualTo("bar");
+ assertThat(create("/foo/bar/baz").subFragment(2, 3).getPathString()).isEqualTo("baz");
+ assertThat(create("/foo/bar/baz").subFragment(0, 0).getPathString()).isEqualTo("/");
+ assertThat(create("foo/bar/baz").subFragment(0, 0).getPathString()).isEqualTo("");
+ assertThat(create("foo/bar/baz").subFragment(1, 1).getPathString()).isEqualTo("");
+
+ assertThat(create("/foo/bar/baz").subFragment(0).getPathString()).isEqualTo("/foo/bar/baz");
+ assertThat(create("foo/bar/baz").subFragment(0).getPathString()).isEqualTo("foo/bar/baz");
+ assertThat(create("/foo/bar/baz").subFragment(1).getPathString()).isEqualTo("bar/baz");
+ assertThat(create("foo/bar/baz").subFragment(1).getPathString()).isEqualTo("bar/baz");
+ assertThat(create("foo/bar/baz").subFragment(2).getPathString()).isEqualTo("baz");
+ assertThat(create("foo/bar/baz").subFragment(3).getPathString()).isEqualTo("");
+
+ MoreAsserts.expectThrows(
+ IndexOutOfBoundsException.class, () -> create("foo/bar/baz").subFragment(3, 2));
+ MoreAsserts.expectThrows(
+ IndexOutOfBoundsException.class, () -> create("foo/bar/baz").subFragment(4, 4));
+ MoreAsserts.expectThrows(
+ IndexOutOfBoundsException.class, () -> create("foo/bar/baz").subFragment(3, 2));
+ MoreAsserts.expectThrows(
+ IndexOutOfBoundsException.class, () -> create("foo/bar/baz").subFragment(4));
}
@Test
public void testStartsWith() {
- PathFragment foobar = PathFragment.create("/foo/bar");
- PathFragment foobarRelative = PathFragment.create("foo/bar");
+ PathFragment foobar = create("/foo/bar");
+ PathFragment foobarRelative = create("foo/bar");
// (path, prefix) => true
assertThat(foobar.startsWith(foobar)).isTrue();
- assertThat(foobar.startsWith(PathFragment.create("/"))).isTrue();
- assertThat(foobar.startsWith(PathFragment.create("/foo"))).isTrue();
- assertThat(foobar.startsWith(PathFragment.create("/foo/"))).isTrue();
- assertThat(foobar.startsWith(PathFragment.create("/foo/bar/")))
- .isTrue(); // Includes trailing slash.
+ assertThat(foobar.startsWith(create("/"))).isTrue();
+ assertThat(foobar.startsWith(create("/foo"))).isTrue();
+ assertThat(foobar.startsWith(create("/foo/"))).isTrue();
+ assertThat(foobar.startsWith(create("/foo/bar/"))).isTrue(); // Includes trailing slash.
// (prefix, path) => false
- assertThat(PathFragment.create("/foo").startsWith(foobar)).isFalse();
- assertThat(PathFragment.create("/").startsWith(foobar)).isFalse();
+ assertThat(create("/foo").startsWith(foobar)).isFalse();
+ assertThat(create("/").startsWith(foobar)).isFalse();
// (absolute, relative) => false
assertThat(foobar.startsWith(foobarRelative)).isFalse();
@@ -373,69 +339,53 @@ public class PathFragmentTest {
// (relative path, relative prefix) => true
assertThat(foobarRelative.startsWith(foobarRelative)).isTrue();
- assertThat(foobarRelative.startsWith(PathFragment.create("foo"))).isTrue();
- assertThat(foobarRelative.startsWith(PathFragment.create(""))).isTrue();
+ assertThat(foobarRelative.startsWith(create("foo"))).isTrue();
+ assertThat(foobarRelative.startsWith(create(""))).isTrue();
// (path, sibling) => false
- assertThat(PathFragment.create("/foo/wiz").startsWith(foobar)).isFalse();
- assertThat(foobar.startsWith(PathFragment.create("/foo/wiz"))).isFalse();
-
- // Does not normalize.
- PathFragment foodotbar = PathFragment.create("foo/./bar");
- assertThat(foodotbar.startsWith(foodotbar)).isTrue();
- assertThat(foodotbar.startsWith(PathFragment.create("foo/."))).isTrue();
- assertThat(foodotbar.startsWith(PathFragment.create("foo/./"))).isTrue();
- assertThat(foodotbar.startsWith(PathFragment.create("foo/./bar"))).isTrue();
- assertThat(foodotbar.startsWith(PathFragment.create("foo/bar"))).isFalse();
+ assertThat(create("/foo/wiz").startsWith(foobar)).isFalse();
+ assertThat(foobar.startsWith(create("/foo/wiz"))).isFalse();
}
@Test
public void testCheckAllPathsStartWithButAreNotEqualTo() {
// Check passes:
- PathFragment.checkAllPathsAreUnder(toPathsSet("a/b", "a/c"),
- PathFragment.create("a"));
+ PathFragment.checkAllPathsAreUnder(toPathsSet("a/b", "a/c"), create("a"));
// Check trivially passes:
- PathFragment.checkAllPathsAreUnder(ImmutableList.<PathFragment>of(),
- PathFragment.create("a"));
+ PathFragment.checkAllPathsAreUnder(ImmutableList.<PathFragment>of(), create("a"));
// Check fails when some path does not start with startingWithPath:
- try {
- PathFragment.checkAllPathsAreUnder(toPathsSet("a/b", "b/c"),
- PathFragment.create("a"));
- fail();
- } catch (IllegalArgumentException expected) {
- }
+ MoreAsserts.expectThrows(
+ IllegalArgumentException.class,
+ () -> PathFragment.checkAllPathsAreUnder(toPathsSet("a/b", "b/c"), create("a")));
// Check fails when some path is equal to startingWithPath:
- try {
- PathFragment.checkAllPathsAreUnder(toPathsSet("a/b", "a"),
- PathFragment.create("a"));
- fail();
- } catch (IllegalArgumentException expected) {
- }
+ MoreAsserts.expectThrows(
+ IllegalArgumentException.class,
+ () -> PathFragment.checkAllPathsAreUnder(toPathsSet("a/b", "a"), create("a")));
}
@Test
public void testEndsWith() {
- PathFragment foobar = PathFragment.create("/foo/bar");
- PathFragment foobarRelative = PathFragment.create("foo/bar");
+ PathFragment foobar = create("/foo/bar");
+ PathFragment foobarRelative = create("foo/bar");
// (path, suffix) => true
assertThat(foobar.endsWith(foobar)).isTrue();
- assertThat(foobar.endsWith(PathFragment.create("bar"))).isTrue();
- assertThat(foobar.endsWith(PathFragment.create("foo/bar"))).isTrue();
- assertThat(foobar.endsWith(PathFragment.create("/foo/bar"))).isTrue();
- assertThat(foobar.endsWith(PathFragment.create("/bar"))).isFalse();
+ assertThat(foobar.endsWith(create("bar"))).isTrue();
+ assertThat(foobar.endsWith(create("foo/bar"))).isTrue();
+ assertThat(foobar.endsWith(create("/foo/bar"))).isTrue();
+ assertThat(foobar.endsWith(create("/bar"))).isFalse();
// (prefix, path) => false
- assertThat(PathFragment.create("/foo").endsWith(foobar)).isFalse();
- assertThat(PathFragment.create("/").endsWith(foobar)).isFalse();
+ assertThat(create("/foo").endsWith(foobar)).isFalse();
+ assertThat(create("/").endsWith(foobar)).isFalse();
// (suffix, path) => false
- assertThat(PathFragment.create("/bar").endsWith(foobar)).isFalse();
- assertThat(PathFragment.create("bar").endsWith(foobar)).isFalse();
- assertThat(PathFragment.create("").endsWith(foobar)).isFalse();
+ assertThat(create("/bar").endsWith(foobar)).isFalse();
+ assertThat(create("bar").endsWith(foobar)).isFalse();
+ assertThat(create("").endsWith(foobar)).isFalse();
// (absolute, relative) => true
assertThat(foobar.endsWith(foobarRelative)).isTrue();
@@ -445,18 +395,32 @@ public class PathFragmentTest {
// (relative path, relative prefix) => true
assertThat(foobarRelative.endsWith(foobarRelative)).isTrue();
- assertThat(foobarRelative.endsWith(PathFragment.create("bar"))).isTrue();
- assertThat(foobarRelative.endsWith(PathFragment.create(""))).isTrue();
+ assertThat(foobarRelative.endsWith(create("bar"))).isTrue();
+ assertThat(foobarRelative.endsWith(create(""))).isTrue();
// (path, sibling) => false
- assertThat(PathFragment.create("/foo/wiz").endsWith(foobar)).isFalse();
- assertThat(foobar.endsWith(PathFragment.create("/foo/wiz"))).isFalse();
+ assertThat(create("/foo/wiz").endsWith(foobar)).isFalse();
+ assertThat(foobar.endsWith(create("/foo/wiz"))).isFalse();
+ }
+
+ @Test
+ public void testToRelative() {
+ assertThat(create("/foo/bar").toRelative()).isEqualTo(create("foo/bar"));
+ assertThat(create("/").toRelative()).isEqualTo(create(""));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("foo").toRelative());
+ }
+
+ @Test
+ public void testGetDriveStr() {
+ assertThat(create("/foo/bar").getDriveStr()).isEqualTo("/");
+ assertThat(create("/").getDriveStr()).isEqualTo("/");
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("foo").getDriveStr());
}
static List<PathFragment> toPaths(List<String> strs) {
List<PathFragment> paths = Lists.newArrayList();
for (String s : strs) {
- paths.add(PathFragment.create(s));
+ paths.add(create(s));
}
return paths;
}
@@ -464,15 +428,26 @@ public class PathFragmentTest {
static ImmutableSet<PathFragment> toPathsSet(String... strs) {
ImmutableSet.Builder<PathFragment> builder = ImmutableSet.builder();
for (String str : strs) {
- builder.add(PathFragment.create(str));
+ builder.add(create(str));
}
return builder.build();
}
@Test
public void testCompareTo() throws Exception {
- List<String> pathStrs = ImmutableList.of(
- "", "/", "//", ".", "/./", "foo/.//bar", "foo", "/foo", "foo/bar", "foo/Bar", "Foo/bar");
+ List<String> pathStrs =
+ ImmutableList.of(
+ "",
+ "/",
+ "foo",
+ "/foo",
+ "foo/bar",
+ "foo.bar",
+ "foo/bar.baz",
+ "foo/bar/baz",
+ "foo/barfile",
+ "foo/Bar",
+ "Foo/bar");
List<PathFragment> paths = toPaths(pathStrs);
// First test that compareTo is self-consistent.
for (PathFragment x : paths) {
@@ -493,36 +468,80 @@ public class PathFragmentTest {
}
}
}
- // Now test that compareTo does what we expect. The exact ordering here doesn't matter much,
- // but there are three things to notice: 1. absolute < relative, 2. comparison is lexicographic
- // 3. repeated slashes are ignored. (PathFragment("//") prints as "/").
+ // Now test that compareTo does what we expect. The exact ordering here doesn't matter much.
Collections.shuffle(paths);
Collections.sort(paths);
- List<PathFragment> expectedOrder = toPaths(ImmutableList.of(
- "/", "//", "/./", "/foo", "", ".", "Foo/bar", "foo", "foo/.//bar", "foo/Bar", "foo/bar"));
+ List<PathFragment> expectedOrder =
+ toPaths(
+ ImmutableList.of(
+ "",
+ "/",
+ "/foo",
+ "Foo/bar",
+ "foo",
+ "foo.bar",
+ "foo/Bar",
+ "foo/bar",
+ "foo/bar.baz",
+ "foo/bar/baz",
+ "foo/barfile"));
assertThat(paths).isEqualTo(expectedOrder);
}
@Test
public void testGetSafePathString() {
- assertThat(PathFragment.create("/").getSafePathString()).isEqualTo("/");
- assertThat(PathFragment.create("/abc").getSafePathString()).isEqualTo("/abc");
- assertThat(PathFragment.create("").getSafePathString()).isEqualTo(".");
+ assertThat(create("/").getSafePathString()).isEqualTo("/");
+ assertThat(create("/abc").getSafePathString()).isEqualTo("/abc");
+ assertThat(create("").getSafePathString()).isEqualTo(".");
assertThat(PathFragment.EMPTY_FRAGMENT.getSafePathString()).isEqualTo(".");
- assertThat(PathFragment.create("abc/def").getSafePathString()).isEqualTo("abc/def");
+ assertThat(create("abc/def").getSafePathString()).isEqualTo("abc/def");
}
@Test
public void testNormalize() {
- assertThat(PathFragment.create("/a/b").normalize()).isEqualTo(PathFragment.create("/a/b"));
- assertThat(PathFragment.create("/a/./b").normalize()).isEqualTo(PathFragment.create("/a/b"));
- assertThat(PathFragment.create("/a/../b").normalize()).isEqualTo(PathFragment.create("/b"));
- assertThat(PathFragment.create("a/b").normalize()).isEqualTo(PathFragment.create("a/b"));
- assertThat(PathFragment.create("a/../../b").normalize()).isEqualTo(PathFragment.create("../b"));
- assertThat(PathFragment.create("a/../..").normalize()).isEqualTo(PathFragment.create(".."));
- assertThat(PathFragment.create("a/../b").normalize()).isEqualTo(PathFragment.create("b"));
- assertThat(PathFragment.create("a/b/../b").normalize()).isEqualTo(PathFragment.create("a/b"));
- assertThat(PathFragment.create("/..").normalize()).isEqualTo(PathFragment.create("/.."));
+ assertThat(create("/a/b")).isEqualTo(create("/a/b"));
+ assertThat(create("/a/./b")).isEqualTo(create("/a/b"));
+ assertThat(create("/a/../b")).isEqualTo(create("/b"));
+ assertThat(create("a/b")).isEqualTo(create("a/b"));
+ assertThat(create("a/../../b")).isEqualTo(create("../b"));
+ assertThat(create("a/../..")).isEqualTo(create(".."));
+ assertThat(create("a/../b")).isEqualTo(create("b"));
+ assertThat(create("a/b/../b")).isEqualTo(create("a/b"));
+ assertThat(create("/..")).isEqualTo(create("/.."));
+ assertThat(create("..")).isEqualTo(create(".."));
+ }
+
+ @Test
+ public void testSegments() {
+ assertThat(create("").segmentCount()).isEqualTo(0);
+ assertThat(create("a").segmentCount()).isEqualTo(1);
+ assertThat(create("a/b").segmentCount()).isEqualTo(2);
+ assertThat(create("a/b/c").segmentCount()).isEqualTo(3);
+ assertThat(create("/").segmentCount()).isEqualTo(0);
+ assertThat(create("/a").segmentCount()).isEqualTo(1);
+ assertThat(create("/a/b").segmentCount()).isEqualTo(2);
+ assertThat(create("/a/b/c").segmentCount()).isEqualTo(3);
+
+ assertThat(create("").getSegments()).isEmpty();
+ assertThat(create("a").getSegments()).containsExactly("a").inOrder();
+ assertThat(create("a/b").getSegments()).containsExactly("a", "b").inOrder();
+ assertThat(create("a/b/c").getSegments()).containsExactly("a", "b", "c").inOrder();
+ assertThat(create("/").getSegments()).isEmpty();
+ assertThat(create("/a").getSegments()).containsExactly("a").inOrder();
+ assertThat(create("/a/b").getSegments()).containsExactly("a", "b").inOrder();
+ assertThat(create("/a/b/c").getSegments()).containsExactly("a", "b", "c").inOrder();
+
+ assertThat(create("a").getSegment(0)).isEqualTo("a");
+ assertThat(create("a/b").getSegment(0)).isEqualTo("a");
+ assertThat(create("a/b").getSegment(1)).isEqualTo("b");
+ assertThat(create("a/b/c").getSegment(2)).isEqualTo("c");
+ assertThat(create("/a").getSegment(0)).isEqualTo("a");
+ assertThat(create("/a/b").getSegment(0)).isEqualTo("a");
+ assertThat(create("/a/b").getSegment(1)).isEqualTo("b");
+ assertThat(create("/a/b/c").getSegment(2)).isEqualTo("c");
+
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("").getSegment(0));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("a/b").getSegment(2));
}
@Test
@@ -552,7 +571,7 @@ public class PathFragmentTest {
}
private void checkSerialization(String pathFragmentString, int expectedSize) throws Exception {
- PathFragment a = PathFragment.create(pathFragmentString);
+ PathFragment a = create(pathFragmentString);
byte[] sa = TestUtils.serializeObject(a);
assertThat(sa).hasLength(expectedSize);
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
index 1f18d07475..b2a8a52977 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
@@ -14,9 +14,9 @@
package com.google.devtools.build.lib.vfs;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assert.fail;
+import static com.google.devtools.build.lib.vfs.PathFragment.create;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,303 +30,199 @@ public class PathFragmentWindowsTest {
@Test
public void testWindowsSeparator() {
- assertThat(PathFragment.create("bar\\baz").toString()).isEqualTo("bar/baz");
- assertThat(PathFragment.create("c:\\bar\\baz").toString()).isEqualTo("C:/bar/baz");
+ assertThat(create("bar\\baz").toString()).isEqualTo("bar/baz");
+ assertThat(create("c:\\bar\\baz").toString()).isEqualTo("C:/bar/baz");
}
@Test
public void testIsAbsoluteWindows() {
- assertThat(PathFragment.create("C:/").isAbsolute()).isTrue();
- assertThat(PathFragment.create("C:/").isAbsolute()).isTrue();
- assertThat(PathFragment.create("C:/foo").isAbsolute()).isTrue();
- assertThat(PathFragment.create("d:/foo/bar").isAbsolute()).isTrue();
+ assertThat(create("C:/").isAbsolute()).isTrue();
+ assertThat(create("C:/").isAbsolute()).isTrue();
+ assertThat(create("C:/foo").isAbsolute()).isTrue();
+ assertThat(create("d:/foo/bar").isAbsolute()).isTrue();
- assertThat(PathFragment.create("*:/").isAbsolute()).isFalse();
+ assertThat(create("*:/").isAbsolute()).isFalse();
}
@Test
public void testAbsoluteAndAbsoluteLookingPaths() {
- PathFragment p1 = PathFragment.create("/c");
- assertThat(p1.isAbsolute()).isTrue();
- assertThat(p1.getDriveLetter()).isEqualTo('\0');
- assertThat(p1.getSegments()).containsExactly("c");
-
- PathFragment p2 = PathFragment.create("/c/");
- assertThat(p2.isAbsolute()).isTrue();
- assertThat(p2.getDriveLetter()).isEqualTo('\0');
- assertThat(p2.getSegments()).containsExactly("c");
-
- PathFragment p3 = PathFragment.create("C:/");
- assertThat(p3.isAbsolute()).isTrue();
- assertThat(p3.getDriveLetter()).isEqualTo('C');
- assertThat(p3.getSegments()).isEmpty();
-
- PathFragment p5 = PathFragment.create("/c:");
+ assertThat(create("/c").isAbsolute()).isTrue();
+ assertThat(create("/c").getSegments()).containsExactly("c");
+
+ assertThat(create("/c/").isAbsolute()).isTrue();
+ assertThat(create("/c/").getSegments()).containsExactly("c");
+
+ assertThat(create("C:/").isAbsolute()).isTrue();
+ assertThat(create("C:/").getSegments()).isEmpty();
+
+ PathFragment p5 = create("/c:");
assertThat(p5.isAbsolute()).isTrue();
- assertThat(p5.getDriveLetter()).isEqualTo('\0');
assertThat(p5.getSegments()).containsExactly("c:");
+ assertThat(create("C:").isAbsolute()).isFalse();
- assertThat(p1).isEqualTo(p2);
- assertThat(p1).isNotEqualTo(p3);
- assertThat(p1).isNotEqualTo(p5);
- assertThat(p3).isNotEqualTo(p5);
- }
+ assertThat(create("/c:").isAbsolute()).isTrue();
+ assertThat(create("/c:").getSegments()).containsExactly("c:");
- @Test
- public void testIsAbsoluteWindowsBackslash() {
- assertThat(PathFragment.create(new File("C:\\blah")).isAbsolute()).isTrue();
- assertThat(PathFragment.create(new File("C:\\")).isAbsolute()).isTrue();
- assertThat(PathFragment.create(new File("\\blah")).isAbsolute()).isTrue();
- assertThat(PathFragment.create(new File("\\")).isAbsolute()).isTrue();
+ assertThat(create("/c")).isEqualTo(create("/c/"));
+ assertThat(create("/c")).isNotEqualTo(create("C:/"));
+ assertThat(create("/c")).isNotEqualTo(create("C:"));
+ assertThat(create("/c")).isNotEqualTo(create("/c:"));
+ assertThat(create("C:/")).isNotEqualTo(create("C:"));
+ assertThat(create("C:/")).isNotEqualTo(create("/c:"));
}
@Test
- public void testIsNormalizedWindows() {
- assertThat(PathFragment.create("C:/").isNormalized()).isTrue();
- assertThat(PathFragment.create("C:/absolute/path").isNormalized()).isTrue();
- assertThat(PathFragment.create("C:/absolute/./path").isNormalized()).isFalse();
- assertThat(PathFragment.create("C:/absolute/../path").isNormalized()).isFalse();
+ public void testIsAbsoluteWindowsBackslash() {
+ assertThat(create(new File("C:\\blah").getPath()).isAbsolute()).isTrue();
+ assertThat(create(new File("C:\\").getPath()).isAbsolute()).isTrue();
+ assertThat(create(new File("\\blah").getPath()).isAbsolute()).isTrue();
+ assertThat(create(new File("\\").getPath()).isAbsolute()).isTrue();
}
@Test
public void testRootNodeReturnsRootStringWindows() {
- PathFragment rootFragment = PathFragment.create("C:/");
- assertThat(rootFragment.getPathString()).isEqualTo("C:/");
+ assertThat(create("C:/").getPathString()).isEqualTo("C:/");
}
@Test
public void testGetRelativeWindows() {
- assertThat(PathFragment.create("C:/a").getRelative("b").getPathString()).isEqualTo("C:/a/b");
- assertThat(PathFragment.create("C:/a/b").getRelative("c/d").getPathString())
- .isEqualTo("C:/a/b/c/d");
- assertThat(PathFragment.create("C:/a").getRelative("C:/b").getPathString()).isEqualTo("C:/b");
- assertThat(PathFragment.create("C:/a/b").getRelative("C:/c/d").getPathString())
- .isEqualTo("C:/c/d");
- assertThat(PathFragment.create("a").getRelative("C:/b").getPathString()).isEqualTo("C:/b");
- assertThat(PathFragment.create("a/b").getRelative("C:/c/d").getPathString())
- .isEqualTo("C:/c/d");
- }
-
- private void assertGetRelative(String path, String relative, PathFragment expected)
- throws Exception {
- PathFragment actual = PathFragment.create(path).getRelative(relative);
- assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
- assertThat(actual).isEqualTo(expected);
- assertThat(actual.getDriveLetter()).isEqualTo(expected.getDriveLetter());
- assertThat(actual.hashCode()).isEqualTo(expected.hashCode());
- }
-
- private void assertRelativeTo(String path, String relativeTo, String... expectedPathSegments)
- throws Exception {
- PathFragment expected = PathFragment.createAlreadyInterned('\0', false, expectedPathSegments);
- PathFragment actual = PathFragment.create(path).relativeTo(relativeTo);
- assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
- assertThat(actual).isEqualTo(expected);
- assertThat(actual.getDriveLetter()).isEqualTo(expected.getDriveLetter());
- assertThat(actual.hashCode()).isEqualTo(expected.hashCode());
- }
-
- private void assertCantComputeRelativeTo(String path, String relativeTo) throws Exception {
- try {
- PathFragment.create(path).relativeTo(relativeTo);
- fail("expected failure");
- } catch (Exception e) {
- assertThat(e).hasMessageThat().contains("is not beneath");
- }
- }
-
- private static PathFragment makePath(char drive, boolean absolute, String... segments) {
- return PathFragment.createAlreadyInterned(drive, absolute, segments);
+ assertThat(create("C:/a").getRelative("b").getPathString()).isEqualTo("C:/a/b");
+ assertThat(create("C:/a/b").getRelative("c/d").getPathString()).isEqualTo("C:/a/b/c/d");
+ assertThat(create("C:/a").getRelative("C:/b").getPathString()).isEqualTo("C:/b");
+ assertThat(create("C:/a/b").getRelative("C:/c/d").getPathString()).isEqualTo("C:/c/d");
+ assertThat(create("a").getRelative("C:/b").getPathString()).isEqualTo("C:/b");
+ assertThat(create("a/b").getRelative("C:/c/d").getPathString()).isEqualTo("C:/c/d");
}
@Test
public void testGetRelativeMixed() throws Exception {
- assertGetRelative("a", "b", makePath('\0', false, "a", "b"));
- assertGetRelative("a", "/b", makePath('\0', true, "b"));
- assertGetRelative("a", "E:/b", makePath('E', true, "b"));
+ assertThat(create("a").getRelative("b")).isEqualTo(create("a/b"));
+ assertThat(create("a").getRelative("/b")).isEqualTo(create("/b"));
+ assertThat(create("a").getRelative("E:/b")).isEqualTo(create("E:/b"));
- assertGetRelative("/a", "b", makePath('\0', true, "a", "b"));
- assertGetRelative("/a", "/b", makePath('\0', true, "b"));
- assertGetRelative("/a", "E:/b", makePath('E', true, "b"));
+ assertThat(create("/a").getRelative("b")).isEqualTo(create("/a/b"));
+ assertThat(create("/a").getRelative("/b")).isEqualTo(create("/b"));
+ assertThat(create("/a").getRelative("E:/b")).isEqualTo(create("E:/b"));
- assertGetRelative("D:/a", "b", makePath('D', true, "a", "b"));
- assertGetRelative("D:/a", "/b", makePath('D', true, "b"));
- assertGetRelative("D:/a", "E:/b", makePath('E', true, "b"));
+ assertThat(create("D:/a").getRelative("b")).isEqualTo(create("D:/a/b"));
+ assertThat(create("D:/a").getRelative("/b")).isEqualTo(create("/b"));
+ assertThat(create("D:/a").getRelative("E:/b")).isEqualTo(create("E:/b"));
}
@Test
public void testRelativeTo() throws Exception {
- assertRelativeTo("", "");
- assertCantComputeRelativeTo("", "a");
+ assertThat(create("").relativeTo("").getPathString()).isEqualTo("");
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("").relativeTo("a"));
- assertRelativeTo("a", "", "a");
- assertRelativeTo("a", "a");
- assertCantComputeRelativeTo("a", "b");
- assertRelativeTo("a/b", "a", "b");
+ assertThat(create("a").relativeTo("")).isEqualTo(create("a"));
+ assertThat(create("a").relativeTo("a").getPathString()).isEqualTo("");
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("a").relativeTo("b"));
+ assertThat(create("a/b").relativeTo("a")).isEqualTo(create("b"));
- assertCantComputeRelativeTo("C:/", "");
- assertRelativeTo("C:/", "C:/");
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("C:/").relativeTo(""));
+ assertThat(create("C:/").relativeTo("C:/").getPathString()).isEqualTo("");
}
@Test
public void testGetChildWorks() {
- PathFragment pf = PathFragment.create("../some/path");
- assertThat(pf.getChild("hi")).isEqualTo(PathFragment.create("../some/path/hi"));
- }
-
- // Tests after here test the canonicalization
- private void assertRegular(String expected, String actual) {
- PathFragment exp = PathFragment.create(expected);
- PathFragment act = PathFragment.create(actual);
- assertThat(exp.getPathString()).isEqualTo(expected);
- assertThat(act.getPathString()).isEqualTo(expected);
- assertThat(act).isEqualTo(exp);
- assertThat(act.hashCode()).isEqualTo(exp.hashCode());
+ assertThat(create("../some/path").getChild("hi")).isEqualTo(create("../some/path/hi"));
}
@Test
public void testEmptyPathToEmptyPathWindows() {
- assertRegular("C:/", "C:/");
- }
-
- private void assertAllEqual(PathFragment... ps) {
- assertThat(ps.length).isGreaterThan(1);
- for (int i = 1; i < ps.length; i++) {
- String msg = "comparing items 0 and " + i;
- assertWithMessage(msg + " for getPathString")
- .that(ps[i].getPathString())
- .isEqualTo(ps[0].getPathString());
- assertWithMessage(msg + " for equals").that(ps[0]).isEqualTo(ps[i]);
- assertWithMessage(msg + " for hashCode").that(ps[0].hashCode()).isEqualTo(ps[i].hashCode());
- }
- }
-
- @Test
- public void testEmptyRelativePathToEmptyPathWindows() {
- // Surprising but correct behavior: a PathFragment made of just a drive identifier (and not the
- // absolute path "C:/") is equal not only to the empty fragment, but (therefore) also to other
- // drive identifiers.
- // This makes sense if you consider that these are still empty paths, the drive letter adds no
- // information to the path itself.
- assertAllEqual(
- PathFragment.EMPTY_FRAGMENT,
- PathFragment.createAlreadyInterned('\0', false, new String[0]),
- PathFragment.createAlreadyInterned('C', false, new String[0]),
- PathFragment.createAlreadyInterned('D', false, new String[0]));
- assertAllEqual(PathFragment.create("/c"), PathFragment.create("/c/"));
- assertThat(PathFragment.create("C:/")).isNotEqualTo(PathFragment.create("/c"));
- assertThat(PathFragment.create("C:/foo")).isNotEqualTo(PathFragment.create("/c/foo"));
-
- assertThat(PathFragment.create("C:/")).isNotEqualTo(PathFragment.create("C:"));
- assertThat(PathFragment.create("C:/").getPathString())
- .isNotEqualTo(PathFragment.create("C:").getPathString());
+ assertThat(create("C:/")).isEqualTo(create("C:/"));
}
@Test
public void testWindowsVolumeUppercase() {
- assertRegular("C:/", "c:/");
+ assertThat(create("C:/")).isEqualTo(create("c:/"));
}
@Test
public void testRedundantSlashesWindows() {
- assertRegular("C:/", "C:///");
- assertRegular("C:/foo/bar", "C:/foo///bar");
- assertRegular("C:/foo/bar", "C:////foo//bar");
+ assertThat(create("C:/")).isEqualTo(create("C:///"));
+ assertThat(create("C:/foo/bar")).isEqualTo(create("C:/foo///bar"));
+ assertThat(create("C:/foo/bar")).isEqualTo(create("C:////foo//bar"));
}
@Test
public void testSimpleNameToSimpleNameWindows() {
- assertRegular("C:/foo", "C:/foo");
+ assertThat(create("C:/foo")).isEqualTo(create("C:/foo"));
}
@Test
public void testStripsTrailingSlashWindows() {
- assertRegular("C:/foo/bar", "C:/foo/bar/");
+ assertThat(create("C:/foo/bar")).isEqualTo(create("C:/foo/bar/"));
}
@Test
public void testGetParentDirectoryWindows() {
- PathFragment fooBarWizAbs = PathFragment.create("C:/foo/bar/wiz");
- PathFragment fooBarAbs = PathFragment.create("C:/foo/bar");
- PathFragment fooAbs = PathFragment.create("C:/foo");
- PathFragment rootAbs = PathFragment.create("C:/");
- assertThat(fooBarWizAbs.getParentDirectory()).isEqualTo(fooBarAbs);
- assertThat(fooBarAbs.getParentDirectory()).isEqualTo(fooAbs);
- assertThat(fooAbs.getParentDirectory()).isEqualTo(rootAbs);
- assertThat(rootAbs.getParentDirectory()).isNull();
-
- // Note, this is suprising but correct behaviour:
- assertThat(PathFragment.create("C:/foo/bar/..").getParentDirectory()).isEqualTo(fooBarAbs);
+ assertThat(create("C:/foo/bar/wiz").getParentDirectory()).isEqualTo(create("C:/foo/bar"));
+ assertThat(create("C:/foo/bar").getParentDirectory()).isEqualTo(create("C:/foo"));
+ assertThat(create("C:/foo").getParentDirectory()).isEqualTo(create("C:/"));
+ assertThat(create("C:/").getParentDirectory()).isNull();
}
@Test
public void testSegmentsCountWindows() {
- assertThat(PathFragment.create("C:/foo").segmentCount()).isEqualTo(1);
- assertThat(PathFragment.create("C:/").segmentCount()).isEqualTo(0);
+ assertThat(create("C:/foo").segmentCount()).isEqualTo(1);
+ assertThat(create("C:/").segmentCount()).isEqualTo(0);
}
@Test
public void testGetSegmentWindows() {
- assertThat(PathFragment.create("C:/foo/bar").getSegment(0)).isEqualTo("foo");
- assertThat(PathFragment.create("C:/foo/bar").getSegment(1)).isEqualTo("bar");
- assertThat(PathFragment.create("C:/foo/").getSegment(0)).isEqualTo("foo");
- assertThat(PathFragment.create("C:/foo").getSegment(0)).isEqualTo("foo");
+ assertThat(create("C:/foo/bar").getSegment(0)).isEqualTo("foo");
+ assertThat(create("C:/foo/bar").getSegment(1)).isEqualTo("bar");
+ assertThat(create("C:/foo/").getSegment(0)).isEqualTo("foo");
+ assertThat(create("C:/foo").getSegment(0)).isEqualTo("foo");
}
@Test
public void testBasenameWindows() throws Exception {
- assertThat(PathFragment.create("C:/foo/bar").getBaseName()).isEqualTo("bar");
- assertThat(PathFragment.create("C:/foo").getBaseName()).isEqualTo("foo");
+ assertThat(create("C:/foo/bar").getBaseName()).isEqualTo("bar");
+ assertThat(create("C:/foo").getBaseName()).isEqualTo("foo");
// Never return the drive name as a basename.
- assertThat(PathFragment.create("C:/").getBaseName()).isEmpty();
- }
-
- private static void assertPath(String expected, PathFragment actual) {
- assertThat(actual.getPathString()).isEqualTo(expected);
+ assertThat(create("C:/").getBaseName()).isEmpty();
}
@Test
public void testReplaceNameWindows() throws Exception {
- assertPath("C:/foo/baz", PathFragment.create("C:/foo/bar").replaceName("baz"));
- assertThat(PathFragment.create("C:/").replaceName("baz")).isNull();
+ assertThat(create("C:/foo/bar").replaceName("baz").getPathString()).isEqualTo("C:/foo/baz");
+ assertThat(create("C:/").replaceName("baz")).isNull();
}
@Test
public void testStartsWithWindows() {
- assertThat(PathFragment.create("C:/foo/bar").startsWith(PathFragment.create("C:/foo")))
- .isTrue();
- assertThat(PathFragment.create("C:/foo/bar").startsWith(PathFragment.create("C:/"))).isTrue();
- assertThat(PathFragment.create("C:/").startsWith(PathFragment.create("C:/"))).isTrue();
+ assertThat(create("C:/foo/bar").startsWith(create("C:/foo"))).isTrue();
+ assertThat(create("C:/foo/bar").startsWith(create("C:/"))).isTrue();
+ assertThat(create("C:/").startsWith(create("C:/"))).isTrue();
// The first path is absolute, the second is not.
- assertThat(PathFragment.create("C:/foo/bar").startsWith(PathFragment.create("C:"))).isFalse();
- assertThat(PathFragment.create("C:/").startsWith(PathFragment.create("C:"))).isFalse();
+ assertThat(create("C:/foo/bar").startsWith(create("C:"))).isFalse();
+ assertThat(create("C:/").startsWith(create("C:"))).isFalse();
}
@Test
public void testEndsWithWindows() {
- assertThat(PathFragment.create("C:/foo/bar").endsWith(PathFragment.create("bar"))).isTrue();
- assertThat(PathFragment.create("C:/foo/bar").endsWith(PathFragment.create("foo/bar"))).isTrue();
- assertThat(PathFragment.create("C:/foo/bar").endsWith(PathFragment.create("C:/foo/bar")))
- .isTrue();
- assertThat(PathFragment.create("C:/").endsWith(PathFragment.create("C:/"))).isTrue();
+ assertThat(create("C:/foo/bar").endsWith(create("bar"))).isTrue();
+ assertThat(create("C:/foo/bar").endsWith(create("foo/bar"))).isTrue();
+ assertThat(create("C:/foo/bar").endsWith(create("C:/foo/bar"))).isTrue();
+ assertThat(create("C:/").endsWith(create("C:/"))).isTrue();
}
@Test
public void testGetSafePathStringWindows() {
- assertThat(PathFragment.create("C:/").getSafePathString()).isEqualTo("C:/");
- assertThat(PathFragment.create("C:/abc").getSafePathString()).isEqualTo("C:/abc");
- assertThat(PathFragment.create("C:/abc/def").getSafePathString()).isEqualTo("C:/abc/def");
+ assertThat(create("C:/").getSafePathString()).isEqualTo("C:/");
+ assertThat(create("C:/abc").getSafePathString()).isEqualTo("C:/abc");
+ assertThat(create("C:/abc/def").getSafePathString()).isEqualTo("C:/abc/def");
}
@Test
public void testNormalizeWindows() {
- assertThat(PathFragment.create("C:/a/b").normalize()).isEqualTo(PathFragment.create("C:/a/b"));
- assertThat(PathFragment.create("C:/a/./b").normalize())
- .isEqualTo(PathFragment.create("C:/a/b"));
- assertThat(PathFragment.create("C:/a/../b").normalize()).isEqualTo(PathFragment.create("C:/b"));
- assertThat(PathFragment.create("C:/../b").normalize())
- .isEqualTo(PathFragment.create("C:/../b"));
+ assertThat(create("C:/a/b")).isEqualTo(create("C:/a/b"));
+ assertThat(create("C:/a/./b")).isEqualTo(create("C:/a/b"));
+ assertThat(create("C:/a/../b")).isEqualTo(create("C:/b"));
+ assertThat(create("C:/../b")).isEqualTo(create("C:/../b"));
}
@Test
@@ -335,8 +231,21 @@ public class PathFragmentWindowsTest {
// of drive C:\".
// Bazel doesn't resolve such paths, and just takes them literally like normal path segments.
// If the user attempts to open files under such paths, the file system API will give an error.
- assertThat(PathFragment.create("C:").isAbsolute()).isFalse();
- assertThat(PathFragment.create("C:").getDriveLetter()).isEqualTo('\0');
- assertThat(PathFragment.create("C:").getSegments()).containsExactly("C:");
+ assertThat(create("C:").isAbsolute()).isFalse();
+ assertThat(create("C:").getSegments()).containsExactly("C:");
+ }
+
+ @Test
+ public void testToRelative() {
+ assertThat(create("C:/foo/bar").toRelative()).isEqualTo(create("foo/bar"));
+ assertThat(create("C:/").toRelative()).isEqualTo(create(""));
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("foo").toRelative());
+ }
+
+ @Test
+ public void testGetDriveStr() {
+ assertThat(create("C:/foo/bar").getDriveStr()).isEqualTo("C:/");
+ assertThat(create("C:/").getDriveStr()).isEqualTo("C:/");
+ MoreAsserts.expectThrows(IllegalArgumentException.class, () -> create("foo").getDriveStr());
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathTest.java
deleted file mode 100644
index eaad458438..0000000000
--- a/src/test/java/com/google/devtools/build/lib/vfs/PathTest.java
+++ /dev/null
@@ -1,341 +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 static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.testing.EqualsTester;
-import com.google.common.testing.GcFinalization;
-import com.google.devtools.build.lib.clock.BlazeClock;
-import com.google.devtools.build.lib.skyframe.serialization.InjectingObjectCodecAdapter;
-import com.google.devtools.build.lib.skyframe.serialization.testutils.FsUtils;
-import com.google.devtools.build.lib.skyframe.serialization.testutils.ObjectCodecTester;
-import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.lang.ref.WeakReference;
-import java.net.URI;
-import java.util.Collections;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * A test for {@link Path}.
- */
-@RunWith(JUnit4.class)
-public class PathTest {
- private FileSystem filesystem;
- private Path root;
-
- @Before
- public final void initializeFileSystem() throws Exception {
- filesystem = new InMemoryFileSystem(BlazeClock.instance());
- root = filesystem.getRootDirectory();
- Path first = root.getChild("first");
- first.createDirectory();
- }
-
- @Test
- public void testStartsWithWorksForSelf() {
- assertStartsWithReturns(true, "/first/child", "/first/child");
- }
-
- @Test
- public void testStartsWithWorksForChild() {
- assertStartsWithReturns(true,
- "/first/child", "/first/child/grandchild");
- }
-
- @Test
- public void testStartsWithWorksForDeepDescendant() {
- assertStartsWithReturns(true,
- "/first/child", "/first/child/grandchild/x/y/z");
- }
-
- @Test
- public void testStartsWithFailsForParent() {
- assertStartsWithReturns(false, "/first/child", "/first");
- }
-
- @Test
- public void testStartsWithFailsForSibling() {
- assertStartsWithReturns(false, "/first/child", "/first/child2");
- }
-
- @Test
- public void testStartsWithFailsForLinkToDescendant()
- throws Exception {
- Path linkTarget = filesystem.getPath("/first/linked_to");
- FileSystemUtils.createEmptyFile(linkTarget);
- Path second = filesystem.getPath("/second/");
- second.createDirectory();
- second.getChild("child_link").createSymbolicLink(linkTarget);
- assertStartsWithReturns(false, "/first", "/second/child_link");
- }
-
- @Test
- public void testStartsWithFailsForNullPrefix() {
- try {
- filesystem.getPath("/first").startsWith(null);
- fail();
- } catch (Exception e) {
- }
- }
-
- private void assertStartsWithReturns(boolean expected,
- String ancestor,
- String descendant) {
- Path parent = filesystem.getPath(ancestor);
- Path child = filesystem.getPath(descendant);
- assertThat(child.startsWith(parent)).isEqualTo(expected);
- }
-
- @Test
- public void testGetChildWorks() {
- assertGetChildWorks("second");
- assertGetChildWorks("...");
- assertGetChildWorks("....");
- }
-
- private void assertGetChildWorks(String childName) {
- assertThat(filesystem.getPath("/first").getChild(childName))
- .isEqualTo(filesystem.getPath("/first/" + childName));
- }
-
- @Test
- public void testGetChildFailsForChildWithSlashes() {
- assertGetChildFails("second/third");
- assertGetChildFails("./third");
- assertGetChildFails("../third");
- assertGetChildFails("second/..");
- assertGetChildFails("second/.");
- assertGetChildFails("/third");
- assertGetChildFails("third/");
- }
-
- private void assertGetChildFails(String childName) {
- try {
- filesystem.getPath("/first").getChild(childName);
- fail();
- } catch (IllegalArgumentException e) {
- // Expected.
- }
- }
-
- @Test
- public void testGetChildFailsForDotAndDotDot() {
- assertGetChildFails(".");
- assertGetChildFails("..");
- }
-
- @Test
- public void testGetChildFailsForEmptyString() {
- assertGetChildFails("");
- }
-
- @Test
- public void testRelativeToWorks() {
- assertRelativeToWorks("apple", "/fruit/apple", "/fruit");
- assertRelativeToWorks("apple/jonagold", "/fruit/apple/jonagold", "/fruit");
- }
-
- @Test
- public void testGetRelativeWithStringWorks() {
- assertGetRelativeWorks("/first/x/y", "y");
- assertGetRelativeWorks("/y", "/y");
- assertGetRelativeWorks("/first/x/x", "./x");
- assertGetRelativeWorks("/first/y", "../y");
- assertGetRelativeWorks("/", "../../../../..");
- }
-
- @Test
- public void testAsFragmentWorks() {
- assertAsFragmentWorks("/");
- assertAsFragmentWorks("//");
- assertAsFragmentWorks("/first");
- assertAsFragmentWorks("/first/x/y");
- assertAsFragmentWorks("/first/x/y.foo");
- }
-
- @Test
- public void testGetRelativeWithFragmentWorks() {
- Path dir = filesystem.getPath("/first/x");
- assertThat(dir.getRelative(PathFragment.create("y")).toString()).isEqualTo("/first/x/y");
- assertThat(dir.getRelative(PathFragment.create("./x")).toString()).isEqualTo("/first/x/x");
- assertThat(dir.getRelative(PathFragment.create("../y")).toString()).isEqualTo("/first/y");
- }
-
- @Test
- public void testGetRelativeWithAbsoluteFragmentWorks() {
- Path root = filesystem.getPath("/first/x");
- assertThat(root.getRelative(PathFragment.create("/x/y")).toString()).isEqualTo("/x/y");
- }
-
- @Test
- public void testGetRelativeWithAbsoluteStringWorks() {
- Path root = filesystem.getPath("/first/x");
- assertThat(root.getRelative("/x/y").toString()).isEqualTo("/x/y");
- }
-
- @Test
- public void testComparableSortOrder() {
- Path zzz = filesystem.getPath("/zzz");
- Path ZZZ = filesystem.getPath("/ZZZ");
- Path abc = filesystem.getPath("/abc");
- Path aBc = filesystem.getPath("/aBc");
- Path AbC = filesystem.getPath("/AbC");
- Path ABC = filesystem.getPath("/ABC");
- List<Path> list = Lists.newArrayList(zzz, ZZZ, ABC, aBc, AbC, abc);
- Collections.sort(list);
- assertThat(list).containsExactly(ABC, AbC, ZZZ, aBc, abc, zzz).inOrder();
- }
-
- @Test
- public void testParentOfRootIsRoot() {
- assertThat(root.getRelative("..")).isSameAs(root);
-
- assertThat(root.getRelative("broken/../../dots")).isSameAs(root.getRelative("dots"));
- }
-
- @Test
- public void testSingleSegmentEquivalence() {
- assertThat(root.getRelative("aSingleSegment")).isSameAs(root.getRelative("aSingleSegment"));
- }
-
- @Test
- public void testSiblingNonEquivalenceString() {
- assertThat(root.getRelative("aDifferentSegment"))
- .isNotSameAs(root.getRelative("aSingleSegment"));
- }
-
- @Test
- public void testSiblingNonEquivalenceFragment() {
- assertThat(root.getRelative(PathFragment.create("aDifferentSegment")))
- .isNotSameAs(root.getRelative(PathFragment.create("aSingleSegment")));
- }
-
- @Test
- public void testHashCodeStableAcrossGarbageCollections() {
- Path parent = filesystem.getPath("/a");
- PathFragment childFragment = PathFragment.create("b");
- Path child = parent.getRelative(childFragment);
- WeakReference<Path> childRef = new WeakReference<>(child);
- int childHashCode1 = childRef.get().hashCode();
- assertThat(parent.getRelative(childFragment).hashCode()).isEqualTo(childHashCode1);
- child = null;
- GcFinalization.awaitClear(childRef);
- int childHashCode2 = parent.getRelative(childFragment).hashCode();
- assertThat(childHashCode2).isEqualTo(childHashCode1);
- }
-
- @Test
- public void testSerialization() throws Exception {
- FileSystem oldFileSystem = Path.getFileSystemForSerialization();
- try {
- Path.setFileSystemForSerialization(filesystem);
- Path root = filesystem.getPath("/");
- Path p1 = filesystem.getPath("/foo");
- Path p2 = filesystem.getPath("/foo/bar");
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(bos);
-
- oos.writeObject(root);
- oos.writeObject(p1);
- oos.writeObject(p2);
-
- ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
- ObjectInputStream ois = new ObjectInputStream(bis);
-
- Path dsRoot = (Path) ois.readObject();
- Path dsP1 = (Path) ois.readObject();
- Path dsP2 = (Path) ois.readObject();
-
- new EqualsTester()
- .addEqualityGroup(root, dsRoot)
- .addEqualityGroup(p1, dsP1)
- .addEqualityGroup(p2, dsP2)
- .testEquals();
-
- assertThat(p2.startsWith(p1)).isTrue();
- assertThat(p2.startsWith(dsP1)).isTrue();
- assertThat(dsP2.startsWith(p1)).isTrue();
- assertThat(dsP2.startsWith(dsP1)).isTrue();
-
- // Regression test for a very specific bug in compareTo involving our incorrect usage of
- // reference equality rather than logical equality.
- String relativePathStringA = "child/grandchildA";
- String relativePathStringB = "child/grandchildB";
- assertThat(
- p1.getRelative(relativePathStringA).compareTo(dsP1.getRelative(relativePathStringB)))
- .isEqualTo(
- p1.getRelative(relativePathStringA).compareTo(p1.getRelative(relativePathStringB)));
- } finally {
- Path.setFileSystemForSerialization(oldFileSystem);
- }
- }
-
- @Test
- public void testAbsolutePathRoot() {
- assertThat(new Path(null).toString()).isEqualTo("/");
- }
-
- @Test
- public void testAbsolutePath() {
- Path segment = new Path(null, "bar.txt",
- new Path(null, "foo", new Path(null)));
- assertThat(segment.toString()).isEqualTo("/foo/bar.txt");
- }
-
- @Test
- public void testToURI() throws Exception {
- Path p = root.getRelative("/tmp/foo bar.txt");
- URI uri = p.toURI();
- assertThat(uri.toString()).isEqualTo("file:///tmp/foo%20bar.txt");
- }
-
- @Test
- public void testCodec() throws Exception {
- ObjectCodecTester.newBuilder(
- new InjectingObjectCodecAdapter<>(Path.CODEC, FsUtils.TEST_FILESYSTEM_PROVIDER))
- .addSubjects(
- ImmutableList.of(
- FsUtils.TEST_FILESYSTEM.getPath("/"),
- FsUtils.TEST_FILESYSTEM.getPath("/some/path"),
- FsUtils.TEST_FILESYSTEM.getPath("/some/other/path/with/empty/last/fragment/")))
- .buildAndRunTests();
- }
-
- private void assertAsFragmentWorks(String expected) {
- assertThat(filesystem.getPath(expected).asFragment()).isEqualTo(PathFragment.create(expected));
- }
-
- private void assertGetRelativeWorks(String expected, String relative) {
- assertThat(filesystem.getPath("/first/x").getRelative(relative))
- .isEqualTo(filesystem.getPath(expected));
- }
-
- private void assertRelativeToWorks(String expected, String relative, String original) {
- assertThat(filesystem.getPath(relative).relativeTo(filesystem.getPath(original)))
- .isEqualTo(PathFragment.create(expected));
- }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathTrieTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathTrieTest.java
deleted file mode 100644
index 0807b4aa5d..0000000000
--- a/src/test/java/com/google/devtools/build/lib/vfs/PathTrieTest.java
+++ /dev/null
@@ -1,78 +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 static com.google.common.truth.Truth.assertThat;
-import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@link PathTrie}. */
-@RunWith(JUnit4.class)
-public class PathTrieTest {
- @Test
- public void empty() {
- PathTrie<Integer> pathTrie = new PathTrie<>();
- assertThat(pathTrie.get(PathFragment.EMPTY_FRAGMENT)).isNull();
- assertThat(pathTrie.get(PathFragment.create("/x"))).isNull();
- assertThat(pathTrie.get(PathFragment.create("/x/y"))).isNull();
- }
-
- @Test
- public void simpleBranches() {
- PathTrie<Integer> pathTrie = new PathTrie<>();
- pathTrie.put(PathFragment.create("/a"), 1);
- pathTrie.put(PathFragment.create("/b"), 2);
-
- assertThat(pathTrie.get(PathFragment.EMPTY_FRAGMENT)).isNull();
- assertThat(pathTrie.get(PathFragment.create("/a"))).isEqualTo(1);
- assertThat(pathTrie.get(PathFragment.create("/a/b"))).isEqualTo(1);
- assertThat(pathTrie.get(PathFragment.create("/a/b/c"))).isEqualTo(1);
- assertThat(pathTrie.get(PathFragment.create("/b"))).isEqualTo(2);
- assertThat(pathTrie.get(PathFragment.create("/b/c"))).isEqualTo(2);
- }
-
- @Test
- public void nestedDirectories() {
- PathTrie<Integer> pathTrie = new PathTrie<>();
- pathTrie.put(PathFragment.create("/a/b/c"), 3);
- assertThat(pathTrie.get(PathFragment.EMPTY_FRAGMENT)).isNull();
- assertThat(pathTrie.get(PathFragment.create("/a"))).isNull();
- assertThat(pathTrie.get(PathFragment.create("/a/b"))).isNull();
- assertThat(pathTrie.get(PathFragment.create("/a/b/c"))).isEqualTo(3);
- assertThat(pathTrie.get(PathFragment.create("/a/b/c/d"))).isEqualTo(3);
-
- pathTrie.put(PathFragment.create("/a"), 1);
- assertThat(pathTrie.get(PathFragment.EMPTY_FRAGMENT)).isNull();
- assertThat(pathTrie.get(PathFragment.create("/b"))).isNull();
- assertThat(pathTrie.get(PathFragment.create("/a"))).isEqualTo(1);
- assertThat(pathTrie.get(PathFragment.create("/a/b"))).isEqualTo(1);
- assertThat(pathTrie.get(PathFragment.create("/a/b/c"))).isEqualTo(3);
- assertThat(pathTrie.get(PathFragment.create("/a/b/c/d"))).isEqualTo(3);
-
- pathTrie.put(PathFragment.ROOT_FRAGMENT, 0);
- assertThat(pathTrie.get(PathFragment.EMPTY_FRAGMENT)).isEqualTo(0);
- assertThat(pathTrie.get(PathFragment.create("/b"))).isEqualTo(0);
- assertThat(pathTrie.get(PathFragment.create("/a"))).isEqualTo(1);
- assertThat(pathTrie.get(PathFragment.create("/a/b"))).isEqualTo(1);
- }
-
- @Test
- public void unrootedDirectoriesError() {
- PathTrie<Integer> pathTrie = new PathTrie<>();
- assertThrows(IllegalArgumentException.class, () -> pathTrie.put(PathFragment.create("a"), 1));
- }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java
index 4720a6e773..ba7029daf2 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java
@@ -16,6 +16,7 @@ package com.google.devtools.build.lib.vfs;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.FileSystem.NotASymlinkException;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -197,7 +198,8 @@ public abstract class SymlinkAwareFileSystemTest extends FileSystemTest {
linkPath.delete();
createSymbolicLink(linkPath, relative);
if (testFS.supportsSymbolicLinksNatively(linkPath)) {
- assertThat(linkPath.getFileSize(Symlinks.NOFOLLOW)).isEqualTo(linkTarget.length());
+ assertThat(linkPath.getFileSize(Symlinks.NOFOLLOW))
+ .isEqualTo(relative.getSafePathString().length());
assertThat(linkPath.readSymbolicLink()).isEqualTo(relative);
}
}
@@ -205,6 +207,10 @@ public abstract class SymlinkAwareFileSystemTest extends FileSystemTest {
@Test
public void testLinkToRootResolvesCorrectly() throws IOException {
+ if (OS.getCurrent() == OS.WINDOWS) {
+ // This test cannot be run on Windows, it mixes "/" paths with "C:/" paths
+ return;
+ }
Path rootPath = testFS.getPath("/");
try {
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java
index 1e877a5255..3799cfdd46 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java
@@ -163,7 +163,7 @@ public class UnionFileSystemTest extends SymlinkAwareFileSystemTest {
// FileSystemTest.setUp() silently creates the test root on the filesystem...
Path testDirUnderRoot = unionfs.getPath(workingDir.asFragment().subFragment(0, 1));
- assertThat(unionfs.getDirectoryEntries(unionfs.getRootDirectory()))
+ assertThat(unionfs.getDirectoryEntries(unionfs.getPath("/")))
.containsExactly(
foo.getBaseName(),
bar.getBaseName(),
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnixLocalPathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java
index 2cdb4014ae..306ca9bc08 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/UnixLocalPathTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java
@@ -17,15 +17,13 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.testing.EqualsTester;
-import com.google.devtools.build.lib.vfs.LocalPath.OsPathPolicy;
-import com.google.devtools.build.lib.vfs.LocalPath.UnixOsPathPolicy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-/** Tests the unix implementation of {@link LocalPath}. */
+/** Tests the unix implementation of {@link Path}. */
@RunWith(JUnit4.class)
-public class UnixLocalPathTest extends LocalPathAbstractTest {
+public class UnixPathTest extends PathAbstractTest {
@Test
public void testEqualsAndHashCodeUnix() {
@@ -37,8 +35,6 @@ public class UnixLocalPathTest extends LocalPathAbstractTest {
@Test
public void testRelativeToUnix() {
- // Cannot relativize absolute and non-absolute
- assertThat(create("c/d").getRelative("/a/b").getPathString()).isEqualTo("/a/b");
assertThat(create("/").relativeTo(create("/")).getPathString()).isEmpty();
assertThat(create("/foo").relativeTo(create("/foo")).getPathString()).isEmpty();
assertThat(create("/foo/bar/baz").relativeTo(create("/foo")).getPathString())
@@ -53,16 +49,24 @@ public class UnixLocalPathTest extends LocalPathAbstractTest {
}
@Test
- public void testIsAbsoluteUnix() {
- assertThat(create("/absolute/test").isAbsolute()).isTrue();
- assertThat(create("relative/test").isAbsolute()).isFalse();
- }
-
- @Test
public void testGetRelativeUnix() {
assertThat(create("/a").getRelative("b").getPathString()).isEqualTo("/a/b");
+ assertThat(create("/a/b").getRelative("c/d").getPathString()).isEqualTo("/a/b/c/d");
+ assertThat(create("/c/d").getRelative("/a/b").getPathString()).isEqualTo("/a/b");
+ assertThat(create("/a").getRelative("").getPathString()).isEqualTo("/a");
assertThat(create("/").getRelative("").getPathString()).isEqualTo("/");
- assertThat(create("c/d").getRelative("/a/b").getPathString()).isEqualTo("/a/b");
+ assertThat(create("/a/b").getRelative("../foo").getPathString()).isEqualTo("/a/foo");
+
+ // Make sure any fast path of Path#getRelative(PathFragment) works
+ assertThat(create("/a/b").getRelative(PathFragment.create("../foo")).getPathString())
+ .isEqualTo("/a/foo");
+
+ // Make sure any fast path of Path#getRelative(PathFragment) works
+ assertThat(create("/c/d").getRelative(PathFragment.create("/a/b")).getPathString())
+ .isEqualTo("/a/b");
+
+ // Test normalization
+ assertThat(create("/a").getRelative(".").getPathString()).isEqualTo("/a");
}
@Test
@@ -107,14 +111,10 @@ public class UnixLocalPathTest extends LocalPathAbstractTest {
@Test
public void testGetParentDirectoryUnix() {
- LocalPath fooBarWizAbs = create("/foo/bar/wiz");
- LocalPath fooBarAbs = create("/foo/bar");
- LocalPath fooAbs = create("/foo");
- LocalPath rootAbs = create("/");
- assertThat(fooBarWizAbs.getParentDirectory()).isEqualTo(fooBarAbs);
- assertThat(fooBarAbs.getParentDirectory()).isEqualTo(fooAbs);
- assertThat(fooAbs.getParentDirectory()).isEqualTo(rootAbs);
- assertThat(rootAbs.getParentDirectory()).isNull();
+ assertThat(create("/foo/bar/wiz").getParentDirectory()).isEqualTo(create("/foo/bar"));
+ assertThat(create("/foo/bar").getParentDirectory()).isEqualTo(create("/foo"));
+ assertThat(create("/foo").getParentDirectory()).isEqualTo(create("/"));
+ assertThat(create("/").getParentDirectory()).isNull();
}
@Test
@@ -127,8 +127,7 @@ public class UnixLocalPathTest extends LocalPathAbstractTest {
@Test
public void testStartsWithUnix() {
- LocalPath foobar = create("/foo/bar");
- LocalPath foobarRelative = create("foo/bar");
+ Path foobar = create("/foo/bar");
// (path, prefix) => true
assertThat(foobar.startsWith(foobar)).isTrue();
@@ -141,13 +140,6 @@ public class UnixLocalPathTest extends LocalPathAbstractTest {
assertThat(create("/foo").startsWith(foobar)).isFalse();
assertThat(create("/").startsWith(foobar)).isFalse();
- // (absolute, relative) => false
- assertThat(foobar.startsWith(foobarRelative)).isFalse();
- assertThat(foobarRelative.startsWith(foobar)).isFalse();
-
- // relative paths start with nothing, absolute paths do not
- assertThat(foobar.startsWith(create(""))).isFalse();
-
// (path, sibling) => false
assertThat(create("/foo/wiz").startsWith(foobar)).isFalse();
assertThat(foobar.startsWith(create("/foo/wiz"))).isFalse();
@@ -162,8 +154,10 @@ public class UnixLocalPathTest extends LocalPathAbstractTest {
assertThat(create("/..")).isEqualTo(create("/.."));
}
- @Override
- protected OsPathPolicy getFilePathOs() {
- return new UnixOsPathPolicy();
+ @Test
+ public void testParentOfRootIsRootUnix() {
+ assertThat(create("/..")).isEqualTo(create("/"));
+ assertThat(create("/../../../../../..")).isEqualTo(create("/"));
+ assertThat(create("/../../../foo")).isEqualTo(create("/foo"));
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/WindowsLocalPathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/WindowsPathTest.java
index ac5acef1f4..c7592197e1 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/WindowsLocalPathTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/WindowsPathTest.java
@@ -17,18 +17,16 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.testing.EqualsTester;
import com.google.devtools.build.lib.testutil.MoreAsserts;
-import com.google.devtools.build.lib.vfs.LocalPath.OsPathPolicy;
-import com.google.devtools.build.lib.vfs.LocalPath.WindowsOsPathPolicy;
-import com.google.devtools.build.lib.vfs.LocalPath.WindowsOsPathPolicy.ShortPathResolver;
+import com.google.devtools.build.lib.vfs.WindowsOsPathPolicy.ShortPathResolver;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-/** Tests windows-specific parts of {@link LocalPath} */
+/** Tests windows-specific parts of {@link Path} */
@RunWith(JUnit4.class)
-public class WindowsLocalPathTest extends LocalPathAbstractTest {
+public class WindowsPathTest extends PathAbstractTest {
private static final class MockShortPathResolver implements ShortPathResolver {
// Full path to resolved child mapping.
@@ -52,26 +50,18 @@ public class WindowsLocalPathTest extends LocalPathAbstractTest {
}
}
- private final MockShortPathResolver shortPathResolver = new MockShortPathResolver();
-
- @Override
- protected OsPathPolicy getFilePathOs() {
- return new WindowsOsPathPolicy(shortPathResolver);
- }
-
@Test
public void testEqualsAndHashcodeWindows() {
new EqualsTester()
- .addEqualityGroup(create("a/b"), create("A/B"))
.addEqualityGroup(create("/a/b"), create("/A/B"))
.addEqualityGroup(create("c:/a/b"), create("C:\\A\\B"))
- .addEqualityGroup(create("something/else"))
+ .addEqualityGroup(create("C:/something/else"))
.testEquals();
}
@Test
public void testCaseIsPreserved() {
- assertThat(create("a/B").getPathString()).isEqualTo("a/B");
+ assertThat(create("C:/a/B").getPathString()).isEqualTo("C:/a/B");
}
@Test
@@ -96,69 +86,62 @@ public class WindowsLocalPathTest extends LocalPathAbstractTest {
public void testGetParentDirectoryWindows() {
assertThat(create("C:/foo").getParentDirectory()).isEqualTo(create("C:/"));
assertThat(create("C:/").getParentDirectory()).isNull();
+ assertThat(create("/").getParentDirectory()).isNull();
}
@Test
- public void testisAbsoluteWindows() {
- assertThat(create("C:/").isAbsolute()).isTrue();
- // test that msys paths turn into absolute paths
- assertThat(create("/").isAbsolute()).isTrue();
+ public void testParentOfRootIsRootWindows() {
+ assertThat(create("C:/..")).isEqualTo(create("C:/"));
+ assertThat(create("C:/../../../../../..")).isEqualTo(create("C:/"));
+ assertThat(create("C:/../../../foo")).isEqualTo(create("C:/foo"));
}
@Test
public void testRelativeToWindows() {
- assertThat(create("C:/foo").relativeTo(create("C:/"))).isEqualTo(create("foo"));
+ assertThat(create("C:/foo").relativeTo(create("C:/")).getPathString()).isEqualTo("foo");
// Case insensitivity test
- assertThat(create("C:/foo/bar").relativeTo(create("C:/FOO"))).isEqualTo(create("bar"));
+ assertThat(create("C:/foo/bar").relativeTo(create("C:/FOO")).getPathString()).isEqualTo("bar");
MoreAsserts.assertThrows(
IllegalArgumentException.class, () -> create("D:/foo").relativeTo(create("C:/")));
}
@Test
- public void testAbsoluteUnixPathIsRelativeToWindowsUnixRoot() {
- assertThat(create("/").getPathString()).isEqualTo("C:/fake/msys");
- assertThat(create("/foo/bar").getPathString()).isEqualTo("C:/fake/msys/foo/bar");
- assertThat(create("/foo/bar").getPathString()).isEqualTo("C:/fake/msys/foo/bar");
- }
-
- @Test
- public void testAbsoluteUnixPathReferringToDriveIsRecognized() {
- assertThat(create("/c/foo").getPathString()).isEqualTo("C:/foo");
- assertThat(create("/c/foo").getPathString()).isEqualTo("C:/foo");
- assertThat(create("/c:").getPathString()).isNotEqualTo("C:/foo");
- }
-
- @Test
public void testResolvesShortenedPaths() {
+ MockShortPathResolver shortPathResolver = new MockShortPathResolver();
+ WindowsOsPathPolicy osPathPolicy = new WindowsOsPathPolicy(shortPathResolver);
shortPathResolver.resolutions.put("d:/progra~1", "program files");
shortPathResolver.resolutions.put("d:/program files/micros~1", "microsoft something");
shortPathResolver.resolutions.put(
"d:/program files/microsoft something/foo/~bar~1", "~bar_hello");
// Assert normal shortpath resolution.
- LocalPath normal = create("d:/progra~1/micros~1/foo/~bar~1/baz");
- // The path string has an upper-case drive letter because that's how path printing works.
- assertThat(normal.getPathString())
+ assertThat(normalize(osPathPolicy, "d:/progra~1/micros~1/foo/~bar~1/baz"))
.isEqualTo("D:/program files/microsoft something/foo/~bar_hello/baz");
- LocalPath notYetExistent = create("d:/progra~1/micros~1/foo/will~1.exi/bar");
- // The path string has an upper-case drive letter because that's how path printing works.
- assertThat(notYetExistent.getPathString())
+ assertThat(normalize(osPathPolicy, "d:/progra~1/micros~1/foo/will~1.exi/bar"))
.isEqualTo("D:/program files/microsoft something/foo/will~1.exi/bar");
- LocalPath msRoot = create("d:/progra~1/micros~1");
- assertThat(msRoot.getPathString()).isEqualTo("D:/program files/microsoft something");
+ assertThat(normalize(osPathPolicy, "d:/progra~1/micros~1"))
+ .isEqualTo("D:/program files/microsoft something");
// Pretend that a path we already failed to resolve once came into existence.
shortPathResolver.resolutions.put(
"d:/program files/microsoft something/foo/will~1.exi", "will.exist");
// Assert that this time we can resolve the previously non-existent path.
- LocalPath nowExists = create("d:/progra~1/micros~1/foo/will~1.exi/bar");
// The path string has an upper-case drive letter because that's how path printing works.
- assertThat(nowExists.getPathString())
+ assertThat(normalize(osPathPolicy, "d:/progra~1/micros~1/foo/will~1.exi/bar"))
.isEqualTo("D:/program files/microsoft something/foo/will.exist/bar");
- // Assert relative paths that look like short paths are untouched
- assertThat(create("progra~1").getPathString()).isEqualTo("progra~1");
+ // Check needsToNormalized
+ assertThat(osPathPolicy.needsToNormalize("d:/progra~1/micros~1/foo/will~1.exi/bar"))
+ .isEqualTo(WindowsOsPathPolicy.NEEDS_SHORT_PATH_NORMALIZATION);
+ assertThat(osPathPolicy.needsToNormalize("will~1.exi"))
+ .isEqualTo(WindowsOsPathPolicy.NEEDS_SHORT_PATH_NORMALIZATION);
+ assertThat(osPathPolicy.needsToNormalize("d:/no-normalization"))
+ .isEqualTo(WindowsOsPathPolicy.NORMALIZED); // Sanity check
+ }
+
+ private static String normalize(OsPathPolicy osPathPolicy, String str) {
+ return osPathPolicy.normalize(str, osPathPolicy.needsToNormalize(str));
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/windows/PathWindowsTest.java b/src/test/java/com/google/devtools/build/lib/windows/PathWindowsTest.java
deleted file mode 100644
index e77de52d83..0000000000
--- a/src/test/java/com/google/devtools/build/lib/windows/PathWindowsTest.java
+++ /dev/null
@@ -1,298 +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.windows;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.devtools.build.lib.clock.BlazeClock;
-import com.google.devtools.build.lib.vfs.FileSystem;
-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.vfs.Root;
-import com.google.devtools.build.lib.vfs.RootedPath;
-import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-import com.google.devtools.build.lib.windows.WindowsFileSystem.WindowsPath;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** A test for windows aspects of {@link Path}. */
-@RunWith(JUnit4.class)
-public class PathWindowsTest {
-
- private static final class MockShortPathResolver implements Function<String, String> {
- public List<String> resolutionQueries = new ArrayList<>();
-
- // Full path to resolved child mapping.
- public Map<String, String> resolutions = new HashMap<>();
-
- @Override
- public String apply(String path) {
- path = path.toLowerCase();
- resolutionQueries.add(path);
- return resolutions.get(path);
- }
- }
-
- private FileSystem filesystem;
- private WindowsPath root;
- private final MockShortPathResolver shortPathResolver = new MockShortPathResolver();
-
- @Before
- public final void initializeFileSystem() throws Exception {
- filesystem =
- new InMemoryFileSystem(BlazeClock.instance()) {
- @Override
- protected PathFactory getPathFactory() {
- return WindowsFileSystem.getPathFactoryForTesting(shortPathResolver);
- }
-
- @Override
- public boolean isFilePathCaseSensitive() {
- return false;
- }
- };
- root = (WindowsPath) filesystem.getRootDirectory().getRelative("C:/");
- root.createDirectory();
- }
-
- private void assertAsFragmentWorks(String expected) {
- assertThat(filesystem.getPath(expected).asFragment()).isEqualTo(PathFragment.create(expected));
- }
-
- @Test
- public void testWindowsPath() {
- Path p = filesystem.getPath("C:/foo/bar");
- assertThat(p.getPathString()).isEqualTo("C:/foo/bar");
- assertThat(p.toString()).isEqualTo("C:/foo/bar");
- }
-
- @Test
- public void testAsFragmentWindows() {
- assertAsFragmentWorks("C:/");
- assertAsFragmentWorks("C://");
- assertAsFragmentWorks("C:/first");
- assertAsFragmentWorks("C:/first/x/y");
- assertAsFragmentWorks("C:/first/x/y.foo");
- }
-
- @Test
- public void testGetRelativeWithFragmentWindows() {
- Path dir = filesystem.getPath("C:/first/x");
- assertThat(dir.getRelative(PathFragment.create("y")).toString()).isEqualTo("C:/first/x/y");
- assertThat(dir.getRelative(PathFragment.create("./x")).toString()).isEqualTo("C:/first/x/x");
- assertThat(dir.getRelative(PathFragment.create("../y")).toString()).isEqualTo("C:/first/y");
- assertThat(dir.getRelative(PathFragment.create("../y")).toString()).isEqualTo("C:/first/y");
- assertThat(dir.getRelative(PathFragment.create("../../../y")).toString()).isEqualTo("C:/y");
- }
-
- @Test
- public void testGetRelativeWithAbsoluteFragmentWindows() {
- Path x = filesystem.getPath("C:/first/x");
- assertThat(x.getRelative(PathFragment.create("C:/x/y")).toString()).isEqualTo("C:/x/y");
- }
-
- @Test
- public void testGetRelativeWithAbsoluteStringWorksWindows() {
- Path x = filesystem.getPath("C:/first/x");
- assertThat(x.getRelative("C:/x/y").toString()).isEqualTo("C:/x/y");
- }
-
- @Test
- public void testParentOfRootIsRootWindows() {
- assertThat(root).isSameAs(root.getRelative(".."));
- assertThat(root.getRelative("dots")).isSameAs(root.getRelative("broken/../../dots"));
- }
-
- @Test
- public void testStartsWithWorksOnWindows() {
- assertStartsWithReturnsOnWindows(true, "C:/first/x", "C:/first/x/y");
- assertStartsWithReturnsOnWindows(true, "c:/first/x", "C:/FIRST/X/Y");
- assertStartsWithReturnsOnWindows(true, "C:/FIRST/X", "c:/first/x/y");
- assertStartsWithReturnsOnWindows(false, "C:/", "/");
- assertStartsWithReturnsOnWindows(false, "C:/", "D:/");
- assertStartsWithReturnsOnWindows(false, "C:/", "D:/foo");
- }
-
- @Test
- public void testGetRelative() {
- Path x = filesystem.getPath("C:\\first\\x");
- Path other = x.getRelative("a\\b\\c");
- assertThat(other.asFragment().getPathString()).isEqualTo("C:/first/x/a/b/c");
- }
-
- private static void assertStartsWithReturnsOnWindows(
- boolean expected, String ancestor, String descendant) {
- FileSystem windowsFileSystem = new WindowsFileSystem();
- Path parent = windowsFileSystem.getPath(ancestor);
- Path child = windowsFileSystem.getPath(descendant);
- assertThat(child.startsWith(parent)).isEqualTo(expected);
- }
-
- @Test
- public void testResolvesShortenedPaths() {
- shortPathResolver.resolutions.put("d:/progra~1", "program files");
- shortPathResolver.resolutions.put("d:/program files/micros~1", "microsoft something");
- shortPathResolver.resolutions.put(
- "d:/program files/microsoft something/foo/~bar~1", "~bar_hello");
-
- // Assert normal shortpath resolution.
- Path normal = root.getRelative("d:/progra~1/micros~1/foo/~bar~1/baz");
- // The path string has an upper-case drive letter because that's how path printing works.
- assertThat(normal.getPathString())
- .isEqualTo("D:/program files/microsoft something/foo/~bar_hello/baz");
- // Assert that we only try to resolve the path segments that look like they may be shortened.
- assertThat(shortPathResolver.resolutionQueries)
- .containsExactly(
- "d:/progra~1",
- "d:/program files/micros~1",
- "d:/program files/microsoft something/foo/~bar~1");
-
- // Assert resolving a path that has a segment which doesn't exist but later will.
- shortPathResolver.resolutionQueries.clear();
- Path notYetExistent = root.getRelative("d:/progra~1/micros~1/foo/will~1.exi/bar");
- // The path string has an upper-case drive letter because that's how path printing works.
- assertThat(notYetExistent.getPathString())
- .isEqualTo("D:/program files/microsoft something/foo/will~1.exi/bar");
- // Assert that we only try to resolve the path segments that look like they may be shortened.
- assertThat(shortPathResolver.resolutionQueries)
- .containsExactly(
- "d:/progra~1",
- "d:/program files/micros~1",
- "d:/program files/microsoft something/foo/will~1.exi");
-
- // Assert that the paths we failed to resolve don't get cached.
- final List<String> children = new ArrayList<>(2);
- Predicate<Path> collector =
- new Predicate<Path>() {
- @Override
- public boolean apply(Path child) {
- children.add(child.getPathString());
- return true;
- }
- };
-
- WindowsPath msRoot = (WindowsPath) root.getRelative("d:/progra~1/micros~1");
- assertThat(msRoot.getPathString()).isEqualTo("D:/program files/microsoft something");
- msRoot.applyToChildren(collector);
- // The path string has an upper-case drive letter because that's how path printing works.
- assertThat(children).containsExactly("D:/program files/microsoft something/foo");
-
- // Assert that the non-resolvable path was not cached.
- children.clear();
- WindowsPath foo = (WindowsPath) msRoot.getRelative("foo");
- foo.applyToChildren(collector);
- assertThat(children).containsExactly("D:/program files/microsoft something/foo/~bar_hello");
-
- // Pretend that a path we already failed to resolve once came into existence.
- shortPathResolver.resolutions.put(
- "d:/program files/microsoft something/foo/will~1.exi", "will.exist");
-
- // Assert that this time we can resolve the previously non-existent path.
- shortPathResolver.resolutionQueries.clear();
- Path nowExists = root.getRelative("d:/progra~1/micros~1/foo/will~1.exi/bar");
- // The path string has an upper-case drive letter because that's how path printing works.
- assertThat(nowExists.getPathString())
- .isEqualTo("D:/program files/microsoft something/foo/will.exist/bar");
- // Assert that we only try to resolve the path segments that look like they may be shortened.
- assertThat(shortPathResolver.resolutionQueries)
- .containsExactly(
- "d:/progra~1",
- "d:/program files/micros~1",
- "d:/program files/microsoft something/foo/will~1.exi");
-
- // Assert that this time we cached the previously non-existent path.
- children.clear();
- foo.applyToChildren(collector);
- // The path strings have upper-case drive letters because that's how path printing works.
- children.clear();
- foo.applyToChildren(collector);
- assertThat(children)
- .containsExactly(
- "D:/program files/microsoft something/foo/~bar_hello",
- "D:/program files/microsoft something/foo/will.exist");
- }
-
- @Test
- public void testCaseInsensitivePathFragment() {
- // equals
- assertThat(PathFragment.create("c:/FOO/BAR")).isEqualTo(PathFragment.create("c:\\foo\\bar"));
- assertThat(PathFragment.create("c:/FOO/BAR")).isNotEqualTo(PathFragment.create("d:\\foo\\bar"));
- assertThat(PathFragment.create("c:/FOO/BAR")).isNotEqualTo(PathFragment.create("/foo/bar"));
- // equals for the string representation
- assertThat(PathFragment.create("c:/FOO/BAR").toString())
- .isNotEqualTo(PathFragment.create("c:/foo/bar").toString());
- // hashCode
- assertThat(PathFragment.create("c:/FOO/BAR").hashCode())
- .isEqualTo(PathFragment.create("c:\\foo\\bar").hashCode());
- assertThat(PathFragment.create("c:/FOO/BAR").hashCode())
- .isNotEqualTo(PathFragment.create("d:\\foo\\bar").hashCode());
- assertThat(PathFragment.create("c:/FOO/BAR").hashCode())
- .isNotEqualTo(PathFragment.create("/foo/bar").hashCode());
- // compareTo
- assertThat(PathFragment.create("c:/FOO/BAR").compareTo(PathFragment.create("c:\\foo\\bar")))
- .isEqualTo(0);
- assertThat(PathFragment.create("c:/FOO/BAR").compareTo(PathFragment.create("d:\\foo\\bar")))
- .isLessThan(0);
- assertThat(PathFragment.create("c:/FOO/BAR").compareTo(PathFragment.create("/foo/bar")))
- .isGreaterThan(0);
- // startsWith
- assertThat(PathFragment.create("c:/FOO/BAR").startsWith(PathFragment.create("c:\\foo")))
- .isTrue();
- assertThat(PathFragment.create("c:/FOO/BAR").startsWith(PathFragment.create("d:\\foo")))
- .isFalse();
- // endsWith
- assertThat(PathFragment.create("c:/FOO/BAR/BAZ").endsWith(PathFragment.create("bar\\baz")))
- .isTrue();
- assertThat(PathFragment.create("c:/FOO/BAR/BAZ").endsWith(PathFragment.create("/bar/baz")))
- .isFalse();
- assertThat(PathFragment.create("c:/FOO/BAR/BAZ").endsWith(PathFragment.create("d:\\bar\\baz")))
- .isFalse();
- // relativeTo
- assertThat(
- PathFragment.create("c:/FOO/BAR/BAZ/QUX")
- .relativeTo(PathFragment.create("c:\\foo\\bar")))
- .isEqualTo(PathFragment.create("Baz/Qux"));
- }
-
- @Test
- public void testCaseInsensitiveRootedPath() {
- Path ancestor = filesystem.getPath("C:\\foo\\bar");
- assertThat(ancestor).isInstanceOf(WindowsPath.class);
- Path child = filesystem.getPath("C:\\FOO\\Bar\\baz");
- assertThat(child).isInstanceOf(WindowsPath.class);
- assertThat(child.startsWith(ancestor)).isTrue();
- assertThat(child.relativeTo(ancestor)).isEqualTo(PathFragment.create("baz"));
- RootedPath actual = RootedPath.toRootedPath(Root.fromPath(ancestor), child);
- assertThat(actual.getRoot()).isEqualTo(Root.fromPath(ancestor));
- assertThat(actual.getRootRelativePath()).isEqualTo(PathFragment.create("baz"));
- }
-
- @Test
- public void testToURI() {
- // See https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/
- Path p = root.getRelative("Temp\\Foo Bar.txt");
- URI uri = p.toURI();
- assertThat(uri.toString()).isEqualTo("file:///C:/Temp/Foo%20Bar.txt");
- }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
index 8937c453a7..3e9cd27c93 100644
--- a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
@@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.common.base.Function;
-import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
@@ -27,16 +26,13 @@ import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Symlinks;
-import com.google.devtools.build.lib.windows.WindowsFileSystem.WindowsPath;
import com.google.devtools.build.lib.windows.jni.WindowsFileOperations;
import com.google.devtools.build.lib.windows.util.WindowsTestUtil;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
@@ -246,42 +242,22 @@ public class WindowsFileSystemTest {
assertThat(p.getPathString()).endsWith(longPath);
assertThat(p).isEqualTo(fs.getPath(scratchRoot).getRelative(shortPath));
assertThat(p).isEqualTo(fs.getPath(scratchRoot).getRelative(longPath));
- assertThat(fs.getPath(scratchRoot).getRelative(shortPath)).isSameAs(p);
- assertThat(fs.getPath(scratchRoot).getRelative(longPath)).isSameAs(p);
+ assertThat(fs.getPath(scratchRoot).getRelative(shortPath)).isEqualTo(p);
+ assertThat(fs.getPath(scratchRoot).getRelative(longPath)).isEqualTo(p);
}
@Test
public void testUnresolvableShortPathWhichIsThenCreated() throws Exception {
String shortPath = "unreso~1.sho/foo/will~1.exi/bar/hello.txt";
- String longPrefix = "unresolvable.shortpath/foo/";
- String longPath = longPrefix + "will.exist/bar/hello.txt";
- testUtil.scratchDir(longPrefix);
- final WindowsPath foo = (WindowsPath) fs.getPath(scratchRoot).getRelative(longPrefix);
-
+ String longPath = "unresolvable.shortpath/foo/will.exist/bar/hello.txt";
// Assert that we can create an unresolvable path.
Path p = fs.getPath(scratchRoot).getRelative(shortPath);
- assertThat(p.getPathString()).endsWith(longPrefix + "will~1.exi/bar/hello.txt");
- // Assert that said path is not cached in its parent's `children` cache.
- final List<String> children = new ArrayList<>();
- Predicate<Path> collector =
- new Predicate<Path>() {
- @Override
- public boolean apply(Path child) {
- children.add(child.relativeTo(foo).getPathString());
- return true;
- }
- };
- foo.applyToChildren(collector);
- assertThat(children).isEmpty();
+ assertThat(p.getPathString()).endsWith(shortPath);
// Assert that we can then create the whole path, and can now resolve the short form.
testUtil.scratchFile(longPath, "hello");
Path q = fs.getPath(scratchRoot).getRelative(shortPath);
assertThat(q.getPathString()).endsWith(longPath);
- // Assert however that the unresolved and resolved Path objects are different, and only the
- // resolved one is cached.
assertThat(p).isNotEqualTo(q);
- foo.applyToChildren(collector);
- assertThat(children).containsExactly("will.exist");
}
/**
@@ -297,18 +273,11 @@ public class WindowsFileSystemTest {
Path p2 = fs.getPath(scratchRoot).getRelative("longpa~1");
assertThat(p1.exists()).isFalse();
assertThat(p1).isEqualTo(p2);
- assertThat(p1).isNotSameAs(p2);
testUtil.scratchDir("longpathnow");
Path q1 = fs.getPath(scratchRoot).getRelative("longpa~1");
- Path q2 = fs.getPath(scratchRoot).getRelative("longpa~1");
assertThat(q1.exists()).isTrue();
- assertThat(q1).isEqualTo(q2);
- // Assert q1 == q2, because we could successfully resolve the short path to a long name and we
- // cache them by the long name, so it's irrelevant they were created from a 8dot3 name, or what
- // that name resolves to later in time.
- assertThat(q1).isSameAs(q2);
- assertThat(q1).isSameAs(fs.getPath(scratchRoot).getRelative("longpathnow"));
+ assertThat(q1).isEqualTo(fs.getPath(scratchRoot).getRelative("longpathnow"));
// Delete the original resolution of "longpa~1" ("longpathnow").
assertThat(q1.delete()).isTrue();
@@ -317,14 +286,8 @@ public class WindowsFileSystemTest {
// Create a directory whose 8dot3 name is also "longpa~1" but its long name is different.
testUtil.scratchDir("longpaththen");
Path r1 = fs.getPath(scratchRoot).getRelative("longpa~1");
- Path r2 = fs.getPath(scratchRoot).getRelative("longpa~1");
assertThat(r1.exists()).isTrue();
- assertThat(r1).isEqualTo(r2);
- assertThat(r1).isSameAs(r2);
- assertThat(r1).isSameAs(fs.getPath(scratchRoot).getRelative("longpaththen"));
- // r1 == r2 and q1 == q2, but r1 != q1, because the resolution of "longpa~1" changed over time.
- assertThat(r1).isNotEqualTo(q1);
- assertThat(r1).isNotSameAs(q1);
+ assertThat(r1).isEqualTo(fs.getPath(scratchRoot).getRelative("longpaththen"));
}
@Test