aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/windows
diff options
context:
space:
mode:
authorGravatar tomlu <tomlu@google.com>2018-02-08 15:32:00 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-08 15:34:11 -0800
commita729b9b4c3d7844a7d44934bf3365f92633c0a60 (patch)
tree6329f4baf5b0b83ea6e3bd577b78b8d49afea9f1 /src/main/java/com/google/devtools/build/lib/windows
parent0ab46f0dd95f735056add4dd8a90a76944b81d00 (diff)
Replace path implementation.
Path and PathFragment have been replaced with String-based implementations. They are pretty similar, but each method is dissimilar enough that I did not feel sharing code was appropriate. A summary of changes: PATH ==== * Subsumes LocalPath (deleted, its tests repurposed) * Use a simple string to back Path * Path instances are no longer interned; Reference equality will no longer work * Always normalized (same as before) * Some operations will now be slower, like instance compares (which were previously just a reference check) * Multiple identical paths will now consume more memory since they are not interned PATH FRAGMENT ============= * Use a simple string to back PathFragment * No more segment arrays with interned strings * Always normalized * Remove isNormalized * Replace some isNormalizied uses with containsUpLevelReferences() to check if path fragments try to escape their scope * To check if user input is normalized, supply static methods on PathFragment to validate the string before constructing a PathFragment * Because PathFragments are always normalized, we have to replace checks for literal "." from PathFragment#getPathString to PathFragment#getSafePathString. The latter returns "." for the empty string. * The previous implementation supported efficient segment semantics (segment count, iterating over segments). This is now expensive since we do longer have a segment array. ARTIFACT ======== * Remove Path instance. It is instead dynamically constructed on request. This is necessary to avoid this CL becoming a memory regression. RELNOTES: None PiperOrigin-RevId: 185062932
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/windows')
-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
2 files changed, 1 insertions, 248 deletions
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) {