diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/GenerateRuntimeGraph.cs b/src/libraries/Microsoft.NETCore.Platforms/src/GenerateRuntimeGraph.cs
index cbee78b71a6b06..1421200ead56ae 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/src/GenerateRuntimeGraph.cs
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/GenerateRuntimeGraph.cs
@@ -53,6 +53,24 @@ public ITaskItem[] RuntimeGroups
set;
}
+ ///
+ /// Additional runtime identifiers to add to the graph.
+ ///
+ public string[] AdditionalRuntimeIdentifiers
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Parent RID to use for any unknown AdditionalRuntimeIdentifer.
+ ///
+ public string AdditionalRuntimeIdentifierParent
+ {
+ get;
+ set;
+ }
+
///
/// Optional source Runtime.json to use as a starting point when merging additional RuntimeGroups
///
@@ -134,7 +152,11 @@ public override bool Execute()
runtimeGraph = new RuntimeGraph();
}
- foreach (var runtimeGroup in RuntimeGroups.NullAsEmpty().Select(i => new RuntimeGroup(i)))
+ List runtimeGroups = RuntimeGroups.NullAsEmpty().Select(i => new RuntimeGroup(i)).ToList();
+
+ AddRuntimeIdentifiers(runtimeGroups);
+
+ foreach (var runtimeGroup in runtimeGroups)
{
runtimeGraph = SafeMerge(runtimeGraph, runtimeGroup);
}
@@ -291,6 +313,21 @@ private void ValidateImports(RuntimeGraph runtimeGraph, IDictionary runtimeGroups)
+ {
+ if (AdditionalRuntimeIdentifiers == null || AdditionalRuntimeIdentifiers.Length == 0)
+ {
+ return;
+ }
+
+ RuntimeGroupCollection runtimeGroupCollection = new RuntimeGroupCollection(runtimeGroups);
+
+ foreach (string additionalRuntimeIdentifier in AdditionalRuntimeIdentifiers)
+ {
+ runtimeGroupCollection.AddRuntimeIdentifier(additionalRuntimeIdentifier, AdditionalRuntimeIdentifierParent);
+ }
+ }
+
private static IDictionary> GetCompatibilityMap(RuntimeGraph graph)
{
Dictionary> compatibilityMap = new Dictionary>();
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj b/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj
index 3c00187ddb18e1..6fa925cc2caeb9 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/Microsoft.NETCore.Platforms.csproj
@@ -1,4 +1,4 @@
-
+
$(NetCoreAppToolCurrent);net472
@@ -12,6 +12,9 @@
true
$(NoWarn);NU5128
Provides runtime information required to resolve target framework, platform, and runtime specific implementations of .NETCore packages.
+
+
+ $(AdditionalRuntimeIdentifiers);$(OutputRID)
@@ -24,11 +27,14 @@
+
+
-
+
+
@@ -38,5 +44,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs
index 357790fbfdd54d..6f08d1a510e567 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/RID.cs
@@ -1,37 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+using System.Diagnostics;
using System.Text;
namespace Microsoft.NETCore.Platforms.BuildTasks
{
- internal class RID
+ public class RID
{
+ internal const char VersionDelimiter = '.';
+ internal const char ArchitectureDelimiter = '-';
+ internal const char QualifierDelimiter = '-';
+
public string BaseRID { get; set; }
- public string VersionDelimiter { get; set; }
- public string Version { get; set; }
- public string ArchitectureDelimiter { get; set; }
+ public bool OmitVersionDelimiter { get; set; }
+ public RuntimeVersion Version { get; set; }
public string Architecture { get; set; }
- public string QualifierDelimiter { get; set; }
public string Qualifier { get; set; }
public override string ToString()
{
StringBuilder builder = new StringBuilder(BaseRID);
- if (HasVersion())
+ if (HasVersion)
{
- builder.Append(VersionDelimiter);
+ if (!OmitVersionDelimiter)
+ {
+ builder.Append(VersionDelimiter);
+ }
builder.Append(Version);
}
- if (HasArchitecture())
+ if (HasArchitecture)
{
builder.Append(ArchitectureDelimiter);
builder.Append(Architecture);
}
- if (HasQualifier())
+ if (HasQualifier)
{
builder.Append(QualifierDelimiter);
builder.Append(Qualifier);
@@ -40,20 +47,153 @@ public override string ToString()
return builder.ToString();
}
- public bool HasVersion()
+ private enum RIDPart : int
+ {
+ Base = 0,
+ Version,
+ Architecture,
+ Qualifier,
+ Max = Qualifier
+ }
+
+ 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 = 0;
+
+ // qualifier is indistinguishable from arch so we cannot distinguish it for parsing purposes
+ Debug.Assert(ArchitectureDelimiter == QualifierDelimiter);
+
+ for (int i = 0; i < runtimeIdentifier.Length; i++)
+ {
+ char current = runtimeIdentifier[i];
+ partLength = i - partStart;
+
+ switch (parseState)
+ {
+ case RIDPart.Base:
+ // treat any number as the start of the version
+ if (current == VersionDelimiter || (current >= '0' && current <= '9'))
+ {
+ SetPart();
+ partStart = i;
+ if (current == VersionDelimiter)
+ {
+ omitVersionDelimiter = false;
+ partStart = i + 1;
+ }
+ parseState = RIDPart.Version;
+ }
+ // version might be omitted
+ else if (current == ArchitectureDelimiter)
+ {
+ // ensure there's no version later in the string
+ if (runtimeIdentifier.IndexOf(VersionDelimiter, i) != -1)
+ {
+ break;
+ }
+ SetPart();
+ partStart = i + 1; // skip delimiter
+ parseState = RIDPart.Architecture;
+ }
+ break;
+ case RIDPart.Version:
+ if (current == ArchitectureDelimiter)
+ {
+ SetPart();
+ partStart = i + 1; // skip delimiter
+ parseState = RIDPart.Architecture;
+ }
+ break;
+ case RIDPart.Architecture:
+ if (current == QualifierDelimiter)
+ {
+ SetPart();
+ partStart = i + 1; // skip delimiter
+ parseState = RIDPart.Qualifier;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ partLength = runtimeIdentifier.Length - partStart;
+ if (partLength > 0)
+ {
+ SetPart();
+ }
+
+ string GetPart(RIDPart part)
+ {
+ return parts[(int)part];
+ }
+
+ void SetPart()
+ {
+ if (partLength == 0)
+ {
+ throw new ArgumentException($"Unexpected delimiter at position {partStart} in \"{runtimeIdentifier}\"");
+ }
+
+ parts[(int)parseState] = runtimeIdentifier.Substring(partStart, partLength);
+ }
+
+ string version = GetPart(RIDPart.Version);
+
+ if (version == null)
+ {
+ omitVersionDelimiter = false;
+ }
+
+ return new RID()
+ {
+ 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 Version != null;
+ return Equals(obj as RID);
}
- public bool HasArchitecture()
+ public bool Equals(RID obj)
{
- return Architecture != null;
+ return object.ReferenceEquals(obj, this) ||
+ (obj is not null &&
+ BaseRID == obj.BaseRID &&
+ (Version == null || OmitVersionDelimiter == obj.OmitVersionDelimiter) &&
+ Version == obj.Version &&
+ Architecture == obj.Architecture &&
+ Qualifier == obj.Qualifier);
+
}
- public bool HasQualifier()
+ public override int GetHashCode()
{
- return Qualifier != null;
+#if NETFRAMEWORK
+ return BaseRID.GetHashCode();
+#else
+ HashCode hashCode = default;
+ hashCode.Add(BaseRID);
+ hashCode.Add(Version);
+ hashCode.Add(Architecture);
+ hashCode.Add(Qualifier);
+ return hashCode.ToHashCode();
+#endif
}
}
-
}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroup.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroup.cs
index 2ddd60022c426a..76fde331a1a3a8 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroup.cs
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroup.cs
@@ -1,49 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Generic;
-using System.Linq;
using Microsoft.Build.Framework;
using NuGet.RuntimeModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
namespace Microsoft.NETCore.Platforms.BuildTasks
{
-
- internal class RuntimeGroup
+ public class RuntimeGroup
{
private const string rootRID = "any";
- private const char VersionDelimiter = '.';
- private const char ArchitectureDelimiter = '-';
- private const char QualifierDelimiter = '-';
public RuntimeGroup(ITaskItem item)
{
BaseRID = item.ItemSpec;
Parent = item.GetString(nameof(Parent));
- Versions = item.GetStrings(nameof(Versions));
+ Versions = new HashSet(item.GetStrings(nameof(Versions)).Select(v => new RuntimeVersion(v)));
TreatVersionsAsCompatible = item.GetBoolean(nameof(TreatVersionsAsCompatible), true);
OmitVersionDelimiter = item.GetBoolean(nameof(OmitVersionDelimiter));
ApplyVersionsToParent = item.GetBoolean(nameof(ApplyVersionsToParent));
- Architectures = item.GetStrings(nameof(Architectures));
- AdditionalQualifiers = item.GetStrings(nameof(AdditionalQualifiers));
+ Architectures = new HashSet(item.GetStrings(nameof(Architectures)));
+ AdditionalQualifiers = new HashSet(item.GetStrings(nameof(AdditionalQualifiers)));
OmitRIDs = new HashSet(item.GetStrings(nameof(OmitRIDs)));
OmitRIDDefinitions = new HashSet(item.GetStrings(nameof(OmitRIDDefinitions)));
OmitRIDReferences = new HashSet(item.GetStrings(nameof(OmitRIDReferences)));
}
+ public RuntimeGroup(string baseRID, string parent, bool treatVersionsAsCompatible = true, bool omitVersionDelimiter = false, bool applyVersionsToParent = false, IEnumerable additionalQualifiers = null)
+ {
+ BaseRID = baseRID;
+ Parent = parent;
+ Versions = new HashSet();
+ TreatVersionsAsCompatible = treatVersionsAsCompatible;
+ OmitVersionDelimiter = omitVersionDelimiter;
+ ApplyVersionsToParent = applyVersionsToParent;
+ Architectures = new HashSet();
+ AdditionalQualifiers = new HashSet(additionalQualifiers.NullAsEmpty());
+ OmitRIDs = new HashSet();
+ OmitRIDDefinitions = new HashSet();
+ OmitRIDReferences = new HashSet();
+ }
+
public string BaseRID { get; }
public string Parent { get; }
- public IEnumerable Versions { get; }
+ public ICollection Versions { get; }
public bool TreatVersionsAsCompatible { get; }
public bool OmitVersionDelimiter { get; }
public bool ApplyVersionsToParent { get; }
- public IEnumerable Architectures { get; }
- public IEnumerable AdditionalQualifiers { get; }
+ public ICollection Architectures { get; }
+ public ICollection AdditionalQualifiers { get; }
public ICollection OmitRIDs { get; }
public ICollection OmitRIDDefinitions { get; }
public ICollection OmitRIDReferences { get; }
- private class RIDMapping
+ public void ApplyRid(RID rid)
+ {
+ if (!rid.BaseRID.Equals(BaseRID, StringComparison.Ordinal))
+ {
+ throw new ArgumentException($"Cannot apply {nameof(RID)} with {nameof(RID.BaseRID)} {rid.BaseRID} to {nameof(RuntimeGroup)} with {nameof(RuntimeGroup.BaseRID)} {BaseRID}.", nameof(rid));
+ }
+
+ if (rid.HasArchitecture)
+ {
+ Architectures.Add(rid.Architecture);
+ }
+
+ if (rid.HasVersion)
+ {
+ Versions.Add(rid.Version);
+ }
+
+ if (rid.HasQualifier)
+ {
+ AdditionalQualifiers.Add(rid.Qualifier);
+ }
+ }
+
+ internal class RIDMapping
{
public RIDMapping(RID runtimeIdentifier)
{
@@ -62,21 +97,20 @@ public RIDMapping(RID runtimeIdentifier, IEnumerable imports)
public IEnumerable Imports { get; }
}
- private RID CreateRuntime(string baseRid, string version = null, string architecture = null, string qualifier = null)
+
+ internal RID CreateRuntime(string baseRid, RuntimeVersion version = null, string architecture = null, string qualifier = null)
{
return new RID()
{
BaseRID = baseRid,
- VersionDelimiter = OmitVersionDelimiter ? string.Empty : VersionDelimiter.ToString(),
Version = version,
- ArchitectureDelimiter = ArchitectureDelimiter.ToString(),
+ OmitVersionDelimiter = OmitVersionDelimiter,
Architecture = architecture,
- QualifierDelimiter = QualifierDelimiter.ToString(),
Qualifier = qualifier
};
}
- private IEnumerable GetRIDMappings()
+ internal IEnumerable GetRIDMappings()
{
// base =>
// Parent
@@ -102,7 +136,7 @@ private IEnumerable GetRIDMappings()
yield return new RIDMapping(CreateRuntime(BaseRID, architecture: architecture), imports);
}
- string lastVersion = null;
+ RuntimeVersion lastVersion = null;
foreach (var version in Versions)
{
// base + version =>
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroupCollection.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroupCollection.cs
new file mode 100644
index 00000000000000..970da5c1f86652
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeGroupCollection.cs
@@ -0,0 +1,159 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Build.Framework;
+using NuGet.RuntimeModel;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+ public class RuntimeGroupCollection
+ {
+ private ICollection allRuntimeGroups;
+ private Dictionary> runtimeGroupsByBaseRID;
+ private HashSet knownRIDs;
+
+ public RuntimeGroupCollection(ICollection runtimeGroups)
+ {
+ allRuntimeGroups = runtimeGroups;
+ runtimeGroupsByBaseRID = runtimeGroups.GroupBy(rg => rg.BaseRID).ToDictionary(g => g.Key, g => new List(g.AsEnumerable()));
+
+ knownRIDs = new HashSet(allRuntimeGroups.SelectMany(rg => rg.GetRIDMappings()).Select(mapping => mapping.RuntimeIdentifier));
+ }
+
+ ///
+ /// Locate an existing RuntimeGroup to append to.
+ /// Existing group must have matching baseRID, then we choose based on closest version,
+ /// and prefer matching arch and qualifier.
+ /// If no match is found, then a new RID heirarchy is created.
+ ///
+ ///
+ ///
+ public void AddRuntimeIdentifier(string runtimeIdentifier, string parent)
+ {
+ RID rid = RID.Parse(runtimeIdentifier);
+
+ AddRuntimeIdentifier(rid, parent);
+ }
+
+ public void AddRuntimeIdentifier(RID rid, string parent)
+ {
+ // Do nothing if we already know about the RID
+ if (knownRIDs.Contains(rid))
+ {
+ return;
+ }
+
+ RuntimeGroup runtimeGroup = null;
+
+ if (runtimeGroupsByBaseRID.TryGetValue(rid.BaseRID, out var candidateRuntimeGroups))
+ {
+ RuntimeVersion closestVersion = null;
+
+ foreach (var candidate in candidateRuntimeGroups)
+ {
+ if (rid.HasVersion)
+ {
+ // Find the closest previous version
+ foreach (var version in candidate.Versions)
+ {
+ // a previous version
+ if (version <= rid.Version)
+ {
+ // haven't yet found a match or this is a closer match
+ if (closestVersion == null || version > closestVersion)
+ {
+ closestVersion = version;
+ runtimeGroup = candidate;
+ }
+ else if (version == closestVersion)
+ {
+ // found a tie in version, examine other fields
+ considerCandidate();
+ }
+ }
+ }
+ }
+
+ // if we don't have a version, or if we couldn't find any match, consider other fields
+ if (!rid.HasVersion)
+ {
+ considerCandidate();
+ }
+
+ // if we don't have a match yet, take this as it matches on baseRID
+ runtimeGroup ??= candidate;
+
+ void considerCandidate()
+ {
+ // is this a better match?
+ if (!rid.HasArchitecture || candidate.Architectures.Contains(rid.Architecture))
+ {
+ if (!rid.HasQualifier || candidate.AdditionalQualifiers.Contains(rid.Qualifier))
+ {
+ // matched on arch and qualifier.
+ runtimeGroup = candidate;
+ }
+ else if (rid.HasArchitecture && !runtimeGroup.Architectures.Contains(rid.Architecture))
+ {
+ // matched only on arch and existing match doesn't match arch
+ runtimeGroup = candidate;
+ }
+ }
+ }
+ }
+
+ Debug.Assert(runtimeGroup != null, "Empty candidates?");
+ }
+ else
+ {
+ // This is an unknown base RID, we'll need to add a new group.
+ if (string.IsNullOrEmpty(parent))
+ {
+ throw new InvalidOperationException($"AdditionalRuntimeIdentifier {rid} was specified, which could not be found in any existing {nameof(RuntimeGroup)}, and no {nameof(parent)} was specified.");
+ }
+
+ runtimeGroup = new RuntimeGroup(rid.BaseRID, parent);
+
+ AddRuntimeGroup(runtimeGroup);
+ }
+
+ runtimeGroup.ApplyRid(rid);
+
+ // Compute the portion of the RID graph produced from this modified RuntimeGroup
+ var ridMappings = runtimeGroup.GetRIDMappings();
+
+ // Record any newly defined RIDs in our set of known RIDs
+ foreach (RID definedRID in ridMappings.Select(mapping => mapping.RuntimeIdentifier))
+ {
+ knownRIDs.Add(definedRID);
+ }
+
+ // Make sure that any RID imported is added as well. This allows users to specify
+ // a single new RID and we'll add any new RIDs up the parent chain that might be needed.
+ foreach (RID importedRID in ridMappings.SelectMany(mapping => mapping.Imports))
+ {
+ // This should not introduce any new RuntimeGroups, so we specify parent as null
+ AddRuntimeIdentifier(importedRID, null);
+ }
+
+ }
+
+ private void AddRuntimeGroup(RuntimeGroup runtimeGroup)
+ {
+ List baseRuntimeGroups;
+
+ if (!runtimeGroupsByBaseRID.TryGetValue(runtimeGroup.BaseRID, out baseRuntimeGroups))
+ {
+ runtimeGroupsByBaseRID[runtimeGroup.BaseRID] = baseRuntimeGroups = new List();
+ }
+
+ baseRuntimeGroups.Add(runtimeGroup);
+ allRuntimeGroups.Add(runtimeGroup);
+ }
+
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeVersion.cs b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeVersion.cs
new file mode 100644
index 00000000000000..954659fa6e0724
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/RuntimeVersion.cs
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks
+{
+
+ ///
+ /// A Version class that also supports a single integer (major only)
+ ///
+ public sealed class RuntimeVersion : IComparable, IComparable, IEquatable
+ {
+ private string versionString;
+ private Version version;
+ private bool hasMinor;
+
+ public RuntimeVersion(string versionString)
+ {
+ // intentionally don't support the type of version that omits the separators as it is abiguous.
+ // for example Windows 8.1 was encoded as win81, where as Windows 10.0 was encoded as win10
+ this.versionString = versionString;
+ string toParse = versionString;
+#if NETCOREAPP
+ if (!toParse.Contains('.'))
+#else
+ if (toParse.IndexOf('.') == -1)
+#endif
+ {
+ toParse += ".0";
+ hasMinor = false;
+ }
+ else
+ {
+ hasMinor = true;
+ }
+ version = Version.Parse(toParse);
+ }
+
+ public int CompareTo(object obj)
+ {
+ if (obj == null)
+ {
+ return 1;
+ }
+
+ 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)
+ {
+ if (other == null)
+ {
+ return 1;
+ }
+
+ int versionResult = version.CompareTo(other?.version);
+
+ if (versionResult == 0)
+ {
+ if (!hasMinor && other.hasMinor)
+ {
+ return -1;
+ }
+
+ if (hasMinor && !other.hasMinor)
+ {
+ return 1;
+ }
+
+ return string.CompareOrdinal(versionString, other.versionString);
+ }
+
+ return versionResult;
+ }
+
+ public bool Equals(RuntimeVersion other)
+ {
+ return object.ReferenceEquals(other, this) ||
+ (other != null &&
+ 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()
+ {
+ return versionString;
+ }
+
+ public static bool operator ==(RuntimeVersion v1, RuntimeVersion v2)
+ {
+ if (v2 is null)
+ {
+ 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)
+ {
+ if (v1 is null)
+ {
+ return !(v2 is null);
+ }
+
+ return v1.CompareTo(v2) < 0;
+ }
+
+ public static bool operator <=(RuntimeVersion v1, RuntimeVersion v2)
+ {
+ if (v1 is null)
+ {
+ return true;
+ }
+
+ 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;
+ }
+}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props
index 75f41db11693e4..af77b2320c8b4a 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props
+++ b/src/libraries/Microsoft.NETCore.Platforms/src/runtimeGroups.props
@@ -269,7 +269,7 @@
-
+
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs
index 652ca7f87ec334..53bc785e09deee 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/GenerateRuntimeGraphTests.cs
@@ -2,9 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
+using System.Runtime.CompilerServices;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
+using NuGet.RuntimeModel;
using Xunit;
using Xunit.Abstractions;
@@ -21,11 +23,11 @@ public GenerateRuntimeGraphTests(ITestOutputHelper output)
_engine = new TestBuildEngine(_log);
}
- [Fact]
- public void CanCreateRuntimeGraph()
- {
- string runtimeFile = "runtime.json";
+ const string DefaultRuntimeFile = "runtime.json";
+ private static ITaskItem[] DefaultRuntimeGroupItems { get; } = GetDefaultRuntimeGroupItems();
+ private static ITaskItem[] GetDefaultRuntimeGroupItems()
+ {
Project runtimeGroupProps = new Project("runtimeGroups.props");
ITaskItem[] runtimeGroups = runtimeGroupProps.GetItems("RuntimeGroupWithQualifiers")
@@ -33,26 +35,207 @@ public void CanCreateRuntimeGraph()
Assert.NotEmpty(runtimeGroups);
+ return runtimeGroups;
+ }
+
+ private static ITaskItem CreateItem(ProjectItem projectItem)
+ {
+ TaskItem item = new TaskItem(projectItem.EvaluatedInclude);
+ foreach (var metadatum in projectItem.Metadata)
+ {
+ item.SetMetadata(metadatum.Name, metadatum.EvaluatedValue);
+ }
+ return item;
+ }
+
+ [Fact]
+ public void CanCreateRuntimeGraph()
+ {
// will generate and compare to existing file.
GenerateRuntimeGraph task = new GenerateRuntimeGraph()
{
BuildEngine = _engine,
- RuntimeGroups = runtimeGroups,
- RuntimeJson = runtimeFile
+ RuntimeGroups = DefaultRuntimeGroupItems,
+ RuntimeJson = DefaultRuntimeFile,
+ UpdateRuntimeFiles = false
};
task.Execute();
_log.AssertNoErrorsOrWarnings();
}
- private static ITaskItem CreateItem(ProjectItem projectItem)
+
+ [Fact]
+ public void CanIgnoreExistingInferRids()
{
- TaskItem item = new TaskItem(projectItem.EvaluatedInclude);
- foreach(var metadatum in projectItem.Metadata)
+ // will generate and compare to existing file.
+ GenerateRuntimeGraph task = new GenerateRuntimeGraph()
{
- item.SetMetadata(metadatum.Name, metadatum.EvaluatedValue);
- }
- return item;
+ BuildEngine = _engine,
+ RuntimeGroups = DefaultRuntimeGroupItems,
+ RuntimeJson = DefaultRuntimeFile,
+ AdditionalRuntimeIdentifiers = new[] { "rhel.9-x64", "centos.9-arm64", "win-x64" },
+ UpdateRuntimeFiles = false
+ };
+
+ _log.Reset();
+ task.Execute();
+ _log.AssertNoErrorsOrWarnings();
+ }
+
+ ///
+ /// Runs GenerateRuntimeGraph task specifying AdditionalRuntimeIdentifiers then asserts that the
+ /// generated runtime.json has the expected additions (and no more).
+ ///
+ /// additional RIDs
+ /// entries that are expected to be added to the RuntimeGraph
+ /// parent to use when adding a new RID
+ /// a unique prefix to use for the generated
+ private void AssertRuntimeGraphAdditions(string[] additionalRIDs, RuntimeDescription[] expectedAdditions, string additionalRIDParent = null, [CallerMemberName] string runtimeFilePrefix = null)
+ {
+ string runtimeFile = runtimeFilePrefix + ".runtime.json";
+
+ GenerateRuntimeGraph task = new GenerateRuntimeGraph()
+ {
+ BuildEngine = _engine,
+ RuntimeGroups = DefaultRuntimeGroupItems,
+ RuntimeJson = runtimeFile,
+ AdditionalRuntimeIdentifiers = additionalRIDs,
+ AdditionalRuntimeIdentifierParent = additionalRIDParent,
+ UpdateRuntimeFiles = true
+ };
+
+ _log.Reset();
+ task.Execute();
+ _log.AssertNoErrorsOrWarnings();
+
+ RuntimeGraph expected = RuntimeGraph.Merge(
+ JsonRuntimeFormat.ReadRuntimeGraph(DefaultRuntimeFile),
+ new RuntimeGraph(expectedAdditions));
+
+ RuntimeGraph actual = JsonRuntimeFormat.ReadRuntimeGraph(runtimeFile);
+
+ // Should this assert fail, it's helpful to diff DefaultRuntimeFile and runtimeFile to see the additions.
+ Assert.Equal(expected, actual);
}
+
+ [Fact]
+ public void CanAddVersionsToExistingGroups()
+ {
+ var additionalRIDs = new[] { "ubuntu.22.04-arm64" };
+ var expectedAdditions = new[]
+ {
+ new RuntimeDescription("ubuntu.22.04", new[] { "ubuntu" }),
+ new RuntimeDescription("ubuntu.22.04-x64", new[] { "ubuntu.22.04", "ubuntu-x64" }),
+ new RuntimeDescription("ubuntu.22.04-x86", new[] { "ubuntu.22.04", "ubuntu-x86" }),
+ new RuntimeDescription("ubuntu.22.04-arm", new[] { "ubuntu.22.04", "ubuntu-arm" }),
+ new RuntimeDescription("ubuntu.22.04-arm64", new[] { "ubuntu.22.04", "ubuntu-arm64" })
+ };
+
+ AssertRuntimeGraphAdditions(additionalRIDs, expectedAdditions);
+ }
+
+ [Fact]
+ public void CanAddParentVersionsToExistingGroups()
+ {
+ var additionalRIDs = new[] { "centos.9.2-arm64" };
+ var expectedAdditions = new[]
+ {
+ new RuntimeDescription("centos.9.2", new[] { "centos", "rhel.9.2" }),
+ new RuntimeDescription("centos.9.2-x64", new[] { "centos.9.2", "centos-x64", "rhel.9.2-x64" }),
+ new RuntimeDescription("centos.9.2-arm64", new[] { "centos.9.2", "centos-arm64", "rhel.9.2-arm64" }),
+
+ // rhel RIDs are implicitly created since centos imports versioned RHEL RIDs
+ new RuntimeDescription("rhel.9.2", new[] { "rhel.9" }),
+ new RuntimeDescription("rhel.9.2-x64", new[] { "rhel.9.2", "rhel.9-x64" }),
+ new RuntimeDescription("rhel.9.2-arm64", new[] { "rhel.9.2", "rhel.9-arm64" })
+ };
+
+ AssertRuntimeGraphAdditions(additionalRIDs, expectedAdditions);
+ }
+
+ [Fact]
+ public void CanAddMajorVersionsToExistingGroups()
+ {
+
+ var additionalRIDs = new[] { "rhel.10-x64" };
+ var expectedAdditions = new[]
+ {
+ // Note that rhel doesn't treat major versions as compatible, however we do since it's closest and we don't represent this policy in the RuntimeGroups explicitly.
+ // We could add a rule that wouldn't insert a new major version if we see existing groups are split by major version.
+ new RuntimeDescription("rhel.10", new[] { "rhel.9" }),
+ new RuntimeDescription("rhel.10-x64", new[] { "rhel.10", "rhel.9-x64" }),
+ new RuntimeDescription("rhel.10-arm64", new[] { "rhel.10", "rhel.9-arm64" })
+ };
+
+ AssertRuntimeGraphAdditions(additionalRIDs, expectedAdditions);
+ }
+
+ [Fact]
+ public void CanAddArchitectureToExistingGroups()
+ {
+ var additionalRIDs = new[] { "win10-x128" };
+ var expectedAdditions = new[]
+ {
+ new RuntimeDescription("win10-x128", new[] { "win10", "win81-x128" }),
+ new RuntimeDescription("win10-x128-aot", new[] { "win10-aot", "win10-x128", "win10", "win81-x128-aot" }),
+ new RuntimeDescription("win81-x128-aot", new[] { "win81-aot", "win81-x128", "win81", "win8-x128-aot" }),
+ new RuntimeDescription("win81-x128", new[] { "win81", "win8-x128" }),
+ new RuntimeDescription("win8-x128-aot", new[] { "win8-aot", "win8-x128", "win8", "win7-x128-aot" }),
+ new RuntimeDescription("win8-x128", new[] { "win8", "win7-x128" }),
+ new RuntimeDescription("win7-x128-aot", new[] { "win7-aot", "win7-x128", "win7", "win-x128-aot" }),
+ new RuntimeDescription("win7-x128", new[] { "win7", "win-x128" }),
+ new RuntimeDescription("win-x128-aot", new[] { "win-aot", "win-x128" }),
+ new RuntimeDescription("win-x128", new[] { "win" })
+ };
+
+ AssertRuntimeGraphAdditions(additionalRIDs, expectedAdditions);
+ }
+
+
+ [Fact]
+ public void CanAddArchitectureAndVersionToExistingGroups()
+ {
+ var additionalRIDs = new[] { "osx.12-powerpc" };
+ var expectedAdditions = new[]
+ {
+ new RuntimeDescription("osx.12-powerpc", new[] { "osx.12", "osx.11.0-powerpc" }),
+ new RuntimeDescription("osx.12-arm64", new[] { "osx.12", "osx.11.0-arm64" }),
+ new RuntimeDescription("osx.12-x64", new[] { "osx.12", "osx.11.0-x64" }),
+ new RuntimeDescription("osx.12", new[] { "osx.11.0" }),
+ // our RID model doesn't give priority to architecture, so the new architecture is applied to all past versions
+ new RuntimeDescription("osx.11.0-powerpc", new[] { "osx.11.0", "osx.10.16-powerpc" }),
+ new RuntimeDescription("osx.10.16-powerpc", new[] { "osx.10.16", "osx.10.15-powerpc" }),
+ new RuntimeDescription("osx.10.15-powerpc", new[] { "osx.10.15", "osx.10.14-powerpc" }),
+ new RuntimeDescription("osx.10.14-powerpc", new[] { "osx.10.14", "osx.10.13-powerpc" }),
+ new RuntimeDescription("osx.10.13-powerpc", new[] { "osx.10.13", "osx.10.12-powerpc" }),
+ new RuntimeDescription("osx.10.12-powerpc", new[] { "osx.10.12", "osx.10.11-powerpc" }),
+ new RuntimeDescription("osx.10.11-powerpc", new[] { "osx.10.11", "osx.10.10-powerpc" }),
+ new RuntimeDescription("osx.10.10-powerpc", new[] { "osx.10.10", "osx-powerpc" }),
+ new RuntimeDescription("unix-powerpc", new[] { "unix" }),
+ new RuntimeDescription("osx-powerpc", new[] { "osx", "unix-powerpc" }),
+ };
+
+ AssertRuntimeGraphAdditions(additionalRIDs, expectedAdditions);
+ }
+
+ [Fact]
+ public void CanAddNewGroups()
+ {
+ var additionalRIDs = new[] { "yolinux.42.0-quantum" };
+ var expectedAdditions = new[]
+ {
+ new RuntimeDescription("unix-quantum", new[] { "unix" }),
+ new RuntimeDescription("linux-quantum", new[] { "linux", "unix-quantum" }),
+ new RuntimeDescription("linux-musl-quantum", new[] { "linux-musl", "linux-quantum" }),
+ new RuntimeDescription("yolinux", new[] { "linux-musl" }),
+ new RuntimeDescription("yolinux-quantum", new[] { "yolinux", "linux-musl-quantum" }),
+ new RuntimeDescription("yolinux.42.0", new[] { "yolinux" }),
+ new RuntimeDescription("yolinux.42.0-quantum", new[] { "yolinux.42.0", "yolinux-quantum" })
+ };
+
+ AssertRuntimeGraphAdditions(additionalRIDs, expectedAdditions, "linux-musl");
+ }
+
}
}
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/Microsoft.NETCore.Platforms.Tests.csproj b/src/libraries/Microsoft.NETCore.Platforms/tests/Microsoft.NETCore.Platforms.Tests.csproj
index cb254a5cb5070b..d39b20d6d9f5e2 100644
--- a/src/libraries/Microsoft.NETCore.Platforms/tests/Microsoft.NETCore.Platforms.Tests.csproj
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/Microsoft.NETCore.Platforms.Tests.csproj
@@ -1,4 +1,4 @@
-
+
$(NetCoreAppCurrent);net472
true
@@ -14,6 +14,8 @@
+
+
diff --git a/src/libraries/Microsoft.NETCore.Platforms/tests/RidTests.cs b/src/libraries/Microsoft.NETCore.Platforms/tests/RidTests.cs
new file mode 100644
index 00000000000000..227bcbdd10d4db
--- /dev/null
+++ b/src/libraries/Microsoft.NETCore.Platforms/tests/RidTests.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.NETCore.Platforms.BuildTasks.Tests
+{
+ public class RidTests
+ {
+ public static IEnumerable