Skip to content

Commit 77050e3

Browse files
Copilotbaronfel
andauthored
Add SelectRuntimeIdentifierSpecificItems MSBuild task for filtering items by compatible RuntimeIdentifier (#50615)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com>
1 parent b0f9127 commit 77050e3

File tree

3 files changed

+271
-0
lines changed

3 files changed

+271
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using FluentAssertions;
5+
using Microsoft.Build.Framework;
6+
using Microsoft.Build.Utilities;
7+
using Xunit;
8+
9+
namespace Microsoft.NET.Build.Tasks.UnitTests
10+
{
11+
public class GivenASelectRuntimeIdentifierSpecificItems
12+
{
13+
[Fact]
14+
public void ItSelectsCompatibleItems()
15+
{
16+
// Arrange
17+
var testRuntimeGraphPath = CreateTestRuntimeGraph();
18+
var items = new[]
19+
{
20+
CreateTaskItem("Item1", "linux-x64"),
21+
CreateTaskItem("Item2", "win-x64"),
22+
CreateTaskItem("Item3", "linux"),
23+
CreateTaskItem("Item4", "ubuntu.18.04-x64")
24+
};
25+
26+
var task = new SelectRuntimeIdentifierSpecificItems()
27+
{
28+
TargetRuntimeIdentifier = "ubuntu.18.04-x64",
29+
Items = items,
30+
RuntimeIdentifierGraphPath = testRuntimeGraphPath,
31+
BuildEngine = new MockBuildEngine()
32+
};
33+
34+
// Act
35+
bool result = task.Execute();
36+
37+
// Assert
38+
result.Should().BeTrue();
39+
task.SelectedItems.Should().HaveCount(3); // linux-x64, linux, ubuntu.18.04-x64 should be compatible
40+
task.SelectedItems.Should().Contain(i => i.ItemSpec == "Item1"); // linux-x64
41+
task.SelectedItems.Should().Contain(i => i.ItemSpec == "Item3"); // linux
42+
task.SelectedItems.Should().Contain(i => i.ItemSpec == "Item4"); // ubuntu.18.04-x64
43+
task.SelectedItems.Should().NotContain(i => i.ItemSpec == "Item2"); // win-x64
44+
}
45+
46+
[Fact]
47+
public void ItSelectsItemsWithExactMatch()
48+
{
49+
// Arrange
50+
var testRuntimeGraphPath = CreateTestRuntimeGraph();
51+
var items = new[]
52+
{
53+
CreateTaskItem("Item1", "win-x64"),
54+
CreateTaskItem("Item2", "linux-x64")
55+
};
56+
57+
var task = new SelectRuntimeIdentifierSpecificItems()
58+
{
59+
TargetRuntimeIdentifier = "win-x64",
60+
Items = items,
61+
RuntimeIdentifierGraphPath = testRuntimeGraphPath,
62+
BuildEngine = new MockBuildEngine()
63+
};
64+
65+
// Act
66+
bool result = task.Execute();
67+
68+
// Assert
69+
result.Should().BeTrue();
70+
task.SelectedItems.Should().HaveCount(1);
71+
task.SelectedItems[0].ItemSpec.Should().Be("Item1");
72+
}
73+
74+
[Fact]
75+
public void ItSkipsItemsWithoutRuntimeIdentifierMetadata()
76+
{
77+
// Arrange
78+
var testRuntimeGraphPath = CreateTestRuntimeGraph();
79+
var items = new[]
80+
{
81+
CreateTaskItem("Item1", "linux-x64"),
82+
CreateTaskItem("Item2", null), // No runtime identifier
83+
CreateTaskItem("Item3", "") // Empty runtime identifier
84+
};
85+
86+
var task = new SelectRuntimeIdentifierSpecificItems()
87+
{
88+
TargetRuntimeIdentifier = "linux-x64",
89+
Items = items,
90+
RuntimeIdentifierGraphPath = testRuntimeGraphPath,
91+
BuildEngine = new MockBuildEngine()
92+
};
93+
94+
// Act
95+
bool result = task.Execute();
96+
97+
// Assert
98+
result.Should().BeTrue();
99+
task.SelectedItems.Should().HaveCount(1);
100+
task.SelectedItems[0].ItemSpec.Should().Be("Item1");
101+
}
102+
103+
[Fact]
104+
public void ItUsesCustomRuntimeIdentifierMetadata()
105+
{
106+
// Arrange
107+
var testRuntimeGraphPath = CreateTestRuntimeGraph();
108+
var item = new TaskItem("Item1");
109+
item.SetMetadata("CustomRID", "linux-x64");
110+
111+
var task = new SelectRuntimeIdentifierSpecificItems()
112+
{
113+
TargetRuntimeIdentifier = "ubuntu.18.04-x64",
114+
Items = new[] { item },
115+
RuntimeIdentifierItemMetadata = "CustomRID",
116+
RuntimeIdentifierGraphPath = testRuntimeGraphPath,
117+
BuildEngine = new MockBuildEngine()
118+
};
119+
120+
// Act
121+
bool result = task.Execute();
122+
123+
// Assert
124+
result.Should().BeTrue();
125+
task.SelectedItems.Should().HaveCount(1);
126+
task.SelectedItems[0].ItemSpec.Should().Be("Item1");
127+
}
128+
129+
[Fact]
130+
public void ItReturnsEmptyArrayWhenNoItemsProvided()
131+
{
132+
// Arrange
133+
var testRuntimeGraphPath = CreateTestRuntimeGraph();
134+
135+
var task = new SelectRuntimeIdentifierSpecificItems()
136+
{
137+
TargetRuntimeIdentifier = "linux-x64",
138+
Items = new ITaskItem[0],
139+
RuntimeIdentifierGraphPath = testRuntimeGraphPath,
140+
BuildEngine = new MockBuildEngine()
141+
};
142+
143+
// Act
144+
bool result = task.Execute();
145+
146+
// Assert
147+
result.Should().BeTrue();
148+
task.SelectedItems.Should().BeEmpty();
149+
}
150+
151+
private static TaskItem CreateTaskItem(string itemSpec, string? runtimeIdentifier)
152+
{
153+
var item = new TaskItem(itemSpec);
154+
if (!string.IsNullOrEmpty(runtimeIdentifier))
155+
{
156+
item.SetMetadata("RuntimeIdentifier", runtimeIdentifier);
157+
}
158+
return item;
159+
}
160+
161+
private static string CreateTestRuntimeGraph()
162+
{
163+
// Create a minimal runtime graph for testing
164+
var runtimeGraph = @"{
165+
""runtimes"": {
166+
""linux"": {},
167+
""linux-x64"": {
168+
""#import"": [""linux""]
169+
},
170+
""ubuntu"": {
171+
""#import"": [""linux""]
172+
},
173+
""ubuntu.18.04"": {
174+
""#import"": [""ubuntu""]
175+
},
176+
""ubuntu.18.04-x64"": {
177+
""#import"": [""ubuntu.18.04"", ""linux-x64""]
178+
},
179+
""win"": {},
180+
""win-x64"": {
181+
""#import"": [""win""]
182+
}
183+
}
184+
}";
185+
186+
var tempFile = Path.GetTempFileName();
187+
File.WriteAllText(tempFile, runtimeGraph);
188+
return tempFile;
189+
}
190+
}
191+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Build.Framework;
5+
using NuGet.RuntimeModel;
6+
7+
namespace Microsoft.NET.Build.Tasks;
8+
9+
/// <summary>
10+
/// MSBuild task that filters a set of Items by matching on compatible RuntimeIdentifier.
11+
/// This task filters an Item list by those items that contain a specific Metadata that is
12+
/// compatible with a specified Runtime Identifier, according to a given RuntimeIdentifierGraph file.
13+
/// </summary>
14+
public class SelectRuntimeIdentifierSpecificItems : TaskBase
15+
{
16+
/// <summary>
17+
/// The target runtime identifier to check compatibility against.
18+
/// </summary>
19+
[Required]
20+
public string TargetRuntimeIdentifier { get; set; } = null!;
21+
22+
/// <summary>
23+
/// The list of candidate items to filter.
24+
/// </summary>
25+
[Required]
26+
public ITaskItem[] Items { get; set; } = null!;
27+
28+
/// <summary>
29+
/// The name of the MSBuild metadata to check on each item. Defaults to "RuntimeIdentifier".
30+
/// </summary>
31+
public string? RuntimeIdentifierItemMetadata { get; set; } = "RuntimeIdentifier";
32+
33+
/// <summary>
34+
/// Path to the RuntimeIdentifierGraph file.
35+
/// </summary>
36+
[Required]
37+
public string RuntimeIdentifierGraphPath { get; set; } = null!;
38+
39+
/// <summary>
40+
/// The filtered items that are compatible with the <see cref="TargetRuntimeIdentifier"/>
41+
/// </summary>
42+
[Output]
43+
public ITaskItem[]? SelectedItems { get; set; }
44+
45+
protected override void ExecuteCore()
46+
{
47+
if (Items.Length == 0)
48+
{
49+
SelectedItems = Array.Empty<ITaskItem>();
50+
return;
51+
}
52+
53+
string ridMetadata = RuntimeIdentifierItemMetadata ?? "RuntimeIdentifier";
54+
55+
RuntimeGraph runtimeGraph = new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeIdentifierGraphPath);
56+
57+
var selectedItems = new List<ITaskItem>();
58+
59+
foreach (var item in Items)
60+
{
61+
string? itemRuntimeIdentifier = item.GetMetadata(ridMetadata);
62+
63+
if (string.IsNullOrEmpty(itemRuntimeIdentifier))
64+
{
65+
// Item doesn't have the runtime identifier metadata, skip it
66+
continue;
67+
}
68+
69+
// Check if the item's runtime identifier is compatible with the target runtime identifier
70+
if (runtimeGraph.AreCompatible(TargetRuntimeIdentifier, itemRuntimeIdentifier))
71+
{
72+
selectedItems.Add(item);
73+
}
74+
}
75+
76+
SelectedItems = selectedItems.ToArray();
77+
}
78+
}

src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,8 @@ Copyright (c) .NET Foundation. All rights reserved.
366366

367367
<UsingTask TaskName="Microsoft.NET.Build.Tasks.GetDefaultPlatformTargetForNetFramework"
368368
AssemblyFile="$(MicrosoftNETBuildTasksAssembly)" />
369+
<UsingTask TaskName="Microsoft.NET.Build.Tasks.SelectRuntimeIdentifierSpecificItems"
370+
AssemblyFile="$(MicrosoftNETBuildTasksAssembly)" />
369371

370372
<!--
371373
Switch our default .NETFramework CPU architecture choice back to AnyCPU before

0 commit comments

Comments
 (0)