aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java9
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java54
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java27
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java13
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/Path.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java10
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java11
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java21
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java11
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java34
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/repository/BUILD1
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/repository/CompressedTarFunctionTest.java126
-rw-r--r--src/test/java/com/google/devtools/build/lib/rules/repository/test_decompress_archive.tar.gzbin0 -> 216 bytes
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java91
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java74
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java20
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java19
17 files changed, 511 insertions, 26 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java
index 8db394ed21..daf6e66267 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java
@@ -57,13 +57,18 @@ public abstract class CompressedTarFunction implements Decompressor {
if (entry.isDirectory()) {
FileSystemUtils.createDirectoryAndParents(filename);
} else {
- if (entry.isSymbolicLink()) {
+ if (entry.isSymbolicLink() || entry.isLink()) {
PathFragment linkName = new PathFragment(entry.getLinkName());
if (linkName.isAbsolute()) {
linkName = linkName.relativeTo(PathFragment.ROOT_DIR);
linkName = descriptor.repositoryPath().getRelative(linkName).asFragment();
}
- FileSystemUtils.ensureSymbolicLink(filename, linkName);
+ if (entry.isSymbolicLink()) {
+ FileSystemUtils.ensureSymbolicLink(filename, linkName);
+ } else {
+ FileSystemUtils.createHardLink(
+ filename, descriptor.repositoryPath().getRelative(linkName));
+ }
} else {
Files.copy(
tarStream, filename.getPathFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
index dd067d71c0..3e3b94da63 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -22,12 +22,12 @@ import com.google.common.io.ByteSource;
import com.google.common.io.CharStreams;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.vfs.Dirent.Type;
-
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.nio.file.FileAlreadyExistsException;
import java.util.Collection;
import java.util.List;
@@ -128,6 +128,21 @@ public abstract class FileSystem {
*/
public abstract boolean supportsSymbolicLinksNatively();
+ /**
+ * Returns whether or not the FileSystem supports hard links.
+ *
+ * <p>Returns true if FileSystem supports the following:
+ *
+ * <ul>
+ * <li>{@link #createFSDependentHardLink(Path, Path)}
+ * </ul>
+ *
+ * The above calls may result in an {@link UnsupportedOperationException} on a FileSystem where
+ * this method returns {@code false}. The implementation can try to emulate these calls at its own
+ * discretion.
+ */
+ protected abstract boolean supportsHardLinksNatively();
+
/***
* Returns true if file path is case-sensitive on this file system. Default is true.
*/
@@ -665,4 +680,41 @@ public abstract class FileSystem {
* See {@link Path#renameTo} for specification.
*/
protected abstract void renameTo(Path sourcePath, Path targetPath) throws IOException;
+
+
+ /**
+ * Create a new hard link file at "linkPath" for file at "originalPath".
+ *
+ * @param linkPath The path of the new link file to be created
+ * @param originalPath The path of the original file
+ * @throws IOException if the original file does not exist or the link file already exists
+ */
+ protected void createHardLink(Path linkPath, Path originalPath) throws IOException {
+
+ if (!originalPath.exists()) {
+ throw new FileNotFoundException(
+ "File \""
+ + originalPath.getBaseName()
+ + "\" linked from \""
+ + linkPath.getBaseName()
+ + "\" does not exist");
+ }
+
+ if (linkPath.exists()) {
+ throw new FileAlreadyExistsException(
+ "New link file \"" + linkPath.getBaseName() + "\" already exists");
+ }
+
+ createFSDependentHardLink(linkPath, originalPath);
+ }
+
+ /**
+ * Create a new hard link file at "linkPath" for file at "originalPath".
+ *
+ * @param linkPath The path of the new link file to be created
+ * @param originalPath The path of the original file
+ * @throws IOException if there was an I/O error
+ */
+ protected abstract void createFSDependentHardLink(Path linkPath, Path originalPath)
+ throws IOException;
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java
index cf12b50f21..ee1af6cc97 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java
@@ -932,4 +932,31 @@ public class FileSystemUtils {
}
return false;
}
+
+
+ /**
+ * Create a new hard link file at "linkPath" for file at "originalPath". If "originalPath" is a
+ * directory, then for each entry, create link under "linkPath" recursively.
+ *
+ * @param linkPath The path of the new link file to be created
+ * @param originalPath The path of the original file
+ * @throws IOException if there was an error executing {@link Path#createHardLink}
+ */
+ public static void createHardLink(Path linkPath, Path originalPath) throws IOException {
+
+ // Regular file
+ if (originalPath.isFile()) {
+ Path parentDir = linkPath.getParentDirectory();
+ if (!parentDir.exists()) {
+ FileSystemUtils.createDirectoryAndParents(parentDir);
+ }
+ originalPath.createHardLink(linkPath);
+ // Directory
+ } else if (originalPath.isDirectory()) {
+ for (Path originalSubpath : originalPath.getDirectoryEntries()) {
+ Path linkSubpath = linkPath.getRelative(originalSubpath.relativeTo(originalPath));
+ createHardLink(linkSubpath, originalSubpath);
+ }
+ }
+ }
}
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 dead488021..7cc10aa8eb 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
@@ -191,6 +191,11 @@ public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat {
}
@Override
+ public boolean supportsHardLinksNatively() {
+ return true;
+ }
+
+ @Override
public boolean isFilePathCaseSensitive() {
return true;
}
@@ -468,4 +473,12 @@ public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat {
throw new IllegalStateException(e);
}
}
+
+ @Override
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+ throws IOException {
+ Files.createLink(
+ java.nio.file.Paths.get(linkPath.toString()),
+ java.nio.file.Paths.get(originalPath.toString()));
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
index 532a5f022a..657bb03c41 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
@@ -826,6 +826,16 @@ public class Path implements Comparable<Path>, Serializable {
}
/**
+ * Create a hard link for the current path.
+ *
+ * @param link the path of the new link
+ * @throws IOException if there was an error executing {@link FileSystem#createHardLink}
+ */
+ public void createHardLink(Path link) throws IOException {
+ fileSystem.createHardLink(link, this);
+ }
+
+ /**
* Returns the canonical path for this path, by repeatedly replacing symbolic
* links with their referents. Analogous to realpath(3).
*
@@ -1135,7 +1145,8 @@ public class Path implements Comparable<Path>, Serializable {
// requires us to always go up to the top-level directory and copy all segments into a new
// string array.
// This was previously showing up as a hotspot in a profile of globbing a large directory.
- Path a = this, b = o;
+ Path a = this;
+ Path b = o;
int maxDepth = Math.min(a.depth, b.depth);
while (a.depth > maxDepth) {
a = a.getParentDirectory();
@@ -1148,7 +1159,8 @@ public class Path implements Comparable<Path>, Serializable {
// If a is the same as this, this.depth must be less than o.depth.
return equals(a) ? -1 : 1;
}
- Path previousa, previousb;
+ Path previousa;
+ Path previousb;
do {
previousa = a;
previousb = b;
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
index cbc2753dbb..5f58eb532e 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
@@ -76,6 +76,11 @@ public abstract class ReadonlyFileSystem extends AbstractFileSystem {
}
@Override
+ public boolean supportsHardLinksNatively() {
+ return false;
+ }
+
+ @Override
public boolean isFilePathCaseSensitive() {
return true;
}
@@ -105,4 +110,9 @@ public abstract class ReadonlyFileSystem extends AbstractFileSystem {
throw modificationException();
}
+ @Override
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+ throws IOException {
+ throw modificationException();
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java
index a3950b0cd6..f72019b7c3 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java
@@ -61,6 +61,11 @@ public abstract class ReadonlyFileSystemWithCustomStat extends AbstractFileSyste
}
@Override
+ public boolean supportsHardLinksNatively() {
+ return false;
+ }
+
+ @Override
public boolean isFilePathCaseSensitive() {
return true;
}
@@ -76,6 +81,12 @@ public abstract class ReadonlyFileSystemWithCustomStat extends AbstractFileSyste
}
@Override
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+ throws IOException {
+ throw modificationException();
+ }
+
+ @Override
protected void renameTo(Path sourcePath, Path targetPath) throws IOException {
throw modificationException();
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
index cf67d1e749..a0a09555f2 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
@@ -173,6 +173,11 @@ public class UnionFileSystem extends FileSystem {
}
@Override
+ public boolean supportsHardLinksNatively() {
+ return true;
+ }
+
+ @Override
public boolean isFilePathCaseSensitive() {
return this.isCaseSensitive;
}
@@ -435,4 +440,20 @@ public class UnionFileSystem extends FileSystem {
sourceDelegate.delete(sourcePath);
}
}
+
+ @Override
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+ throws IOException {
+ checkModifiable();
+
+ FileSystem originalDelegate = getDelegate(originalPath);
+ FileSystem linkDelegate = getDelegate(linkPath);
+
+ if (!originalDelegate.equals(linkDelegate) || !linkDelegate.supportsHardLinksNatively()) {
+ throw new UnsupportedOperationException(
+ "Attempted to create a hard link, but hard link support is disabled.");
+ }
+ linkDelegate.createFSDependentHardLink(
+ adjustPath(linkPath, linkDelegate), adjustPath(originalPath, originalDelegate));
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
index ce7ee93bab..53db44cc22 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
@@ -289,6 +289,11 @@ public class UnixFileSystem extends AbstractFileSystemWithCustomStat {
}
@Override
+ public boolean supportsHardLinksNatively() {
+ return true;
+ }
+
+ @Override
public boolean isFilePathCaseSensitive() {
return true;
}
@@ -403,4 +408,10 @@ public class UnixFileSystem extends AbstractFileSystemWithCustomStat {
profiler.logSimpleTask(startTime, ProfilerTask.VFS_MD5, name);
}
}
+
+ @Override
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+ throws IOException {
+ NativePosixFiles.link(originalPath.toString(), linkPath.toString());
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
index dd974e55e4..e763ebe4e8 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
@@ -655,6 +655,11 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem {
}
@Override
+ public boolean supportsHardLinksNatively() {
+ return true;
+ }
+
+ @Override
public boolean isFilePathCaseSensitive() {
return true;
}
@@ -931,4 +936,33 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem {
throw Error.EACCES.exception(targetPath);
}
}
+
+ @Override
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+ throws IOException {
+
+ // Same check used when creating a symbolic link
+ if (originalPath.equals(rootPath)) {
+ throw Error.EACCES.exception(originalPath);
+ }
+
+ InMemoryDirectoryInfo linkParent;
+ synchronized (this) {
+ linkParent = getDirectory(linkPath.getParentDirectory());
+ // Same check used when creating a symbolic link
+ if (!linkParent.outOfScope()) {
+ if (linkParent.getChild(linkPath.getBaseName()) != null) {
+ throw Error.EEXIST.exception(linkPath);
+ }
+ insert(
+ linkParent,
+ linkPath.getBaseName(),
+ getDirectory(originalPath.getParentDirectory()).getChild(originalPath.getBaseName()),
+ linkPath);
+ return;
+ }
+ }
+ // If we get here, we're out of scope.
+ getDelegatedPath(linkParent.getEscapingPath(), originalPath).createHardLink(linkPath);
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD b/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
index c5df5f3661..24be41f2f8 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
@@ -7,6 +7,7 @@ filegroup(
java_test(
name = "RepositoryTests",
srcs = glob(["*.java"]),
+ data = ["test_decompress_archive.tar.gz"],
tags = ["rules"],
test_class = "com.google.devtools.build.lib.AllTests",
deps = [
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/CompressedTarFunctionTest.java b/src/test/java/com/google/devtools/build/lib/rules/repository/CompressedTarFunctionTest.java
new file mode 100644
index 0000000000..b638b21052
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/CompressedTarFunctionTest.java
@@ -0,0 +1,126 @@
+// 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.rules.repository;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.bazel.repository.CompressedTarFunction;
+import com.google.devtools.build.lib.bazel.repository.DecompressorDescriptor;
+import com.google.devtools.build.lib.bazel.repository.TarGzFunction;
+import com.google.devtools.build.lib.testutil.BlazeTestUtils;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.OS;
+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.UnixFileSystem;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.zip.GZIPInputStream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests decompressing archives. */
+@RunWith(JUnit4.class)
+public class CompressedTarFunctionTest {
+
+ /* Regular file */
+ private static final String REGULAR_FILE_NAME = "regularFile";
+
+ /* Hard link file, created by ln <REGULAR_FILE_NAME> <HARD_LINK_FILE_NAME> */
+ private static final String HARD_LINK_FILE_NAME = "hardLinkFile";
+
+ /* Symbolic(Soft) link file, created by ln -s <REGULAR_FILE_NAME> <SYMBOLIC_LINK_FILE_NAME> */
+ private static final String SYMBOLIC_LINK_FILE_NAME = "symbolicLinkFile";
+
+ private static final String PATH_TO_TEST_ARCHIVE =
+ "/com/google/devtools/build/lib/rules/repository/";
+
+ /* Tarball, created by
+ * tar -czf <ARCHIVE_NAME> <REGULAR_FILE_NAME> <HARD_LINK_FILE_NAME> <SYMBOLIC_LINK_FILE_NAME>
+ */
+ private static final String ARCHIVE_NAME = "test_decompress_archive.tar.gz";
+
+ private FileSystem testFS;
+ private Path workingDir;
+ private Path tarballPath;
+ private Path outDir;
+ private DecompressorDescriptor.Builder descriptorBuilder;
+
+ @Before
+ public void setUpFs() throws Exception {
+
+ testFS = OS.getCurrent() == OS.WINDOWS ? new JavaIoFileSystem() : new UnixFileSystem();
+
+ tarballPath =
+ testFS
+ .getPath(BlazeTestUtils.runfilesDir())
+ .getRelative(TestConstants.JAVATESTS_ROOT + PATH_TO_TEST_ARCHIVE + ARCHIVE_NAME);
+
+ workingDir = testFS.getPath(new File(TestUtils.tmpDir()).getCanonicalPath());
+ outDir = workingDir.getRelative("out");
+
+ descriptorBuilder =
+ DecompressorDescriptor.builder()
+ .setDecompressor(TarGzFunction.INSTANCE)
+ .setRepositoryPath(outDir)
+ .setArchivePath(tarballPath);
+ }
+
+ /**
+ * Test decompressing a tar.gz file with hard link file and symbolic link file inside
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testDecompress() throws Exception {
+
+ Path outputDir =
+ new CompressedTarFunction() {
+ @Override
+ protected InputStream getDecompressorStream(DecompressorDescriptor descriptor)
+ throws IOException {
+ return new GZIPInputStream(new FileInputStream(descriptor.archivePath().getPathFile()));
+ }
+ }.decompress(descriptorBuilder.build());
+
+ assertThat(outputDir.exists()).isTrue();
+ assertThat(outputDir.getRelative(REGULAR_FILE_NAME).exists()).isTrue();
+ assertThat(outputDir.getRelative(REGULAR_FILE_NAME).getFileSize()).isNotEqualTo(0);
+ assertThat(outputDir.getRelative(REGULAR_FILE_NAME).isSymbolicLink()).isFalse();
+ assertThat(outputDir.getRelative(HARD_LINK_FILE_NAME).exists()).isTrue();
+ assertThat(outputDir.getRelative(HARD_LINK_FILE_NAME).getFileSize()).isNotEqualTo(0);
+ assertThat(outputDir.getRelative(HARD_LINK_FILE_NAME).isSymbolicLink()).isFalse();
+ assertThat(outputDir.getRelative(SYMBOLIC_LINK_FILE_NAME).exists()).isTrue();
+ assertThat(outputDir.getRelative(SYMBOLIC_LINK_FILE_NAME).getFileSize()).isNotEqualTo(0);
+ assertThat(outputDir.getRelative(SYMBOLIC_LINK_FILE_NAME).isSymbolicLink()).isTrue();
+ assertThat(
+ Files.isSameFile(
+ java.nio.file.Paths.get(outputDir.getRelative(REGULAR_FILE_NAME).toString()),
+ java.nio.file.Paths.get(outputDir.getRelative(HARD_LINK_FILE_NAME).toString())))
+ .isTrue();
+ assertThat(
+ Files.isSameFile(
+ java.nio.file.Paths.get(outputDir.getRelative(REGULAR_FILE_NAME).toString()),
+ java.nio.file.Paths.get(outputDir.getRelative(SYMBOLIC_LINK_FILE_NAME).toString())))
+ .isTrue();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/test_decompress_archive.tar.gz b/src/test/java/com/google/devtools/build/lib/rules/repository/test_decompress_archive.tar.gz
new file mode 100644
index 0000000000..f951e97860
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/test_decompress_archive.tar.gz
Binary files differ
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
index aba86119ab..8b4255b510 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.vfs;
import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -26,18 +27,17 @@ import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.unix.NativePosixFiles;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.util.Preconditions;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.file.FileAlreadyExistsException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
/**
* This class handles the generic tests that any filesystem must pass.
@@ -50,6 +50,7 @@ public abstract class FileSystemTest {
private long savedTime;
protected FileSystem testFS;
protected boolean supportsSymlinks;
+ protected boolean supportsHardlinks;
protected Path workingDir;
// Some useful examples of various kinds of files (mnemonic: "x" = "eXample")
@@ -67,6 +68,7 @@ public abstract class FileSystemTest {
workingDir = testFS.getPath(getTestTmpDir());
cleanUpWorkingDirectory(workingDir);
supportsSymlinks = testFS.supportsSymbolicLinksNatively();
+ supportsHardlinks = testFS.supportsHardLinksNatively();
// % ls -lR
// -rw-rw-r-- xFile
@@ -1203,7 +1205,7 @@ public abstract class FileSystemTest {
public void testWritingToReadOnlyFileThrowsException() throws Exception {
xFile.setWritable(false);
try {
- FileSystemUtils.writeContent(xFile, "hello, world!".getBytes());
+ FileSystemUtils.writeContent(xFile, "hello, world!".getBytes(UTF_8));
fail("No exception thrown.");
} catch (IOException e) {
assertThat(e).hasMessage(xFile + " (Permission denied)");
@@ -1212,7 +1214,7 @@ public abstract class FileSystemTest {
@Test
public void testReadingFromUnreadableFileThrowsException() throws Exception {
- FileSystemUtils.writeContent(xFile, "hello, world!".getBytes());
+ FileSystemUtils.writeContent(xFile, "hello, world!".getBytes(UTF_8));
xFile.setReadable(false);
try {
FileSystemUtils.readContent(xFile);
@@ -1367,4 +1369,79 @@ public abstract class FileSystemTest {
}
}
+ @Test
+ public void testCreateHardLink_Success() throws Exception {
+ if (!supportsHardlinks) {
+ return;
+ }
+ xFile.createHardLink(xLink);
+ assertTrue(xFile.exists());
+ assertTrue(xLink.exists());
+ assertTrue(xFile.isFile());
+ assertTrue(xLink.isFile());
+ assertTrue(isHardLinked(xFile, xLink));
+ }
+
+ @Test
+ public void testCreateHardLink_NeitherOriginalNorLinkExists() throws Exception {
+ if (!supportsHardlinks) {
+ return;
+ }
+
+ /* Neither original file nor link file exists */
+ xFile.delete();
+ try {
+ xFile.createHardLink(xLink);
+ fail("expected FileNotFoundException: File \"xFile\" linked from \"xLink\" does not exist");
+ } catch (FileNotFoundException expected) {
+ assertThat(expected).hasMessage("File \"xFile\" linked from \"xLink\" does not exist");
+ }
+ assertFalse(xFile.exists());
+ assertFalse(xLink.exists());
+ }
+
+ @Test
+ public void testCreateHardLink_OriginalDoesNotExistAndLinkExists() throws Exception {
+
+ if (!supportsHardlinks) {
+ return;
+ }
+
+ /* link file exists and original file does not exist */
+ xFile.delete();
+ FileSystemUtils.createEmptyFile(xLink);
+
+ try {
+ xFile.createHardLink(xLink);
+ fail("expected FileNotFoundException: File \"xFile\" linked from \"xLink\" does not exist");
+ } catch (FileNotFoundException expected) {
+ assertThat(expected).hasMessage("File \"xFile\" linked from \"xLink\" does not exist");
+ }
+ assertFalse(xFile.exists());
+ assertTrue(xLink.exists());
+ }
+
+ @Test
+ public void testCreateHardLink_BothOriginalAndLinkExist() throws Exception {
+
+ if (!supportsHardlinks) {
+ return;
+ }
+ /* Both original file and link file exist */
+ FileSystemUtils.createEmptyFile(xLink);
+
+ try {
+ xFile.createHardLink(xLink);
+ fail("expected FileAlreadyExistsException: New link file \"xLink\" already exists");
+ } catch (FileAlreadyExistsException expected) {
+ assertThat(expected).hasMessage("New link file \"xLink\" already exists");
+ }
+ assertTrue(xFile.exists());
+ assertTrue(xLink.exists());
+ assertFalse(isHardLinked(xFile, xLink));
+ }
+
+ protected boolean isHardLinked(Path a, Path b) throws IOException {
+ return testFS.stat(a, false).getNodeId() == testFS.stat(b, false).getNodeId();
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
index 5c4aaa32d7..bcecada52e 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
@@ -38,17 +38,15 @@ import com.google.common.collect.Lists;
import com.google.devtools.build.lib.testutil.BlazeTestUtils;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
/**
* This class tests the file system utilities.
@@ -64,6 +62,7 @@ public class FileSystemUtilsTest {
clock = new ManualClock();
fileSystem = new InMemoryFileSystem(clock);
workingDir = fileSystem.getPath("/workingDir");
+ workingDir.createDirectory();
}
Path topDir;
@@ -769,4 +768,67 @@ public class FileSystemUtilsTest {
long timestamp = file.getLastModifiedTime(Symlinks.NOFOLLOW);
assertEquals(prevTimeMillis, timestamp);
}
+
+ @Test
+ public void testCreateHardLinkForFile_Success() throws Exception {
+
+ /* Original file exists and link file does not exist */
+ Path originalPath = workingDir.getRelative("original");
+ Path linkPath = workingDir.getRelative("link");
+ FileSystemUtils.createEmptyFile(originalPath);
+ FileSystemUtils.createHardLink(linkPath, originalPath);
+ assertTrue(originalPath.exists());
+ assertTrue(linkPath.exists());
+ assertEquals(
+ fileSystem.stat(originalPath, false).getNodeId(),
+ fileSystem.stat(linkPath, false).getNodeId());
+ }
+
+ @Test
+ public void testCreateHardLinkForEmptyDirectory_Success() throws Exception {
+
+ Path originalDir = workingDir.getRelative("originalDir");
+ Path linkPath = workingDir.getRelative("link");
+
+ FileSystemUtils.createDirectoryAndParents(originalDir);
+
+ /* Original directory is empty, no link to be created. */
+ FileSystemUtils.createHardLink(linkPath, originalDir);
+ assertFalse(linkPath.exists());
+ }
+
+ @Test
+ public void testCreateHardLinkForNonEmptyDirectory_Success() throws Exception {
+
+ /* Test when original path is a directory */
+ Path originalDir = workingDir.getRelative("originalDir");
+ Path linkPath = workingDir.getRelative("link");
+ Path originalPath1 = originalDir.getRelative("original1");
+ Path originalPath2 = originalDir.getRelative("original2");
+ Path originalPath3 = originalDir.getRelative("original3");
+ Path linkPath1 = linkPath.getRelative("original1");
+ Path linkPath2 = linkPath.getRelative("original2");
+ Path linkPath3 = linkPath.getRelative("original3");
+
+ FileSystemUtils.createDirectoryAndParents(originalDir);
+ FileSystemUtils.createEmptyFile(originalPath1);
+ FileSystemUtils.createEmptyFile(originalPath2);
+ FileSystemUtils.createEmptyFile(originalPath3);
+
+ /* Three link files created under linkPath */
+ FileSystemUtils.createHardLink(linkPath, originalDir);
+ assertTrue(linkPath.exists());
+ assertTrue(linkPath1.exists());
+ assertTrue(linkPath2.exists());
+ assertTrue(linkPath3.exists());
+ assertEquals(
+ fileSystem.stat(originalPath1, false).getNodeId(),
+ fileSystem.stat(linkPath1, false).getNodeId());
+ assertEquals(
+ fileSystem.stat(originalPath2, false).getNodeId(),
+ fileSystem.stat(linkPath2, false).getNodeId());
+ assertEquals(
+ fileSystem.stat(originalPath3, false).getNodeId(),
+ fileSystem.stat(linkPath3, false).getNodeId());
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java
index efbd5b0dbf..75bbe93571 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java
@@ -16,7 +16,10 @@ package com.google.devtools.build.lib.vfs;
import static org.junit.Assert.assertEquals;
import com.google.devtools.build.lib.testutil.ManualClock;
-
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.attribute.BasicFileAttributes;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -59,4 +62,19 @@ public class JavaIoFileSystemTest extends SymlinkAwareFileSystemTest {
file.setLastModifiedTime(-1L);
assertEquals(42000L, file.getLastModifiedTime());
}
+
+ @Override
+ protected boolean isHardLinked(Path a, Path b) throws IOException {
+ return Files.readAttributes(
+ java.nio.file.Paths.get(a.toString()),
+ BasicFileAttributes.class,
+ LinkOption.NOFOLLOW_LINKS)
+ .fileKey()
+ .equals(
+ Files.readAttributes(
+ java.nio.file.Paths.get(b.toString()),
+ BasicFileAttributes.class,
+ LinkOption.NOFOLLOW_LINKS)
+ .fileKey());
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
index f012b75463..f321ab93e3 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.vfs;
import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -24,16 +25,14 @@ import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.util.Preconditions;
-
-import org.junit.Before;
-import org.junit.Test;
-
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
/**
* Generic tests for any file system that implements {@link ScopeEscapableFileSystem},
@@ -82,6 +81,10 @@ public abstract class ScopeEscapableFileSystemTest extends SymlinkAwareFileSyste
return true;
}
+ @Override public boolean supportsHardLinksNatively() {
+ return true;
+ }
+
@Override
public boolean isFilePathCaseSensitive() {
return true;
@@ -113,7 +116,9 @@ public abstract class ScopeEscapableFileSystemTest extends SymlinkAwareFileSyste
@Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) {
throw re();
}
-
+ @Override protected void createFSDependentHardLink(Path linkPath, Path originalPath) {
+ throw re();
+ }
@Override protected PathFragment readSymbolicLink(Path path) { throw re(); }
@Override protected InputStream getInputStream(Path path) { throw re(); }
@Override protected Collection<Path> getDirectoryEntries(Path path) { throw re(); }
@@ -639,12 +644,12 @@ public abstract class ScopeEscapableFileSystemTest extends SymlinkAwareFileSyste
};
scopedFS().setDelegator(delegator);
- delegator.setState(new ByteArrayInputStream("blah".getBytes()));
+ delegator.setState(new ByteArrayInputStream("blah".getBytes(UTF_8)));
InputStream is = fileLink.getInputStream();
assertEquals(fileLinkTarget, delegator.lastPath());
assertSame(delegator.objectState(), is);
- delegator.setState(new ByteArrayInputStream("blah2".getBytes()));
+ delegator.setState(new ByteArrayInputStream("blah2".getBytes(UTF_8)));
is = dirLink.getInputStream();
assertEquals(dirLinkTarget, delegator.lastPath());
assertSame(delegator.objectState(), is);