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. +