Skip to content

Commit 67ab062

Browse files
Strip all package types from inner multi-rid tool packages so they don't appear on search-based experiences (#49616)
Co-authored-by: Joel Verhagen <[email protected]>
1 parent 4b1075b commit 67ab062

File tree

3 files changed

+149
-12
lines changed

3 files changed

+149
-12
lines changed

src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,18 @@ NOTE: This file is imported from the following contexts, so be aware when writin
380380
PackageType = 'MyDotnetTool' -> 'DotnetTool;MyDotnetTool'
381381
382382
_PaddedPackageType is used to ensure that the PackageType is semicolon delimited and can be easily checked for an existing DotnetTool package type.
383+
384+
All of this should only apply for the 'outer' tool package - the 'inner' RID-specific tool packages are always of type DotnetToolRidPackage.
385+
This is so that the inner packages do not appear on any tool search results.
383386
-->
384387

385-
<AddPackageType CurrentPackageType="$(PackageType)" PackageTypeToAdd="$(_ToolPackageType)">
388+
<AddPackageType Condition="$(_ToolPackageType) == 'DotnetTool'" CurrentPackageType="$(PackageType)" PackageTypeToAdd="$(_ToolPackageType)">
386389
<Output TaskParameter="UpdatedPackageType" PropertyName="PackageType" />
387390
</AddPackageType>
388391

392+
<PropertyGroup Condition="$(_ToolPackageType) == 'DotnetToolRidPackage'">
393+
<PackageType>DotnetToolRidPackage</PackageType>
394+
</PropertyGroup>
389395
</Target>
390396

391397
<!-- Orchestrator for making the N RID-specific tool packages if this Tool supports that mode.

test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics.CodeAnalysis;
45
using System.IO.Compression;
6+
using NuGet.Packaging;
7+
using NuGet.Packaging.Core;
58

69
namespace Microsoft.DotNet.PackageInstall.Tests
710
{
@@ -312,35 +315,116 @@ .. expectedRids.Select(rid => $"{toolSettings.ToolPackageId}.{rid}.{toolSettings
312315
.And.HaveStdOutContaining("Hello Tool!");
313316
}
314317

315-
private void EnsurePackageIsFdd(string packagePath)
318+
[Fact]
319+
public void StripsPackageTypesFromInnerToolPackages()
320+
{
321+
var toolSettings = new TestToolBuilder.TestToolSettings()
322+
{
323+
RidSpecific = true,
324+
AdditionalPackageTypes = ["TestPackageType"]
325+
};
326+
string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings, collectBinlogs: true);
327+
328+
var packages = Directory.GetFiles(toolPackagesPath, "*.nupkg");
329+
var packageIdentifier = toolSettings.ToolPackageId;
330+
var expectedRids = ToolsetInfo.LatestRuntimeIdentifiers.Split(';');
331+
332+
packages.Length.Should().Be(expectedRids.Length + 1, "There should be one package for the tool-wrapper and one for each RID");
333+
foreach (string rid in expectedRids)
334+
{
335+
var packageName = $"{toolSettings.ToolPackageId}.{rid}.{toolSettings.ToolPackageVersion}";
336+
var package = packages.FirstOrDefault(p => p.EndsWith(packageName + ".nupkg"));
337+
package.Should()
338+
.NotBeNull($"Package {packageName} should be present in the tool packages directory")
339+
.And.Satisfy<string>(EnsurePackageIsAnExecutable)
340+
.And.Satisfy<string>(EnsurePackageOnlyHasToolRidPackageType);
341+
}
342+
343+
// top-level package should declare all of the rids
344+
var topLevelPackage = packages.First(p => p.EndsWith($"{packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg"));
345+
topLevelPackage.Should().NotBeNull($"Package {packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg should be present in the tool packages directory")
346+
.And.Satisfy<string>(EnsurePackageHasNoRunner)
347+
.And.Satisfy<string>(EnsurePackageHasToolPackageTypeAnd(toolSettings.AdditionalPackageTypes!));
348+
var foundRids = GetRidsInSettingsFile(topLevelPackage);
349+
foundRids.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
350+
}
351+
352+
private Action<string> EnsurePackageHasToolPackageTypeAnd(string[] additionalPackageTypes) => (string packagePath) =>
353+
{
354+
var nuspec = GetPackageNuspec(packagePath);
355+
var packageTypes = nuspec.GetPackageTypes();
356+
PackageType[] expectedPackageTypes = [new PackageType("DotnetTool", PackageType.EmptyVersion), .. additionalPackageTypes.Select(t => new PackageType(t, PackageType.EmptyVersion))];
357+
packageTypes.Should().NotBeNull("The PackageType element should not be null.")
358+
.And.HaveCount(1 + additionalPackageTypes.Length, "The package should have a PackageType element for each additional type.")
359+
.And.BeEquivalentTo(expectedPackageTypes, "The PackageType should be 'DotnetTool'.");
360+
};
361+
362+
static void EnsurePackageOnlyHasToolRidPackageType(string packagePath)
363+
{
364+
var nuspec = GetPackageNuspec(packagePath);
365+
var packageTypes = nuspec.GetPackageTypes();
366+
packageTypes.Should().NotBeNull("The PackageType element should not be null.")
367+
.And.HaveCount(1, "The package should only have a single PackageType element.")
368+
.And.Contain(new PackageType("DotnetToolRidPackage", PackageType.EmptyVersion), "The PackageType should be 'DotnetToolRidPackage'.");
369+
}
370+
371+
private static NuspecReader GetPackageNuspec(string packagePath)
372+
{
373+
using var zipArchive = ZipFile.OpenRead(packagePath);
374+
var nuspecEntry = zipArchive.Entries.First(e => e.Name.EndsWith("nuspec")!);
375+
var stream = nuspecEntry.Open();
376+
return new NuspecReader(stream);
377+
}
378+
379+
static void EnsurePackageIsFdd(string packagePath)
316380
{
317381
var settingsXml = GetToolSettingsFile(packagePath);
318382
var runner = GetRunnerFromSettingsFile(settingsXml);
319383
runner.Should().Be("dotnet", "The tool should be packaged as a framework-dependent executable (FDD) with a 'dotnet' runner.");
320384
}
321385

322-
private void EnsurePackageIsAnExecutable(string packagePath)
386+
static void EnsurePackageHasNoRunner(string packagePath)
387+
{
388+
var settingsXml = GetToolSettingsFile(packagePath);
389+
if (TryGetRunnerFromSettingsFile(settingsXml, out _))
390+
{
391+
throw new Exception("The tool settings file should not contain a 'Runner' attribute.");
392+
}
393+
}
394+
395+
static void EnsurePackageIsAnExecutable(string packagePath)
323396
{
324397
var settingsXml = GetToolSettingsFile(packagePath);
325398
var runner = GetRunnerFromSettingsFile(settingsXml);
326399
runner.Should().Be("executable", "The tool should be packaged as a executable with an 'executable' runner.");
327400
}
328401

329-
private object GetRunnerFromSettingsFile(XElement settingsXml)
402+
static string GetRunnerFromSettingsFile(XElement settingsXml)
403+
{
404+
if (TryGetRunnerFromSettingsFile(settingsXml, out string? runner))
405+
{
406+
return runner;
407+
} else
408+
{
409+
throw new InvalidOperationException("The tool settings file does not contain a 'Runner' attribute.");
410+
}
411+
}
412+
static bool TryGetRunnerFromSettingsFile(XElement settingsXml, [NotNullWhen(true)] out string? runner)
330413
{
331-
return settingsXml.Elements("Commands").First().Elements("Command").First().Attribute("Runner")?.Value
332-
?? throw new InvalidOperationException("The tool settings file does not contain a 'Runner' attribute.");
414+
var commandNode = settingsXml.Elements("Commands").First().Elements("Command").First();
415+
runner = commandNode.Attributes().FirstOrDefault(a => a.Name == "Runner")?.Value;
416+
return runner is not null;
333417
}
334418

335-
private string[] GetRidsInSettingsFile(string packagePath)
419+
static string[] GetRidsInSettingsFile(string packagePath)
336420
{
337421
var settingsXml = GetToolSettingsFile(packagePath);
338422
var rids = GetRidsInSettingsFile(settingsXml);
339423
rids.Should().NotBeEmpty("The tool settings file should contain at least one RuntimeIdentifierPackage element.");
340424
return rids;
341425
}
342426

343-
private string[] GetRidsInSettingsFile(XElement settingsXml)
427+
static string[] GetRidsInSettingsFile(XElement settingsXml)
344428
{
345429
var nodes = (settingsXml.Nodes()
346430
.First(n => n is XElement e && e.Name == "RuntimeIdentifierPackages") as XElement)!.Nodes()
@@ -350,7 +434,7 @@ private string[] GetRidsInSettingsFile(XElement settingsXml)
350434
return nodes;
351435
}
352436

353-
private XElement GetToolSettingsFile(string packagePath)
437+
static XElement GetToolSettingsFile(string packagePath)
354438
{
355439
using var zipArchive = ZipFile.OpenRead(packagePath);
356440
var nuspecEntry = zipArchive.Entries.First(e => e.Name == "DotnetToolSettings.xml")!;
@@ -363,7 +447,7 @@ private XElement GetToolSettingsFile(string packagePath)
363447
/// <summary>
364448
/// Opens the nupkg and verifies that it does not contain a dependency on the given dll.
365449
/// </summary>
366-
private void EnsurePackageLacksTrimmedDependency(string packagePath, string dll)
450+
static void EnsurePackageLacksTrimmedDependency(string packagePath, string dll)
367451
{
368452
using var zipArchive = ZipFile.OpenRead(packagePath);
369453
zipArchive.Entries.Should().NotContain(

test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class TestToolSettings
2727
public string ToolPackageId { get; set; } = "TestTool";
2828
public string ToolPackageVersion { get; set; } = "1.0.0";
2929
public string ToolCommandName { get; set; } = "TestTool";
30+
public string[]? AdditionalPackageTypes { get; set; } = null;
3031

3132
public bool NativeAOT { get; set { field = value; this.RidSpecific = value; } } = false;
3233
public bool SelfContained { get; set { field = value; this.RidSpecific = value; } } = false;
@@ -35,15 +36,56 @@ public class TestToolSettings
3536
public bool RidSpecific { get; set; } = false;
3637
public bool IncludeCurrentRid { get; set; } = true;
3738

38-
public string GetIdentifier() => $"{ToolPackageId}-{ToolPackageVersion}-{ToolCommandName}-{(NativeAOT ? "nativeaot" : SelfContained ? "selfcontained" : Trimmed ? "trimmed" : "managed")}{(RidSpecific ? "-specific" : "")}{(IncludeAnyRid ? "-anyrid" : "")}{(IncludeCurrentRid ? "" : "-no-current-rid")}";
39+
public string GetIdentifier() {
40+
var builder = new StringBuilder();
41+
builder.Append(ToolPackageId.ToLowerInvariant());
42+
builder.Append('-');
43+
builder.Append(ToolPackageVersion.ToLowerInvariant());
44+
builder.Append('-');
45+
builder.Append(ToolCommandName.ToLowerInvariant());
46+
if (NativeAOT)
47+
{
48+
builder.Append("-nativeaot");
49+
}
50+
else if (SelfContained)
51+
{
52+
builder.Append("-selfcontained");
53+
}
54+
else if (Trimmed)
55+
{
56+
builder.Append("-trimmed");
57+
}
58+
else
59+
{
60+
builder.Append("-managed");
61+
}
62+
if (RidSpecific)
63+
{
64+
builder.Append("-specific");
65+
}
66+
if (IncludeAnyRid)
67+
{
68+
builder.Append("-anyrid");
69+
}
70+
if (!IncludeCurrentRid)
71+
{
72+
builder.Append("-no-current-rid");
73+
}
74+
if (AdditionalPackageTypes is not null && AdditionalPackageTypes.Length > 0)
75+
{
76+
builder.Append('-');
77+
builder.Append(string.Join("-", AdditionalPackageTypes.Select(p => p.ToLowerInvariant())));
78+
}
79+
80+
return builder.ToString();
81+
}
3982
}
4083

4184

4285
public string CreateTestTool(ITestOutputHelper log, TestToolSettings toolSettings, bool collectBinlogs = false)
4386
{
4487
var targetDirectory = Path.Combine(TestContext.Current.TestExecutionDirectory, "TestTools", toolSettings.GetIdentifier());
4588

46-
4789
var testProject = new TestProject(toolSettings.ToolPackageId)
4890
{
4991
TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
@@ -82,6 +124,11 @@ public string CreateTestTool(ITestOutputHelper log, TestToolSettings toolSetting
82124
testProject.AdditionalProperties["PublishTrimmed"] = "true";
83125
}
84126

127+
if (toolSettings.AdditionalPackageTypes is not null && toolSettings.AdditionalPackageTypes.Length > 0)
128+
{
129+
testProject.AdditionalProperties["PackageType"] = string.Join(";", toolSettings.AdditionalPackageTypes);
130+
}
131+
85132
testProject.SourceFiles.Add("Program.cs", "Console.WriteLine(\"Hello Tool!\");");
86133

87134
var testAssetManager = new TestAssetsManager(log);

0 commit comments

Comments
 (0)