diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index 67bd964ecc3d..532dc315da7c 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -10,7 +10,7 @@ import java.io.{ ByteArrayOutputStream } import java.net.URL -import java.nio.file.{Files, Paths} +import java.nio.file.{FileAlreadyExistsException, Files, Paths} /** * An abstraction over files for use in the reflection/compiler libraries. @@ -235,17 +235,21 @@ abstract class AbstractFile extends Iterable[AbstractFile] { file } - private def fileOrSubdirectoryNamed(name: String, isDir: Boolean): AbstractFile = { - val lookup = lookupName(name, isDir) - if (lookup != null) lookup - else { - Files.createDirectories(jpath) - val path = jpath.resolve(name) - if (isDir) Files.createDirectory(path) - else Files.createFile(path) - new PlainFile(new File(path)) + private def fileOrSubdirectoryNamed(name: String, isDir: Boolean): AbstractFile = + lookupName(name, isDir) match { + case null => + // the optional exception may be thrown for symlinks, notably /tmp on MacOS. + // isDirectory tests for existing directory. The default behavior is hypothetical isDirectory(jpath, FOLLOW_LINKS). + try Files.createDirectories(jpath) + catch { case _: FileAlreadyExistsException if Files.isDirectory(jpath) => } + + // a race condition in creating the entry after the failed lookup may throw + val path = jpath.resolve(name) + if (isDir) Files.createDirectory(path) + else Files.createFile(path) + new PlainFile(new File(path)) + case lookup => lookup } - } /** * Get the file in this directory with the given name, diff --git a/compiler/test/dotty/tools/io/AbstractFileTest.scala b/compiler/test/dotty/tools/io/AbstractFileTest.scala new file mode 100644 index 000000000000..221df8dfc227 --- /dev/null +++ b/compiler/test/dotty/tools/io/AbstractFileTest.scala @@ -0,0 +1,55 @@ +package dotty.tools.io + +import org.junit.Test + +import dotty.tools.io.AbstractFile +import java.nio.file.Files._ + +class AbstractFileTest { + // + // Cope with symbolic links. Exercised by -d output. + // + // BytecodeWriters#getFile ensures d.isDirectory for elements in path, + // but d.fileNamed and d.subdirectoryNamed also Files.createDirectories + // for prefixes, which may optionally fail on an existing symbolic link. + // + private def exerciseSymbolicLinks(temp: Directory): Unit = { + val base = { + val target = createTempDirectory(temp.jpath, "real") + val link = temp.jpath.resolve("link") + createSymbolicLink(link, target) // may bail early if unsupported + AbstractFile.getDirectory(link) + } + + val file = base.fileNamed("foo") + assert(file.exists && !file.isDirectory) + + val dir = base.subdirectoryNamed("bar") + assert(dir.isDirectory) + val leaf = dir.fileNamed("basement") + assert(leaf.exists && !leaf.isDirectory) + + val nested = AbstractFile.getDirectory(createSymbolicLink(dir.jpath.resolve("link"), dir.subdirectoryNamed("nested").jpath)) + val doubly = nested.fileNamed("doubly") + assert(nested.exists && nested.isDirectory) // /tmp/link/bar/link + assert(doubly.exists && !doubly.isDirectory) // /tmp/link/bar/link/doubly + assert(dir.subdirectoryNamed("link").exists) + } + @Test def t6450(): Unit = + try Directory.inTempDirectory(exerciseSymbolicLinks) + catch { case _: UnsupportedOperationException => () } +} + +/* Was: +[error] Test dotty.tools.io.AbstractFileTest.t6450 failed: java.nio.file.FileAlreadyExistsException: /var/folders/2_/xb149z895wb5f1y632xp2bw40000gq/T/temp9110019868196066936/link, took 0.124 sec +[error] at sun.nio.fs.UnixException.translateToIOException(UnixException.java:88) +[error] at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) +[error] at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) +[error] at sun.nio.fs.UnixFileSystemProvider.createDirectory(UnixFileSystemProvider.java:384) +[error] at java.nio.file.Files.createDirectory(Files.java:674) +[error] at java.nio.file.Files.createAndCheckIsDirectory(Files.java:781) +[error] at java.nio.file.Files.createDirectories(Files.java:727) +[error] at dotty.tools.io.AbstractFile.fileOrSubdirectoryNamed(AbstractFile.scala:242) +[error] at dotty.tools.io.AbstractFile.fileNamed(AbstractFile.scala:262) +[error] at dotty.tools.io.AbstractFileTest.exerciseSymbolicLinks(AbstractFileTest.scala:25) + */