From 93cfd500593ef5a4461c05d88c90488ac1bf7811 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Sep 2025 20:41:04 +0000
Subject: [PATCH 1/7] Initial plan
From ce83b907f46c68e350584e689f773a76fe7d71d0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Sep 2025 20:58:42 +0000
Subject: [PATCH 2/7] Implement SelectRuntimeIdentifierSpecificItems MSBuild
task with tests
Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com>
---
...enASelectRuntimeIdentifierSpecificItems.cs | 191 ++++++++++++++++++
.../SelectRuntimeIdentifierSpecificItems.cs | 100 +++++++++
2 files changed, 291 insertions(+)
create mode 100644 src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenASelectRuntimeIdentifierSpecificItems.cs
create mode 100644 src/Tasks/Microsoft.NET.Build.Tasks/SelectRuntimeIdentifierSpecificItems.cs
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenASelectRuntimeIdentifierSpecificItems.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenASelectRuntimeIdentifierSpecificItems.cs
new file mode 100644
index 000000000000..c6dea601a9ff
--- /dev/null
+++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenASelectRuntimeIdentifierSpecificItems.cs
@@ -0,0 +1,191 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using FluentAssertions;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xunit;
+
+namespace Microsoft.NET.Build.Tasks.UnitTests
+{
+ public class GivenASelectRuntimeIdentifierSpecificItems
+ {
+ [Fact]
+ public void ItSelectsCompatibleItems()
+ {
+ // Arrange
+ var testRuntimeGraphPath = CreateTestRuntimeGraph();
+ var items = new[]
+ {
+ CreateTaskItem("Item1", "linux-x64"),
+ CreateTaskItem("Item2", "win-x64"),
+ CreateTaskItem("Item3", "linux"),
+ CreateTaskItem("Item4", "ubuntu.18.04-x64")
+ };
+
+ var task = new SelectRuntimeIdentifierSpecificItems()
+ {
+ TargetRuntimeIdentifier = "ubuntu.18.04-x64",
+ Items = items,
+ RuntimeIdentifierGraphPath = testRuntimeGraphPath,
+ BuildEngine = new MockBuildEngine()
+ };
+
+ // Act
+ bool result = task.Execute();
+
+ // Assert
+ result.Should().BeTrue();
+ task.SelectedItems.Should().HaveCount(3); // linux-x64, linux, ubuntu.18.04-x64 should be compatible
+ task.SelectedItems.Should().Contain(i => i.ItemSpec == "Item1"); // linux-x64
+ task.SelectedItems.Should().Contain(i => i.ItemSpec == "Item3"); // linux
+ task.SelectedItems.Should().Contain(i => i.ItemSpec == "Item4"); // ubuntu.18.04-x64
+ task.SelectedItems.Should().NotContain(i => i.ItemSpec == "Item2"); // win-x64
+ }
+
+ [Fact]
+ public void ItSelectsItemsWithExactMatch()
+ {
+ // Arrange
+ var testRuntimeGraphPath = CreateTestRuntimeGraph();
+ var items = new[]
+ {
+ CreateTaskItem("Item1", "win-x64"),
+ CreateTaskItem("Item2", "linux-x64")
+ };
+
+ var task = new SelectRuntimeIdentifierSpecificItems()
+ {
+ TargetRuntimeIdentifier = "win-x64",
+ Items = items,
+ RuntimeIdentifierGraphPath = testRuntimeGraphPath,
+ BuildEngine = new MockBuildEngine()
+ };
+
+ // Act
+ bool result = task.Execute();
+
+ // Assert
+ result.Should().BeTrue();
+ task.SelectedItems.Should().HaveCount(1);
+ task.SelectedItems[0].ItemSpec.Should().Be("Item1");
+ }
+
+ [Fact]
+ public void ItSkipsItemsWithoutRuntimeIdentifierMetadata()
+ {
+ // Arrange
+ var testRuntimeGraphPath = CreateTestRuntimeGraph();
+ var items = new[]
+ {
+ CreateTaskItem("Item1", "linux-x64"),
+ CreateTaskItem("Item2", null), // No runtime identifier
+ CreateTaskItem("Item3", "") // Empty runtime identifier
+ };
+
+ var task = new SelectRuntimeIdentifierSpecificItems()
+ {
+ TargetRuntimeIdentifier = "linux-x64",
+ Items = items,
+ RuntimeIdentifierGraphPath = testRuntimeGraphPath,
+ BuildEngine = new MockBuildEngine()
+ };
+
+ // Act
+ bool result = task.Execute();
+
+ // Assert
+ result.Should().BeTrue();
+ task.SelectedItems.Should().HaveCount(1);
+ task.SelectedItems[0].ItemSpec.Should().Be("Item1");
+ }
+
+ [Fact]
+ public void ItUsesCustomRuntimeIdentifierMetadata()
+ {
+ // Arrange
+ var testRuntimeGraphPath = CreateTestRuntimeGraph();
+ var item = new TaskItem("Item1");
+ item.SetMetadata("CustomRID", "linux-x64");
+
+ var task = new SelectRuntimeIdentifierSpecificItems()
+ {
+ TargetRuntimeIdentifier = "ubuntu.18.04-x64",
+ Items = new[] { item },
+ RuntimeIdentifierItemMetadata = "CustomRID",
+ RuntimeIdentifierGraphPath = testRuntimeGraphPath,
+ BuildEngine = new MockBuildEngine()
+ };
+
+ // Act
+ bool result = task.Execute();
+
+ // Assert
+ result.Should().BeTrue();
+ task.SelectedItems.Should().HaveCount(1);
+ task.SelectedItems[0].ItemSpec.Should().Be("Item1");
+ }
+
+ [Fact]
+ public void ItReturnsEmptyArrayWhenNoItemsProvided()
+ {
+ // Arrange
+ var testRuntimeGraphPath = CreateTestRuntimeGraph();
+
+ var task = new SelectRuntimeIdentifierSpecificItems()
+ {
+ TargetRuntimeIdentifier = "linux-x64",
+ Items = new ITaskItem[0],
+ RuntimeIdentifierGraphPath = testRuntimeGraphPath,
+ BuildEngine = new MockBuildEngine()
+ };
+
+ // Act
+ bool result = task.Execute();
+
+ // Assert
+ result.Should().BeTrue();
+ task.SelectedItems.Should().BeEmpty();
+ }
+
+ private static TaskItem CreateTaskItem(string itemSpec, string? runtimeIdentifier)
+ {
+ var item = new TaskItem(itemSpec);
+ if (!string.IsNullOrEmpty(runtimeIdentifier))
+ {
+ item.SetMetadata("RuntimeIdentifier", runtimeIdentifier);
+ }
+ return item;
+ }
+
+ private static string CreateTestRuntimeGraph()
+ {
+ // Create a minimal runtime graph for testing
+ var runtimeGraph = @"{
+ ""runtimes"": {
+ ""linux"": {},
+ ""linux-x64"": {
+ ""#import"": [""linux""]
+ },
+ ""ubuntu"": {
+ ""#import"": [""linux""]
+ },
+ ""ubuntu.18.04"": {
+ ""#import"": [""ubuntu""]
+ },
+ ""ubuntu.18.04-x64"": {
+ ""#import"": [""ubuntu.18.04"", ""linux-x64""]
+ },
+ ""win"": {},
+ ""win-x64"": {
+ ""#import"": [""win""]
+ }
+ }
+}";
+
+ var tempFile = Path.GetTempFileName();
+ File.WriteAllText(tempFile, runtimeGraph);
+ return tempFile;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/SelectRuntimeIdentifierSpecificItems.cs b/src/Tasks/Microsoft.NET.Build.Tasks/SelectRuntimeIdentifierSpecificItems.cs
new file mode 100644
index 000000000000..f83b4868f48b
--- /dev/null
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/SelectRuntimeIdentifierSpecificItems.cs
@@ -0,0 +1,100 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable disable
+
+using Microsoft.Build.Framework;
+using NuGet.RuntimeModel;
+
+namespace Microsoft.NET.Build.Tasks
+{
+ ///
+ /// MSBuild task that filters a set of Items by matching on compatible RuntimeIdentifier.
+ /// This task filters an Item list by those items that contain a specific Metadata that is
+ /// compatible with a specified Runtime Identifier, according to a given RuntimeIdentifierGraph file.
+ ///
+ public class SelectRuntimeIdentifierSpecificItems : TaskBase
+ {
+ ///
+ /// The target runtime identifier to check compatibility against.
+ ///
+ [Required]
+ public string TargetRuntimeIdentifier { get; set; } = string.Empty;
+
+ ///
+ /// The list of candidate items to filter.
+ ///
+ [Required]
+ public ITaskItem[] Items { get; set; } = [];
+
+ ///
+ /// The name of the MSBuild metadata to check on each item. Defaults to "RuntimeIdentifier".
+ ///
+ public string RuntimeIdentifierItemMetadata { get; set; } = "RuntimeIdentifier";
+
+ ///
+ /// Path to the RuntimeIdentifierGraph file.
+ ///
+ [Required]
+ public string RuntimeIdentifierGraphPath { get; set; } = string.Empty;
+
+ ///
+ /// The filtered items that are compatible with the target runtime identifier.
+ ///
+ [Output]
+ public ITaskItem[] SelectedItems { get; set; } = [];
+
+ protected override void ExecuteCore()
+ {
+ if (Items == null || Items.Length == 0)
+ {
+ SelectedItems = [];
+ return;
+ }
+
+ RuntimeGraph runtimeGraph = new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeIdentifierGraphPath);
+
+ var selectedItems = new List();
+
+ foreach (var item in Items)
+ {
+ string itemRuntimeIdentifier = item.GetMetadata(RuntimeIdentifierItemMetadata);
+
+ if (string.IsNullOrEmpty(itemRuntimeIdentifier))
+ {
+ // Item doesn't have the runtime identifier metadata, skip it
+ continue;
+ }
+
+ // Check if the item's runtime identifier is compatible with the target runtime identifier
+ if (IsCompatibleRuntimeIdentifier(runtimeGraph, TargetRuntimeIdentifier, itemRuntimeIdentifier))
+ {
+ selectedItems.Add(item);
+ }
+ }
+
+ SelectedItems = selectedItems.ToArray();
+ }
+
+ ///
+ /// Determines if a candidate runtime identifier is compatible with a target runtime identifier.
+ ///
+ /// The runtime graph containing compatibility information.
+ /// The target runtime identifier.
+ /// The candidate runtime identifier to check.
+ /// True if the candidate is compatible with the target, false otherwise.
+ private static bool IsCompatibleRuntimeIdentifier(RuntimeGraph runtimeGraph, string targetRuntimeIdentifier, string candidateRuntimeIdentifier)
+ {
+ if (string.Equals(targetRuntimeIdentifier, candidateRuntimeIdentifier, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ // Get the list of compatible runtime identifiers for the target
+ var compatibleRuntimeIdentifiers = runtimeGraph.ExpandRuntime(targetRuntimeIdentifier);
+
+ // Check if the candidate runtime identifier is in the list of compatible ones
+ return compatibleRuntimeIdentifiers.Contains(candidateRuntimeIdentifier, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
\ No newline at end of file
From bf9dbae91053e76143a56b6fabaff8d6059ccf8d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 3 Sep 2025 21:32:20 +0000
Subject: [PATCH 3/7] Add UsingTask declaration for
SelectRuntimeIdentifierSpecificItems in
Microsoft.NET.RuntimeIdentifierInference.targets
Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com>
---
.../targets/Microsoft.NET.RuntimeIdentifierInference.targets | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets
index 7bb069dbc417..fbf9522c3e78 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets
@@ -366,6 +366,8 @@ Copyright (c) .NET Foundation. All rights reserved.
+