-
-
Notifications
You must be signed in to change notification settings - Fork 73
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
fix for issue-201 #202
fix for issue-201 #202
Conversation
Subsequent commit fixes compile errors for scala 2 versions. |
Just noticed that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I have not completely made up my mind about this issue and your approach to handle it. Nevertheless, thank you for this PR. I already have some review comments to share.
The issue involves file system roots, a topic we have a dedicated issue for (#170) and also a PR (#196). We should aim for a consistent solution for both issues.
Sounds good. I will try to see how this PR can be made compatible with (#196). I assume we want to be compatible with Java's |
verify compatibile with (#196); combine conditionals, remove unused code
I edited in the changes in (#196) and verified no conflicts, so the two PRs should merge without any issues. They appear to be mostly orthogonal concerns. Afterwards, I removed the other PR code and did more testing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the longish review. I'm still making up my mind about this issue. It's really hard to reason about the specifics of an OS I don't use.
Oh, and please avoid squashing while we are in the review process. It hinders to follow the incremental process. We will squash when we merge. |
Makes sense. |
After some thought, a better name than |
I have been verifying that this PR fixes the |
…ive tooManyUps exception
It turns out that the tests in I enabled all I just noticed an incorrect comment, I'll fix it. |
I manually verified that this fixes the scala-cli bug VirtusLab/scala-cli#2359 |
os/test/src/PathTests.scala
Outdated
implicit class ExtendString(s: String) { | ||
def posix: String = s.replace('\\', '/') | ||
def norm: String = if (s.startsWith(platformPrefix)) { | ||
s.posix // already has platformPrefix | ||
} else { | ||
s"$platformPrefix${s.posix}" | ||
} | ||
} | ||
implicit class ExtendOsPath(p: os.Path) { | ||
def posix: String = p.toNIO.toString.posix | ||
def norm: String = p.toString.norm | ||
} | ||
implicit class ExtendPath(p: java.nio.file.Path) { | ||
def posix: String = p.toString.posix | ||
def norm: String = p.toString.norm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use normal helper methods here, rather than extension methods, unless we have good reason to do otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done (will push changes soon)
os/test/src/PathTests.scala
Outdated
import utest.{assert => _, _} | ||
import java.net.URI | ||
object PathTests extends TestSuite { | ||
def enableTest = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If enableTest
is true
, we should be able to remove it and remove all the conditionals guarding the test cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove them.
os/src/Path.scala
Outdated
val normalized = { | ||
val f = if (rootRelative(f0)) { | ||
Paths.get(s"$platformPrefix$f0") | ||
} else { | ||
implicitly[PathConvertible[T]].apply(f0) | ||
} | ||
if (f.iterator.asScala.count(_.startsWith("..")) > f.getNameCount / 2) { | ||
throw PathError.AbsolutePathOutsideRoot | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can re-arrange this to move the throw
outside of the val normalized
? All these are method-local val
s anyway, so there's no visibility/privacy concerns, and it would be nice to remove a layer of nesting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
Ieft a few comments. I am also not super familiar with windows, but if the old tests continue passing and the new tests pass then that gives us some confidence. @philwalk could you update the PR description to:
After that, I think I'd be OK with merging this, unless @lefou has any other concerns |
os/src/Path.scala
Outdated
*/ | ||
def rootRelative[T: PathConvertible](f0: T): Boolean = rootRelative(f0.toString) | ||
|
||
def rootRelative(s: String): Boolean = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this overload, given that the implicit object StringConvertible extends PathConvertible[String]
already exists? If not we should remove it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Combined the two:
def rootRelative[T: PathConvertible](f0: T): Boolean = {
if (platformPrefix.isEmpty) {
false // non-Windows os
} else {
f0.toString.take(1) match {
case "\\" | "/" => true
case _ => false
}
}
}
BTW, some tests that pass on X test.os.FilesystemMetadataTests.isFile.0 17ms
X test.os.FilesystemMetadataTests.isDir.0 18ms
X test.os.FilesystemMetadataTests.isLink.0 18ms
X test.os.FilesystemMetadataTests.mtime.0 18ms
X test.os.ListingWalkingTests.list.0 15ms
X test.os.ListingWalkingTests.walk.0 19ms
X test.os.ManipulatingFilesFoldersTests.exists.0 27ms
X test.os.ManipulatingFilesFoldersTests.followLink.0 17ms If I'm not mistaken, these rely on finding the following in the TestUtil.isInstalled("curl")
TestUtil.isInstalled("gzip")
TestUtil.isInstalled("shasum")
TestUtil.isInstalled("echo")
TestUtil.isInstalled("python") All of these are in my PATH, although I haven't tracked down the exact cause of the failures. |
os/src/Path.scala
Outdated
* @return current working drive if Windows, empty string elsewhere. | ||
* Paths.get(platformPrefix) == current working directory on all platforms. | ||
*/ | ||
lazy val platformPrefix: String = Paths.get(".").toAbsolutePath.getRoot.toString match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I'm fine with this PR, but I'm still struggling with the term platformPrefix
. Hhow about rootPrefix
or maybe also something that has the "drive" in it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like driveRoot
instead of platformPrefix
and driveRelative
instead of rootRelative
.
I merged #196 with this PR as an experiment, and all tests pass. |
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 onWindows
versus other platforms.Example code:
Output on Linux or OSX:
isAbsolute: true /omg /omg
On Windows:
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 thecurrent working directory
.Because all platforms, including
Windows
, support absolute paths andposix relative
paths, it's convenient when writing platform-independent code to viewdriveRelative
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, adriveRelative
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 independentThis PR enables
Unix()
tests inos/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(...)
oros.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 withjava.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 onWindows
and an empty String elsewhere.With this approach, the following assertion would be valid on all platforms:
Then
driveRoot
in this PR would becomepwd.rootPrefix
.There is another (perhaps never used?) very subtle feature of
Windows
filesystem: each drive has a different value forpwd
. It may not be necessary to model this inos-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 byjava.nio.file.Paths.get()
, some of which might be surprising.Relevant information: My system drives are C: and F:, but not J:
First, some unsurprising results:
Now some less obvious results:
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
I can set the
working drive
for F: in aCMD
session before I start up thejvm
, and it does affect the output ofPaths.get()
.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 driveworking directory
.