diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs
index d8afd22d9298..41de1742689c 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs
@@ -14,6 +14,12 @@ namespace Microsoft.NET.Build.Tasks
{
public class GetPackagesToPrune : TaskBase
{
+ // Minimum .NET Core version that supports package pruning
+ private const int FrameworkReferenceMinVersion = 3;
+
+ // Minimum .NET Core version that uses prune package data instead of framework package data
+ private const int PrunePackageDataMinMajorVersion = 10;
+
[Required]
public string TargetFrameworkIdentifier { get; set; }
@@ -35,6 +41,16 @@ public class GetPackagesToPrune : TaskBase
[Required]
public bool AllowMissingPrunePackageData { get; set; }
+ ///
+ /// Specifies whether the task should try to "fall back" to the nearest compatible framework with prune package data
+ /// if the data can't be found for the current TargetFramework. This can be helpful when initially bringing up the
+ /// build for a new framework version.
+ ///
+ /// Note that this does not use NuGet's framework matching algorithm, it simply keeps the same TargetFrameworkIdentifier,
+ /// and decrements the minor version if it's non-zero, or the major version if the minor version is zero.
+ ///
+ public bool LoadPrunePackageDataFromNearestFramework { get; set; }
+
[Output]
public ITaskItem[] PackagesToPrune { get; set; }
@@ -43,11 +59,13 @@ class CacheKey
public string TargetFrameworkIdentifier { get; set; }
public string TargetFrameworkVersion { get; set; }
public HashSet FrameworkReferences { get; set; }
+ public bool LoadPrunePackageDataFromNearestFramework { get; set; }
public override bool Equals(object obj) => obj is CacheKey key &&
TargetFrameworkIdentifier == key.TargetFrameworkIdentifier &&
TargetFrameworkVersion == key.TargetFrameworkVersion &&
- FrameworkReferences.SetEquals(key.FrameworkReferences);
+ FrameworkReferences.SetEquals(key.FrameworkReferences) &&
+ LoadPrunePackageDataFromNearestFramework == key.LoadPrunePackageDataFromNearestFramework;
public override int GetHashCode()
{
#if NET
@@ -58,6 +76,7 @@ public override int GetHashCode()
{
hashCode.Add(frameworkReference);
}
+ hashCode.Add(LoadPrunePackageDataFromNearestFramework);
return hashCode.ToHashCode();
#else
int hashCode = 1436330440;
@@ -68,6 +87,7 @@ public override int GetHashCode()
{
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(frameworkReference);
}
+ hashCode = hashCode * -1521134295 + LoadPrunePackageDataFromNearestFramework.GetHashCode();
return hashCode;
#endif
}
@@ -102,7 +122,8 @@ protected override void ExecuteCore()
{
TargetFrameworkIdentifier = TargetFrameworkIdentifier,
TargetFrameworkVersion = TargetFrameworkVersion,
- FrameworkReferences = runtimeFrameworks.ToHashSet()
+ FrameworkReferences = runtimeFrameworks.ToHashSet(),
+ LoadPrunePackageDataFromNearestFramework = LoadPrunePackageDataFromNearestFramework
};
// Cache framework package values per build
@@ -124,16 +145,12 @@ static TaskItem[] LoadPackagesToPrune(CacheKey key, string[] targetingPackRoots,
var targetFrameworkVersion = Version.Parse(key.TargetFrameworkVersion);
- if (key.FrameworkReferences.Count == 0 && key.TargetFrameworkIdentifier.Equals(".NETCoreApp") && targetFrameworkVersion.Major >= 3)
+ if (key.FrameworkReferences.Count == 0 && key.TargetFrameworkIdentifier.Equals(".NETCoreApp") && targetFrameworkVersion.Major >= FrameworkReferenceMinVersion)
{
// For .NET Core projects (3.0 and higher), don't prune any packages if there are no framework references
return Array.Empty();
}
- // Use hard-coded / generated "framework package data" for .NET 9 and lower, .NET Framework, and .NET Standard
- // Use bundled "prune package data" for .NET 10 and higher. During the redist build, this comes from targeting packs and is laid out in the PrunePackageData folder.
- bool useFrameworkPackageData = !key.TargetFrameworkIdentifier.Equals(".NETCoreApp") || targetFrameworkVersion.Major < 10;
-
// Call DefaultIfEmpty() so that target frameworks without framework references will load data
foreach (var frameworkReference in key.FrameworkReferences.DefaultIfEmpty(""))
{
@@ -147,41 +164,7 @@ static TaskItem[] LoadPackagesToPrune(CacheKey key, string[] targetingPackRoots,
}
log.LogMessage(MessageImportance.Low, $"Loading packages to prune for {key.TargetFrameworkIdentifier} {key.TargetFrameworkVersion} {frameworkReference}");
- Dictionary packagesForFrameworkReference;
- if (useFrameworkPackageData)
- {
- packagesForFrameworkReference = LoadPackagesToPruneFromFrameworkPackages(key.TargetFrameworkIdentifier, key.TargetFrameworkVersion, frameworkReference);
- if (packagesForFrameworkReference != null)
- {
- log.LogMessage("Loaded prune package data from framework packages");
- }
- else
- {
- log.LogMessage("Failed to load prune package data from framework packages");
- }
- }
- else
- {
- log.LogMessage("Loading prune package data from PrunePackageData folder");
- packagesForFrameworkReference = LoadPackagesToPruneFromPrunePackageData(key.TargetFrameworkIdentifier, key.TargetFrameworkVersion, frameworkReference, prunePackageDataRoot);
-
- // For the version of the runtime that matches the current SDK version, we don't include the prune package data in the PrunePackageData folder. Rather,
- // we can load it from the targeting packs that are packaged with the SDK.
- if (packagesForFrameworkReference == null)
- {
- log.LogMessage("Failed to load prune package data from PrunePackageData folder, loading from targeting packs instead");
- packagesForFrameworkReference = LoadPackagesToPruneFromTargetingPack(log, key.TargetFrameworkIdentifier, key.TargetFrameworkVersion, frameworkReference, targetingPackRoots);
- }
-
- // Fall back to framework packages data for older framework for WindowsDesktop if necessary
- // https://github.com/dotnet/windowsdesktop/issues/4904
- if (packagesForFrameworkReference == null && frameworkReference.Equals("Microsoft.WindowsDesktop.App", StringComparison.OrdinalIgnoreCase))
- {
- log.LogMessage("Failed to load prune package data for WindowsDesktop from targeting packs, loading from framework packages instead");
- packagesForFrameworkReference = LoadPackagesToPruneFromFrameworkPackages(key.TargetFrameworkIdentifier, key.TargetFrameworkVersion, frameworkReference,
- acceptNearestMatch: true);
- }
- }
+ Dictionary packagesForFrameworkReference = TryLoadPackagesToPruneForVersion(log, key.TargetFrameworkIdentifier, key.TargetFrameworkVersion, frameworkReference, targetingPackRoots, prunePackageDataRoot, key.LoadPrunePackageDataFromNearestFramework);
if (packagesForFrameworkReference == null)
{
@@ -276,6 +259,83 @@ static Dictionary LoadPackagesToPruneFromTargetingPack(Log
return null;
}
+ static Dictionary TryLoadPackagesToPruneForVersion(Logger log, string targetFrameworkIdentifier, string targetFrameworkVersion, string frameworkReference, string[] targetingPackRoots, string prunePackageDataRoot, bool loadPrunePackageDataFromNearestFramework)
+ {
+ var currentVersion = Version.Parse(targetFrameworkVersion);
+
+ // Try loading for the current version and then iteratively try previous versions if enabled
+ while (true)
+ {
+ string currentVersionString = $"{currentVersion.Major}.{currentVersion.Minor}";
+
+ // Use hard-coded / generated "framework package data" for .NET 9 and lower, .NET Framework, and .NET Standard
+ // Use bundled "prune package data" for .NET 10 and higher. During the redist build, this comes from targeting packs and is laid out in the PrunePackageData folder.
+ bool useFrameworkPackageData = !targetFrameworkIdentifier.Equals(".NETCoreApp") || currentVersion.Major < PrunePackageDataMinMajorVersion;
+
+ Dictionary packages = null;
+
+ if (useFrameworkPackageData)
+ {
+ packages = LoadPackagesToPruneFromFrameworkPackages(targetFrameworkIdentifier, currentVersionString, frameworkReference);
+ if (packages != null)
+ {
+ log.LogMessage("Loaded prune package data from framework packages");
+ }
+ else
+ {
+ log.LogMessage("Failed to load prune package data from framework packages");
+ }
+ }
+ else
+ {
+ log.LogMessage("Loading prune package data from PrunePackageData folder");
+ packages = LoadPackagesToPruneFromPrunePackageData(targetFrameworkIdentifier, currentVersionString, frameworkReference, prunePackageDataRoot);
+
+ // For the version of the runtime that matches the current SDK version, we don't include the prune package data in the PrunePackageData folder. Rather,
+ // we can load it from the targeting packs that are packaged with the SDK.
+ if (packages == null)
+ {
+ log.LogMessage("Failed to load prune package data from PrunePackageData folder, loading from targeting packs instead");
+ packages = LoadPackagesToPruneFromTargetingPack(log, targetFrameworkIdentifier, currentVersionString, frameworkReference, targetingPackRoots);
+ }
+
+ // Fall back to framework packages data for older framework for WindowsDesktop if necessary
+ // https://github.com/dotnet/windowsdesktop/issues/4904
+ if (packages == null && frameworkReference.Equals("Microsoft.WindowsDesktop.App", StringComparison.OrdinalIgnoreCase))
+ {
+ log.LogMessage("Failed to load prune package data for WindowsDesktop from targeting packs, loading from framework packages instead");
+ packages = LoadPackagesToPruneFromFrameworkPackages(targetFrameworkIdentifier, currentVersionString, frameworkReference,
+ acceptNearestMatch: true);
+ }
+ }
+
+ // If we found packages or we're not supposed to fall back, return what we have
+ if (packages != null || !loadPrunePackageDataFromNearestFramework)
+ {
+ return packages;
+ }
+
+ // Determine the next version to try
+ // If minor version is non-zero, decrement it first
+ if (currentVersion.Minor > 0)
+ {
+ currentVersion = new Version(currentVersion.Major, currentVersion.Minor - 1);
+ log.LogMessage($"LoadPrunePackageDataFromNearestFramework is enabled, trying to load from framework version {currentVersion.Major}.{currentVersion.Minor}");
+ }
+ // Otherwise, decrement major version and reset minor to 0
+ else if (currentVersion.Major > 2)
+ {
+ currentVersion = new Version(currentVersion.Major - 1, 0);
+ log.LogMessage($"LoadPrunePackageDataFromNearestFramework is enabled, trying to load from framework version {currentVersion.Major}.{currentVersion.Minor}");
+ }
+ else
+ {
+ // We've exhausted all versions to try
+ return null;
+ }
+ }
+ }
+
static void AddPackagesToPrune(Dictionary packagesToPrune, IEnumerable<(string id, NuGetVersion version)> packagesToAdd, Logger log)
{
foreach (var package in packagesToAdd)
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets
index 9ffde1b6fad3..09a8aa19808e 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets
@@ -61,6 +61,7 @@ Copyright (c) .NET Foundation. All rights reserved.
$(NetCoreRoot)\sdk\$(NETCoreSdkVersion)\PrunePackageData\
$(NetCoreTargetingPackRoot)
false
+ false
+ AllowMissingPrunePackageData="$(AllowMissingPrunePackageData)"
+ LoadPrunePackageDataFromNearestFramework="$(LoadPrunePackageDataFromNearestFramework)">