diff options
author | tomlu <tomlu@google.com> | 2018-02-08 15:32:00 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-02-08 15:34:11 -0800 |
commit | a729b9b4c3d7844a7d44934bf3365f92633c0a60 (patch) | |
tree | 6329f4baf5b0b83ea6e3bd577b78b8d49afea9f1 /src/main/java/com/google/devtools/build/lib/windows | |
parent | 0ab46f0dd95f735056add4dd8a90a76944b81d00 (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.java | 245 | ||||
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/windows/WindowsSubprocessFactory.java | 4 |
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) { |