diff options
12 files changed, 507 insertions, 5 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/BUILD b/src/main/java/com/google/devtools/build/lib/windows/jni/BUILD index ed8ca69799..d295eacb0a 100644 --- a/src/main/java/com/google/devtools/build/lib/windows/jni/BUILD +++ b/src/main/java/com/google/devtools/build/lib/windows/jni/BUILD @@ -1,17 +1,38 @@ -package( - default_visibility = [ - "//src/main/java/com/google/devtools/build/lib:__subpackages__", - "//src/test/java/com/google/devtools/build/lib:__subpackages__", +package(default_visibility = ["//visibility:private"]) + +package_group( + name = "android-junctions-prod", + packages = [ + "//src/tools/android/java/com/google/devtools/build/android/junctions", + ], +) + +package_group( + name = "bazel-prod", + packages = [ + "//src/main/java/com/google/devtools/build/lib/...", + ], +) + +package_group( + name = "bazel-tests", + packages = [ + "//src/test/java/com/google/devtools/build/lib/...", ], ) filegroup( name = "srcs", srcs = glob(["**"]), + visibility = [":bazel-prod"], ) java_library( name = "jni", + visibility = [ + ":bazel-prod", + ":bazel-tests", + ], exports = [ ":file", ":processes", @@ -21,12 +42,14 @@ java_library( java_library( name = "file", srcs = ["WindowsFileOperations.java"], + visibility = [":android-junctions-prod"], deps = [":jni-loader"], ) java_library( name = "processes", srcs = ["WindowsProcesses.java"], + visibility = [":bazel-prod"], deps = [":jni-loader"], ) diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java index b68c1a8b07..974fe4e6dc 100644 --- a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java +++ b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java @@ -29,7 +29,7 @@ public class WindowsJniLoader { try { System.loadLibrary("windows_jni"); } catch (UnsatisfiedLinkError ex) { - // We are probably in tests, let's try to find the library in the runfiles + // Try to find the library in the runfiles. try { System.load(WindowsRunfiles.getRunfile("io_bazel/src/main/native/windows/windows_jni.dll")); } catch (IOException e) { diff --git a/src/main/native/windows/BUILD b/src/main/native/windows/BUILD index 926643c878..fe5873f37f 100644 --- a/src/main/native/windows/BUILD +++ b/src/main/native/windows/BUILD @@ -45,5 +45,6 @@ genrule( visibility = [ "//src:__pkg__", "//src/test/java/com/google/devtools/build/lib:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android:__subpackages__", ], ) diff --git a/src/test/java/com/google/devtools/build/android/BUILD b/src/test/java/com/google/devtools/build/android/BUILD index 29e8b2a04a..ab7dd8982a 100644 --- a/src/test/java/com/google/devtools/build/android/BUILD +++ b/src/test/java/com/google/devtools/build/android/BUILD @@ -3,6 +3,7 @@ filegroup( srcs = glob(["**"]) + [ "//src/test/java/com/google/devtools/build/android/idlclass:srcs", "//src/test/java/com/google/devtools/build/android/dexer:srcs", + "//src/test/java/com/google/devtools/build/android/junctions:srcs", "//src/test/java/com/google/devtools/build/android/resources:srcs", "//src/test/java/com/google/devtools/build/android/testing/manifestmerge:srcs", "//src/test/java/com/google/devtools/build/android/ziputils:srcs", diff --git a/src/test/java/com/google/devtools/build/android/junctions/BUILD b/src/test/java/com/google/devtools/build/android/junctions/BUILD new file mode 100644 index 0000000000..ad4ae01400 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/junctions/BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:private"]) + +package_group( + name = "android-tests", + packages = [ + "//src/test/java/com/google/devtools/build/android/...", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = [":android-tests"], +) + +java_test( + name = "JunctionCreatorTest", + size = "small", + srcs = select({ + "//src:windows": ["WindowsJunctionCreatorTest.java"], + "//src:windows_msvc": ["WindowsJunctionCreatorTest.java"], + "//src:windows_msys": ["WindowsJunctionCreatorTest.java"], + "//conditions:default": ["NoopJunctionCreatorTest.java"], + }), + test_class = select({ + "//src:windows": "com.google.devtools.build.android.junctions.WindowsJunctionCreatorTest", + "//src:windows_msvc": "com.google.devtools.build.android.junctions.WindowsJunctionCreatorTest", + "//src:windows_msys": "com.google.devtools.build.android.junctions.WindowsJunctionCreatorTest", + "//conditions:default": "com.google.devtools.build.android.junctions.NoopJunctionCreatorTest", + }), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/junctions", + "//third_party:junit4", + "//third_party:truth", + ], +) diff --git a/src/test/java/com/google/devtools/build/android/junctions/NoopJunctionCreatorTest.java b/src/test/java/com/google/devtools/build/android/junctions/NoopJunctionCreatorTest.java new file mode 100644 index 0000000000..a99d568e8f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/junctions/NoopJunctionCreatorTest.java @@ -0,0 +1,49 @@ +// Copyright 2017 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.android.junctions; + +import static com.google.common.truth.Truth.assertThat; + +import java.nio.file.FileSystems; +import java.nio.file.Path; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for NoopJunctionCreator. */ +@RunWith(JUnit4.class) +public class NoopJunctionCreatorTest { + private Path tmproot = null; + + @Before + public void acquireTmpRoot() { + String tmpEnv = System.getenv("TEST_TMPDIR"); + assertThat(tmpEnv).isNotNull(); + tmproot = FileSystems.getDefault().getPath(tmpEnv); + // Cast Path to Object to disambiguate which assertThat-overload to use. + assertThat((Object) tmproot).isNotNull(); + } + + @Test + public void testNoopJunctionCreator() throws Exception { + JunctionCreator jc = new NoopJunctionCreator(); + // Cast Path to Object to disambiguate which assertThat-overload to use. + assertThat((Object) jc.create(null)).isNull(); + + Path p = tmproot.resolve("foo"); + // Cast Path to Object to disambiguate which assertThat-overload to use. + assertThat((Object) jc.create(p)).isSameAs(p); + } +} diff --git a/src/test/java/com/google/devtools/build/android/junctions/WindowsJunctionCreatorTest.java b/src/test/java/com/google/devtools/build/android/junctions/WindowsJunctionCreatorTest.java new file mode 100644 index 0000000000..22ea0f22a1 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/junctions/WindowsJunctionCreatorTest.java @@ -0,0 +1,186 @@ +// Copyright 2017 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.android.junctions; + +import static com.google.common.truth.Truth.assertThat; + +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for WindowsJunctionCreator. */ +@RunWith(JUnit4.class) +public class WindowsJunctionCreatorTest { + private Path tmproot = null; + + @Before + public void acquireTmpRoot() { + String tmpEnv = System.getenv("TEST_TMPDIR"); + assertThat(tmpEnv).isNotNull(); + tmproot = FileSystems.getDefault().getPath(tmpEnv); + // Cast Path to Object to disambiguate which assertThat-overload to use. + assertThat((Object) tmproot).isNotNull(); + } + + @Test + public void testNullInput() throws Exception { + try (JunctionCreator jc = new WindowsJunctionCreator(tmproot.resolve("foo"))) { + // Cast Path to Object to disambiguate which assertThat-overload to use. + assertThat((Object) jc.create(null)).isNull(); + } + } + + @Test + public void testFileInput() throws Exception { + Path dir = tmproot.resolve("foo"); // [tmproot]/foo/ + Path file = dir.resolve("bar"); // [tmproot]/foo/bar + Path juncroot = tmproot.resolve("junc"); // [tmproot]/junc/ + Path junc = juncroot.resolve("0"); // [tmproot]/junc/0 -> [tmproot]/foo/bar + Path fileViaJunc = junc.resolve("bar"); // [tmproot]/junc/0/bar + try { + Files.createDirectories(dir); + Files.createDirectories(juncroot); + + // Create a scratch file and assert its existence. + Files.write(file, "hello".getBytes()); + assertThat(file.toFile().exists()).isTrue(); + + // Assert that the junction doesn't exist yet. + assertThat(junc.toFile().exists()).isFalse(); + assertThat(fileViaJunc.toFile().exists()).isFalse(); + + try (JunctionCreator jc = new WindowsJunctionCreator(juncroot)) { + // Assert creation of a junction for a file (more precisely, for its parent directory). + // Cast Path to Object to disambiguate which assertThat-overload to use. + assertThat((Object) jc.create(file)).isEqualTo(fileViaJunc); + // Assert that the junction now exists. + assertThat(junc.toFile().exists()).isTrue(); + // Sanity check: the `file` should still exist. + assertThat(file.toFile().exists()).isTrue(); + // Assert that the junction indeed points to `dir`, by asserting the existence of `file` + // through the junction. + assertThat(fileViaJunc.toFile().exists()).isTrue(); + } + // Assert that WindowsJunctionCreator.close cleaned up the junction and root directory, but + // not the files in the directory that the junction pointed to. + assertThat(junc.toFile().exists()).isFalse(); + assertThat(juncroot.toFile().exists()).isFalse(); + // Assert that the `file` and `directory` still exist. Deleting the junction should not have + // affected them. + assertThat(file.toFile().exists()).isTrue(); + assertThat(dir.toFile().exists()).isTrue(); + } finally { + file.toFile().delete(); + dir.toFile().delete(); + } + } + + @Test + public void testJunctionCaching() throws Exception { + Path dir1 = tmproot.resolve("foo"); // [tmproot]/foo/ + Path dir2 = dir1.resolve("foo"); // [tmproot]/foo/foo/ + Path file1 = dir1.resolve("bar"); // [tmproot]/foo/bar + Path file2 = dir1.resolve("baz"); // [tmproot]/foo/baz + Path file3 = dir2.resolve("bar"); // [tmproot]/foo/foo/bar + Path juncroot = tmproot.resolve("junc"); // [tmproot]/junc/ + Path junc0 = juncroot.resolve("0"); // [tmproot]/junc/0 -> [tmproot]/foo/ + Path junc1 = juncroot.resolve("1"); // [tmproot]/junc/1 -> [tmproot]/foo/foo/ + Path file1junc = junc0.resolve("bar"); // [tmproot]/junc/0/bar + Path file2junc = junc0.resolve("baz"); // [tmproot]/junc/0/baz + Path file3junc = junc1.resolve("bar"); // [tmproot]/junc/1/bar + + try { + Files.createDirectories(dir2); + Files.createDirectories(juncroot); + + // Create scratch files and assert their existence. + Files.write(file1, "i am file1".getBytes()); + Files.write(file2, "i am file2".getBytes()); + Files.write(file3, "i am file3".getBytes()); + assertThat(file1.toFile().exists()).isTrue(); + assertThat(file2.toFile().exists()).isTrue(); + assertThat(file3.toFile().exists()).isTrue(); + + // Assert that the junctions don't exist yet. + assertThat(junc0.toFile().exists()).isFalse(); + assertThat(junc1.toFile().exists()).isFalse(); + assertThat(file1junc.toFile().exists()).isFalse(); + assertThat(file2junc.toFile().exists()).isFalse(); + assertThat(file3junc.toFile().exists()).isFalse(); + + try (JunctionCreator jc = new WindowsJunctionCreator(juncroot)) { + Path dir1juncActual = jc.create(dir1); + Path file1juncActual = jc.create(file1); + Path dir2juncActual = jc.create(dir2); + Path file3juncActual = jc.create(file3); + Path file2juncActual = jc.create(file2); + + // Assert that the junctions now exists. + assertThat(dir1juncActual.toFile().exists()).isTrue(); + assertThat(dir2juncActual.toFile().exists()).isTrue(); + assertThat(file1juncActual.toFile().exists()).isTrue(); + assertThat(file2juncActual.toFile().exists()).isTrue(); + assertThat(file3juncActual.toFile().exists()).isTrue(); + + // Assert that the junctions were chached. + // Cast Path to Object to disambiguate which assertThat-overload to use. + assertThat((Object) dir1juncActual).isEqualTo(junc0); + assertThat((Object) dir2juncActual).isEqualTo(junc1); + assertThat((Object) file1juncActual).isEqualTo(file1junc); + assertThat((Object) file2juncActual).isEqualTo(file2junc); + assertThat((Object) file3juncActual).isEqualTo(file3junc); + assertThat((Object) file1juncActual.getParent()).isEqualTo(junc0); + assertThat((Object) file2juncActual.getParent()).isEqualTo(junc0); + assertThat((Object) file3juncActual.getParent()).isEqualTo(junc1); + + // Assert that the directory junctions indeed point where they should, by asserting the + // existence of `file1`, `file2`, and `file3` through them. + assertThat(dir1juncActual.resolve("bar").toFile().exists()).isTrue(); + assertThat(dir1juncActual.resolve("baz").toFile().exists()).isTrue(); + assertThat(dir2juncActual.resolve("bar").toFile().exists()).isTrue(); + + // Assert that the file junctions indeed point where they should, by asserting the contents + // we can read from them. + assertThat(Files.readAllBytes(file1junc)).isEqualTo("i am file1".getBytes()); + assertThat(Files.readAllBytes(file2junc)).isEqualTo("i am file2".getBytes()); + assertThat(Files.readAllBytes(file3junc)).isEqualTo("i am file3".getBytes()); + } + + // Assert that WindowsJunctionCreator.close cleaned up the junction and root directory, but + // not the files in the directory that the junction pointed to. + assertThat(junc0.toFile().exists()).isFalse(); + assertThat(junc1.toFile().exists()).isFalse(); + assertThat(file1junc.toFile().exists()).isFalse(); + assertThat(file2junc.toFile().exists()).isFalse(); + assertThat(file3junc.toFile().exists()).isFalse(); + assertThat(juncroot.toFile().exists()).isFalse(); + + // Assert that the original files (and consequently the directories) still exist. Deleting the + // junction should not have affected them. + assertThat(file1.toFile().exists()).isTrue(); + assertThat(file2.toFile().exists()).isTrue(); + assertThat(file3.toFile().exists()).isTrue(); + } finally { + file3.toFile().delete(); + file2.toFile().delete(); + file1.toFile().delete(); + dir2.toFile().delete(); + dir1.toFile().delete(); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD index d3bc605e3e..5f1de9dbc3 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD @@ -9,6 +9,7 @@ filegroup( "classes_deploy.jar", "//src/tools/android/java/com/google/devtools/build/android/desugar:embedded_tools", "//src/tools/android/java/com/google/devtools/build/android/incrementaldeployment:embedded_tools", + "//src/tools/android/java/com/google/devtools/build/android/junctions:embedded_tools", "//src/tools/android/java/com/google/devtools/build/android/proto:srcs", ], ) @@ -38,6 +39,7 @@ java_library( "//src/main/java/com/google/devtools/common/options", "//src/main/protobuf:option_filters_java_proto", "//src/main/protobuf:package_manifest_java_proto", + "//src/tools/android/java/com/google/devtools/build/android/junctions", "//src/tools/android/java/com/google/devtools/build/android/proto:serialize_format_java_pb", "//src/tools/android/java/com/google/devtools/build/android/resources", "//third_party:android_common_25_0_0", @@ -58,6 +60,7 @@ filegroup( "//src/tools/android/java/com/google/devtools/build/android/ideinfo:srcs", "//src/tools/android/java/com/google/devtools/build/android/idlclass:srcs", "//src/tools/android/java/com/google/devtools/build/android/incrementaldeployment:srcs", + "//src/tools/android/java/com/google/devtools/build/android/junctions:srcs", "//src/tools/android/java/com/google/devtools/build/android/proto:srcs", "//src/tools/android/java/com/google/devtools/build/android/resources:srcs", "//src/tools/android/java/com/google/devtools/build/android/ziputils:srcs", diff --git a/src/tools/android/java/com/google/devtools/build/android/junctions/BUILD b/src/tools/android/java/com/google/devtools/build/android/junctions/BUILD new file mode 100644 index 0000000000..d54aecd406 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/junctions/BUILD @@ -0,0 +1,47 @@ +package(default_visibility = ["//visibility:private"]) + +package_group( + name = "android-prod", + packages = [ + "//src/tools/android/java/com/google/devtools/build/android", + ], +) + +package_group( + name = "android-tests", + packages = [ + "//src/test/java/com/google/devtools/build/android/...", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = [":android-prod"], +) + +filegroup( + name = "embedded_tools", + srcs = glob(["*.java"]), + visibility = [":android-prod"], +) + +java_library( + name = "junctions", + srcs = glob(["*.java"]), + data = select({ + "//src:windows": ["//src/main/native/windows:windows_jni"], + "//src:windows_msvc": ["//src/main/native/windows:windows_jni"], + "//src:windows_msys": ["//src/main/native/windows:windows_jni"], + "//conditions:default": [], + }), + visibility = [ + ":android-prod", + ":android-tests", + ], + deps = [ + "//src/main/java/com/google/devtools/build/lib/windows/jni:file", + "//third_party:guava", + "//third_party:jsr305", + ], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/junctions/JunctionCreator.java b/src/tools/android/java/com/google/devtools/build/android/junctions/JunctionCreator.java new file mode 100644 index 0000000000..fe82e34180 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/junctions/JunctionCreator.java @@ -0,0 +1,46 @@ +// Copyright 2017 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.android.junctions; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Path; +import javax.annotation.Nullable; + +/** + * Interface to create junctions (directory symlinks). + * + * <p>Junctions are directory symlinks on NTFS filesystems. They are useful on Windows, because + * creating them doesn't require any privileges, as opposed to the creation of file symlinks which + * does. + * + * <p>On Windows, Bazel and the Android BusyBox uses junctions to work around path length + * limitations of the Windows Shell and of tools like aapt.exe and the PNG cruncher. The limit is + * 260 characters for all paths. The filesystem supports longer paths than that, but the tools + * usually don't. To work around that limitation, we create junctions that have short paths but + * point to long paths (this is allowed). + * + * <p>On Linux/MacOS the junction creator may have a no-op implementation. + */ +public interface JunctionCreator extends Closeable { + /** + * Returns an equivalent path to `target`, which may or may not be the same as `target`. + * + * <p>Depending on the implementation, this method may return `target` itself, or may create a + * junction that points to `target` (if `target` is a directory) or the parent of it (if `target` + * is a file). + */ + @Nullable + public abstract Path create(@Nullable Path target) throws IOException; +} diff --git a/src/tools/android/java/com/google/devtools/build/android/junctions/NoopJunctionCreator.java b/src/tools/android/java/com/google/devtools/build/android/junctions/NoopJunctionCreator.java new file mode 100644 index 0000000000..5b061f3fbd --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/junctions/NoopJunctionCreator.java @@ -0,0 +1,30 @@ +// Copyright 2017 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.android.junctions; + +import java.io.IOException; +import java.nio.file.Path; +import javax.annotation.Nullable; + +/** A no-op JunctionCreator implementation that just returns the input path. */ +public final class NoopJunctionCreator implements JunctionCreator { + @Nullable + @Override + public Path create(@Nullable Path path) throws IOException { + return path; + } + + @Override + public void close() throws IOException {} +} diff --git a/src/tools/android/java/com/google/devtools/build/android/junctions/WindowsJunctionCreator.java b/src/tools/android/java/com/google/devtools/build/android/junctions/WindowsJunctionCreator.java new file mode 100644 index 0000000000..278b55d34c --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/junctions/WindowsJunctionCreator.java @@ -0,0 +1,80 @@ +// Copyright 2017 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.android.junctions; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.windows.jni.WindowsFileOperations; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Junction creator implementation for Windows. + * + * <p>Creates a junction (or uses a cached one) for a path. If the path is a directory, the junction + * points to it, and the returned path is the junction's path. If the path is a file, the junction + * points to its parent, and the returned path is the file's path through the junction. + * + * <p>The `close` method deletes all junctions that this object created, along with the `dir` + * directory where the junctions are created. The purpose of this is to avoid other methods (such as + * ScopedTemporaryDirectory.close) to traverse these junctions believing they are regular + * directories and deleting files in them that are actually outside of the directory tree. + */ +public final class WindowsJunctionCreator implements JunctionCreator { + private final Path dir; + private Map<Path, Path> paths; // allocated lazily, but semantically final + private int junctionIndex = 0; + + public WindowsJunctionCreator(Path dir) { + this.dir = Preconditions.checkNotNull(dir); + } + + @Nullable + public Path create(@Nullable Path path) throws IOException { + if (path == null) { + return null; + } + + if (paths == null) { + paths = new HashMap<>(); + } + path = path.toAbsolutePath(); + if (path.toFile().isDirectory()) { + Path link = paths.get(path); + if (link == null) { + link = dir.resolve(Integer.toString(junctionIndex++)); + WindowsFileOperations.createJunction(link.toString(), path.toString()); + paths.put(path, link); + } + return link; + } + + Path parent = path.getParent(); + return (parent == null) ? path : create(parent).resolve(path.getFileName()); + } + + @Override + public void close() throws IOException { + // Delete all junctions, otherwise the temp directory deleter would follow them and delete files + // from directories they point to. + if (paths != null) { + for (Path link : paths.values()) { + link.toFile().delete(); + } + } + dir.toFile().delete(); + } +} |