using System.Diagnostics;
namespace Microsoft.NETCore.Platforms.BuildTasks
public sealed class RuntimeVersion : IComparable, IComparable<RuntimeVersion>, IEquatable<RuntimeVersion>
private readonly string versionString;
private readonly Version version;
private readonly bool hasMinor;
public RuntimeVersion(string versionString)
this.versionString = versionString;
string toParse = versionString;
if (!toParse.Contains('.'))
if (toParse.IndexOf('.') == -1)
version = Version.Parse(toParse);
public int CompareTo(object obj)
if (obj is RuntimeVersion version)
return CompareTo(version);
throw new ArgumentException($"Cannot compare {nameof(RuntimeVersion)} to object of type {obj.GetType()}.", nameof(obj));
public int CompareTo(RuntimeVersion other)
int versionResult = version.CompareTo(other?.version);
if (!hasMinor && other.hasMinor)
if (hasMinor && !other.hasMinor)
return string.CompareOrdinal(versionString, other.versionString);
public bool Equals(RuntimeVersion other)
return object.ReferenceEquals(other, this) ||
versionString.Equals(other.versionString, StringComparison.Ordinal));
public override bool Equals(object obj)
return Equals(obj as RuntimeVersion);
public override int GetHashCode()
return versionString.GetHashCode();
public override string ToString()
public static bool operator ==(RuntimeVersion v1, RuntimeVersion v2)
return (v1 is null) ? true : false;
return ReferenceEquals(v2, v1) ? true : v2.Equals(v1);
public static bool operator !=(RuntimeVersion v1, RuntimeVersion v2) => !(v1 == v2);
public static bool operator <(RuntimeVersion v1, RuntimeVersion v2)
return v1.CompareTo(v2) < 0;
public static bool operator <=(RuntimeVersion v1, RuntimeVersion v2)
return v1.CompareTo(v2) <= 0;
public static bool operator >(RuntimeVersion v1, RuntimeVersion v2) => v2 < v1;
public static bool operator >=(RuntimeVersion v1, RuntimeVersion v2) => v2 <= v1;
internal const char VersionDelimiter = '.';
internal const char ArchitectureDelimiter = '-';
internal const char QualifierDelimiter = '-';
public string BaseRID { get; set; }
public bool OmitVersionDelimiter { get; set; }
public RuntimeVersion Version { get; set; }
public string Architecture { get; set; }
public string Qualifier { get; set; }
public override string ToString()
StringBuilder builder = new StringBuilder(BaseRID);
if (!OmitVersionDelimiter)
builder.Append(VersionDelimiter);
builder.Append(ArchitectureDelimiter);
builder.Append(Architecture);
builder.Append(QualifierDelimiter);
builder.Append(Qualifier);
return builder.ToString();
private enum RIDPart : int
public static RID Parse(string runtimeIdentifier)
string[] parts = new string[(int)RIDPart.Max + 1];
bool omitVersionDelimiter = true;
RIDPart parseState = RIDPart.Base;
int partStart = 0, partLength;
Debug.Assert(ArchitectureDelimiter == QualifierDelimiter);
for (int i = 0; i < runtimeIdentifier.Length; i++)
char current = runtimeIdentifier[i];
partLength = i - partStart;
if (current == VersionDelimiter || (current >= '0' && current <= '9'))
if (current == VersionDelimiter)
omitVersionDelimiter = false;
parseState = RIDPart.Version;
else if (current == ArchitectureDelimiter)
if (runtimeIdentifier.IndexOf(VersionDelimiter, i) != -1)
parseState = RIDPart.Architecture;
if (current == ArchitectureDelimiter)
parseState = RIDPart.Architecture;
case RIDPart.Architecture:
if (current == QualifierDelimiter)
parseState = RIDPart.Qualifier;
partLength = runtimeIdentifier.Length - partStart;
string GetPart(RIDPart part)
throw new ArgumentException($"Unexpected delimiter at position {partStart} in \"{runtimeIdentifier}\"");
parts[(int)parseState] = runtimeIdentifier.Substring(partStart, partLength);
string version = GetPart(RIDPart.Version);
omitVersionDelimiter = false;
BaseRID = GetPart(RIDPart.Base),
OmitVersionDelimiter = omitVersionDelimiter,
Version = version == null ? null : new RuntimeVersion(version),
Architecture = GetPart(RIDPart.Architecture),
Qualifier = GetPart(RIDPart.Qualifier)
public bool HasVersion => Version != null;
public bool HasArchitecture => Architecture != null;
public bool HasQualifier => Qualifier != null;
public override bool Equals(object obj)
return Equals(obj as RID);
public bool Equals(RID obj)
return object.ReferenceEquals(obj, this) ||
BaseRID == obj.BaseRID &&
(Version == null || OmitVersionDelimiter == obj.OmitVersionDelimiter) &&
Version == obj.Version &&
Architecture == obj.Architecture &&
Qualifier == obj.Qualifier);
public override int GetHashCode()
return BaseRID.GetHashCode();
HashCode hashCode = default;
hashCode.Add(Architecture);
return hashCode.ToHashCode();
public static void Main()
RID rid1 = RID.Parse("linux-musl-x64");
Console.WriteLine("RID: " + rid1);
Console.WriteLine("Version: " + rid1.Version);
Console.WriteLine("BaseRID: " + rid1.BaseRID);
Console.WriteLine("Qualifier: " + rid1.Qualifier);
Console.WriteLine("Arch: " + rid1.Architecture);
Console.WriteLine("-----");
RID rid2 = RID.Parse("linux-x64");
Console.WriteLine("RID: " + rid2);
Console.WriteLine("Version: " + rid2.Version);
Console.WriteLine("BaseRID: " + rid2.BaseRID);
Console.WriteLine("Qualifier: " + rid2.Qualifier);
Console.WriteLine("Arch: " + rid2.Architecture);
Console.WriteLine("-----");
RID rid3 = RID.Parse("alpine.3.17-x64");
Console.WriteLine("RID: " + rid3);
Console.WriteLine("Version: " + rid3.Version);
Console.WriteLine("BaseRID: " + rid3.BaseRID);
Console.WriteLine("Qualifier: " + rid3.Qualifier);
Console.WriteLine("Arch: " + rid3.Architecture);
Console.WriteLine("-----");