diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs
index 5b4cecbcfd88..7dbc792028a2 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs
@@ -112,7 +112,7 @@ public DependencyContextBuilder(
runtimeIdentifier,
string.IsNullOrWhiteSpace(platformLibraryName));
- _isPortable = _isFrameworkDependent && string.IsNullOrEmpty(_runtimeIdentifier);
+ _isPortable = _isFrameworkDependent && (string.IsNullOrEmpty(_runtimeIdentifier) || _runtimeIdentifier == "any");
if (_isFrameworkDependent != true || _isPortable != true)
{
@@ -317,7 +317,7 @@ public DependencyContext Build(string[] userRuntimeAssemblies = null)
* 1. If runtimeAssemblyGroups, nativeLibraryGroups, dependencies, and resourceAssemblies are all empty, remove this runtimeLibrary as well as any dependencies on it.
* 2. Add all runtimeLibraries to a list of to-be-processed libraries called libraryCandidatesForRemoval
* 3. libraryCandidatesForRemoval.Pop() --> if there are no runtimeAssemblyGroups, nativeLibraryGroups, or resourceAssemblies, and either dependencies is empty or all
- * dependencies have something else that depends on them, remove it (and from libraryCandidatesForRemoval), adding everything that depends on this to
+ * dependencies have something else that depends on them, remove it (and from libraryCandidatesForRemoval), adding everything that depends on this to
* libraryCandidatesForRemoval if it isn't already there
* Repeat 3 until libraryCandidatesForRemoval is empty
*/
@@ -483,8 +483,8 @@ public DependencyContext Build(string[] userRuntimeAssemblies = null)
runtimeSignature: string.Empty,
_isPortable);
- // Compute the runtime fallback graph
- //
+ // Compute the runtime fallback graph
+ //
// If the input RuntimeGraph is empty, or we're not compiling
// for a specific RID, then an runtime fallback graph is empty
//
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs
index 8b89525c9fd1..e3245a4f5103 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs
@@ -31,6 +31,11 @@ public class GenerateDepsFile : TaskBase
public string RuntimeIdentifier { get; set; }
+ ///
+ /// Strips the RID if it's any, because that's not reasonable
+ ///
+ private string EffectiveRuntimeIdentifier => string.IsNullOrEmpty(RuntimeIdentifier) ? null : RuntimeIdentifier == "any" ? null : RuntimeIdentifier;
+
public string PlatformLibraryName { get; set; }
public ITaskItem[] RuntimeFrameworks { get; set; }
@@ -95,7 +100,7 @@ public class GenerateDepsFile : TaskBase
public bool IncludeProjectsNotInAssetsFile { get; set; }
- // List of runtime identifer (platform part only) to validate for runtime assets
+ // List of runtime identifier (platform part only) to validate for runtime assets
// If set, the task will warn on any RIDs that aren't in the list
public string[] ValidRuntimeIdentifierPlatformsForAssets { get; set; }
@@ -137,7 +142,7 @@ private void WriteDepsFile(string depsFilePath)
LockFile lockFile = new LockFileCache(this).GetLockFile(AssetsFilePath);
projectContext = lockFile.CreateProjectContext(
TargetFramework,
- RuntimeIdentifier,
+ EffectiveRuntimeIdentifier,
PlatformLibraryName,
RuntimeFrameworks,
IsSelfContained);
@@ -226,7 +231,7 @@ bool ShouldIncludeRuntimeAsset(ITaskItem item)
RuntimeFrameworks,
isSelfContained: IsSelfContained,
platformLibraryName: PlatformLibraryName,
- runtimeIdentifier: RuntimeIdentifier,
+ runtimeIdentifier: EffectiveRuntimeIdentifier,
targetFramework: TargetFramework);
}
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs b/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs
index 5c03457ffc3a..6da136843d6b 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs
@@ -94,7 +94,7 @@ public static ProjectContext CreateProjectContext(
public static bool IsFrameworkDependent(ITaskItem[] runtimeFrameworks, bool isSelfContained, string runtimeIdentifier, bool hasPlatformLibrary)
{
return (hasPlatformLibrary || runtimeFrameworks?.Any() == true) &&
- (!isSelfContained || string.IsNullOrEmpty(runtimeIdentifier));
+ (!isSelfContained || (string.IsNullOrEmpty(runtimeIdentifier) || runtimeIdentifier == "any"));
}
public static LockFileTargetLibrary GetLibrary(this LockFileTarget lockFileTarget, string libraryName)
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs
index db3c6905fc12..5569f32a4cde 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs
@@ -335,6 +335,7 @@ out List knownRuntimePacksForTargetFramework
var runtimeRequiredByDeployment
= (SelfContained || ReadyToRunEnabled) &&
!string.IsNullOrEmpty(RuntimeIdentifier) &&
+ RuntimeIdentifier != "any" &&
selectedRuntimePack != null &&
!string.IsNullOrEmpty(selectedRuntimePack.Value.RuntimePackNamePatterns);
@@ -372,6 +373,13 @@ var runtimeRequiredByDeployment
continue;
}
+ if (runtimeIdentifier == "any")
+ {
+ // The `any` RID represents a platform-agnostic target. As such, it has no
+ // platform-specific runtime pack associated with it.
+ continue;
+ }
+
// Pass in null for the runtimePacks list, as for these runtime identifiers we only want to
// download the runtime packs, but not use the assets from them
ProcessRuntimeIdentifier(runtimeIdentifier, runtimePackForRuntimeIDProcessing, runtimePackVersion, additionalFrameworkReferencesForRuntimePack: null,
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs
index ae5308daad31..00c6bf6fdcc3 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/ResolveAppHosts.cs
@@ -188,6 +188,13 @@ protected override void ExecuteCore()
{
foreach (var otherRuntimeIdentifier in OtherRuntimeIdentifiers)
{
+ // The 'any' RID represents a platform-agnostic platform. As such, it has no
+ // apphost pack associated with it.
+ if (otherRuntimeIdentifier == "any")
+ {
+ continue;
+ }
+
// Download any apphost packages for other runtime identifiers.
// This allows you to specify the list of RIDs in RuntimeIdentifiers and only restore once,
// and then build for each RuntimeIdentifier without restoring separately.
diff --git a/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs b/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs
index d9da99103c5c..fdf93944da13 100644
--- a/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs
+++ b/test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs
@@ -260,11 +260,9 @@ public void PackageToolWithAnyRid()
.And.Satisfy(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");
+ var topLevelPackage = packages.FirstOrDefault(p => p.EndsWith($"{packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg"));
+ topLevelPackage.Should().NotBeNull($"Package {packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg should be present in the tool packages directory")
+ .And.Satisfy(SupportAllOfTheseRuntimes([.. expectedRids, "any"]));
}
[Fact]
@@ -353,6 +351,45 @@ public void StripsPackageTypesFromInnerToolPackages()
foundRids.Should().BeEquivalentTo(expectedRids, "The top-level package should declare all of the RIDs for the tools it contains");
}
+ [Fact]
+ public void MixedPackageTypesBuildInASingleBatchSuccessfully()
+ {
+ var toolSettings = new TestToolBuilder.TestToolSettings()
+ {
+ RidSpecific = true,
+ IncludeAnyRid = true,
+ SelfContained = true // ensure that the RID-specific packages get runtime packs/assets - but the any RID package does not!
+ };
+ string toolPackagesPath = ToolBuilder.CreateTestTool(Log, toolSettings, collectBinlogs: true);
+
+ var packages = Directory.GetFiles(toolPackagesPath, "*.nupkg");
+ var packageIdentifier = toolSettings.ToolPackageId;
+ var ridSpecificPackages = ToolsetInfo.LatestRuntimeIdentifiers.Split(';');
+ packages.Length.Should().Be(ridSpecificPackages.Length + 1 + 1, "There should be one package for the tool-wrapper and one for each RID, and one for the any rid");
+ foreach (string rid in ridSpecificPackages)
+ {
+ 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)
+ .And.Satisfy(EnsurePackageOnlyHasToolRidPackageType);
+ }
+
+ var agnosticFallbackPackageId = $"{toolSettings.ToolPackageId}.any.{toolSettings.ToolPackageVersion}";
+ var agnosticFallbackPackage = packages.FirstOrDefault(p => p.EndsWith(agnosticFallbackPackageId + ".nupkg"));
+ agnosticFallbackPackage.Should()
+ .NotBeNull($"Package {agnosticFallbackPackageId} should be present in the tool packages directory")
+ .And.Satisfy(EnsurePackageIsFdd)
+ .And.Satisfy(EnsurePackageOnlyHasToolRidPackageType);
+
+ // top-level package should declare all of the rids
+ var topLevelPackage = packages.First(p => p.EndsWith($"{packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg"));
+ topLevelPackage.Should().NotBeNull($"Package {packageIdentifier}.{toolSettings.ToolPackageVersion}.nupkg should be present in the tool packages directory")
+ .And.Satisfy(EnsurePackageHasNoRunner)
+ .And.Satisfy(SupportAllOfTheseRuntimes([..ridSpecificPackages, "any"]));
+ }
+
private Action EnsurePackageHasToolPackageTypeAnd(string[] additionalPackageTypes) => (string packagePath) =>
{
var nuspec = GetPackageNuspec(packagePath);
@@ -363,6 +400,13 @@ private Action EnsurePackageHasToolPackageTypeAnd(string[] additionalPac
.And.BeEquivalentTo(expectedPackageTypes, "The PackageType should be 'DotnetTool'.");
};
+ private Action SupportAllOfTheseRuntimes(string[] runtimes) => (string packagePath) =>
+ {
+ var settingsXml = GetToolSettingsFile(packagePath);
+ var rids = GetRidsInSettingsFile(settingsXml);
+ rids.Should().BeEquivalentTo(runtimes, "The tool settings file should contain all of the specified RuntimeIdentifierPackage elements.");
+ };
+
static void EnsurePackageOnlyHasToolRidPackageType(string packagePath)
{
var nuspec = GetPackageNuspec(packagePath);
diff --git a/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs b/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs
index 45aba2344727..40136f094758 100644
--- a/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs
+++ b/test/Microsoft.DotNet.PackageInstall.Tests/TestToolBuilder.cs
@@ -28,12 +28,28 @@ public class TestToolSettings
public string ToolPackageVersion { get; set; } = "1.0.0";
public string ToolCommandName { get; set; } = "TestTool";
public string[]? AdditionalPackageTypes { get; set; } = null;
-
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;
+
+ ///
+ /// If set, the generated tool will include the any RID in the list of RIDs to target.
+ /// This will cause a framework-dependent, platform-agnostic package to be created.
+ ///
public bool IncludeAnyRid { get; set { field = value; } } = false;
+
+ ///
+ /// If set, the generated tool will target all of the RIDs specified in .
+ /// Defaults to .
+ ///
public bool RidSpecific { get; set; } = false;
+
+ ///
+ /// If set, the generated tool will include the current executing platform's RID in the list of RIDs to target
+ /// (which is otherwise made of .) If set to ,
+ /// the current RID will be stripped from that set.
+ /// Defaults to .
+ ///
public bool IncludeCurrentRid { get; set; } = true;
public string GetIdentifier() {