-
-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional root in Path and support for roots for Windows #170
Comments
I think it sounds reasonable |
We do a similar thing with |
We definitely need some test coverage and documentation for the edge cases, e.g. making a path relative to another fails when the root is not the same. |
There is one issue that has to be given some consideration: If we return the root (drive letter) in segments on Windows, then we run into a couple of problems:
Instead, we could add a non-optional The I propose changing the |
Do we have to return Anyone who wants it can call |
Now, we claim that there should be no need to use lower level Java API, which isn't fully true unless we cover Windows file system roots. But there is a reason, why Unix-like OSes don't have the "drive" distinction. It sucks. I think, we should not include the root in the segments, but handle it properly in (de-)serialization. |
Maybe, we can just invent some new type to deal with full Windows paths including a drive letter and provide some way to resolve it from a |
I wonder if #167 is related to this issue? JVM on linux cna have multiple filesystem roots too IIRC, with ZipFileSystems and in-process virtual file systems, so it's not purely a windows-ism. Presumably whatever we do for Windows should work for those as well |
One could argue that root on Unix-like OSes is just |
Do we target these use cases? Do we have a way in os-lib API to select a files system? |
I've never thought about it, but if we're aiming for OS-Lib to be a feature-complete wrapper of the And I think it shouldn't be too much complexity either. I think once we get the |
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>
Resolve #170 This PR adds a `def root(root: String, fileSystem: FileSystem): Path` in `os` package object. Additionally, it adds `root` and `filesystem` members to `Path`. It addresses two problems: - Specifying custom roots for a path - Using custom filesystems ## Filesystem Filesystem was added as a field of the `os.Path`. It was not added as a Root field for simplicity and consistency with Java's `nio`. Root is a part of the path; filesystem is the context of the whole path. One filesystem can have many roots; one root can have many subdirectories. ## Root as String A string is the simplest type that can represent the path's root. It also corresponds to the mental model of the path - usually, developers perceive it as a String. Therefore - String was chosen as the type of root. Pull request: #196 --------- Co-authored-by: Li Haoyi <[email protected]>
Per the following discussion: #136
I propose that we:
Path
root
insegments
. Java'sPath
returns segments without the drive letter, but keeps the information. I.e. - the path is relative, root is not present, and the .getRoot on segments return null, but the segments can be brought back to be absolute with.toAbsolutePath
.segments
in os-lib do something different that Java's Path iterator, as it returns only Strings, not relative paths that can be made absolute.RootPath
class to exposeapply
accepting a drive letter. It could be the type ofos.root
, so people could do eitheros.root / ...
oros.root("C:") / ...
. We could also addos.drive("C:")
or something like that, and leave theos.root
defaulting to the system drive.The text was updated successfully, but these errors were encountered: