未验证 提交 bbea5516 编写于 作者: E Eric StJohn 提交者: GitHub

Add support for adding RIDs to runtime.json during build (#50818)

* Add support for adding RIDs to RidGraph during build

* Address code review feedback

* Ensure GenerateRuntimeJson runs before Pack

The pack task itself doesn't do anything but depend on a sequence of tasks.
As a result running a target `BeforeTargets="Pack"` actually runs after
the package is created, since dependencies run before BeforeTargets.

Sequence the target before GenerateNuspec instead.

* Only add target RID to the RID graph
上级 03be59b0
......@@ -53,6 +53,24 @@ public ITaskItem[] RuntimeGroups
set;
}
/// <summary>
/// Additional runtime identifiers to add to the graph.
/// </summary>
public string[] AdditionalRuntimeIdentifiers
{
get;
set;
}
/// <summary>
/// Parent RID to use for any unknown AdditionalRuntimeIdentifer.
/// </summary>
public string AdditionalRuntimeIdentifierParent
{
get;
set;
}
/// <summary>
/// Optional source Runtime.json to use as a starting point when merging additional RuntimeGroups
/// </summary>
......@@ -134,7 +152,11 @@ public override bool Execute()
runtimeGraph = new RuntimeGraph();
}
foreach (var runtimeGroup in RuntimeGroups.NullAsEmpty().Select(i => new RuntimeGroup(i)))
List<RuntimeGroup> 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<string, stri
}
}
private void AddRuntimeIdentifiers(ICollection<RuntimeGroup> 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<string, IEnumerable<string>> GetCompatibilityMap(RuntimeGraph graph)
{
Dictionary<string, IEnumerable<string>> compatibilityMap = new Dictionary<string, IEnumerable<string>>();
......
<Project Sdk="Microsoft.NET.SDK">
<Project Sdk="Microsoft.NET.SDK">
<!-- These are wrapper project files for packaging.-->
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppToolCurrent);net472</TargetFrameworks>
......@@ -12,6 +12,9 @@
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<NoWarn>$(NoWarn);NU5128</NoWarn> <!-- No Dependencies-->
<PackageDescription>Provides runtime information required to resolve target framework, platform, and runtime specific implementations of .NETCore packages.</PackageDescription>
<!-- When building from source, ensure the RID we're building for is part of the RID graph -->
<AdditionalRuntimeIdentifiers Condition="'$(DotNetBuildFromSource)' == 'true'">$(AdditionalRuntimeIdentifiers);$(OutputRID)</AdditionalRuntimeIdentifiers>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
......@@ -24,11 +27,14 @@
<Compile Include="Extensions.cs" />
<Compile Include="GenerateRuntimeGraph.cs" />
<Compile Include="RID.cs" />
<Compile Include="RuntimeGroupCollection.cs" />
<Compile Include="RuntimeGroup.cs" />
<Compile Include="RuntimeVersion.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="runtime.json" PackagePath="/" />
<Content Condition="'$(AdditionalRuntimeIdentifiers)' == ''" Include="runtime.json" PackagePath="/" />
<Content Condition="'$(AdditionalRuntimeIdentifiers)' != ''" Include="$(IntermediateOutputPath)runtime.json" PackagePath="/" />
<Content Include="_._" PackagePath="lib/netstandard1.0" />
</ItemGroup>
......@@ -38,5 +44,14 @@
<PackageReference Include="NuGet.ProjectModel" Version="$(RefOnlyNugetProjectModelVersion)" />
</ItemGroup>
<Target Name="GenerateRuntimeJson" Condition="'$(AdditionalRuntimeIdentifiers)' != ''" BeforeTargets="GenerateNuspec">
<MakeDir Directories="$(IntermediateOutputPath)" />
<GenerateRuntimeGraph RuntimeGroups="@(RuntimeGroupWithQualifiers)"
AdditionalRuntimeIdentifiers="$(AdditionalRuntimeIdentifiers)"
AdditionalRuntimeIdentifierParent="$(AdditionalRuntimeIdentifierParent)"
RuntimeJson="$(IntermediateOutputPath)runtime.json"
UpdateRuntimeFiles="True" />
</Target>
<Import Project="runtimeGroups.props" />
</Project>
\ No newline at end of file
// 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
}
}
}
// 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<RuntimeVersion>(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<string>(item.GetStrings(nameof(Architectures)));
AdditionalQualifiers = new HashSet<string>(item.GetStrings(nameof(AdditionalQualifiers)));
OmitRIDs = new HashSet<string>(item.GetStrings(nameof(OmitRIDs)));
OmitRIDDefinitions = new HashSet<string>(item.GetStrings(nameof(OmitRIDDefinitions)));
OmitRIDReferences = new HashSet<string>(item.GetStrings(nameof(OmitRIDReferences)));
}
public RuntimeGroup(string baseRID, string parent, bool treatVersionsAsCompatible = true, bool omitVersionDelimiter = false, bool applyVersionsToParent = false, IEnumerable<string> additionalQualifiers = null)
{
BaseRID = baseRID;
Parent = parent;
Versions = new HashSet<RuntimeVersion>();
TreatVersionsAsCompatible = treatVersionsAsCompatible;
OmitVersionDelimiter = omitVersionDelimiter;
ApplyVersionsToParent = applyVersionsToParent;
Architectures = new HashSet<string>();
AdditionalQualifiers = new HashSet<string>(additionalQualifiers.NullAsEmpty());
OmitRIDs = new HashSet<string>();
OmitRIDDefinitions = new HashSet<string>();
OmitRIDReferences = new HashSet<string>();
}
public string BaseRID { get; }
public string Parent { get; }
public IEnumerable<string> Versions { get; }
public ICollection<RuntimeVersion> Versions { get; }
public bool TreatVersionsAsCompatible { get; }
public bool OmitVersionDelimiter { get; }
public bool ApplyVersionsToParent { get; }
public IEnumerable<string> Architectures { get; }
public IEnumerable<string> AdditionalQualifiers { get; }
public ICollection<string> Architectures { get; }
public ICollection<string> AdditionalQualifiers { get; }
public ICollection<string> OmitRIDs { get; }
public ICollection<string> OmitRIDDefinitions { get; }
public ICollection<string> 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<RID> imports)
public IEnumerable<RID> 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<RIDMapping> GetRIDMappings()
internal IEnumerable<RIDMapping> GetRIDMappings()
{
// base =>
// Parent
......@@ -102,7 +136,7 @@ private IEnumerable<RIDMapping> GetRIDMappings()
yield return new RIDMapping(CreateRuntime(BaseRID, architecture: architecture), imports);
}
string lastVersion = null;
RuntimeVersion lastVersion = null;
foreach (var version in Versions)
{
// base + version =>
......
// 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<RuntimeGroup> allRuntimeGroups;
private Dictionary<string, List<RuntimeGroup>> runtimeGroupsByBaseRID;
private HashSet<RID> knownRIDs;
public RuntimeGroupCollection(ICollection<RuntimeGroup> runtimeGroups)
{
allRuntimeGroups = runtimeGroups;
runtimeGroupsByBaseRID = runtimeGroups.GroupBy(rg => rg.BaseRID).ToDictionary(g => g.Key, g => new List<RuntimeGroup>(g.AsEnumerable()));
knownRIDs = new HashSet<RID>(allRuntimeGroups.SelectMany(rg => rg.GetRIDMappings()).Select(mapping => mapping.RuntimeIdentifier));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="runtimeIdentifier"></param>
/// <param name="parent"></param>
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<RuntimeGroup> baseRuntimeGroups;
if (!runtimeGroupsByBaseRID.TryGetValue(runtimeGroup.BaseRID, out baseRuntimeGroups))
{
runtimeGroupsByBaseRID[runtimeGroup.BaseRID] = baseRuntimeGroups = new List<RuntimeGroup>();
}
baseRuntimeGroups.Add(runtimeGroup);
allRuntimeGroups.Add(runtimeGroup);
}
}
}
// 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
{
/// <summary>
/// A Version class that also supports a single integer (major only)
/// </summary>
public sealed class RuntimeVersion : IComparable, IComparable<RuntimeVersion>, IEquatable<RuntimeVersion>
{
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;
}
}
......@@ -269,7 +269,7 @@
</PropertyGroup>
<UsingTask TaskName="GenerateRuntimeGraph" AssemblyFile="$(_generateRuntimeGraphTask)"/>
<Target Name="GenerateRuntimeJson" BeforeTargets="Pack">
<Target Name="UpdateRuntimeJson" BeforeTargets="Pack">
<!-- Generates a Runtime graph using RuntimeGroups and diffs it with the graph described by runtime.json and runtime.compatibility.json
Specifying UpdateRuntimeFiles=true skips the diff and updates those files.
The graph can be visualized using the generated dmgl -->
......
......@@ -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();
}
/// <summary>
/// Runs GenerateRuntimeGraph task specifying AdditionalRuntimeIdentifiers then asserts that the
/// generated runtime.json has the expected additions (and no more).
/// </summary>
/// <param name="additionalRIDs">additional RIDs</param>
/// <param name="expectedAdditions">entries that are expected to be added to the RuntimeGraph</param>
/// <param name="additionalRIDParent">parent to use when adding a new RID</param>
/// <param name="runtimeFilePrefix">a unique prefix to use for the generated </param>
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");
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);net472</TargetFrameworks>
<IgnoreForCI Condition="'$(TargetOS)' == 'Browser'">true</IgnoreForCI>
......@@ -14,6 +14,8 @@
<Compile Include="AssemblyInfo.cs" />
<Compile Include="GenerateRuntimeGraphTests.cs" />
<Compile Include="Log.cs" />
<Compile Include="RidTests.cs" />
<Compile Include="RuntimeVersionTests.cs" />
<Compile Include="TestBuildEngine.cs" />
</ItemGroup>
......
// 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<object[]> ValidRIDData()
{
yield return new object[] { "win10-x64", new RID() { BaseRID = "win", OmitVersionDelimiter = true, Version = new RuntimeVersion("10"), Architecture = "x64" } };
yield return new object[] { "win10", new RID() { BaseRID = "win", OmitVersionDelimiter = true, Version = new RuntimeVersion("10")} };
yield return new object[] { "linux", new RID() { BaseRID = "linux" } };
yield return new object[] { "linux-x64", new RID() { BaseRID = "linux", Architecture = "x64" } };
yield return new object[] { "linux-x64", new RID() { BaseRID = "linux", Architecture = "x64" } };
yield return new object[] { "debian.10-x64", new RID() { BaseRID = "debian", Version = new RuntimeVersion("10"), Architecture = "x64" } };
yield return new object[] { "linuxmint.19.2-x64", new RID() { BaseRID = "linuxmint", Version = new RuntimeVersion("19.2"), Architecture = "x64" } };
yield return new object[] { "ubuntu.14.04-x64", new RID() { BaseRID = "ubuntu", Version = new RuntimeVersion("14.04"), Architecture = "x64" } };
yield return new object[] { "foo-bar.42-arm", new RID() { BaseRID = "foo-bar", Version = new RuntimeVersion("42"), Architecture = "arm" } };
yield return new object[] { "foo-bar-arm", new RID() { BaseRID = "foo", Architecture = "bar", Qualifier = "arm" } }; // demonstrates ambiguity, avoid using `-` in base
yield return new object[] { "linux-musl-x64", new RID() { BaseRID = "linux", Architecture = "musl", Qualifier = "x64" } }; // yes, we already have ambiguous RIDs
}
[Theory]
[MemberData(nameof(ValidRIDData))]
internal void ParseCorrectly(string input, RID expected)
{
RID actual = RID.Parse(input);
Assert.Equal(expected, actual);
}
[Theory]
[MemberData(nameof(ValidRIDData))]
internal void ToStringAsExpected(string expected, RID rid)
{
string actual = rid.ToString();
Assert.Equal(expected, actual);
}
}
}
// 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 RuntimeVersionTests
{
public enum Comparison
{
LessThan,
Equal,
GreaterThan
}
public static IEnumerable<object[]> ComparisonData()
{
yield return new object[] { "0.0", "00.0", Comparison.LessThan };
yield return new object[] { "2.0", "1.0", Comparison.GreaterThan };
yield return new object[] { "2", "1.0", Comparison.GreaterThan };
yield return new object[] { "2", "1", Comparison.GreaterThan };
yield return new object[] { "10", "10.0", Comparison.LessThan };
yield return new object[] { "10", "10.00", Comparison.LessThan };
yield return new object[] { "10.0", "10.0", Comparison.Equal };
yield return new object[] { "10.0", null, Comparison.GreaterThan };
yield return new object[] { "8", "8", Comparison.Equal };
}
[MemberData(nameof(ComparisonData))]
[Theory]
public static void CompareTo(string vs1, string vs2, Comparison expected)
{
RuntimeVersion v1 = new RuntimeVersion(vs1);
RuntimeVersion v2 = vs2 == null ? null : new RuntimeVersion(vs2);
int actual = v1.CompareTo(v2);
int invActual = v2?.CompareTo(v1) ?? -1;
switch (expected)
{
case Comparison.LessThan:
Assert.True(actual < 0);
Assert.True(invActual > 0);
break;
case Comparison.Equal:
Assert.Equal(0, actual);
Assert.Equal(0, invActual);
break;
case Comparison.GreaterThan:
Assert.True(actual > 0);
Assert.True(invActual < 0);
break;
}
}
[MemberData(nameof(ComparisonData))]
[Theory]
public static void GreaterThan(string vs1, string vs2, Comparison expected)
{
RuntimeVersion v1 = new RuntimeVersion(vs1);
RuntimeVersion v2 = vs2 == null ? null : new RuntimeVersion(vs2);
bool actual = v1 > v2;
bool invActual = v2 > v1;
switch (expected)
{
case Comparison.LessThan:
Assert.False(actual);
Assert.True(invActual);
break;
case Comparison.Equal:
Assert.False(actual);
Assert.False(invActual);
break;
case Comparison.GreaterThan:
Assert.True(actual);
Assert.False(invActual);
break;
}
}
[MemberData(nameof(ComparisonData))]
[Theory]
public static void GreaterThanOrEqual(string vs1, string vs2, Comparison expected)
{
RuntimeVersion v1 = new RuntimeVersion(vs1);
RuntimeVersion v2 = vs2 == null ? null : new RuntimeVersion(vs2);
bool actual = v1 >= v2;
bool invActual = v2 >= v1;
switch (expected)
{
case Comparison.LessThan:
Assert.False(actual);
Assert.True(invActual);
break;
case Comparison.Equal:
Assert.True(actual);
Assert.True(invActual);
break;
case Comparison.GreaterThan:
Assert.True(actual);
Assert.False(invActual);
break;
}
}
[MemberData(nameof(ComparisonData))]
[Theory]
public static void LessThan(string vs1, string vs2, Comparison expected)
{
RuntimeVersion v1 = new RuntimeVersion(vs1);
RuntimeVersion v2 = vs2 == null ? null : new RuntimeVersion(vs2);
bool actual = v1 < v2;
bool invActual = v2 < v1;
switch (expected)
{
case Comparison.LessThan:
Assert.True(actual);
Assert.False(invActual);
break;
case Comparison.Equal:
Assert.False(actual);
Assert.False(invActual);
break;
case Comparison.GreaterThan:
Assert.False(actual);
Assert.True(invActual);
break;
}
}
[MemberData(nameof(ComparisonData))]
[Theory]
public static void LessThanOrEqual(string vs1, string vs2, Comparison expected)
{
RuntimeVersion v1 = new RuntimeVersion(vs1);
RuntimeVersion v2 = vs2 == null ? null : new RuntimeVersion(vs2);
bool actual = v1 <= v2;
bool invActual = v2 <= v1;
switch (expected)
{
case Comparison.LessThan:
Assert.True(actual);
Assert.False(invActual);
break;
case Comparison.Equal:
Assert.True(actual);
Assert.True(invActual);
break;
case Comparison.GreaterThan:
Assert.False(actual);
Assert.True(invActual);
break;
}
}
[MemberData(nameof(ComparisonData))]
[Theory]
public static void Equal(string vs1, string vs2, Comparison expected)
{
RuntimeVersion v1 = new RuntimeVersion(vs1);
RuntimeVersion v2 = vs2 == null ? null : new RuntimeVersion(vs2);
bool actual = v1 == v2;
bool invActual = v2 == v1;
switch (expected)
{
case Comparison.LessThan:
Assert.False(actual);
Assert.False(invActual);
break;
case Comparison.Equal:
Assert.True(actual);
Assert.True(invActual);
break;
case Comparison.GreaterThan:
Assert.False(actual);
Assert.False(invActual);
break;
}
}
[MemberData(nameof(ComparisonData))]
[Theory]
public static void GetHashCodeUnique(string vs1, string vs2, Comparison expected)
{
RuntimeVersion v1 = new RuntimeVersion(vs1);
RuntimeVersion v2 = vs2 == null ? null : new RuntimeVersion(vs2);
int h1 = v1.GetHashCode();
int h2 = v2?.GetHashCode() ?? 0;
switch (expected)
{
case Comparison.LessThan:
Assert.NotEqual(h1, h2);
break;
case Comparison.Equal:
Assert.Equal(h1, h2);
break;
case Comparison.GreaterThan:
Assert.NotEqual(h1, h2);
break;
}
}
public static IEnumerable<object[]> ValidVersions()
{
yield return new object[] { "0" };
yield return new object[] { "00" };
yield return new object[] { "000" };
yield return new object[] { "1" };
yield return new object[] { "1.0" };
yield return new object[] { "1.1" };
yield return new object[] { "1.01" };
yield return new object[] { "1.2.3.4" };
yield return new object[] { "1.02.03.04" };
}
[MemberData(nameof(ValidVersions))]
[Theory]
public static void RoundTripToString(string expected)
{
RuntimeVersion version = new RuntimeVersion(expected);
string actual = version.ToString();
Assert.Equal(expected, actual);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册