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(); }