Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Thanks for a great library! Hopefully this change will increase interest among windows shell environment developers. This fixes #201 The essence of the fix is to support paths with a leading slash (`'/' or '\'`) on Windows. In this PR, this path type is referred to as `driveRelative`, due to subtle differences in how it is handled on `Windows` versus other platforms. Example code: ```scala val p1 = java.nio.file.Paths.get("/omg") printf("isAbsolute: %s\n", p1.isAbsolute) printf("%s\n", p1) printf("%s\n", os.Path("/omg")) ``` Output on Linux or OSX: ```sh isAbsolute: true /omg /omg ``` On Windows: ```scala isAbsolute: false \omg java.lang.IllegalArgumentException: requirement failed: \omg is not an absolute path at os.Path.<init>(Path.scala:474) at os.Path$.apply(Path.scala:426) at oslibChek$package$.main(oslibChek.sc:11) at oslibChek$package.main(oslibChek.sc) ``` ### Background On Windows, a `driveRelative` path is considered relative because a drive letter prefix is required to fully resolve it. Like other platforms, Windows also supports `posix relative` paths, which are relative to the `current working directory`. Because all platforms, including `Windows`, support absolute paths and `posix relative` paths, it's convenient when writing platform-independent code to view `driveRelative` paths as absolute paths, as they are on other platforms. On Windows, the `current working drive` is an immutable value that is captured on jvm startup. Therefore, a `driveRelative` path on Windows unambiguously refers to a unique file with a hidden drive letter. This PR treats `driveRelative` paths as absolute paths in Windows, and has no effect on other platforms. #### Making `os/test/src/PathTests` platform independent This PR enables `Unix()` tests in `os/test/src/PathTests.scala` so they also verify required semantics in Windows. ### How this PR relates to #170 and #196 The purpose of #196 seems to be to add full support on Windows, without resorting to `java.nio` classes and methods. The addition of `os.root(...)` or `os.drive(...)` would be convenient for people writing windows-only code, whereas this PR is intended to allow writing platform-independent code, so the concerns seem to be orthogonal. It seems important not to alter `Path.segments` in a way that is incompatible with `java.nio` segments. An alternate way to preserve drive letter information would be to add a method to one of the `Path.scala` traits that returns a drive letter if appropriate on `Windows` and an empty String elsewhere. ```scala trait PathChunk { def segments: Seq[String] def rootPrefix: String // empty String, or Windows drive letter def ups: Int } ``` With this approach, the following assertion would be valid on all platforms: ```scala os.Path(pwd.rootPrefix) ==> pwd ``` Then `driveRoot` in this PR would become `pwd.rootPrefix`. There is another (perhaps never used?) very subtle feature of `Windows` filesystem: each drive has a different value for `pwd`. It may not be necessary to model this in `os-lib`, since these values are immutable in a running jvm, and there are existing workarounds. ### Additional Details regarding unique aspects of Windows Filesystem Paths For completeness, the following lengthy `scala` REPL session illustrates Path values returned by `java.nio.file.Paths.get()`, some of which might be surprising. <details> Relevant information: My system drives are C: and F:, but not J: First, some unsurprising results: ```scala c:\work-directory> scala.bat scala> import java.nio.file.Paths Welcome to Scala 3.3.1-RC5 (17.0.2, Java OpenJDK 64-Bit Server VM). Type in expressions for evaluation. Or try :help. scala> Paths.get("/") val res0: java.nio.file.Path = \ scala> Paths.get("/").isAbsolute val res2: Boolean = false scala> Paths.get("/").toAbsolutePath val res1: java.nio.file.Path = C:\ scala> Paths.get("c:/").toAbsolutePath C:\work-directory scala> Paths.get("f:/").toAbsolutePath F:\ scala> Paths.get("f:/..").normalize.toAbsolutePath f:\ scala> Paths.get("c:/..").normalize.toAbsolutePath c:\ ``` Now some less obvious results: ```scala scala> Paths.get("c:").toAbsolutePath C:\work-directory scala> Paths.get("f:").toAbsolutePath F:\ scala> Paths.get("f:/..").normalize.toAbsolutePath f:\ scala> Paths.get("c:/..").normalize.toAbsolutePath c:\ scala> Paths.get("c:..").normalize.toAbsolutePath C:\work-directory scala> Paths.get("f:..").normalize.toAbsolutePath F:\.. scala> Paths.get("F:Users") val res1: java.nio.file.Path = F:Users scala> Paths.get("F:Users").toAbsolutePath val res2: java.nio.file.Path = F:\work-directory\Users scala> Paths.get("j:").toAbsolutePath java.io.IOError: java.io.IOException: Unable to get working directory of drive 'J' at java.base/sun.nio.fs.WindowsPath.toAbsolutePath(WindowsPath.java:926) at java.base/sun.nio.fs.WindowsPath.toAbsolutePath(WindowsPath.java:42) ... 35 elided Caused by: java.io.IOException: Unable to get working directory of drive 'J' ... 37 more ``` The error message returned for a non-existing drive seems to imply that existing drives have a `working directory`. This is consistent with the `Windows API` as described here: [GetFullPathNameA](https://learn.microsoft.com/en-gb/windows/win32/api/fileapi/nf-fileapi-getfullpathnamea) I can set the `working drive` for F: in a `CMD` session before I start up the `jvm`, and it does affect the output of `Paths.get()`. ```CMD c:\work-directory>F: f:\>cd work-directory F:\work-directory>scala.bat Welcome to Scala 3.3.1-RC5 (17.0.2, Java OpenJDK 64-Bit Server VM). Type in expressions for evaluation. Or try :help. scala> import java.nio.file.Paths scala> Paths.get("/") val res0: java.nio.file.Path = \ scala> Paths.get("/").toAbsolutePath val res1: java.nio.file.Path = C:\ scala> Paths.get("f:") val res2: java.nio.file.Path = f: scala> Paths.get("f:").toAbsolutePath val res3: java.nio.file.Path = F:\work-directory scala> Paths.get("c:").toAbsolutePath val res4: java.nio.file.Path = C:\work-directory scala> Paths.get("c:") val res5: java.nio.file.Path = c: ``` Regarding the interpretation of a drive letter expression not followed by a '/' or '\\', the rule is that it represents the `working directory` for that drive. These values are immutable: after the `JVM` starts up it's not possible to change a drive `working directory`. </details>
- Loading branch information