diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets
index 2b7615f39f0d..0a46936983fe 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets
@@ -189,8 +189,8 @@ NOTE: This file is imported from the following contexts, so be aware when writin
- dotnet
- executable
+ dotnet
+ executable
diff --git a/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs b/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs
index e05ed084199e..67c16da3a37f 100644
--- a/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs
+++ b/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs
@@ -49,7 +49,7 @@ public void InstallAndRunNativeAotGlobalTool()
{
NativeAOT = true
};
- string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings);
+ string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings, collectBinlogs: true);
var testDirectory = _testAssetsManager.CreateTestDirectory();
@@ -138,10 +138,9 @@ public void InstallAndRunNativeAotLocalTool()
[Fact]
public void PackagesMultipleToolsWithASingleInvocation()
{
-
var toolSettings = new TestToolBuilder.TestToolSettings()
{
- SelfContained = true
+ RidSpecific = true
};
string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings);
@@ -154,21 +153,15 @@ public void PackagesMultipleToolsWithASingleInvocation()
{
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");
+ package.Should()
+ .NotBeNull($"Package {packageName} should be present in the tool packages directory")
+ .And.Satisfy(EnsurePackageIsAnExecutable);
}
// top-level package should declare all of the rids
var topLevelPackage = packages.First(p => p.EndsWith($"{packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg"));
- using var zipArchive = ZipFile.OpenRead(topLevelPackage);
- var nuspecEntry = zipArchive.GetEntry($"tools/{ToolsetInfo.CurrentTargetFramework}/any/DotnetToolSettings.xml")!;
- var stream = nuspecEntry.Open();
- var xml = XDocument.Load(stream, LoadOptions.None);
- var packageNodes =
- (xml.Root!.Nodes()
- .First(n => n is XElement e && e.Name == "RuntimeIdentifierPackages") as XElement)!.Nodes()
- .Where(n => (n as XElement)!.Name == "RuntimeIdentifierPackage")
- .Select(e => (e as XElement)!.Attributes().First(a => a.Name == "RuntimeIdentifier").Value);
- packageNodes.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
+ var foundRids = GetRidsInSettingsFile(topLevelPackage);
+ foundRids.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
}
[Fact]
@@ -189,22 +182,94 @@ public void PackagesMultipleTrimmedToolsWithASingleInvocation()
{
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");
- EnsurePackageLacksTrimmedDependency(package!, "System.Xml.dll");
+ package.Should()
+ .NotBeNull($"Package {packageName} should be present in the tool packages directory")
+ .And.Satisfy(EnsurePackageIsAnExecutable)
+ .And.Satisfy((string package) => EnsurePackageLacksTrimmedDependency(package, "System.Xml.dll"));
}
// top-level package should declare all of the rids
var topLevelPackage = packages.First(p => p.EndsWith($"{packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg"));
- using var zipArchive = ZipFile.OpenRead(topLevelPackage);
- var nuspecEntry = zipArchive.GetEntry($"tools/{ToolsetInfo.CurrentTargetFramework}/any/DotnetToolSettings.xml")!;
- var stream = nuspecEntry.Open();
- var xml = XDocument.Load(stream, LoadOptions.None);
- var packageNodes =
- (xml.Root!.Nodes()
+ var foundRids = GetRidsInSettingsFile(topLevelPackage);
+ foundRids.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
+ }
+
+ [Fact]
+ public void PackagesFrameworkDependentRidSpecificPackagesCorrectly()
+ {
+ var toolSettings = new TestToolBuilder.TestToolSettings()
+ {
+ RidSpecific = true,
+ };
+ string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings, collectBinlogs: true);
+
+ var packages = Directory.GetFiles(toolPackagesPath, "*.nupkg");
+ var packageIdentifier = toolSettings.ToolPackageId;
+ var expectedRids = ToolsetInfo.LatestRuntimeIdentifiers.Split(';');
+
+ packages.Length.Should().Be(expectedRids.Length + 1, "There should be one package for the tool-wrapper 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(EnsurePackageIsAnExecutable);
+ }
+
+ // top-level package should declare all of the rids
+ var topLevelPackage = packages.First(p => p.EndsWith($"{packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg"));
+ var foundRids = GetRidsInSettingsFile(topLevelPackage);
+ foundRids.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
+ }
+
+
+ private void EnsurePackageIsFdd(string packagePath)
+ {
+ var settingsXml = GetToolSettingsFile(packagePath);
+ var runner = GetRunnerFromSettingsFile(settingsXml);
+ runner.Should().Be("dotnet", "The tool should be packaged as a framework-dependent executable (FDD) with a 'dotnet' runner.");
+ }
+
+ private void EnsurePackageIsAnExecutable(string packagePath)
+ {
+ var settingsXml = GetToolSettingsFile(packagePath);
+ var runner = GetRunnerFromSettingsFile(settingsXml);
+ runner.Should().Be("executable", "The tool should be packaged as a executable with an 'executable' runner.");
+ }
+
+ private object GetRunnerFromSettingsFile(XElement settingsXml)
+ {
+ return settingsXml.Elements("Commands").First().Elements("Command").First().Attribute("Runner")?.Value
+ ?? throw new InvalidOperationException("The tool settings file does not contain a 'Runner' attribute.");
+ }
+
+ private string[] GetRidsInSettingsFile(string packagePath)
+ {
+ var settingsXml = GetToolSettingsFile(packagePath);
+ var rids = GetRidsInSettingsFile(settingsXml);
+ rids.Should().NotBeEmpty("The tool settings file should contain at least one RuntimeIdentifierPackage element.");
+ return rids;
+ }
+
+ private string[] GetRidsInSettingsFile(XElement settingsXml)
+ {
+ var nodes = (settingsXml.Nodes()
.First(n => n is XElement e && e.Name == "RuntimeIdentifierPackages") as XElement)!.Nodes()
.Where(n => (n as XElement)!.Name == "RuntimeIdentifierPackage")
- .Select(e => (e as XElement)!.Attributes().First(a => a.Name == "RuntimeIdentifier").Value);
- packageNodes.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
+ .Select(e => (e as XElement)!.Attributes().First(a => a.Name == "RuntimeIdentifier").Value)
+ .ToArray();
+ return nodes;
+ }
+
+ private XElement GetToolSettingsFile(string packagePath)
+ {
+ using var zipArchive = ZipFile.OpenRead(packagePath);
+ var nuspecEntry = zipArchive.Entries.First(e => e.Name == "DotnetToolSettings.xml")!;
+ var stream = nuspecEntry.Open();
+ var xml = XDocument.Load(stream, LoadOptions.None);
+ return xml.Root!;
+
}
///
diff --git a/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs b/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs
index ff69a2f5c71c..666ac9ff0fbf 100644
--- a/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs
+++ b/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs
@@ -32,15 +32,17 @@ public class TestToolSettings
public string ToolPackageVersion { get; set; } = "1.0.0";
public string ToolCommandName { get; set; } = "TestTool";
- public bool NativeAOT { get; set; } = false;
- public bool SelfContained { get; set; } = false;
- public bool Trimmed { get; set; } = false;
+ 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 RidSpecific { get; set; } = false;
- public string GetIdentifier() => $"{ToolPackageId}-{ToolPackageVersion}-{ToolCommandName}-{(NativeAOT ? "nativeaot" : SelfContained ? "selfcontained" : Trimmed ? "trimmed" : "managed")}";
+ public string GetIdentifier() => $"{ToolPackageId}-{ToolPackageVersion}-{ToolCommandName}-{(NativeAOT ? "nativeaot" : SelfContained ? "selfcontained" : Trimmed ? "trimmed" : "managed")}{(RidSpecific ? "-specific" : "")}{(IncludeAnyRid ? "-anyrid" : "")}";
}
- public string CreateTestTool(ITestOutputHelper log, TestToolSettings toolSettings)
+ public string CreateTestTool(ITestOutputHelper log, TestToolSettings toolSettings, bool collectBinlogs = false)
{
var targetDirectory = Path.Combine(TestContext.Current.TestExecutionDirectory, "TestTools", toolSettings.GetIdentifier());
@@ -55,22 +57,27 @@ 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;
+
+ if (toolSettings.RidSpecific)
+ {
+ testProject.AdditionalProperties["RuntimeIdentifiers"] = multiRid;
+ }
+
if (toolSettings.NativeAOT)
{
testProject.AdditionalProperties["PublishAot"] = "true";
- testProject.AdditionalProperties["RuntimeIdentifiers"] = RuntimeInformation.RuntimeIdentifier;
}
if (toolSettings.SelfContained)
{
testProject.AdditionalProperties["SelfContained"] = "true";
- testProject.AdditionalProperties["RuntimeIdentifiers"] = ToolsetInfo.LatestRuntimeIdentifiers;
}
if (toolSettings.Trimmed)
{
testProject.AdditionalProperties["PublishTrimmed"] = "true";
- testProject.AdditionalProperties["RuntimeIdentifiers"] = ToolsetInfo.LatestRuntimeIdentifiers;
}
testProject.SourceFiles.Add("Program.cs", "Console.WriteLine(\"Hello Tool!\");");
@@ -101,7 +108,7 @@ public string CreateTestTool(ITestOutputHelper log, TestToolSettings toolSetting
{
new DotnetPackCommand(log)
.WithWorkingDirectory(targetDirectory)
- .Execute()
+ .Execute(collectBinlogs ? $"--bl:{toolSettings.GetIdentifier()}-{{}}" : "")
.Should().Pass();
if (toolSettings.NativeAOT)
@@ -109,7 +116,7 @@ public string CreateTestTool(ITestOutputHelper log, TestToolSettings toolSetting
// For Native AOT tools, we need to repack the tool to include the runtime-specific files that were generated during publish
new DotnetPackCommand(log, "-r", RuntimeInformation.RuntimeIdentifier)
.WithWorkingDirectory(targetDirectory)
- .Execute()
+ .Execute(collectBinlogs ? $"--bl:{toolSettings.GetIdentifier()}-{RuntimeInformation.RuntimeIdentifier}-{{}}" : "")
.Should().Pass();
}