Today have been playing with Process from Foundation and I noticed a strange behaviour when changing the currentDirectoryURL value.
I created a small playground that reproduces this issue
import Foundation
FileManager.default.changeCurrentDirectoryPath("/Users")
let directoryURL = URL(fileURLWithPath: "../usr")
let process = Process()
process.currentDirectoryURL = directoryURL
print(directoryURL.path)
print(process.currentDirectoryURL?.path)
The output:
/usr
Optional("/Users/usr")
I don't really understand why process.currentDirectoryURL?.path is /Users/usr, it's like the ../ from ../usr was ignored.
The currentDirectoryURL property is a relatively recent addition. Historically, NSTask (and hence Process) only supported currentDirectoryPath.
The new currentDirectoryURL property is a wrapper around the old currentDirectoryPath property. Internally the setter converts the URL to a path and the getter does the reverse.
The conversion process in the setter uses url.standardizedURL.path.
The standardizedURL property is documented to return A copy of the URL with any instances of ".." or "." removed from its path. The tricky part here is that it looks just at the URL’s path. If the URL is relative, it ignores the path in the base URL.
I admit the behavior mismatch between macOS and Linux is what bothers me the most. Especially knowing the underlying code is pure Swift. There shouldn't be any problem/
thx for the good explanation. But as @IceIceIce explains there is a strange behavior. When I set a URL that is relative it should return the same url when I get it back.
I maybe found a solution to be fixed in Process.swift. Right there the path property of URL is used which doesn't work when the path was a relative one, as you described very well.
What if we use the relativePath property here since it preserves the format of the path (../usr/):
@blackjacx I think there must be a reason why the Process class uses absolute paths. But still, this doesn't explain why the path is ill-formed in both cases.
I just did a test on both macOS and Linux, using Swift 5.2.4:
In macOS, executing the REPL in /Users:
Welcome to Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53).
Type :help for assistance.
1> import Foundation
2> let url = URL(fileURLWithPath: "../bin")
url: URL = {}
3> print(url)
../bin/ -- file:///Users/
4> print(url.standardized.path)
/Users/bin
And Linux, executing in /home:
1> import Foundation
2> let url = URL(fileURLWithPath: "../bin")
[...]
3> print(url)
../usr/bin/ -- file:///home/
4> print(url.standardized.path)
/usr/bin
Which is confusing, why the hell is there a /usr added in my Linux code, why is the behavior inconsistent?
The difference you're seeing between macOS and Linux is due to the fact that Foundation on Darwin platforms is not the same as swift-corelibs-foundation, which is the pure Swift reimplementation of Foundation for non-Darwin platforms. (i.e., swift-corelibs-foundation's Process.currentDirectoryURL is not the code you're calling on macOS)
The goal of swift-corelibs-foundation is to match the behavior of Darwin's Foundation as closely as possible, but inconsistencies like this sometimes slip through; I think this is worthy of a bug report so either the native currentDirectoryURL behavior is reconsidered, or swift-corelibs-foundation behavior is changed to match.
@itaiferber I agree about the bug report. Diving a little further into the code base shows the Process class is unrelated to the problem. We can trace it back to NSURL, giving an inconsistent behavior for the following:
import Foundation
let nsurl = NSURL(fileURLWithPath: "../bin")
print(nsurl)
print(nsurl.standardized?.path)
Giving different outputs on both macOS and Linux anywhere "../bin" references a valid location
That's true. NSURL parsing is extremely complex, and unlikely to change for myriad backwards-compatibility reasons. It may be easier to account for this within Process itself (in some cases) to provide better defaults.