/** * Given a path, returns the Windows drive letter ('X'), or an null character if no volume name * was specified. */ static char getWindowsDriveLetter(String path) { if (OS.getCurrent() == OS.WINDOWS && path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) { return Character.toUpperCase(path.charAt(0)); } return '\0'; }
/** * Generates a command line to run for the test action, taking into account coverage and {@code * --run_under} settings. * * @param testScript the setup script that invokes the test * @param coverageScript a script interjected between setup script and rest of command line to * collect coverage data. If this is an empty string, it is ignored. * @param testAction The test action. * @return the command line as string list. */ protected List<String> getArgs( String testScript, String coverageScript, TestRunnerAction testAction) { List<String> args = Lists.newArrayList(); if (OS.getCurrent() == OS.WINDOWS) { args.add(testAction.getShExecutable().getPathString()); args.add("-c"); args.add("$0 $*"); } args.add(testScript); TestTargetExecutionSettings execSettings = testAction.getExecutionSettings(); List<String> execArgs = new ArrayList<>(); if (!coverageScript.isEmpty() && isCoverageMode(testAction)) { execArgs.add(coverageScript); } // Execute the test using the alias in the runfiles tree, as mandated by // the Test Encyclopedia. execArgs.add(execSettings.getExecutable().getRootRelativePath().getPathString()); execArgs.addAll(execSettings.getArgs()); // Insert the command prefix specified by the "--run_under=<command-prefix>" option, // if any. if (execSettings.getRunUnder() == null) { args.addAll(execArgs); } else if (execSettings.getRunUnderExecutable() != null) { args.add(execSettings.getRunUnderExecutable().getRootRelativePath().getPathString()); args.addAll(execSettings.getRunUnder().getOptions()); args.addAll(execArgs); } else { args.add(testAction.getConfiguration().getShellExecutable().getPathString()); args.add("-c"); String runUnderCommand = ShellEscaper.escapeString(execSettings.getRunUnder().getCommand()); Path fullySpecified = SearchPath.which( SearchPath.parse( testAction.getTestLog().getPath().getFileSystem(), clientEnv.get("PATH")), runUnderCommand); if (fullySpecified != null) { runUnderCommand = fullySpecified.toString(); } args.add( runUnderCommand + ' ' + ShellEscaper.escapeJoinAll( Iterables.concat(execSettings.getRunUnder().getOptions(), execArgs))); } return args; }
static FileSystem getFileSystem() { return OS.getCurrent() == OS.WINDOWS ? new JavaIoFileSystem() : new UnixFileSystem(); }
public String windowsVolume() { if (OS.getCurrent() != OS.WINDOWS) { return ""; } return (driveLetter != '\0') ? driveLetter + ":" : ""; }
/** * This class represents an immutable UNIX filesystem path, which may be absolute or relative. The * path is maintained as a simple ordered list of path segment strings. * * <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>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. * However, Windows-style backslash separators (C:\\foo\\bar) are explicitly not supported, same * with advanced features like \\\\network\\paths and \\\\?\\unc\\paths. */ @Immutable @ThreadSafe public final class PathFragment implements Comparable<PathFragment>, Serializable { public static final int INVALID_SEGMENT = -1; public static final char SEPARATOR_CHAR = '/'; public static final char EXTRA_SEPARATOR_CHAR = (OS.getCurrent() == OS.WINDOWS) ? '\\' : '/'; public static final String ROOT_DIR = "/"; /** An empty path fragment. */ public static final PathFragment EMPTY_FRAGMENT = new PathFragment(""); public static final Function<String, PathFragment> TO_PATH_FRAGMENT = new Function<String, PathFragment>() { @Override public PathFragment apply(String str) { return new PathFragment(str); } }; public static final Predicate<PathFragment> IS_ABSOLUTE = new Predicate<PathFragment>() { @Override public boolean apply(PathFragment input) { return input.isAbsolute(); } }; private static final Function<PathFragment, String> TO_SAFE_PATH_STRING = new Function<PathFragment, String>() { @Override public String apply(PathFragment path) { return path.getSafePathString(); } }; // We have 3 word-sized fields (segments, hashCode and path), and 2 // byte-sized ones, which fits in 16 bytes. Object sizes are rounded // to 16 bytes. Medium sized builds can easily hold millions of // live PathFragments, so do not add further fields on a whim. // The individual path components. private final String[] segments; // True both for UNIX-style absolute paths ("/foo") and Windows-style ("C:/foo"). private final boolean isAbsolute; // Upper case windows drive letter, or '\0' if none. While a volumeName string is more // general, we create a lot of these objects, so space is at a premium. private final char driveLetter; // hashCode and path are lazily initialized but semantically immutable. private int hashCode; private String path; /** * Construct a PathFragment from a string, which is an absolute or relative UNIX or Windows path. */ public PathFragment(String path) { this.driveLetter = getWindowsDriveLetter(path); if (driveLetter != '\0') { path = path.substring(2); // TODO(bazel-team): Decide what to do about non-absolute paths with a volume name, e.g. C:x. } this.isAbsolute = path.length() > 0 && isSeparator(path.charAt(0)); this.segments = segment(path, isAbsolute ? 1 : 0); } private static boolean isSeparator(char c) { return c == SEPARATOR_CHAR || c == EXTRA_SEPARATOR_CHAR; } /** * Construct a PathFragment from a java.io.File, which is an absolute or relative UNIX path. Does * not support Windows-style Files. */ public PathFragment(File path) { this(path.getPath()); } /** * Constructs a PathFragment, taking ownership of segments. Package-private, because it does not * perform a defensive clone of the segments array. Used here in PathFragment, and by * Path.asFragment() and Path.relativeTo(). */ PathFragment(char driveLetter, boolean isAbsolute, String[] segments) { this.driveLetter = driveLetter; this.isAbsolute = isAbsolute; this.segments = segments; } /** * Construct a PathFragment from a sequence of other PathFragments. The new fragment will be * absolute iff the first fragment was absolute. */ public PathFragment(PathFragment first, PathFragment second, PathFragment... more) { // TODO(bazel-team): The handling of absolute path fragments in this constructor is unexpected. this.segments = new String[sumLengths(first, second, more)]; int offset = 0; offset += addSegments(offset, first); offset += addSegments(offset, second); for (PathFragment fragment : more) { offset += addSegments(offset, fragment); } this.isAbsolute = first.isAbsolute; this.driveLetter = first.driveLetter; } private int addSegments(int offset, PathFragment fragment) { int count = fragment.segmentCount(); System.arraycopy(fragment.segments, 0, this.segments, offset, count); return count; } 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; } /** * Segments the string passed in as argument and returns an array of strings. The split is * performed along occurrences of (sequences of) the slash character. * * @param toSegment the string to segment * @param offset how many characters from the start of the string to ignore. */ private static String[] segment(String toSegment, int offset) { char[] chars = toSegment.toCharArray(); int length = chars.length; // Handle "/" and "" quickly. if (length == offset) { return new String[0]; } // 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(chars[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(chars[i])) { if (i > start) { // to skip repeated separators // Make a copy of the String here to allow the interning to save memory. String.substring // does not make a copy, but refers to the original char array, preventing garbage // collection of the parts that are unnecessary. result[seg] = StringCanonicalizer.intern(new String(chars, start, i - start)); seg++; } start = i + 1; } } if (start < length) { result[seg] = StringCanonicalizer.intern(new String(chars, start, length - start)); seg++; } return result; } private Object writeReplace() { return new PathFragmentSerializationProxy(toString()); } private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Serialization is allowed only by proxy"); } /** * Returns the path string using '/' as the name-separator character. Returns "" if the path is * both relative and empty. */ 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(SEPARATOR_CHAR)); } } } return path; } /** * Returns "." if the path fragment is both relative and empty, or {@link #getPathString} * otherwise. */ // TODO(bazel-team): Change getPathString to do this - this behavior makes more sense. public String getSafePathString() { return (!isAbsolute && (segmentCount() == 0)) ? "." : getPathString(); } /** * Returns a sequence consisting of the {@link #getSafePathString()} return of each item in {@code * fragments}. */ public static Iterable<String> safePathStrings(Iterable<PathFragment> fragments) { return Iterables.transform(fragments, TO_SAFE_PATH_STRING); } /** Returns the subset of {@code paths} that start with {@code startingWithPath}. */ public static ImmutableSet<PathFragment> filterPathsStartingWith( Set<PathFragment> paths, PathFragment startingWithPath) { return ImmutableSet.copyOf(Iterables.filter(paths, startsWithPredicate(startingWithPath))); } public static Predicate<PathFragment> startsWithPredicate(final PathFragment prefix) { return new Predicate<PathFragment>() { @Override public boolean apply(PathFragment pathFragment) { return pathFragment.startsWith(prefix); } }; } /** * 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 String joinSegments(char separatorChar) { if (segments.length == 0 && isAbsolute) { return windowsVolume() + ROOT_DIR; } // 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 = ((driveLetter != '\0') ? 2 : 0) + ((segments.length == 0) ? 0 : (segments.length + 1) * 20); StringBuilder result = new StringBuilder(estimateSize); result.append(windowsVolume()); boolean initialSegment = true; for (String segment : segments) { if (!initialSegment || isAbsolute) { result.append(separatorChar); } initialSegment = false; result.append(segment); } return result.toString(); } /** Return true iff none of the segments are either "." or "..". */ public boolean isNormalized() { for (String segment : segments) { if (segment.equals(".") || segment.equals("..")) { return false; } } return true; } /** * Normalizes the path fragment: removes "." and ".." segments if possible (if there are too many * ".." segments, the resulting PathFragment will still start with ".."). */ 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; } } if (segmentCount == segments.length) { // Optimization, no new PathFragment needs to be created. return this; } return new PathFragment(driveLetter, isAbsolute, subarray(scratchSegments, 0, segmentCount)); } /** * 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 * concatenated. This is a purely syntactic operation, with no path normalization or I/O * performed. */ public PathFragment getRelative(PathFragment otherFragment) { return otherFragment.isAbsolute() ? otherFragment : new PathFragment(this, otherFragment); } /** * 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(new PathFragment(path)); } /** * 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 PathFragment getChild(String baseName) { FileSystemUtils.checkBaseName(baseName); baseName = StringCanonicalizer.intern(baseName); String[] newSegments = Arrays.copyOf(segments, segments.length + 1); newSegments[newSegments.length - 1] = baseName; return new PathFragment(driveLetter, isAbsolute, newSegments); } /** Returns the last segment of this path, or "" for the empty fragment. */ public String getBaseName() { return (segments.length == 0) ? "" : segments[segments.length - 1]; } /** * 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; if (isAbsolute != ancestorDirectory.isAbsolute() || segments.length < ancestorLength) { throw new IllegalArgumentException( "PathFragment " + this + " is not beneath " + ancestorDirectory); } for (int index = 0; index < ancestorLength; index++) { if (!segments[index].equals(ancestorSegments[index])) { throw new IllegalArgumentException( "PathFragment " + this + " is not beneath " + ancestorDirectory); } } int length = segments.length - ancestorLength; String[] resultSegments = subarray(segments, ancestorLength, length); return new PathFragment('\0', false, resultSegments); } /** Returns a relative path fragment to this path, relative to {@code path}. */ public PathFragment relativeTo(String path) { return relativeTo(new PathFragment(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); } /** * 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. * * <p>This is a reflexive, transitive, anti-symmetric relation (i.e. a partial order) */ public boolean startsWith(PathFragment prefix) { if (this.isAbsolute != prefix.isAbsolute || this.segments.length < prefix.segments.length || this.driveLetter != prefix.driveLetter) { return false; } for (int i = 0, len = prefix.segments.length; i < len; i++) { if (!this.segments[i].equals(prefix.segments[i])) { return false; } } return true; } /** * 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; } int offset = this.segments.length - suffix.segments.length; for (int i = 0; i < suffix.segments.length; i++) { if (!this.segments[offset + i].equals(suffix.segments[i])) { return false; } } return true; } private static String[] subarray(String[] array, int start, int length) { String[] subarray = new String[length]; System.arraycopy(array, start, subarray, 0, length); return subarray; } /** * 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>. * * @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, 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)); } boolean isAbsolute = (beginIndex == 0) && this.isAbsolute; return ((beginIndex == 0) && (endIndex == count)) ? this : new PathFragment( driveLetter, isAbsolute, subarray(segments, beginIndex, endIndex - beginIndex)); } /** Returns true iff the path represented by this object is absolute. */ public boolean isAbsolute() { return isAbsolute; } /** Returns the segments of this path fragment. This array should not be modified. */ String[] segments() { return segments; } public String windowsVolume() { if (OS.getCurrent() != OS.WINDOWS) { return ""; } return (driveLetter != '\0') ? driveLetter + ":" : ""; } /** Returns the number of segments in this path. */ public int segmentCount() { return segments.length; } /** * Returns the specified segment of this path; index must be positive and less than numSegments(). */ public String getSegment(int index) { return segments[index]; } /** * 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. */ public int getFirstSegment(Set<String> values) { for (int i = 0; i < segments.length; i++) { if (values.contains(segments[i])) { return i; } } return INVALID_SEGMENT; } /** Returns true iff this path contains uplevel references "..". */ public boolean containsUplevelReferences() { for (String segment : segments) { if (segment.equals("..")) { return true; } } return false; } /** * Returns a relative PathFragment created from this absolute PathFragment using the same segments * and drive letter. */ public PathFragment toRelative() { Preconditions.checkArgument(isAbsolute); return new PathFragment(driveLetter, false, segments); } /** * Given a path, returns the Windows drive letter ('X'), or an null character if no volume name * was specified. */ static char getWindowsDriveLetter(String path) { if (OS.getCurrent() == OS.WINDOWS && path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) { return Character.toUpperCase(path.charAt(0)); } return '\0'; } @Override public int hashCode() { int h = hashCode; if (h == 0) { h = isAbsolute ? 1 : 0; for (String segment : segments) { h = h * 31 + segment.hashCode(); } hashCode = h; } return h; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof PathFragment)) { return false; } PathFragment otherPath = (PathFragment) other; return isAbsolute == otherPath.isAbsolute && Arrays.equals(otherPath.segments, segments); } /** Compares two PathFragments using the lexicographical order. */ @Override public int compareTo(PathFragment p2) { if (isAbsolute != p2.isAbsolute) { return isAbsolute ? -1 : 1; } PathFragment p1 = this; String[] segments1 = p1.segments; String[] segments2 = p2.segments; int len1 = segments1.length; int len2 = segments2.length; int n = Math.min(len1, len2); for (int i = 0; i < n; i++) { String segment1 = segments1[i]; String segment2 = segments2[i]; if (!segment1.equals(segment2)) { return segment1.compareTo(segment2); } } return len1 - len2; } @Override public String toString() { return getPathString(); } }