in Sharpmake/PathUtil.cs [352:463]
public static unsafe string PathGetRelative(string relativeTo, string path, bool ignoreCase = false)
{
// ------------------ THIS IS NOT CORRECT ----------------
// Force to always ignore case, whatever the user ask
// This keep the legacy Sharpmake behavior. It may be fixed at a later date to reduce modification scope.
ignoreCase = true;
// ------------------ THIS IS NOT CORRECT ----------------
relativeTo = SimplifyPath(relativeTo);
path = SimplifyPath(path);
// Check different root
var relativeToLength = relativeTo.Length;
var pathLength = path.Length;
if (relativeToLength == 0 || pathLength == 0 || !IsCharEqual(relativeTo[0], path[0], ignoreCase))
return path;
// Compute common part length
var lastCommonDirSepPosition = 0;
var commonPartLength = 0;
while (commonPartLength < relativeToLength && commonPartLength < pathLength && IsCharEqual(relativeTo[commonPartLength], path[commonPartLength], ignoreCase))
{
if (relativeTo[commonPartLength] == Path.DirectorySeparatorChar)
lastCommonDirSepPosition = commonPartLength;
++commonPartLength;
}
#if NET7_0_OR_GREATER
[Obsolete("Directly use 'char.IsAsciiLetter()' in 'IsCharEqual()' bellow (char.IsAsciiLetter() is available starting net7)")]
#endif
static bool IsAsciiLetter(char c) => (uint)((c | 0x20) - 'a') <= 'z' - 'a';
static bool IsCharEqual(char a, char b, bool ignoreCase) => a == b || (ignoreCase && (a | 0x20) == (b | 0x20) && IsAsciiLetter(a));
// Check if both paths are the same (ignoring the last directory separator if any)
if ((relativeToLength == commonPartLength && pathLength == commonPartLength)
|| (relativeToLength == commonPartLength && pathLength == commonPartLength + 1 && path[commonPartLength] == Path.DirectorySeparatorChar)
|| (pathLength == commonPartLength && relativeToLength == commonPartLength + 1 && relativeTo[commonPartLength] == Path.DirectorySeparatorChar))
{
return ".";
}
// Adjust 'commonPartLength' in case we stopped the comparison in the middle of an entry name:
// -> we went too far, we must move back 'commonPartLength' to the 'lastCommonDirSepPosition' position '+ 1'
// - /abc_def and /abc_xyz
// - /abc_ and /abc
if (commonPartLength < relativeToLength && commonPartLength < pathLength
|| (relativeToLength == commonPartLength && path[commonPartLength] != Path.DirectorySeparatorChar)
|| (pathLength == commonPartLength && relativeTo[commonPartLength] != Path.DirectorySeparatorChar))
{
commonPartLength = lastCommonDirSepPosition + 1;
}
// Compute the number of ".." to add (to get out of the 'relativeTo' path)
var dotDotCount = 0;
var relativeToLengthWithoutTrailingDirSep = relativeTo[^1] == Path.DirectorySeparatorChar ? relativeToLength - 1 : relativeToLength;
if (relativeToLengthWithoutTrailingDirSep > commonPartLength)
{
dotDotCount = 1;
for (int i = commonPartLength + 1; i < relativeToLengthWithoutTrailingDirSep; i++)
{
if (relativeTo[i] == Path.DirectorySeparatorChar)
++dotDotCount;
}
}
// Compute the length of the two parts to write
// - The sequences that looks like this: ".." or "../.." or ...
var dotDotCountSequenceLength = dotDotCount == 0 ? 0 : dotDotCount * 2 + dotDotCount - 1;
// - The remaining from the 'path' not yet added (skip the starting directory separator if any)
var remainingStartPosition = commonPartLength < pathLength && path[commonPartLength] == Path.DirectorySeparatorChar ? commonPartLength + 1 : commonPartLength;
var remainingLength = pathLength - remainingStartPosition;
// This 'if' deviate from the standard .net behavior and is here to keep legacy Sharpmake behavior
// Trim last directory separator if any
if (remainingLength > 0 && path[^1] == Path.DirectorySeparatorChar)
--remainingLength;
// - The directory separator between the two parts (in case there is both dotDots and remains)
var needToInsertDirSepBeforeRemainingPart = dotDotCountSequenceLength > 0 && remainingLength > 0;
var finalLength =
dotDotCountSequenceLength
+ remainingLength
+ (needToInsertDirSepBeforeRemainingPart ? 1 : 0);
// Allocate and write in the buffer
Span<char> arrayPtr = stackalloc char[finalLength];
int writePosition = 0;
if (dotDotCount > 0)
{
arrayPtr[writePosition++] = '.';
arrayPtr[writePosition++] = '.';
for (int i = 0; i < dotDotCount - 1; i++)
{
arrayPtr[writePosition++] = Path.DirectorySeparatorChar;
arrayPtr[writePosition++] = '.';
arrayPtr[writePosition++] = '.';
}
}
if (needToInsertDirSepBeforeRemainingPart)
arrayPtr[writePosition++] = Path.DirectorySeparatorChar;
if (remainingLength > 0)
{
var remainAsSpan = path.AsSpan(remainingStartPosition, remainingLength);
remainAsSpan.CopyTo(arrayPtr.Slice(writePosition, remainingLength));
}
return new string(arrayPtr);
}