diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java | 715 |
1 files changed, 0 insertions, 715 deletions
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; - } -} |