-
Notifications
You must be signed in to change notification settings - Fork 126
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
Improve path normalization and judgment on Windows #191
Conversation
ec0cb88
to
8fe451b
Compare
4d1b06a
to
7b892e5
Compare
@swift-ci test |
@compnerd, this seems reasonable to me |
c21674a
to
4aff4de
Compare
#else | ||
pathSeparator = "/" | ||
#endif | ||
precondition(path.first != pathSeparator) |
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.
This precondition doesn't hold. \path
is valid as a relative path. It is relative to the current drive.
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.
Although it is literally "relative", Windows Shell (and Swift which depends on it) regards \path
\?\path
\\share\path
as absolute paths (according to the response of PathIsRelativeW
). Personally I think these paths should be treated the same with a regular absolute path. For example, \path\..\..
indicates \
like C:\path\..\..
indicates C:\
, but path\..\..
indicates ..
.
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.
UNC paths are always absolute. It is just a drive relative path which is not absolute. That distinction is important as it impacts the lexical traversal of the path string.
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.
PathIsRelativeW
regards \path
as an absolute path. Also, if we treat it as relative, behavior regarding ..
will be unaligned.
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.
Ping @compnerd
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 still think that the differences are material and need to be addressed. Paths in Windows do not behave like Unix, and so you will have differences. e.g.
path1 = "C:\Users\compnerd"
path2 = "D:"
path1 + path2 is not `C:\Users\compnerd\D:" but rather "C:\Windows\System32\WindowsMetadata"
because the current directory on D: happened to be "....\Windows\System32\WindowsMetadata"
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 look into more critical cases here.
However, if I regard \path
as a relative path, it will change the behavior of a bunch of APIs (while this PR focuses on bug fixes), so that change definitely needs to be addressed in a separate PR.
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.
IMO I believe this PR is fully capable of fixing faulty behavior without changing the current judgement of relative paths and absolute paths. To fully support Windows path system, we’d better work on another one which may contain a complete refactoring.
What this PR mainly addressed and fixed is the problem of ..
being ignored, and of crashing on an empty path, which are critical bugs for tools like SwiftPM.
#else | ||
pathSeparator = "/" | ||
#endif | ||
precondition(path.first != pathSeparator) |
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.
UNC paths are always absolute. It is just a drive relative path which is not absolute. That distinction is important as it impacts the lexical traversal of the path string.
98853de
to
9aef445
Compare
#else | ||
pathSeparator = "/" | ||
#endif | ||
precondition(path.first != pathSeparator) |
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 still think that the differences are material and need to be addressed. Paths in Windows do not behave like Unix, and so you will have differences. e.g.
path1 = "C:\Users\compnerd"
path2 = "D:"
path1 + path2 is not `C:\Users\compnerd\D:" but rather "C:\Windows\System32\WindowsMetadata"
because the current directory on D: happened to be "....\Windows\System32\WindowsMetadata"
If possible, I’d like to completely refactor |
@stevapple we'd love to see PRs to that end, its definitely the right direction |
CC @compnerd Regardless of any future improvements, the bug fixed in this PR is critical for correcting SwiftPM behavior on Swift 5.4 and later. I’d like to see it merged into |
I think that this change is way too far for the 5.4 release. I think that it is important to address the issues with the model though. |
I’d expect Shipping a SwiftPM which cannot resolve |
I propose a phased approach: First, please work with @compnerd to get this PR to a mergeable state. Later, we should start landing PRs that refactor TSC to use SwiftSystem.
its too late for 5.4.0, but once we merge this PR into main, we should open a PR against the 5.4 branch so that if/when we roll a 5.4.1 dot release we can include it |
Any future directions? @compnerd |
I don't think that all the previous comments on the change have been addressed yet. |
The current implementation of I would address again that this PR is only meant to correct the behavior for regular and critical cases. This module needs a complete rewrite if you expect it to fully support Windows paths. This is just a capable bug fix — and it's enough to make Swift tools robuster. |
I'm not sure that this is the right way to handle that. It just behaves differently after this change. That doesn't make it more robust. I agree with you that this is in need of a rewrite - and really, this seems like the ideal case and time to migrate the path handling to Swift System. The 5.5 release branch is already in slushy freeze, so we have plenty of time for the next release to get the implementation migrated and corrected. |
I would like to know which cases this PR breaks... I think we'd better get a workaround for 5.4, and then get to rewrite it with Swift System for the 5.5 release. |
It already is too late for all of those. Such a large change for 5.4 is entirely out of the question - it is already finalized. I think that such a change for 5.5 is unreasonable. It is too far in the cycle to make that type of change. This would be best slated for the next release for which we have plenty of time to do by means of replacing the path handling with Swift System. |
Any critical bug fix should be allowed. I don’t see this PR introducing new behavior AFAK (as for the result) and the bugs of old implementation are leading to unexpected crashes and extremely strange behavior. |
Why I regard it as critical — Local debugging relies largely on relative paths, and for now we’re not even able to set the path of a dependency package to Since we’re able to fix it, why not? Letting the bug alone with 5.4 while there’re still plenty of |
import WinSDK
let path = CommandLine.argc > 1 ? CommandLine.arguments[1] : "..\\swift-tools-support-core"
print("user input: " + path)
var buffer: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH + 1))
_ = path.withCString(encodedAs: UTF16.self) { PathCanonicalizeW(&buffer, $0) }
print("normalized (current): " + String(decodingCString: buffer, as: UTF16.self))
var parts: [String] = []
var capacity = 0
for part in path.split(separator: "\\") {
switch part.count {
case 0: continue
case 1 where part.first == ".": continue
case 2 where part.first == "." && part.last == ".":
guard let prev = parts.last else { fallthrough }
if !(prev.count == 2 && prev.first == "." && prev.last == ".") {
parts.removeLast()
capacity -= prev.count
continue
}
fallthrough
default:
parts.append(String(part))
capacity += part.count
}
}
capacity += max(parts.count - 1, 0)
var result = ""
result.reserveCapacity(capacity)
var iter = parts.makeIterator()
if let first = iter.next() {
result.append(contentsOf: first)
while let next = iter.next() {
result.append("\\")
result.append(contentsOf: next)
}
}
print("normalized (updated): " + (result.isEmpty ? "." : result)) Here is a standalone script to demonstrate the behavioral changes on Windows before and after this patch. I'd treat this as a huge and urgent improvement. The current one is so buggy that it needs to be replaced ASAP on all the active branches. C:\Users\stevapple>normalize.exe
user input: ..\swift-tools-support-core
normalized (current): swift-tools-support-core
normalized (updated): ..\swift-tools-support-core
C:\Users\stevapple>normalize.exe ..\hello\hello\..\my-package
user input: ..\hello\hello\..\my-package
normalized (current): hello\my-package
normalized (updated): ..\hello\my-package
C:\Users\stevapple>normalize.exe hello\..\..\hello2\..\my-package
user input: hello\..\..\hello2\..\my-package
normalized (current): \my-package
normalized (updated): ..\my-package
C:\Users\stevapple>normalize.exe .\hello\..\.\hello
user input: .\hello\..\.\hello
normalized (current): \hello
normalized (updated): hello CC: @compnerd @abertelrud |
I strongly disagree with this statement and see this as purely hyperbole, which really does not add much to the discussion. I definitely am using SPM actively both for local and CI purposes, it definitely works fine for local debugging. (I would note that it is not just personal experience, others have reported using SPM on Windows on the Forums, and I've run into other cases on GitHub where packages had started using it).
That doesn't change the fact that the handling does not work for a variety of paths on Windows. This is a very large change that fixes a very particular case (which would require you to go outside of the source tree, which is really not how SPM normally operates). Thus, this is trying to fix a corner case for a corner case. I think that the efforts are better spent on porting to System. Alternatively, I would be fine with temporarily unifying the paths with Foundation. |
Succeeded by #219 |
Closing as it's superseded by #219 |
..
being ignored during normalizing;