diff --git a/src/Installer/redist-installer/targets/GenerateBundledVersions.targets b/src/Installer/redist-installer/targets/GenerateBundledVersions.targets index 4643a2ca3419..139ee5ab5ad2 100644 --- a/src/Installer/redist-installer/targets/GenerateBundledVersions.targets +++ b/src/Installer/redist-installer/targets/GenerateBundledVersions.targets @@ -28,7 +28,6 @@ - @@ -535,6 +534,7 @@ Copyright (c) .NET Foundation. All rights reserved. %24([MSBuild]::NormalizePath('%24(MSBuildThisFileDirectory)..\..\')) %24([MSBuild]::EnsureTrailingSlash('%24(NetCoreRoot)'))packs + %24([MSBuild]::EnsureTrailingSlash('%24(MSBuildThisFileDirectory)'))PrunePackageData <_NetFrameworkHostedCompilersVersion>$(MicrosoftNetCompilersToolsetFrameworkPackageVersion) $(_NETCoreAppTargetFrameworkVersion) diff --git a/src/Layout/redist/redist.csproj b/src/Layout/redist/redist.csproj index 12b0173ae396..b64d28cb153b 100644 --- a/src/Layout/redist/redist.csproj +++ b/src/Layout/redist/redist.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Layout/redist/targets/GenerateLayout.targets b/src/Layout/redist/targets/GenerateLayout.targets index b815d7d74b74..98b4d511e141 100644 --- a/src/Layout/redist/targets/GenerateLayout.targets +++ b/src/Layout/redist/targets/GenerateLayout.targets @@ -459,6 +459,7 @@ PublishFSharp; PublishBlazorWasmTools; PublishStaticWebAssetsTools; + GeneratePackagePruneData; GenerateCliRuntimeConfigurationFiles; MakeFscRunnableAndMoveToPublishDir; RemoveFscFilesAfterPublish; diff --git a/src/Layout/redist/targets/GeneratePackagePruneData.targets b/src/Layout/redist/targets/GeneratePackagePruneData.targets new file mode 100644 index 000000000000..ab011d5a124f --- /dev/null +++ b/src/Layout/redist/targets/GeneratePackagePruneData.targets @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + $(TargetFramework) + true + + + @(TargetingPackForPruneDataCollated->'', ' + ') + + +]]> + + + $(PrunePackDownloadProjectContent.Replace(';', '%3B')) + $(IntermediateOutputPath)PrunePackDownloader\ + $(PrunePackDownloadProjectDirectory)PrunePackPackageDownloader.csproj + + + + + + + + + + + $(NuGetPackageRoot)$([MSBuild]::ValueOrDefault('%(Identity)', '').ToLower())/%(PackageVersion)/data/PackageOverrides.txt + $(OutputPath)/PrunePackageData/%(TargetFrameworkVersion)/%(FrameworkName)/PackageOverrides.txt + + + + + + + + diff --git a/src/Tasks/Common/ConflictResolution/PackageOverride.cs b/src/Tasks/Common/ConflictResolution/PackageOverride.cs index cb206fc7b9b6..d401a7c6ad58 100644 --- a/src/Tasks/Common/ConflictResolution/PackageOverride.cs +++ b/src/Tasks/Common/ConflictResolution/PackageOverride.cs @@ -25,14 +25,14 @@ internal class PackageOverride public string PackageName { get; } public Dictionary OverriddenPackages { get; } - private PackageOverride(string packageName, IEnumerable> overriddenPackages) + private PackageOverride(string packageName, IEnumerable<(string id, OverrideVersion version)> overriddenPackages) { PackageName = packageName; OverriddenPackages = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (Tuple package in overriddenPackages) + foreach (var package in overriddenPackages) { - OverriddenPackages[package.Item1] = package.Item2; + OverriddenPackages[package.id] = package.version; } } @@ -44,27 +44,34 @@ public static PackageOverride Create(ITaskItem packageOverrideItem) return new PackageOverride(packageName, CreateOverriddenPackages(overriddenPackagesString)); } - private static IEnumerable> CreateOverriddenPackages(string overriddenPackagesString) + private static IEnumerable<(string id, OverrideVersion version)> CreateOverriddenPackages(string overriddenPackagesString) { if (!string.IsNullOrEmpty(overriddenPackagesString)) { overriddenPackagesString = overriddenPackagesString.Trim(); string[] overriddenPackagesAndVersions = overriddenPackagesString.Split(new char[] { ';', '\r', '\n', ' ' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string overriddenPackagesAndVersion in overriddenPackagesAndVersions) + return CreateOverriddenPackages(overriddenPackagesAndVersions); + } + return Enumerable.Empty<(string id, OverrideVersion version)>(); + } + + public static IEnumerable<(string id, OverrideVersion version)> CreateOverriddenPackages(IEnumerable packageOverrideFileLines) + { + foreach (string overriddenPackagesAndVersion in packageOverrideFileLines) + { + string trimmedOverriddenPackagesAndVersion = overriddenPackagesAndVersion.Trim(); + int separatorIndex = trimmedOverriddenPackagesAndVersion.IndexOf('|'); + if (separatorIndex != -1) { - string trimmedOverriddenPackagesAndVersion = overriddenPackagesAndVersion.Trim(); - int separatorIndex = trimmedOverriddenPackagesAndVersion.IndexOf('|'); - if (separatorIndex != -1) + string versionString = trimmedOverriddenPackagesAndVersion.Substring(separatorIndex + 1); + string overriddenPackage = trimmedOverriddenPackagesAndVersion.Substring(0, separatorIndex); + if (OverrideVersion.TryParse(versionString, out OverrideVersion? version)) { - string versionString = trimmedOverriddenPackagesAndVersion.Substring(separatorIndex + 1); - string overriddenPackage = trimmedOverriddenPackagesAndVersion.Substring(0, separatorIndex); - if (OverrideVersion.TryParse(versionString, out OverrideVersion? version)) - { - yield return Tuple.Create(overriddenPackage, version); - } + yield return (overriddenPackage, version); } } } + } } } diff --git a/src/Tasks/Common/Resources/Strings.resx b/src/Tasks/Common/Resources/Strings.resx index 995f14ea93d5..c64308267967 100644 --- a/src/Tasks/Common/Resources/Strings.resx +++ b/src/Tasks/Common/Resources/Strings.resx @@ -978,9 +978,13 @@ You may need to build the project on another operating system or architecture, o NETSDK1224: ASP.NET Core framework assets are not supported for the target framework. {StrBegins="NETSDK1224: "} - NETSDK1225: Native compilation is not supported when invoking the Publish target directly. Try running dotnet publish. {StrBegins="NETSDK1225: "} + + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + diff --git a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf index b54862b41a69..5fb24ba71bd3 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: Nástroje projektu (DotnetCliTool) podporují jen cílení na .NET Core 2.2 a nižší. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: Profil publikování s názvem {0} se v projektu nenašel. Nastavte vlastnost PublishProfile na platný název souboru. diff --git a/src/Tasks/Common/Resources/xlf/Strings.de.xlf b/src/Tasks/Common/Resources/xlf/Strings.de.xlf index 93de43041e8f..5b6df335522b 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.de.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.de.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: Projekttools (DotnetCliTool) unterstützen als Ziel nur .NET Core 2.2 und früher. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: Im Projekt wurde kein Veröffentlichungsprofil mit dem Namen "{0}" gefunden. Legen Sie die PublishProfile-Eigenschaft auf einen gültigen Dateinamen fest. diff --git a/src/Tasks/Common/Resources/xlf/Strings.es.xlf b/src/Tasks/Common/Resources/xlf/Strings.es.xlf index 76822ef6d9f2..04e6af9d54c9 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.es.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.es.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: Las herramientas de proyecto (DotnetCliTool) solo admiten como destino .NET Core 2.2 y versiones inferiores. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: No se encontró un perfil de publicación con el nombre '{0}' en el proyecto. Establezca la propiedad PublishProfile en un nombre de archivo válido. diff --git a/src/Tasks/Common/Resources/xlf/Strings.fr.xlf b/src/Tasks/Common/Resources/xlf/Strings.fr.xlf index 7036da8215f4..97da1b1b8c67 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.fr.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.fr.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: les outils de projet (DotnetCliTool) prennent uniquement en charge le ciblage de .NET Core 2.2 et des versions antérieures. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: un profil de publication portant le nom '{0}' est introuvable dans le projet. Affectez un nom de fichier valide à la propriété PublishProfile. diff --git a/src/Tasks/Common/Resources/xlf/Strings.it.xlf b/src/Tasks/Common/Resources/xlf/Strings.it.xlf index c8a8c654d286..e300af1729af 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.it.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.it.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: gli strumenti del progetto (DotnetCliTool) supportano come destinazione solo .NET Core 2.2 e versioni precedenti. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: un profilo di pubblicazione denominato '{0}' non è stato trovato nel progetto. Impostare la proprietà PublishProfile su un nome file valido. diff --git a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf index 2763c883faa8..352d029a6a5d 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: プロジェクト ツール (DotnetCliTool) は、ターゲットが .NET Core 2.2 以下の場合のみサポートされます。 {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: '{0}' という名前の発行プロファイルがプロジェクトに見つかりませんでした。PublishProfile プロパティを有効なファイル名に設定してください。 diff --git a/src/Tasks/Common/Resources/xlf/Strings.ko.xlf b/src/Tasks/Common/Resources/xlf/Strings.ko.xlf index 5c4a7f678d5b..7da884efc812 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ko.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ko.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: 프로젝트 도구(DotnetCliTool)는 .NET Core 2.2 이하를 대상으로 하는 경우만 지원합니다. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: 이름이 '{0}'인 게시 프로필을 프로젝트에서 찾을 수 없습니다. PublishProfile 속성을 유효한 파일 이름으로 설정합니다. diff --git a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf index 847799ff9806..0d941957b36b 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: Narzędzia projektu (DotnetCliTool) obsługują tylko ukierunkowanie na program .NET Core w wersji 2.2 lub niższej. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: w projekcie nie znaleziono profilu publikowania o nazwie „{0}”. Ustaw prawidłową nazwę pliku dla właściwości PublishProfile. diff --git a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf index f9f0fd16d51c..22b5e74189e5 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: as ferramentas do projeto (DotnetCliTool) dão suporte somente para o direcionamento ao .NET Core 2.2 e inferior. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: Um perfil de publicação com o nome '{0}' não foi encontrado no projeto. Defina a propriedade PublishProfile como um nome de arquivo válido. diff --git a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf index 7770f64b31fc..84621a1872ed 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: Project tools (DotnetCliTool) only support targeting .NET Core 2.2 and lower. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. diff --git a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf index 0b6f7aa60b32..78fe0194edbd 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: Proje araçları (DotnetCliTool) yalnızca .NET Core 2.2 veya daha düşük sürümünü hedeflemeyi destekliyor. {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: Projede '{0}' adlı bir yayınlama profili bulunamadı. PublishProfile özelliğini geçerli bir dosya adına ayarlayın. diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf index e1037cfdecad..a611d1e22382 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: NETSDK1093: 项目工具(DotnetCliTool)仅支持面向 .NET Core 2.2 及更低版本。 {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: 在项目中找不到名为“{0}”的发布配置文件。请将 PublishProfile 属性设置为有效的文件名。 diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf index 61f0302195c7..78df7fff2522 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf @@ -791,6 +791,11 @@ The following are names of parameters or literal values and should not be transl NETSDK1093: 專案工具 (DotnetCliTool) 僅支援以 .NET Core 2.2 或更低版本作為目標。 {StrBegins="NETSDK1093: "} + + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + NETSDK1226: Prune Package data not found {0} {1} {2}. To ignore this error, set the AllowMissingPrunePackageData to true. + {StrBegins="NETSDK1226: "} + NETSDK1198: A publish profile with the name '{0}' was not found in the project. Set the PublishProfile property to a valid file name. NETSDK1198: 在專案中找不到名稱為 '{0}' 的發行設定檔。請將 PublishProfile 屬性設定為有效的檔案名稱。 diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/FrameworkPackages/FrameworkPackages.cs b/src/Tasks/Microsoft.NET.Build.Tasks/FrameworkPackages/FrameworkPackages.cs index 7cad5e407067..051b885a847f 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/FrameworkPackages/FrameworkPackages.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/FrameworkPackages/FrameworkPackages.cs @@ -8,6 +8,8 @@ namespace Microsoft.ComponentDetection.Detectors.NuGet; using System.Linq; using global::NuGet.Frameworks; using global::NuGet.Versioning; +using Microsoft.NET.Build.Tasks; +using Microsoft.NET.Build.Tasks.ConflictResolution; /// /// Represents a set of packages that are provided by a specific framework. @@ -73,56 +75,58 @@ internal static void Register(params FrameworkPackages[] toRegister) } } - public static FrameworkPackages[] GetFrameworkPackages(NuGetFramework framework, string[] frameworkReferences, string targetingPackRoot) + public static FrameworkPackages[] GetFrameworkPackages(NuGetFramework framework, string[] frameworkReferences, bool acceptNearestMatch = false) { var frameworkPackages = new List(); + if (frameworkReferences.Length == 0) + { + frameworkReferences = [DefaultFrameworkKey]; + } + foreach (var frameworkReference in frameworkReferences) { var frameworkKey = GetFrameworkKey(frameworkReference); - if (FrameworkPackagesByFramework.TryGetValue(framework, out var frameworkPackagesForVersion) && - frameworkPackagesForVersion.TryGetValue(frameworkKey, out var frameworkPackage)) + + NuGetFramework nearestFramework; + + if (acceptNearestMatch) { - frameworkPackages.Add(frameworkPackage); + var reducer = new FrameworkReducer(); + var candidateFrameworks = FrameworkPackagesByFramework.Where(pair => pair.Value.ContainsKey(frameworkKey)).Select(pair => pair.Key); + nearestFramework = reducer.GetNearest(framework, candidateFrameworks); } else { - // if we didn't predefine the package overrides, load them from the targeting pack - // we might just leave this out since in future frameworks we'll have this functionality built into NuGet.Frameworks - var frameworkPackagesFromPack = LoadFrameworkPackagesFromPack(framework, frameworkReference, targetingPackRoot) ?? new FrameworkPackages(framework, frameworkReference); - - Register(frameworkPackagesFromPack); + nearestFramework = framework; + } - frameworkPackages.Add(frameworkPackagesFromPack); + if (FrameworkPackagesByFramework.TryGetValue(nearestFramework, out var frameworkPackagesForVersion) && + frameworkPackagesForVersion.TryGetValue(frameworkKey, out var frameworkPackage)) + { + frameworkPackages.Add(frameworkPackage); } } return frameworkPackages.ToArray(); } - private static FrameworkPackages LoadFrameworkPackagesFromPack(NuGetFramework framework, string frameworkName, string targetingPackRoot) + public static FrameworkPackages LoadFrameworkPackagesFromPack(Logger log, NuGetFramework framework, string frameworkName, string targetingPackRoot) { if (framework is null || framework.Framework != FrameworkConstants.FrameworkIdentifiers.NetCoreApp) { return null; } - var reducer = new FrameworkReducer(); - var frameworkKey = GetFrameworkKey(frameworkName); - var candidateFrameworks = FrameworkPackagesByFramework.Where(pair => pair.Value.ContainsKey(frameworkKey)).Select(pair => pair.Key); - var nearestFramework = reducer.GetNearest(framework, candidateFrameworks); - - var frameworkPackages = nearestFramework is null ? - new FrameworkPackages(framework, frameworkName) : - new FrameworkPackages(framework, frameworkName, FrameworkPackagesByFramework[nearestFramework][frameworkKey]); - if (!string.IsNullOrEmpty(targetingPackRoot)) { var packsFolder = Path.Combine(targetingPackRoot, frameworkName + ".Ref"); + log.LogMessage("Looking for targeting packs in {0}", packsFolder); if (Directory.Exists(packsFolder)) { var packVersionPattern = $"{framework.Version.Major}.{framework.Version.Minor}.*"; var packDirectories = Directory.GetDirectories(packsFolder, packVersionPattern); + log.LogMessage("Pack directories found: {0}", string.Join(Environment.NewLine, packDirectories)); var packageOverridesFile = packDirectories .Select(d => (Overrides: Path.Combine(d, "data", "PackageOverrides.txt"), Version: ParseVersion(Path.GetFileName(d)))) .Where(d => File.Exists(d.Overrides)) @@ -131,26 +135,30 @@ private static FrameworkPackages LoadFrameworkPackagesFromPack(NuGetFramework fr if (packageOverridesFile is not null) { + log.LogMessage("Found package overrides file {0}", packageOverridesFile); // Adapted from https://github.com/dotnet/sdk/blob/c3a8f72c3a5491c693ff8e49e7406136a12c3040/src/Tasks/Common/ConflictResolution/PackageOverride.cs#L52-L68 - var packageOverrides = File.ReadAllLines(packageOverridesFile); + var packageOverrideLines = File.ReadAllLines(packageOverridesFile); - foreach (var packageOverride in packageOverrides) + var frameworkPackages = new FrameworkPackages(framework, frameworkName); + foreach (var packageOverride in PackageOverride.CreateOverriddenPackages(packageOverrideLines)) { - var packageOverrideParts = packageOverride.Trim().Split('|'); - - if (packageOverrideParts.Length == 2) - { - var packageId = packageOverrideParts[0]; - var packageVersion = ParseVersion(packageOverrideParts[1]); - - frameworkPackages.Packages[packageId] = packageVersion; - } + frameworkPackages.Packages[packageOverride.Item1] = packageOverride.Item2; } + + return frameworkPackages; } + else + { + log.LogMessage("No package overrides found in {0}", packsFolder); + } + } + else + { + log.LogMessage("Targeting pack folder {0} does not exist", packsFolder); } } - return frameworkPackages; + return null; static NuGetVersion ParseVersion(string versionString) => NuGetVersion.TryParse(versionString, out var version) ? version : null; } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs index 07d087be1721..e5585669513d 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/GetPackagesToPrune.cs @@ -6,6 +6,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.ComponentDetection.Detectors.NuGet; +using Microsoft.NET.Build.Tasks.ConflictResolution; using NuGet.Frameworks; using NuGet.Versioning; @@ -22,7 +23,17 @@ public class GetPackagesToPrune : TaskBase [Required] public ITaskItem[] FrameworkReferences { get; set; } - public string TargetingPackRoot { get; set; } + [Required] + public ITaskItem[] TargetingPacks { get; set; } + + [Required] + public string[] TargetingPackRoots { get; set; } + + [Required] + public string PrunePackageDataRoot { get; set; } + + [Required] + public bool AllowMissingPrunePackageData { get; set; } [Output] public ITaskItem[] PackagesToPrune { get; set; } @@ -72,51 +83,208 @@ protected override void ExecuteCore() var filteredFrameworkReferences = FrameworkReferences.Where( i => i.GetMetadata("IsTransitiveFrameworkReference") is string transitiveVal && !transitiveVal.Equals("true", StringComparison.OrdinalIgnoreCase)).ToList(); + // Map framework references to runtime frameworks, so we can correctly handle framework references to profiles. + // For example, for a framework reference of Microsoft.WindowsDesktop.App.WindowsForms, we map it to the + // runtime framework of Microsoft.WindowsDesktop.App, which is what the pruned packages are defined in terms of + List runtimeFrameworks = new List(); + + foreach (var frameworkReference in filteredFrameworkReferences) + { + // Number of framework references is generally low enough that it's not worth putting the targeting packs into a hash set + var targetingPack = TargetingPacks.FirstOrDefault(tp => tp.ItemSpec.Equals(frameworkReference.ItemSpec, StringComparison.OrdinalIgnoreCase)); + if (targetingPack != null) + { + runtimeFrameworks.Add(targetingPack.GetMetadata("RuntimeFrameworkName")); + } + } + CacheKey key = new() { TargetFrameworkIdentifier = TargetFrameworkIdentifier, TargetFrameworkVersion = TargetFrameworkVersion, - FrameworkReferences = filteredFrameworkReferences.Select(i => i.ItemSpec).ToHashSet() + FrameworkReferences = runtimeFrameworks.ToHashSet() }; // Cache framework package values per build var existingResult = BuildEngine4.GetRegisteredTaskObject(key, RegisteredTaskObjectLifetime.Build); if (existingResult != null) { - PackagesToPrune = (ITaskItem[])existingResult; + PackagesToPrune = (TaskItem[])existingResult; return; } - var nugetFramework = new NuGetFramework(TargetFrameworkIdentifier, Version.Parse(TargetFrameworkVersion)); + PackagesToPrune = LoadPackagesToPrune(key, TargetingPackRoots, PrunePackageDataRoot, Log, AllowMissingPrunePackageData); + + BuildEngine4.RegisterTaskObject(key, PackagesToPrune, RegisteredTaskObjectLifetime.Build, true); + } + static TaskItem[] LoadPackagesToPrune(CacheKey key, string[] targetingPackRoots, string prunePackageDataRoot, Logger log, bool allowMissingPrunePackageData) + { Dictionary packagesToPrune = new(); - var frameworkPackages = FrameworkPackages.GetFrameworkPackages(nugetFramework, filteredFrameworkReferences.Select(fr => fr.ItemSpec).ToArray(), TargetingPackRoot) - .SelectMany(packages => packages); + var targetFrameworkVersion = Version.Parse(key.TargetFrameworkVersion); - foreach (var kvp in frameworkPackages) + if (key.FrameworkReferences.Count == 0 && key.TargetFrameworkIdentifier.Equals(".NETCoreApp") && targetFrameworkVersion.Major >= 3) { - if (packagesToPrune.TryGetValue(kvp.Key, out NuGetVersion existingVersion)) + // 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("")) + { + // Filter out framework references we don't expect to have prune data for, such as Microsoft.Windows.SDK.NET.Ref + if (!frameworkReference.Equals(string.Empty, StringComparison.OrdinalIgnoreCase) && + !frameworkReference.Equals("Microsoft.NETCore.App", StringComparison.OrdinalIgnoreCase) && + !frameworkReference.Equals("Microsoft.AspNetCore.App", StringComparison.OrdinalIgnoreCase) && + !frameworkReference.Equals("Microsoft.WindowsDesktop.App", StringComparison.OrdinalIgnoreCase)) { - if (kvp.Value > existingVersion) + continue; + } + 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 { - packagesToPrune[kvp.Key] = kvp.Value; + log.LogMessage("Failed to load prune package data from framework packages"); } } else { - packagesToPrune[kvp.Key] = kvp.Value; + 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); + } + } + + if (packagesForFrameworkReference == null) + { + // We didn't find the data for packages to prune. This indicates that there's a bug in the SDK construction, so fail hard here so that we fix that + // (rather than a warning that might be missed). + // Since this indicates an error in the SDK build, the message probably doesn't need to be localized. + + if (allowMissingPrunePackageData) + { + log.LogMessage($"Prune package data not found for {key.TargetFrameworkIdentifier} {key.TargetFrameworkVersion} {frameworkReference}"); + } + else + { + log.LogError(Strings.PrunePackageDataNotFound, key.TargetFrameworkIdentifier, key.TargetFrameworkVersion, frameworkReference); + } + } + else + { + AddPackagesToPrune(packagesToPrune, packagesForFrameworkReference.Select(kvp => (kvp.Key, kvp.Value)), log); } } - PackagesToPrune = packagesToPrune.Select(p => + return packagesToPrune.Select(p => { var item = new TaskItem(p.Key); - item.SetMetadata("Version", p.Value.ToString()); + + // If a given version of a package is included in a framework, assume that any patches + // to that package will be included in patches to the framework, and thus should be pruned. + // See https://github.com/dotnet/sdk/issues/44566 + // To do this, we set the patch version for the package to be pruned to 32767, which should be + // higher than any actual patch version. + var maxPatch = new NuGetVersion(p.Value.Major, p.Value.Minor, 32767); + + item.SetMetadata("Version", maxPatch.ToString()); return item; }).ToArray(); + } - BuildEngine4.RegisterTaskObject(key, PackagesToPrune, RegisteredTaskObjectLifetime.Build, true); + static Dictionary LoadPackagesToPruneFromFrameworkPackages(string targetFrameworkIdentifier, string targetFrameworkVersion, string frameworkReference, bool acceptNearestMatch = false) + { + var nugetFramework = new NuGetFramework(targetFrameworkIdentifier, Version.Parse(targetFrameworkVersion)); + + // FrameworkPackages just has data for .NET Framework 4.6.1 and doesn't handle framework compatibility, so treat anything greater than .NET Framework as if it were 4.6.1 + if (nugetFramework.IsDesktop() && nugetFramework.Version > new Version(4,6,1)) + { + nugetFramework = NuGetFramework.Parse("net461"); + } + + var frameworkPackages = FrameworkPackages.GetFrameworkPackages(nugetFramework, [frameworkReference], acceptNearestMatch) + .SelectMany(packages => packages) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + return frameworkPackages; + } + + static Dictionary LoadPackagesToPruneFromPrunePackageData(string targetFrameworkIdentifier, string targetFrameworkVersion, string frameworkReference, string prunePackageDataRoot) + { + if (frameworkReference.Equals("Microsoft.NETCore.App", StringComparison.OrdinalIgnoreCase)) + { + string packageOverridesPath = Path.Combine(prunePackageDataRoot, targetFrameworkVersion, frameworkReference, "PackageOverrides.txt"); + if (File.Exists(packageOverridesPath)) + { + var packageOverrideLines = File.ReadAllLines(packageOverridesPath); + var overrides = PackageOverride.CreateOverriddenPackages(packageOverrideLines); + return overrides.ToDictionary(o => o.id, o => o.version); + } + } + + return null; + } + + static Dictionary LoadPackagesToPruneFromTargetingPack(Logger log, string targetFrameworkIdentifier, string targetFrameworkVersion, string frameworkReference, string [] targetingPackRoots) + { + var nugetFramework = new NuGetFramework(targetFrameworkIdentifier, Version.Parse(targetFrameworkVersion)); + + foreach (var targetingPackRoot in targetingPackRoots) + { + var frameworkPackages = FrameworkPackages.LoadFrameworkPackagesFromPack(log, nugetFramework, frameworkReference, targetingPackRoot) + ?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + if (frameworkPackages != null) + { + // We found the framework packages in the targeting pack, so return them + return frameworkPackages; + } + } + return null; + } + + static void AddPackagesToPrune(Dictionary packagesToPrune, IEnumerable<(string id, NuGetVersion version)> packagesToAdd, Logger log) + { + foreach (var package in packagesToAdd) + { + // There are some "inconsistent" versions in the FrameworkPackages data. This happens because, for example, the ASP.NET Core shared framework for .NET 9 inherits + // from the ASP.NET Core shared framework for .NET 8, but not from the base Microsoft.NETCore.App framework for .NET 9. So for something like System.IO.Pipelines, + // which was in ASP.NET in .NET 8 but moved to the base shared framework in .NET 9, we will see an 8.0 version from the ASP.NET shared framework and a 9.0 version + // from the base shared framework. As long as the base shared framework is always referenced together with the ASP.NET shared framework, this shouldn't be a + // problem, and we can just pick the latest version of the package that we see. + if (!packagesToPrune.TryGetValue(package.id, out NuGetVersion existingVersion) || package.version > existingVersion) + { + packagesToPrune[package.id] = package.version; + } + } } } } 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 e4923fb2c1e9..324d7a1ba638 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 @@ -52,12 +52,22 @@ Copyright (c) .NET Foundation. All rights reserved. + + $(NetCoreRoot)\sdk\$(NETCoreSdkVersion)\PrunePackageData\ + $(NetCoreTargetingPackRoot) + false + + + TargetingPacks="@(TargetingPack)" + TargetingPackRoots="$(PrunePackageTargetingPackRoots)" + PrunePackageDataRoot="$(PrunePackageDataRoot)" + AllowMissingPrunePackageData="$(AllowMissingPrunePackageData)"> diff --git a/src/Tasks/sdk-tasks/CollatePackageDownloads.cs b/src/Tasks/sdk-tasks/CollatePackageDownloads.cs new file mode 100644 index 000000000000..2a99eb9b9f56 --- /dev/null +++ b/src/Tasks/sdk-tasks/CollatePackageDownloads.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.Build.Tasks +{ + // Multiple PackageDownload items for the same package are not supported. Rather, to download multiple versions of the same + // package, the PackageDownload items can have a semicolon-separated list of versions (each in brackets) as the Version metadata. + // So this task groups a list of items with PackageVersion metadata into a list of items which can be used as PackageDownloads + public class CollatePackageDownloads : Task + { + [Required] + public ITaskItem[] Packages { get; set; } + + [Output] + public ITaskItem [] PackageDownloads { get; set; } + + public override bool Execute() + { + PackageDownloads = Packages.GroupBy(p => p.ItemSpec) + .Select(g => + { + var packageDownloadItem = new TaskItem(g.Key); + packageDownloadItem.SetMetadata("Version", string.Join(";", + g.Select(p => "[" + p.GetMetadata("PackageVersion") + "]"))); + return packageDownloadItem; + }).ToArray(); + + return true; + } + } +} diff --git a/src/Tasks/sdk-tasks/GeneratePackagePruneDataDownloads.cs b/src/Tasks/sdk-tasks/GeneratePackagePruneDataDownloads.cs new file mode 100644 index 000000000000..ed430e45ac60 --- /dev/null +++ b/src/Tasks/sdk-tasks/GeneratePackagePruneDataDownloads.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NuGet.Versioning; + +namespace Microsoft.DotNet.Build.Tasks +{ + /// + /// Generates a list of targeting packs to download. The package prune data will be extracted from these targeting packs + /// + public class GeneratePackagePruneDataDownloads : Task + { + [Required] + public string NETCoreAppTargetFrameworkVersion { get; set; } + + [Output] + public ITaskItem[] TargetingPackDownloads { get; set; } + + public override bool Execute() + { + List targetingPackDownloads = new(); + + static TaskItem CreateDownload(string frameworkName, string netVersion) + { + var item = new TaskItem($"{frameworkName}.Ref"); + item.SetMetadata("PackageVersion", $"{netVersion}.0"); + item.SetMetadata("TargetFrameworkVersion", netVersion); + item.SetMetadata("FrameworkName", frameworkName); + return item; + } + + void AddTargetingPackDownloads(string netVersion) + { + targetingPackDownloads.Add(CreateDownload("Microsoft.NETCore.App", netVersion)); + targetingPackDownloads.Add(CreateDownload("Microsoft.AspNetCore.App", netVersion)); + targetingPackDownloads.Add(CreateDownload("Microsoft.WindowsDesktop.App", netVersion)); + } + + int maxNetVersion = NuGetVersion.Parse(NETCoreAppTargetFrameworkVersion).Major; + + // Add targeting packs for .NET 10 and higher. Targeting packs for previous versions don't have entirely accurate data, + // so we use the FrameworkPackages data which was generated by running conflict resolution to determine which packages + // would not be used. + // HOWEVER: we don't download the targeting pack for the maximum .NET version here, as we may still be in preview. + // Rather, the GetPackagesToPrune task will load the package prune data for the current version from the + // targeting packs that ship with the SDK. + for (int netVersion = 10; netVersion < maxNetVersion; netVersion++) + { + AddTargetingPackDownloads($"{netVersion}.0"); + } + + TargetingPackDownloads = targetingPackDownloads.ToArray(); + + + return true; + } + } +} diff --git a/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets b/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets index 2f38a9367e51..a839315614f0 100644 --- a/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets +++ b/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets @@ -14,11 +14,13 @@ + + diff --git a/test/Microsoft.NET.Build.Tests/GivenFrameworkReferences.cs b/test/Microsoft.NET.Build.Tests/GivenFrameworkReferences.cs index 3f3cd1adf212..784b7c860fc7 100644 --- a/test/Microsoft.NET.Build.Tests/GivenFrameworkReferences.cs +++ b/test/Microsoft.NET.Build.Tests/GivenFrameworkReferences.cs @@ -307,6 +307,10 @@ public void TargetingPackDownloadCanBeDisabled() // Set targeting pack folder to nonexistent folder so the project won't use installed targeting packs testProject.AdditionalProperties["NetCoreTargetingPackRoot"] = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + // Package pruning may load data from the targeting packs directory. Since we're disabling the targeting pack + // root, we need to allow it to succeed even if it can't find that data. + testProject.AdditionalProperties["AllowMissingPrunePackageData"] = "true"; + var testAsset = _testAssetsManager.CreateTestProject(testProject); string nugetPackagesFolder = Path.Combine(testAsset.TestRoot, "packages"); diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs index ba3e1703ae9d..9c17f2a0739c 100644 --- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs +++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs @@ -8,6 +8,7 @@ using NuGet.Common; using NuGet.Frameworks; using NuGet.ProjectModel; +using NuGet.Versioning; namespace Microsoft.NET.Build.Tests { @@ -310,6 +311,90 @@ public void PlatformPackagesCanBePruned(bool prunePackages) } } + [CoreMSBuildOnlyTheory] + [InlineData(ToolsetInfo.CurrentTargetFramework)] + [InlineData("net9.0")] + [InlineData("net8.0")] + [InlineData("net7.0")] + [InlineData("net6.0")] + [InlineData("netcoreapp3.1")] + [InlineData("netcoreapp3.0")] + [InlineData("netcoreapp2.1")] + [InlineData("netcoreapp2.0")] + [InlineData("netcoreapp1.1", false)] + [InlineData("netcoreapp1.0", false)] + [InlineData("netstandard2.1")] + [InlineData("netstandard2.0")] + [InlineData("netstandard1.1", false)] + [InlineData("netstandard1.0", false)] + [InlineData("net451", false)] + [InlineData("net462")] + [InlineData("net481")] + public void PrunePackageDataSucceeds(string targetFramework, bool shouldPrune = true) + { + var nugetFramework = NuGetFramework.Parse(targetFramework); + + List> GetPrunedPackages(string frameworkReference) + { + var testProject = new TestProject() + { + TargetFrameworks = targetFramework + }; + + testProject.AdditionalProperties["RestoreEnablePackagePruning"] = "True"; + + if (!string.IsNullOrEmpty(frameworkReference)) + { + testProject.FrameworkReferences.Add(frameworkReference); + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && frameworkReference != null && frameworkReference.StartsWith("Microsoft.WindowsDesktop", StringComparison.OrdinalIgnoreCase)) + { + testProject.AdditionalProperties["EnableWindowsTargeting"] = "True"; + } + + var testAsset = _testAssetsManager.CreateTestProject(testProject, callingMethod: nameof(PrunePackageDataSucceeds), identifier: targetFramework + frameworkReference); + + var buildCommand = new BuildCommand(testAsset); + + var prunePackageItemFile = Path.Combine(testAsset.TestRoot, "prunePackageItems.txt"); + + buildCommand.Execute("/t:CollectPrunePackageReferences", "-getItem:PrunePackageReference", $"-getResultOutputFile:{prunePackageItemFile}").Should().Pass(); + + var prunedPackages = ParsePrunePackageReferenceJson(File.ReadAllText(prunePackageItemFile)); + + foreach (var kvp in prunedPackages) + { + NuGetVersion.Parse(kvp.Value).Patch.Should().BeGreaterThan(99, $"Patch for {kvp.Key} should be at least 100"); + } + + return prunedPackages; + } + + var prunedPackages = GetPrunedPackages(""); + if (shouldPrune) + { + prunedPackages.Should().NotBeEmpty(); + } + else + { + prunedPackages.Should().BeEmpty(); + } + + if (nugetFramework.Framework.Equals(".NETCoreApp", StringComparison.OrdinalIgnoreCase) && nugetFramework.Version.Major >= 3) + { + foreach(var frameworkReference in new [] { + "Microsoft.AspNetCore.App", + "Microsoft.WindowsDesktop.App", + "Microsoft.WindowsDesktop.App.WindowsForms", + }) + { + var frameworkPrunedPackages = GetPrunedPackages(frameworkReference); + frameworkPrunedPackages.Count.Should().BeGreaterThan(prunedPackages.Count, frameworkReference + " should have more pruned packages than base framework"); + } + } + } + [Fact] public void TransitiveFrameworkReferencesDoNotAffectPruning() { @@ -337,27 +422,28 @@ public void TransitiveFrameworkReferencesDoNotAffectPruning() var itemsResult1 = getItemsCommand1.Execute("-getItem:PrunePackageReference"); itemsResult1.Should().Pass(); - var items1 = ParseItemsJson(itemsResult1.StdOut); + var items1 = ParsePrunePackageReferenceJson(itemsResult1.StdOut); var getItemsCommand2 = new MSBuildCommand(testAsset, "ResolvePackageAssets;AddTransitiveFrameworkReferences;AddPrunePackageReferences"); var itemsResult2 = getItemsCommand2.Execute("-getItem:PrunePackageReference"); itemsResult2.Should().Pass(); - var items2 = ParseItemsJson(itemsResult2.StdOut); + var items2 = ParsePrunePackageReferenceJson(itemsResult2.StdOut); items2.Should().BeEquivalentTo(items1); - static List> ParseItemsJson(string json) + } + + static List> ParsePrunePackageReferenceJson(string json) + { + List> ret = new(); + var root = JsonNode.Parse(json); + var items = (JsonArray)root["Items"]["PrunePackageReference"]; + foreach (var item in items) { - List> ret = new(); - var root = JsonNode.Parse(json); - var items = (JsonArray) root["Items"]["PrunePackageReference"]; - foreach (var item in items) - { - ret.Add(new KeyValuePair((string)item["Identity"], (string)item["Version"])); - } - return ret; + ret.Add(new KeyValuePair((string)item["Identity"], (string)item["Version"])); } + return ret; } } } diff --git a/test/Microsoft.NET.Build.Tests/GivenTransitiveFrameworkReferencesAreDisabled.cs b/test/Microsoft.NET.Build.Tests/GivenTransitiveFrameworkReferencesAreDisabled.cs index c2d29551f949..9ceccc49e548 100644 --- a/test/Microsoft.NET.Build.Tests/GivenTransitiveFrameworkReferencesAreDisabled.cs +++ b/test/Microsoft.NET.Build.Tests/GivenTransitiveFrameworkReferencesAreDisabled.cs @@ -61,6 +61,10 @@ void TestPackagesNotDownloaded(bool referenceAspNet, bool selfContained, [Caller // Set packs folder to nonexistent folder so the project won't use installed targeting or runtime packs testProject.AdditionalProperties["NetCoreTargetingPackRoot"] = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + // Package pruning may load data from the targeting packs directory. Since we're disabling the targeting pack + // root, we need to allow it to succeed even if it can't find that data. + testProject.AdditionalProperties["AllowMissingPrunePackageData"] = "true"; + var testAsset = _testAssetsManager.CreateTestProject(testProject, testName, identifier: referenceAspNet.ToString()); var buildCommand = new BuildCommand(testAsset); @@ -128,6 +132,10 @@ public void TransitiveFrameworkReferenceGeneratesError() // Set packs folder to nonexistent folder so the project won't use installed targeting or runtime packs testProject.AdditionalProperties["NetCoreTargetingPackRoot"] = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + // Package pruning may load data from the targeting packs directory. Since we're disabling the targeting pack + // root, we need to allow it to succeed even if it can't find that data. + testProject.AdditionalProperties["AllowMissingPrunePackageData"] = "true"; + testProject.ReferencedProjects.Add(referencedProject); var testAsset = _testAssetsManager.CreateTestProject(testProject); diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToStoreAProjectWithDependencies.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToStoreAProjectWithDependencies.cs index 036cc21f146a..52740045748f 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToStoreAProjectWithDependencies.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToStoreAProjectWithDependencies.cs @@ -78,12 +78,6 @@ public void compose_dependencies() "fluentassertions.json/4.12.0/lib/netstandard1.3/FluentAssertions.Json.dll" }; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // https://github.com/dotnet/core-setup/issues/2716 - an unintended native shim is getting published to the runtime store - files_on_disk.Add($"runtime.{_runtimeRid}.runtime.native.system.security.cryptography/1.0.1/runtimes/{_runtimeRid}/native/System.Security.Cryptography.Native{FileConstants.DynamicLibSuffix}"); - } - storeDirectory.Should().OnlyHaveFiles(files_on_disk); } @@ -114,12 +108,6 @@ public void compose_dependencies_noopt() "fluentassertions.json/4.12.0/lib/netstandard1.3/FluentAssertions.Json.dll" }; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // https://github.com/dotnet/core-setup/issues/2716 - an unintended native shim is getting published to the runtime store - files_on_disk.Add($"runtime.{_runtimeRid}.runtime.native.system.security.cryptography/1.0.1/runtimes/{_runtimeRid}/native/System.Security.Cryptography.Native{FileConstants.DynamicLibSuffix}"); - } - storeDirectory.Should().OnlyHaveFiles(files_on_disk); } @@ -157,12 +145,6 @@ public void compose_multifile() "fluentassertions.json/4.12.0/lib/netstandard1.3/FluentAssertions.Json.dll", }; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // https://github.com/dotnet/core-setup/issues/2716 - an unintended native shim is getting published to the runtime store - files_on_disk.Add($"runtime.{_runtimeRid}.runtime.native.system.security.cryptography/1.0.1/runtimes/{_runtimeRid}/native/System.Security.Cryptography.Native{FileConstants.DynamicLibSuffix}"); - } - storeDirectory.Should().OnlyHaveFiles(files_on_disk); var knownpackage = new HashSet