diff options
3 files changed, 175 insertions, 2 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java index fccc070668..6c04c288b7 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java @@ -97,6 +97,7 @@ import com.google.devtools.build.lib.vfs.JavaIoFileSystem; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.UnixFileSystem; +import com.google.devtools.build.lib.vfs.WindowsFileSystem; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyFunctionName; import com.google.devtools.common.options.Option; @@ -992,7 +993,7 @@ public final class BlazeRuntime { return new JavaIoFileSystem(); } // The JNI-based UnixFileSystem is faster, but on Windows it is not available. - return OS.getCurrent() == OS.WINDOWS ? new JavaIoFileSystem() : new UnixFileSystem(); + return OS.getCurrent() == OS.WINDOWS ? new WindowsFileSystem() : new UnixFileSystem(); } /** 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 b03ceccbd3..f095097801 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 @@ -337,7 +337,7 @@ public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat { } } - private boolean fileIsSymbolicLink(File file) { + protected boolean fileIsSymbolicLink(File file) { return Files.isSymbolicLink(file.toPath()); } diff --git a/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java new file mode 100644 index 0000000000..80bb9dca9b --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java @@ -0,0 +1,172 @@ +// Copyright 2016 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.concurrent.ThreadSafety.ThreadSafe; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * Jury-rigged file system for Windows. + */ +@ThreadSafe +public class WindowsFileSystem extends JavaIoFileSystem { + + public static final LinkOption[] NO_OPTIONS = new LinkOption[0]; + public static final LinkOption[] NO_FOLLOW = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; + + @Override + protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) + throws IOException { + // TODO(lberki): Add some JNI to create hard links/junctions instead of calling out to + // cmd.exe + File file = getIoFile(linkPath); + try { + File targetFile = new File(targetFragment.getPathString()); + if (targetFile.isDirectory()) { + createDirectoryJunction(targetFile, file); + } else { + Files.copy(targetFile.toPath(), file.toPath()); + } + } catch (java.nio.file.FileAlreadyExistsException e) { + throw new IOException(linkPath + ERR_FILE_EXISTS); + } catch (java.nio.file.AccessDeniedException e) { + throw new IOException(linkPath + ERR_PERMISSION_DENIED); + } catch (java.nio.file.NoSuchFileException e) { + throw new FileNotFoundException(linkPath + ERR_NO_SUCH_FILE_OR_DIR); + } + } + + private void createDirectoryJunction(File sourceDirectory, File targetPath) throws IOException { + StringBuilder cl = new StringBuilder("cmd.exe /c "); + cl.append("mklink /J "); + cl.append('"'); + cl.append(targetPath.getAbsolutePath()); + cl.append('"'); + cl.append(' '); + cl.append('"'); + cl.append(sourceDirectory.getAbsolutePath()); + cl.append('"'); + Process process = Runtime.getRuntime().exec(cl.toString()); + try { + process.waitFor(); + if (process.exitValue() != 0) { + throw new IOException("Command failed " + cl); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Command failed ", e); + } + } + + @Override + protected boolean fileIsSymbolicLink(File file) { + try { + if (file.isDirectory() && isJunction(file.toPath())) { + return true; + } + } catch (IOException e) { + // Did not work, try in another way + } + return super.fileIsSymbolicLink(file); + } + + private LinkOption[] linkOpts(boolean followSymlinks) { + return followSymlinks ? NO_OPTIONS : NO_FOLLOW; + } + + @Override + protected FileStatus stat(Path path, boolean followSymlinks) throws IOException { + File file = getIoFile(path); + final BasicFileAttributes attributes; + try { + attributes = Files.readAttributes( + file.toPath(), BasicFileAttributes.class, linkOpts(followSymlinks)); + } catch (java.nio.file.FileSystemException e) { + throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); + } + + final boolean isSymbolicLink = !followSymlinks && fileIsSymbolicLink(file); + FileStatus status = new FileStatus() { + @Override + public boolean isFile() { + return attributes.isRegularFile() || (isSpecialFile() && !isDirectory()); + } + + @Override + public boolean isSpecialFile() { + return attributes.isOther(); + } + + @Override + public boolean isDirectory() { + return attributes.isDirectory(); + } + + @Override + public boolean isSymbolicLink() { + return isSymbolicLink; + } + + @Override + public long getSize() throws IOException { + return attributes.size(); + } + + @Override + public long getLastModifiedTime() throws IOException { + return attributes.lastModifiedTime().toMillis(); + } + + @Override + public long getLastChangeTime() { + // This is the best we can do with Java NIO... + return attributes.lastModifiedTime().toMillis(); + } + + @Override + public long getNodeId() { + // TODO(bazel-team): Consider making use of attributes.fileKey(). + return -1; + } + }; + + return status; + } + + @Override + protected boolean isDirectory(Path path, boolean followSymlinks) { + if (!followSymlinks) { + try { + if (isJunction(getIoFile(path).toPath())) { + return false; + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + return super.isDirectory(path, followSymlinks); + } + + private static boolean isJunction(java.nio.file.Path p) throws IOException { + // Jury-rigged + return p.compareTo(p.toRealPath()) != 0; + } +} |