diff --git a/io/src/main/scala/sbt/io/IO.scala b/io/src/main/scala/sbt/io/IO.scala index 78bb2948..a9176db4 100644 --- a/io/src/main/scala/sbt/io/IO.scala +++ b/io/src/main/scala/sbt/io/IO.scala @@ -15,7 +15,7 @@ import java.net.{ URI, URISyntaxException, URL } import java.nio.charset.Charset import java.nio.file.attribute.PosixFilePermissions import java.nio.file.{ Path => NioPath, _ } -import java.util.Properties +import java.util.{ Locale, Properties } import java.util.jar.{ Attributes, JarEntry, JarFile, JarOutputStream, Manifest } import java.util.zip.{ CRC32, ZipEntry, ZipInputStream, ZipOutputStream } @@ -674,7 +674,7 @@ object IO { ) = { val files = sources .flatMap { - case (file, name) => if (file.isFile) (file, normalizeName(name)) :: Nil else Nil + case (file, name) => if (file.isFile) (file, normalizeToSlash(name)) :: Nil else Nil } .sortBy { case (_, name) => name @@ -734,7 +734,7 @@ object IO { private def allDirectoryPaths(files: Iterable[(File, String)]) = TreeSet[String]() ++ (files flatMap { case (_, name) => directoryPaths(name) }) - private def normalizeName(name: String) = { + private def normalizeToSlash(name: String) = { val sep = File.separatorChar if (sep == '/') name else name.replace(sep, '/') } @@ -1171,14 +1171,33 @@ object IO { dirURI.normalize } + private[sbt] val isWindows: Boolean = + System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows") + /** Converts the given File to a URI. If the File is relative, the URI is relative, unlike File.toURI*/ - def toURI(f: File): URI = - if (f.isAbsolute) { - f.toPath.toUri + def toURI(f: File): URI = { + def ensureHeadSlash(name: String) = + if (name.nonEmpty && name.head != File.separatorChar) File.separatorChar + name + else name + + val p = f.getPath + if (isWindows && p.nonEmpty && p.head == File.separatorChar) { + if (p.startsWith("""\\""")) { + // supports \\laptop\My Documents\Some.doc on Windows + new URI(FileScheme, normalizeToSlash(p), null) + } else { + // supports /tmp on Windows + new URI(FileScheme, "", normalizeToSlash(p), null) + } + } else if (f.isAbsolute) { + //not using f.toURI to avoid filesystem syscalls + //we use empty string as host to force file:// instead of just file: + new URI(FileScheme, "", normalizeToSlash(ensureHeadSlash(f.getAbsolutePath)), null) } else { // need to use the three argument URI constructor because the single argument version doesn't encode - new URI(null, normalizeName(f.getPath), null) + new URI(null, normalizeToSlash(f.getPath), null) } + } /** * Resolves `f` against `base`, which must be an absolute directory. diff --git a/io/src/test/scala/sbt/io/IOSpec.scala b/io/src/test/scala/sbt/io/IOSpec.scala index 6dbf4206..fbcf2d1b 100644 --- a/io/src/test/scala/sbt/io/IOSpec.scala +++ b/io/src/test/scala/sbt/io/IOSpec.scala @@ -84,8 +84,8 @@ class IOSpec extends FunSuite { } test("toURI should make URI") { - val u = IO.toURI(file("/etc/hosts").getAbsoluteFile) - assert(u.toString.startsWith("file:///") && u.toString.endsWith("etc/hosts")) + val u = IO.toURI(file("/etc/hosts")) + assert(u.toString == "file:///etc/hosts") } test("it should make u0 URI from a relative path") { @@ -93,9 +93,17 @@ class IOSpec extends FunSuite { assert(u.toString == "src/main/scala") } + test("it should make u0 URI from a relative path on Windows") { + if (IO.isWindows) { + val input = file("""..\My Documents\test""") + val u = IO.toURI(input) + assert(u.toString == "../My%20Documents/test" && IO.toFile(u) == input) + } else () + } + test("it should make URI that roundtrips") { - val u = IO.toURI(file("/etc/hosts").getAbsoluteFile) - assert(IO.toFile(u) == file("/etc/hosts").getAbsoluteFile) + val u = IO.toURI(file("/etc/hosts")) + assert(IO.toFile(u) == file("/etc/hosts")) } test("it should make u0 URI that roundtrips") { @@ -103,6 +111,22 @@ class IOSpec extends FunSuite { assert(IO.toFile(u) == (file("src") / "main" / "scala")) } + test("it should make u3 URI for an absolute path on Windows that roundtrips") { + if (IO.isWindows) { + val input = file("""C:\Documents and Settings\""") + val u = IO.toURI(input) + assert(u.toString == "file:///C:/Documents%20and%20Settings" && IO.toFile(u) == input) + } else () + } + + test("it should make u2 URI for a UNC path on Windows that roundtrips") { + if (IO.isWindows) { + val input = file("""\\laptop\My Documents\Some.doc""") + val u = IO.toURI(input) + assert(u.toString == "file://laptop/My%20Documents/Some.doc" && IO.toFile(u) == input) + } else () + } + test("getModifiedTimeOrZero should return 0L if the file doesn't exists") { assert(IO.getModifiedTimeOrZero(file("/not/existing/path")) == 0L) }