Change relative path calculation#6311
Conversation
|
I assume it's a PR from msbuild into the installer that's failing tests? Can you link it here? |
|
Based on some local testing, I currently believe it needs both this and #6301 to go in for it to work. (I also have a couple local changes.) |
benvillalobos
left a comment
There was a problem hiding this comment.
First pass review. Any idea what the perf impact is here?
There was a problem hiding this comment.
| return sb.ToString(); | |
| return StringBuilderCache.GetStringAndRelease(sb); |
There was a problem hiding this comment.
I was trying to think of that but didn't come up with it. Thanks!
There was a problem hiding this comment.
This seems suspicious to me, but I have no evidence off the top of my head 🤔
There was a problem hiding this comment.
Seeing if fullPath starts with the current working directory could be another way of determining this, though the current method is faster.
There was a problem hiding this comment.
One possible suspicious reason is the (bug) I just found for mac/linux: the path starts with /, which means this will always return false. Now fixed.
ladipro
left a comment
There was a problem hiding this comment.
Can you please add details on path patterns where the old code does not work properly? Thank you!
There was a problem hiding this comment.
Calling Split with StringSplitOptions.RemoveEmptyEntries should be a faster way of filtering out empty components.
There was a problem hiding this comment.
Had to make it char[] because it seems that Split(char, StringSplitOptions) isn't available on .NET Framework. I don't think ifdefs are worth it here. Still a lot nicer than what I had before; thanks!
There was a problem hiding this comment.
What guarantees that the string is not just slashes, meaning that the code won't crash with out of bounds access? The split above uses only the primary directory separator char on the current platform and will accept e.g. forward slashes on Windows.
d770c12 to
1d4fea6
Compare
|
The old code threw exceptions if the URI was deemed invalid for some reason. Two such cases that stood out to me: |
| string fullPath = Path.GetFullPath(path); | ||
|
|
||
| Uri baseUri = new Uri(EnsureTrailingSlash(basePath), UriKind.Absolute); // May throw UriFormatException | ||
| string[] splitBase = fullBase.Split(new char[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); |
There was a problem hiding this comment.
The char array is worth caching. And looks like we already do it in Constants.DirectorySeparatorChar!
| sb.Append(splitPath[pathI]).Append(Path.DirectorySeparatorChar); | ||
| pathI++; | ||
| } | ||
| sb.Remove(sb.Length - 1, 1); |
There was a problem hiding this comment.
nit: I would expect
| sb.Remove(sb.Length - 1, 1); | |
| sb.Length = sb.Length - 1; |
to be slightly faster and also easier to read.
| while (true) | ||
| { | ||
| if (baseI == splitBase.Length) | ||
| { | ||
| if (pathI == splitPath.Length) | ||
| { | ||
| return "."; | ||
| } | ||
| break; | ||
| } | ||
| else if (pathI == splitPath.Length) | ||
| { | ||
| break; | ||
| } | ||
| else if (splitBase[baseI].Equals(splitPath[pathI], PathComparison)) | ||
| { | ||
| baseI++; | ||
| pathI++; | ||
| } | ||
| else | ||
| { | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
I would find the following much easier to read. As long as it's equivalent, please double-check:
| while (true) | |
| { | |
| if (baseI == splitBase.Length) | |
| { | |
| if (pathI == splitPath.Length) | |
| { | |
| return "."; | |
| } | |
| break; | |
| } | |
| else if (pathI == splitPath.Length) | |
| { | |
| break; | |
| } | |
| else if (splitBase[baseI].Equals(splitPath[pathI], PathComparison)) | |
| { | |
| baseI++; | |
| pathI++; | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| } | |
| while (baseI < splitBase.Length && pathI < splitPath.Length && splitBase[baseI].Equals(splitPath[pathI], PathComparison)) | |
| { | |
| baseI++; | |
| pathI++; | |
| } | |
| if (baseI == splitBase.Length && pathI == splitPath.Length) | |
| { | |
| return "."; | |
| } | |
| } |
| int baseI = 0; | ||
| int pathI = 0; |
There was a problem hiding this comment.
nit (last one, promise!): Since the two integers are always incremented together, consider having only one and changing the while (baseI < ... and while (pathI < ... below to for loops. Totally optional, no perf impact, just a matter of subjective taste.
There was a problem hiding this comment.
Yep! This PR came out unusually janky, so I appreciate all the refactoring suggestions.
benvillalobos
left a comment
There was a problem hiding this comment.
I like where this PR ended up. I did some rough testing to see which was faster and it looks like this version is 👍
| { | ||
| return "."; | ||
| } | ||
| // If the paths have no component in common, the only valid relative path is the full path. |
There was a problem hiding this comment.
hyper-nit-9000: newline here.
|
Thanks @benvillalobos for the perf check! |
For some reason, the pre-caching change seems to fail in the installer repo at the build step because of a failing relative path calculation. This version seems to work.