Skip to content

Support any RID in multi-RID tools #49505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,43 +41,49 @@ NOTE: This file is imported from the following contexts, so be aware when writin
<!-- tools are specially-formatted packages, so we tell nuget Pack to not even try to include build output -->
<IncludeBuildOutput>false</IncludeBuildOutput>

<!-- Determine information about all of the potential tool packages to build -->
<!-- If shims are included, we need to make sure we restore for those RIDs so the apphost shims are available during restore/publish.
This means that we need to set RuntimeIdentifiers. However we need to track certain information about the _users_ decisions around RIDs
so that we can correctly decide if we need to package RID-specific tools or not. -->
<_ToolRidsAreOnlyShims>false</_ToolRidsAreOnlyShims>
<_ToolRidsAreOnlyShims Condition="'$(RuntimeIdentifiers)' == '' and $(PackAsToolShimRuntimeIdentifiers) != '' ">true</_ToolRidsAreOnlyShims>
<_UserSpecifiedToolPackageRids Condition="'$(ToolPackageRuntimeIdentifiers)' != ''">$(ToolPackageRuntimeIdentifiers)</_UserSpecifiedToolPackageRids>
<_UserSpecifiedToolPackageRids Condition="'$(_UserSpecifiedToolPackageRids)' == ''">$(RuntimeIdentifiers)</_UserSpecifiedToolPackageRids>
<_HasRIDSpecificTools Condition=" '$(_UserSpecifiedToolPackageRids)' != '' ">true</_HasRIDSpecificTools>
<_HasRIDSpecificTools Condition="'$(_HasRIDSpecificTools)' == ''">false</_HasRIDSpecificTools>
<RuntimeIdentifiers Condition="'$(PackAsToolShimRuntimeIdentifiers)' != ''">$(_UserSpecifiedToolPackageRids);$(PackAsToolShimRuntimeIdentifiers)</RuntimeIdentifiers>

<_IsRidSpecific>false</_IsRidSpecific>
<_IsRidSpecific Condition="'$(RuntimeIdentifier)' != '' and '$(RuntimeIdentifier)' != 'any'">true</_IsRidSpecific>

<!-- Not determine information about this specific build of a single (or more!) tool packages -->
<!-- the publish* properties _can_ be set, but only for the 'inner' RID-specific builds. We need to make sure that for the outer, agnostic build they are unset -->
<!-- RID information is also stripped during Restore, so we need to make sure user
decisions are preserved when Restoring, so that publishing-related packages are implicitly included. -->
<PublishSelfContained Condition="'$(RuntimeIdentifier)' == '' and '$(MSBuildIsRestoring)' != 'true'">false</PublishSelfContained>
<PublishSelfContained Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishSelfContained>
<!-- Have to set SelfContained similarly because PackTool targets are imported _after_ RuntimeIdentifierInference targets, where the Publish* properties are
forwarded to the 'base' properties. -->
<SelfContained Condition="'$(RuntimeIdentifier)' == '' and '$(MSBuildIsRestoring)' != 'true'">false</SelfContained>
<PublishTrimmed Condition="'$(RuntimeIdentifier)' == '' and '$(MSBuildIsRestoring)' != 'true'">false</PublishTrimmed>
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' == '' and '$(MSBuildIsRestoring)' != 'true'">false</PublishReadyToRun>
<PublishSingleFile Condition="'$(RuntimeIdentifier)' == '' and '$(MSBuildIsRestoring)' != 'true'">false</PublishSingleFile>
<SelfContained Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</SelfContained>
<PublishTrimmed Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishTrimmed>
<PublishReadyToRun Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishReadyToRun>
<PublishSingleFile Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishSingleFile>

<!-- We need to know if the inner builds are _intended_ to be AOT even if we then explicitly disable AOT for the outer builds.
Knowing this lets us correctly decide to create the RID-specific inner tools or not when packaging the outer tool. -->
<_InnerToolsPublishAot>false</_InnerToolsPublishAot>
<_InnerToolsPublishAot Condition="'$(RuntimeIdentifier)' == '' and '$(PublishAot)' == 'true'">true</_InnerToolsPublishAot>
<PublishAot Condition="'$(RuntimeIdentifier)' == ''">false</PublishAot>
<_InnerToolsPublishAot Condition="$(_HasRIDSpecificTools) and '$(PublishAot)' == 'true'">true</_InnerToolsPublishAot>
<PublishAot Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishAot>

<!-- we want to ensure that we don't publish any AppHosts for the 'outer', RID-agnostic builds -->
<UseAppHost Condition="'$(RuntimeIdentifier)' == ''">false</UseAppHost>
<UseAppHost Condition="!$(_IsRidSpecific)">false</UseAppHost>
<!-- we want to ensure that we _do_ publish any AppHosts for the 'inner', RID-specific builds -->
<UseAppHost Condition="'$(RuntimeIdentifier)' != ''">true</UseAppHost>

<!-- If shims are included, we need to make sure we restore for those RIDs so the apphost shims are available during restore/publish.
This means that we need to set RuntimeIdentifiers. However we need to track certain information about the _users_ decisions around RIDs
so that we can correctly decide if we need to package RID-specific tools or not. -->
<_ToolRidsAreOnlyShims>false</_ToolRidsAreOnlyShims>
<_ToolRidsAreOnlyShims Condition="'$(RuntimeIdentifiers)' == '' and $(PackAsToolShimRuntimeIdentifiers) != '' ">true</_ToolRidsAreOnlyShims>
<_UserSpecifiedToolPackageRids Condition="'$(ToolPackageRuntimeIdentifiers)' != ''">$(ToolPackageRuntimeIdentifiers)</_UserSpecifiedToolPackageRids>
<_UserSpecifiedToolPackageRids Condition="'$(_UserSpecifiedToolPackageRids)' == ''">$(RuntimeIdentifiers)</_UserSpecifiedToolPackageRids>
<_HasRIDSpecificTools Condition=" '$(_UserSpecifiedToolPackageRids)' != '' ">true</_HasRIDSpecificTools>
<_HasRIDSpecificTools Condition="'$(_HasRIDSpecificTools)' == ''">false</_HasRIDSpecificTools>
<RuntimeIdentifiers Condition="'$(PackAsToolShimRuntimeIdentifiers)' != ''">$(_UserSpecifiedToolPackageRids);$(PackAsToolShimRuntimeIdentifiers)</RuntimeIdentifiers>
<UseAppHost Condition="$(_IsRidSpecific)">true</UseAppHost>

<!-- Tool implementation files are not included in the primary package when the tool has RID-specific packages. So only pack the tool implementation
(and only depend on publish) if there are no RID-specific packages, or if the RuntimeIdentifier is set. -->
<_ToolPackageShouldIncludeImplementation Condition=" '$(PackAsTool)' == 'true' And
('$(_UserSpecifiedToolPackageRids)' == '' Or '$(RuntimeIdentifier)' != '')">true</_ToolPackageShouldIncludeImplementation>
( '$(_UserSpecifiedToolPackageRids)' == ''
or '$(RuntimeIdentifier)' != '')">true</_ToolPackageShouldIncludeImplementation>
<_ToolPackageShouldIncludeImplementation Condition="'$(_ToolPackageShouldIncludeImplementation)' == ''">false</_ToolPackageShouldIncludeImplementation>

<!-- inner builds and non-RID-specific outer builds need publish content-->
Expand Down Expand Up @@ -129,14 +135,13 @@ NOTE: This file is imported from the following contexts, so be aware when writin
<!-- Needs to be in a target so we don't need to worry about evaluation order with NativeBinary property -->
<PropertyGroup Condition="'$(ToolEntryPoint)' == ''">
<ToolEntryPoint>$(TargetFileName)</ToolEntryPoint>
<ToolEntryPoint Condition="'$(RuntimeIdentifier)' != '' and '$(UseAppHost)' == 'true' ">$(AssemblyName)$(_NativeExecutableExtension)</ToolEntryPoint>
<ToolEntryPoint Condition="$(_IsRidSpecific) and '$(UseAppHost)' == 'true' ">$(AssemblyName)$(_NativeExecutableExtension)</ToolEntryPoint>
</PropertyGroup>

<!-- inner-build tool packages get a RID suffix -->
<PropertyGroup Condition="'$(_HasRIDSpecificTools)' != '' And '$(RuntimeIdentifier)' != ''">
<PropertyGroup Condition="'$(_HasRIDSpecificTools)' != '' And $(RuntimeIdentifier) != ''">
<PackageId>$(PackageId).$(RuntimeIdentifier)</PackageId>
</PropertyGroup>

</Target>

<Target Name="PackToolImplementation">
Expand Down Expand Up @@ -385,14 +390,17 @@ NOTE: This file is imported from the following contexts, so be aware when writin

<!-- Orchestrator for making the N RID-specific tool packages if this Tool supports that mode.
We can't call this for AOT'd tools because we can't AOT cross-architecture and cross-platform in .NET today. -->
<Target Name="_CreateRIDSpecificToolPackages" Condition="'$(RuntimeIdentifier)' == '' and $(_HasRIDSpecificTools) and !$(_InnerToolsPublishAot) and !$(_ToolPackageShouldIncludeImplementation)">
<Target Name="_CreateRIDSpecificToolPackages"
Condition="'$(RuntimeIdentifier)' == ''
and $(_HasRIDSpecificTools)
and !$(_InnerToolsPublishAot)
and !$(_ToolPackageShouldIncludeImplementation)">
<PropertyGroup>
<_PackageRids>$(ToolPackageRuntimeIdentifiers)</_PackageRids>
<_PackageRids Condition="'$(_PackageRids)' == ''">$(RuntimeIdentifiers)</_PackageRids>
</PropertyGroup>

<ItemGroup>
<!-- Build the RID-specific packages.-->
<_rids Include="$(_PackageRids)" />
<_RidSpecificToolPackageProject Include="$(MSBuildProjectFullPath)" AdditionalProperties="RuntimeIdentifier=%(_rids.Identity);" />
</ItemGroup>
Expand Down
88 changes: 88 additions & 0 deletions test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,94 @@ public void PackagesFrameworkDependentRidSpecificPackagesCorrectly()
foundRids.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
}

[Fact]
public void PackageToolWithAnyRid()
{
var toolSettings = new TestToolBuilder.TestToolSettings()
{
RidSpecific = true,
IncludeAnyRid = true
};

string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings);

var packages = Directory.GetFiles(toolPackagesPath, "*.nupkg");
var packageIdentifier = toolSettings.ToolPackageId;
var expectedRids = ToolsetInfo.LatestRuntimeIdentifiers.Split(';');

packages.Length.Should().Be(expectedRids.Length + 1 + 1, "There should be one package for the tool-wrapper, one for the top-level manifest, and one for each RID");
foreach (string rid in expectedRids)
{
var packageName = $"{toolSettings.ToolPackageId}.{rid}.{toolSettings.ToolPackageVersion}";
var package = packages.FirstOrDefault(p => p.EndsWith(packageName + ".nupkg"));
package.Should().NotBeNull($"Package {packageName} should be present in the tool packages directory")
.And.Satisfy<string>(EnsurePackageIsAnExecutable);
}

// Ensure that the package with the "any" RID is present
var anyRidPackage = packages.FirstOrDefault(p => p.EndsWith($"{packageIdentifier}.any.{toolSettings.ToolPackageVersion}.nupkg"));
anyRidPackage.Should().NotBeNull($"Package {packageIdentifier}.any.{toolSettings.ToolPackageVersion}.nupkg should be present in the tool packages directory")
.And.Satisfy<string>(EnsurePackageIsFdd);

// top-level package should declare all of the rids
var topLevelPackage = packages.First(p => p.EndsWith($"{packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg"));
var settingsXml = GetToolSettingsFile(topLevelPackage);
var packageNodes = GetRidsInSettingsFile(settingsXml);

packageNodes.Should().BeEquivalentTo([.. expectedRids, "any"], "The top-level package should declare all of the RIDs for the tools it contains");
}

[Fact]
public void InstallAndRunToolFromAnyRid()
{
var toolSettings = new TestToolBuilder.TestToolSettings()
{
IncludeAnyRid = true // will make one package with the "any" RID
};
string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings);
var packages = Directory.GetFiles(toolPackagesPath, "*.nupkg").Select(p => Path.GetFileName(p)).ToArray();
packages.Should().BeEquivalentTo([
$"{toolSettings.ToolPackageId}.{toolSettings.ToolPackageVersion}.nupkg",
$"{toolSettings.ToolPackageId}.any.{toolSettings.ToolPackageVersion}.nupkg"
], "There should be two packages: one for the tool-wrapper and one for the 'any' RID");
var testDirectory = _testAssetsManager.CreateTestDirectory();
var homeFolder = Path.Combine(testDirectory.Path, "home");

new DotnetToolCommand(Log, "exec", toolSettings.ToolPackageId, "--yes", "--add-source", toolPackagesPath)
.WithEnvironmentVariables(homeFolder)
.WithWorkingDirectory(testDirectory.Path)
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello Tool!");
}

[Fact]
public void InstallAndRunToolFromAnyRidWhenOtherRidsArePresentButIncompatible()
{
var toolSettings = new TestToolBuilder.TestToolSettings()
{
IncludeCurrentRid = false,
RidSpecific = true, // will make one package for each RID except the current RID
IncludeAnyRid = true // will make one package with the "any" RID
};
List<string> expectedRids = [ .. ToolsetInfo.LatestRuntimeIdentifiers.Split(';').Where(rid => rid != RuntimeInformation.RuntimeIdentifier), "any"];

string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings);
var packages = Directory.GetFiles(toolPackagesPath, "*.nupkg").Select(p => Path.GetFileName(p)).ToArray();
packages.Should().BeEquivalentTo([
$"{toolSettings.ToolPackageId}.{toolSettings.ToolPackageVersion}.nupkg",
.. expectedRids.Select(rid => $"{toolSettings.ToolPackageId}.{rid}.{toolSettings.ToolPackageVersion}.nupkg"),
], $"There should be { 1 + expectedRids.Count } packages: one for the tool-wrapper and one for each RID except the current RID");
var testDirectory = _testAssetsManager.CreateTestDirectory();
var homeFolder = Path.Combine(testDirectory.Path, "home");

new DotnetToolCommand(Log, "exec", toolSettings.ToolPackageId, "--yes", "--add-source", toolPackagesPath)
.WithEnvironmentVariables(homeFolder)
.WithWorkingDirectory(testDirectory.Path)
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello Tool!");
}

private void EnsurePackageIsFdd(string packagePath)
{
Expand Down
18 changes: 10 additions & 8 deletions test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// 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.Collections.Generic;
using System.Text;

namespace Microsoft.DotNet.PackageInstall.Tests
{
[CollectionDefinition(nameof(TestToolBuilderCollection))]
Expand Down Expand Up @@ -35,10 +31,11 @@ public class TestToolSettings
public bool NativeAOT { get; set { field = value; this.RidSpecific = value; } } = false;
public bool SelfContained { get; set { field = value; this.RidSpecific = value; } } = false;
public bool Trimmed { get; set { field = value; this.RidSpecific = value; } } = false;
public bool IncludeAnyRid { get; set { field = value; this.RidSpecific = value; } } = false;
public bool IncludeAnyRid { get; set { field = value; } } = false;
public bool RidSpecific { get; set; } = false;
public bool IncludeCurrentRid { get; set; } = true;

public string GetIdentifier() => $"{ToolPackageId}-{ToolPackageVersion}-{ToolCommandName}-{(NativeAOT ? "nativeaot" : SelfContained ? "selfcontained" : Trimmed ? "trimmed" : "managed")}{(RidSpecific ? "-specific" : "")}{(IncludeAnyRid ? "-anyrid" : "")}";
public string GetIdentifier() => $"{ToolPackageId}-{ToolPackageVersion}-{ToolCommandName}-{(NativeAOT ? "nativeaot" : SelfContained ? "selfcontained" : Trimmed ? "trimmed" : "managed")}{(RidSpecific ? "-specific" : "")}{(IncludeAnyRid ? "-anyrid" : "")}{(IncludeCurrentRid ? "" : "-no-current-rid")}";
}


Expand All @@ -57,13 +54,18 @@ public string CreateTestTool(ITestOutputHelper log, TestToolSettings toolSetting
testProject.AdditionalProperties["ImplicitUsings"] = "enable";
testProject.AdditionalProperties["Version"] = toolSettings.ToolPackageVersion;

var singleRid = RuntimeInformation.RuntimeIdentifier;
var multiRid = toolSettings.IncludeAnyRid ? $"{ToolsetInfo.LatestRuntimeIdentifiers};any" : ToolsetInfo.LatestRuntimeIdentifiers;
var multiRid = toolSettings.IncludeCurrentRid ? ToolsetInfo.LatestRuntimeIdentifiers : ToolsetInfo.LatestRuntimeIdentifiers.Replace(RuntimeInformation.RuntimeIdentifier, string.Empty).Trim(';');

if (toolSettings.RidSpecific)
{
testProject.AdditionalProperties["RuntimeIdentifiers"] = multiRid;
}
if (toolSettings.IncludeAnyRid)
{
testProject.AdditionalProperties["RuntimeIdentifiers"] = testProject.AdditionalProperties.TryGetValue("RuntimeIdentifiers", out var existingRids)
? $"{existingRids};any"
: "any";
}

if (toolSettings.NativeAOT)
{
Expand Down
Loading