|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.DotNet.MSBuildSdkResolver;
// Note: This is SemVer 2.0.0 https://semver.org/spec/v2.0.0.html
// See the original version of this code here: https://github.com/dotnet/core-setup/blob/master/src/corehost/cli/fxr/fx_ver.cpp
internal sealed class FXVersion(int major, int minor, int patch, string pre = "", string build = "")
{
public int Major { get; } = major;
public int Minor { get; } = minor;
public int Patch { get; } = patch;
public string Pre { get; } = pre;
public string Build { get; } = build;
private static string GetId(string ids, int idStart)
{
int next = ids.IndexOf('.', idStart);
return next == -1 ? ids.Substring(idStart) : ids.Substring(idStart, next - idStart);
}
public static int Compare(FXVersion s1, FXVersion s2)
{
// compare(u.v.w-p+b, x.y.z-q+c)
if (s1.Major != s2.Major)
{
return s1.Major > s2.Major ? 1 : -1;
}
if (s1.Minor != s2.Minor)
{
return s1.Minor > s2.Minor ? 1 : -1;
}
if (s1.Patch != s2.Patch)
{
return s1.Patch > s2.Patch ? 1 : -1;
}
if (string.IsNullOrEmpty(s1.Pre) || string.IsNullOrEmpty(s2.Pre))
{
// Empty (release) is higher precedence than prerelease
return string.IsNullOrEmpty(s1.Pre) ? (string.IsNullOrEmpty(s2.Pre) ? 0 : 1) : -1;
}
// Both are non-empty (may be equal)
// First character of pre is '-' when it is not empty
// First identifier starts at position 1
int idStart = 1;
for (int i = idStart; true; ++i)
{
// C# strings are not null terminated. Pretend to make code similar to fx_ver.cpp
char s1char = (s1.Pre.Length == i) ? '\0' : s1.Pre[i];
char s2char = (s2.Pre.Length == i) ? '\0' : s2.Pre[i];
if (s1char != s2char)
{
// Found first character with a difference
if (s1char == '\0' && s2char == '.')
{
// identifiers both complete, b has an additional identifier
return -1;
}
if (s2char == '\0' && s1char == '.')
{
// identifiers both complete, a has an additional identifier
return 1;
}
// identifiers must not be empty
string id1 = GetId(s1.Pre, idStart);
string id2 = GetId(s2.Pre, idStart);
int id1num;
bool id1IsNum = int.TryParse(id1, out id1num);
int id2num;
bool id2IsNum = int.TryParse(id2, out id2num);
if (id1IsNum && id2IsNum)
{
// Numeric comparison
return (id1num > id2num) ? 1 : -1;
}
else if (id1IsNum || id2IsNum)
{
// Mixed compare. Spec: Number < Text
return id2IsNum ? 1 : -1;
}
// Ascii compare
// Since we are using only ascii characters, unicode ordinal sort == ascii sort
return (s1char > s2char) ? 1 : -1;
}
else
{
// s1char == s2char
if (s1char == '\0')
{
break;
}
if (s1char == '.')
{
idStart = i + 1;
}
}
}
return 0;
}
private static bool ValidIdentifierCharSet(string id)
{
// ids must be of the set [0-9a-zA-Z-]
// ASCII and Unicode ordering
for (int i = 0; i < id.Length; ++i)
{
if (id[i] >= 'A')
{
if ((id[i] > 'Z' && id[i] < 'a') || id[i] > 'z')
{
return false;
}
}
else
{
if ((id[i] < '0' && id[i] != '-') || id[i] > '9')
{
return false;
}
}
}
return true;
}
private static bool ValidIdentifier(string id, bool buildMeta)
{
if (string.IsNullOrEmpty(id))
{
// Identifier must not be empty
return false;
}
if (!ValidIdentifierCharSet(id))
{
// ids must be of the set [0-9a-zA-Z-]
return false;
}
if (!buildMeta && id[0] == '0' && id.Length > 1 && int.TryParse(id, out _))
{
// numeric identifiers must not be padded with 0s
return false;
}
return true;
}
private static bool ValidIdentifiers(string ids)
{
if (string.IsNullOrEmpty(ids))
{
return true;
}
bool prerelease = ids[0] == '-';
bool buildMeta = ids[0] == '+';
if (!(prerelease || buildMeta))
{
// ids must start with '-' or '+' for prerelease & build respectively
return false;
}
int idStart = 1;
int nextId;
while ((nextId = ids.IndexOf('.', idStart)) != -1)
{
if (!ValidIdentifier(ids.Substring(idStart, nextId - idStart), buildMeta))
{
return false;
}
idStart = nextId + 1;
}
if (!ValidIdentifier(ids.Substring(idStart), buildMeta))
{
return false;
}
return true;
}
private static int IndexOfNonNumeric(string s, int startIndex)
{
for (int i = startIndex; i < s.Length; ++i)
{
if ((s[i] < '0') || (s[i] > '9'))
{
return i;
}
}
return -1;
}
public static bool TryParse(string? fxVersionString, out FXVersion? FXVersion)
{
FXVersion = null;
if (string.IsNullOrEmpty(fxVersionString) || fxVersionString == null)
{
return false;
}
int majorSeparator = fxVersionString.IndexOf(".", StringComparison.Ordinal);
if (majorSeparator == -1)
{
return false;
}
int major;
if (!int.TryParse(fxVersionString.Substring(0, majorSeparator), out major))
{
return false;
}
if (majorSeparator > 1 && fxVersionString[0] == '0')
{
// if leading character is 0, and strlen > 1
// then the numeric substring has leading zeroes which is prohibited by the specification.
return false;
}
int minorStart = majorSeparator + 1;
int minorSeparator = fxVersionString.IndexOf(".", minorStart, StringComparison.Ordinal);
if (minorSeparator == -1)
{
return false;
}
int minor;
if (!int.TryParse(fxVersionString.Substring(minorStart, minorSeparator - minorStart), out minor))
{
return false;
}
if (minorSeparator - minorStart > 1 && fxVersionString[minorStart] == '0')
{
// if leading character is 0, and strlen > 1
// then the numeric substring has leading zeroes which is prohibited by the specification.
return false;
}
int patchStart = minorSeparator + 1;
int patchSeparator = IndexOfNonNumeric(fxVersionString, patchStart);
int patch;
if (patchSeparator == -1)
{
if (!int.TryParse(fxVersionString.Substring(patchStart), out patch))
{
return false;
}
if (patchStart + 1 < fxVersionString.Length && fxVersionString[patchStart] == '0')
{
// if leading character is 0, and strlen != 1
// then the numeric substring has leading zeroes which is prohibited by the specification.
return false;
}
FXVersion = new FXVersion(major, minor, patch);
return true;
}
if (!int.TryParse(fxVersionString.Substring(patchStart, patchSeparator - patchStart), out patch))
{
return false;
}
if (patchSeparator - patchStart > 1 && fxVersionString[patchStart] == '0')
{
return false;
}
int preStart = patchSeparator;
int preSeparator = fxVersionString.IndexOf("+", preStart, StringComparison.Ordinal);
string pre = (preSeparator == -1) ? fxVersionString.Substring(preStart) : fxVersionString.Substring(preStart, preSeparator - preStart);
if (!ValidIdentifiers(pre))
{
return false;
}
string build = "";
if (preSeparator != -1)
{
build = fxVersionString.Substring(preSeparator);
if (!ValidIdentifiers(build))
{
return false;
}
}
FXVersion = new FXVersion(major, minor, patch, pre, build);
return true;
}
public override string ToString()
=> (!string.IsNullOrEmpty(Pre), !string.IsNullOrEmpty(Build)) switch
{
(false, false) => $"{Major}.{Minor}.{Patch}",
(true, false) => $"{Major}.{Minor}.{Patch}{Pre}",
(false, true) => $"{Major}.{Minor}.{Patch}{Build}",
(true, true) => $"{Major}.{Minor}.{Patch}{Pre}{Build}",
};
}
|