From 5d9755a228262d54ab274750f2745238f15c329f Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 24 Mar 2026 14:26:20 -0700 Subject: [PATCH 1/6] Add NU1704 warning for packages using deprecated MonoAndroid framework Emit NU1704 when a project targeting net11.0-android or later references a package that resolves MonoAndroid framework assets instead of the modern net6.0-android+ TFMs. The warning is gated on SdkAnalysisLevel >= 11.0.100 and only applies to Android-targeting projects with framework version >= 11. The detection inspects compile-time and runtime assembly paths in the lock file target library for 'monoandroid' folder segments, following the pattern of the previously removed MaccatalystFallback helper. --- .../RestoreCommand/LockFileBuilder.cs | 24 + .../RestoreCommand/MonoAndroidDeprecation.cs | 84 +++ .../NuGet.Commands/Strings.Designer.cs | 9 + src/NuGet.Core/NuGet.Commands/Strings.resx | 5 + .../Utility/SdkAnalysisLevelMinimums.cs | 8 + .../NuGet.Commands/xlf/Strings.cs.xlf | 6 + .../NuGet.Commands/xlf/Strings.de.xlf | 6 + .../NuGet.Commands/xlf/Strings.es.xlf | 6 + .../NuGet.Commands/xlf/Strings.fr.xlf | 6 + .../NuGet.Commands/xlf/Strings.it.xlf | 6 + .../NuGet.Commands/xlf/Strings.ja.xlf | 6 + .../NuGet.Commands/xlf/Strings.ko.xlf | 6 + .../NuGet.Commands/xlf/Strings.pl.xlf | 6 + .../NuGet.Commands/xlf/Strings.pt-BR.xlf | 6 + .../NuGet.Commands/xlf/Strings.ru.xlf | 6 + .../NuGet.Commands/xlf/Strings.tr.xlf | 6 + .../NuGet.Commands/xlf/Strings.zh-Hans.xlf | 6 + .../NuGet.Commands/xlf/Strings.zh-Hant.xlf | 6 + .../NuGet.Common/Errors/NuGetLogCode.cs | 5 + .../NuGet.Common/PublicAPI.Unshipped.txt | 1 + .../MonoAndroidDeprecationTests.cs | 535 ++++++++++++++++++ 21 files changed, 749 insertions(+) create mode 100644 src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs create mode 100644 test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs index d46e8702cb0..f83fb13e319 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs @@ -186,6 +186,8 @@ public LockFile CreateLockFile(LockFile previousLockFile, && (target.TargetFramework is FallbackFramework || target.TargetFramework is AssetTargetFallbackFramework); + bool checkMonoAndroidDeprecation = MonoAndroidDeprecation.ShouldCheck(project, targetGraph.Framework); + foreach (var graphItem in targetGraph.Flattened.OrderBy(x => x.Key)) { var library = graphItem.Key; @@ -280,6 +282,28 @@ public LockFile CreateLockFile(LockFile previousLockFile, librariesWithWarnings.Add(library); } } + + // Log NU1704 warning if the package uses the deprecated MonoAndroid framework + if (checkMonoAndroidDeprecation + && !librariesWithWarnings.Contains(library) + && MonoAndroidDeprecation.UsesMonoAndroidFramework(targetLibrary)) + { + var message = string.Format(CultureInfo.CurrentCulture, + Strings.Warning_MonoAndroidFrameworkDeprecated, + library.Name, + library.Version); + + var logMessage = RestoreLogMessage.CreateWarning( + NuGetLogCode.NU1704, + message, + library.Name, + targetGraph.TargetGraphName); + + _logger.Log(logMessage); + + // only log the warning once per library + librariesWithWarnings.Add(library); + } } } diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs new file mode 100644 index 00000000000..d4c5a449a3e --- /dev/null +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs @@ -0,0 +1,84 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using NuGet.Frameworks; +using NuGet.ProjectModel; + +namespace NuGet.Commands +{ + /// + /// Detects when a package uses the deprecated MonoAndroid framework instead of net6.0-android or later. + /// This warning is gated on .NET 11 SDK (SdkAnalysisLevel >= 11.0.100) and targeting net11.0-android or later. + /// + internal static class MonoAndroidDeprecation + { + /// + /// Determines whether the MonoAndroid deprecation check should be performed for the given project and target framework. + /// + /// The package spec containing restore metadata. + /// The target framework of the current graph. + /// True if the deprecation check should be performed. + internal static bool ShouldCheck(PackageSpec project, NuGetFramework framework) + { + if (project.RestoreMetadata == null) + { + return false; + } + + // Gate on SDK analysis level >= 11.0.100 + if (!SdkAnalysisLevelMinimums.IsEnabled( + project.RestoreMetadata.SdkAnalysisLevel, + project.RestoreMetadata.UsingMicrosoftNETSdk, + SdkAnalysisLevelMinimums.V11_0_100)) + { + return false; + } + + // Only check for .NETCoreApp frameworks targeting android with version >= 11.0 + return StringComparer.OrdinalIgnoreCase.Equals(framework.Framework, FrameworkConstants.FrameworkIdentifiers.NetCoreApp) + && framework.Version.Major >= 11 + && framework.HasPlatform + && framework.Platform.Equals("android", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Checks whether the given lock file target library uses the deprecated MonoAndroid framework + /// by inspecting the paths of compile-time and runtime assemblies. + /// + /// The lock file target library to check. + /// True if the library uses MonoAndroid framework assets. + internal static bool UsesMonoAndroidFramework(LockFileTargetLibrary library) + { + return ContainsMonoAndroidItem(library.CompileTimeAssemblies) + || ContainsMonoAndroidItem(library.RuntimeAssemblies); + } + + private static bool ContainsMonoAndroidItem(IList items) + { + for (int i = 0; i < items.Count; i++) + { + string path = items[i].Path; + + // Paths are like "lib/monoandroid10.0/Assembly.dll" or "ref/monoandroid10.0/Assembly.dll" + // Extract the framework folder segment (between first and second '/'). + int firstSlash = path.IndexOf('/'); + if (firstSlash >= 0) + { + int secondSlash = path.IndexOf('/', firstSlash + 1); + if (secondSlash > firstSlash + 1) + { + var folderName = path.AsSpan(firstSlash + 1, secondSlash - firstSlash - 1); + if (folderName.StartsWith("monoandroid".AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + } + + return false; + } + } +} diff --git a/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs b/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs index 43b322ddfd6..60358e7d295 100644 --- a/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs +++ b/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs @@ -2611,6 +2611,15 @@ internal static string Warning_MinVersionNonInclusive { } } + /// + /// Looks up a localized string similar to Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author.. + /// + internal static string Warning_MonoAndroidFrameworkDeprecated { + get { + return ResourceManager.GetString("Warning_MonoAndroidFrameworkDeprecated", resourceCulture); + } + } + /// /// Looks up a localized string similar to Package '{0}' {1} has a known {2} severity vulnerability, {3}. /// diff --git a/src/NuGet.Core/NuGet.Commands/Strings.resx b/src/NuGet.Core/NuGet.Commands/Strings.resx index 169ac0ec224..d9058496a79 100644 --- a/src/NuGet.Core/NuGet.Commands/Strings.resx +++ b/src/NuGet.Core/NuGet.Commands/Strings.resx @@ -167,6 +167,11 @@ Package '{0}' was restored using '{1}' instead of the project target framework '{2}'. This package may not be fully compatible with your project. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Cycle detected. diff --git a/src/NuGet.Core/NuGet.Commands/Utility/SdkAnalysisLevelMinimums.cs b/src/NuGet.Core/NuGet.Commands/Utility/SdkAnalysisLevelMinimums.cs index 10a708756fe..8797f00ad14 100644 --- a/src/NuGet.Core/NuGet.Commands/Utility/SdkAnalysisLevelMinimums.cs +++ b/src/NuGet.Core/NuGet.Commands/Utility/SdkAnalysisLevelMinimums.cs @@ -33,6 +33,14 @@ internal static class SdkAnalysisLevelMinimums /// internal static readonly NuGetVersion V10_0_300 = new("10.0.300"); + /// + /// Minimum SDK Analysis Level required for: + /// + /// warning when packages use the deprecated MonoAndroid framework + /// + /// + internal static readonly NuGetVersion V11_0_100 = new("11.0.100"); + /// /// Determines whether the feature is enabled based on the SDK analysis level. /// diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.cs.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.cs.xlf index 8d045dbbf49..b28f485e277 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.cs.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.cs.xlf @@ -1486,6 +1486,12 @@ NuGet vyžaduje zdroje HTTPS. Další informace najdete na https://aka.ms/nuget- {0} neposkytuje inkluzivní dolní mez pro závislost {1}. Místo toho bylo přeloženo: {2}. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} Balíček „{0}“ {1} má známé {2} ohrožení zabezpečení závažnosti, {3} diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.de.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.de.xlf index fb51958d874..71f29e03593 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.de.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.de.xlf @@ -1486,6 +1486,12 @@ NuGet erfordert HTTPS-Quellen. Weitere Informationen finden Sie unter https://ak {0} stellt keine inklusive untere Grenze für Abhängigkeiten {1} bereit. {2} wurde stattdessen aufgelöst. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} Das Paket "{0}" {1} weist eine bekannte {2} Schweregrad-Sicherheitsanfälligkeit auf, {3}. diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.es.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.es.xlf index 08e8a0537a0..321d8a27f73 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.es.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.es.xlf @@ -1486,6 +1486,12 @@ NuGet requiere orígenes HTTPS. Consulte https://aka.ms/nuget-https-everywhere p {0} no proporciona un límite inferior inclusivo para la dependencia {1}. {2} se resolvió en su lugar. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} El paquete "{0}" {1} tiene una vulnerabilidad de gravedad {2} conocida, {3} diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.fr.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.fr.xlf index 689c429363f..4b7710be0c5 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.fr.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.fr.xlf @@ -1486,6 +1486,12 @@ NuGet nécessite des sources HTTPS. Reportez-vous à https://aka.ms/nuget-https- {0} ne fournit pas de limite inférieure inclusive pour la dépendance {1}. {2} a été résolu à la place. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} Le package '{0}' {1} présente une vulnérabilité de gravité {2} connue, {3}. diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.it.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.it.xlf index af232eb51ff..25c0ee6cb3d 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.it.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.it.xlf @@ -1486,6 +1486,12 @@ NuGet richiede origini HTTPS. Vedi https://aka.ms/nuget-https-everywhere per alt In {0} non è specificato un limite inferiore inclusivo per la dipendenza {1}. {2} è stato risolto. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} Il pacchetto '{0}' {1} presenta una vulnerabilità nota di gravità {2}, {3} diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.ja.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.ja.xlf index a9849704ef0..d110154ef19 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.ja.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.ja.xlf @@ -1486,6 +1486,12 @@ NuGet には HTTPS ソースが必要です。詳しくは、https://aka.ms/nuge {0} では、依存関係 {1} の下限値 (その値を含む) を指定しませんでした。代わりに、{2} が解決されました。 + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} パッケージ '{0}' {1} に既知の {2} 重大度の脆弱性があります、{3} diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.ko.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.ko.xlf index a8367bf4303..ebb28c38cd8 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.ko.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.ko.xlf @@ -1486,6 +1486,12 @@ NuGet에는 HTTPS 원본이 필요합니다. https://aka.ms/nuget-https-everywhe {0}은(는) 종속성 {1}에 대한 포괄적인 하한을 제공하지 않습니다. 대신 {2}이(가) 확인되었습니다. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} '{0}' {1} 패키지에 알려진 {2} 심각도 취약성인 {3}이(가) 있습니다. diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.pl.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.pl.xlf index 19335382ff2..51fa6be9d6e 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.pl.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.pl.xlf @@ -1486,6 +1486,12 @@ Menedżer NuGet wymaga źródeł HTTPS. Aby uzyskać więcej informacji, sprawd {0} nie zapewnia łącznego dolnego ograniczenia dla zależności {1}. {2} został rozwiązany zamiast tego. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} Pakiet „{0}” {1} ma znane {2} luki w zabezpieczeniach o ważności, {3} diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.pt-BR.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.pt-BR.xlf index a6477b4eaed..743a65d5633 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.pt-BR.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.pt-BR.xlf @@ -1486,6 +1486,12 @@ O NuGet exige fontes HTTPS. Consulte https://aka.ms/nuget-https-everywhere para {0} não fornece um limite inferior inclusivo para a dependência {1}. {2} foi resolvido. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} O pacote '{0}' {1} tem uma {2} vulnerabilidade de gravidade conhecida, {3} diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.ru.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.ru.xlf index ec691099784..81aaea4e8db 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.ru.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.ru.xlf @@ -1486,6 +1486,12 @@ NuGet requires HTTPS sources. Refer to https://aka.ms/nuget-https-everywhere for {0} не задает включенную нижнюю границу для зависимости {1}. Использовалась версия {2}. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} У пакета "{0}" {1} есть известная уязвимость {3} (уровень серьезности: {2}) diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.tr.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.tr.xlf index e7d3d260263..b4f715cbc44 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.tr.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.tr.xlf @@ -1486,6 +1486,12 @@ NuGet için HTTPS kaynakları gereklidir. Daha fazla bilgi için şuraya başvur {0}, {1} bağımlılığı için kapsayıcı bir alt sınır sağlamıyor. Bunun yerine {2} çözümlendi. + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} '{0}' {1} paketinde önem derecesi {2} olan bilinen bir {3} güvenlik açığı var diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hans.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hans.xlf index fa45230aee2..cd908393e9e 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hans.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hans.xlf @@ -1486,6 +1486,12 @@ NuGet 需要 HTTPS 源。有关详细信息,请参阅 https://aka.ms/nuget-htt {0} 不提供依赖项 {1} 的下限(含)。已改为解析 {2}。 + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} 包 "{0}" {1} 具有已知的 {2} 严重性漏洞,{3} diff --git a/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hant.xlf b/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hant.xlf index 519e11a6bce..278fab0c375 100644 --- a/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hant.xlf +++ b/src/NuGet.Core/NuGet.Commands/xlf/Strings.zh-Hant.xlf @@ -1486,6 +1486,12 @@ NuGet 需要 HTTPS 來源。參閱 https://aka.ms/nuget-https-everywhere 以取 {0}未提供相依性 {1} 的內含下限。已改為解析 {2}。 + + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + Package '{0}' {1} uses the deprecated MonoAndroid framework instead of 'net6.0-android' or later. Consider upgrading to a newer version of this package or contacting the package author. + {0} - package id +{1} - package version + Package '{0}' {1} has a known {2} severity vulnerability, {3} 套件 '{0}' {1} 具有已知的 {2} 嚴重性弱點,{3}。 diff --git a/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs b/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs index daedd82dfa0..653a5ba8b9f 100644 --- a/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs +++ b/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs @@ -380,6 +380,11 @@ public enum NuGetLogCode /// NU1703 = 1703, + /// + /// Package uses the deprecated MonoAndroid framework. + /// + NU1704 = 1704, + /// /// Feed error converted to a warning when ignoreFailedSources is true. /// diff --git a/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt index 98a67d75341..46da3b71854 100644 --- a/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt @@ -1,2 +1,3 @@ #nullable enable NuGet.Common.NuGetLogCode.NU1018 = 1018 -> NuGet.Common.NuGetLogCode +NuGet.Common.NuGetLogCode.NU1704 = 1704 -> NuGet.Common.NuGetLogCode diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs new file mode 100644 index 00000000000..2eb91253dae --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs @@ -0,0 +1,535 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#nullable disable + +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using NuGet.Common; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.ProjectModel; +using NuGet.Test.Utility; +using NuGet.Versioning; +using Xunit; + +namespace NuGet.Commands.Test.RestoreCommandTests +{ + public class MonoAndroidDeprecationTests + { + #region ShouldCheck tests + + [Fact] + public void ShouldCheck_Net11Android_SdkLevel11_ReturnsTrue() + { + // Arrange + var spec = CreatePackageSpec("net11.0-android35.0"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net11.0-android35.0"); + + // Act & Assert + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeTrue(); + } + + [Fact] + public void ShouldCheck_Net12Android_SdkLevel11_ReturnsTrue() + { + // Arrange + var spec = CreatePackageSpec("net12.0-android35.0"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net12.0-android35.0"); + + // Act & Assert + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeTrue(); + } + + [Fact] + public void ShouldCheck_Net10Android_SdkLevel11_ReturnsFalse() + { + // net10.0-android has version major 10, which is < 11 + var spec = CreatePackageSpec("net10.0-android35.0"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net10.0-android35.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + } + + [Fact] + public void ShouldCheck_Net11Android_SdkLevel10_ReturnsFalse() + { + // SDK analysis level 10.0.100 is too old + var spec = CreatePackageSpec("net11.0-android35.0"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("10.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net11.0-android35.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + } + + [Fact] + public void ShouldCheck_Net11iOS_SdkLevel11_ReturnsFalse() + { + // iOS platform, not android + var spec = CreatePackageSpec("net11.0-ios18.0"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net11.0-ios18.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + } + + [Fact] + public void ShouldCheck_Net11_NoPlatform_SdkLevel11_ReturnsFalse() + { + // net11.0 without platform + var spec = CreatePackageSpec("net11.0"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net11.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + } + + [Fact] + public void ShouldCheck_NullRestoreMetadata_ReturnsFalse() + { + var spec = new PackageSpec(); + spec.RestoreMetadata = null; + var framework = NuGetFramework.Parse("net11.0-android35.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + } + + [Fact] + public void ShouldCheck_NullSdkAnalysisLevel_UsingMicrosoftNETSdk_ReturnsFalse() + { + // When SdkAnalysisLevel is null and UsingMicrosoftNETSdk is true, IsEnabled returns false + var spec = CreatePackageSpec("net11.0-android35.0"); + spec.RestoreMetadata.SdkAnalysisLevel = null; + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net11.0-android35.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + } + + [Fact] + public void ShouldCheck_NullSdkAnalysisLevel_NotUsingMicrosoftNETSdk_ReturnsTrue() + { + // When SdkAnalysisLevel is null and UsingMicrosoftNETSdk is false, IsEnabled returns true + // but the framework check should still apply + var spec = CreatePackageSpec("net11.0-android35.0"); + spec.RestoreMetadata.SdkAnalysisLevel = null; + spec.RestoreMetadata.UsingMicrosoftNETSdk = false; + var framework = NuGetFramework.Parse("net11.0-android35.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeTrue(); + } + + [Fact] + public void ShouldCheck_Net6Android_SdkLevel11_ReturnsFalse() + { + // net6.0-android has version major 6, which is < 11 + var spec = CreatePackageSpec("net6.0-android31.0"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + var framework = NuGetFramework.Parse("net6.0-android31.0"); + + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + } + + #endregion + + #region UsesMonoAndroidFramework tests + + [Fact] + public void UsesMonoAndroidFramework_CompileTimeAssemblyWithMonoAndroid_ReturnsTrue() + { + var library = new LockFileTargetLibrary(); + library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/a.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + } + + [Fact] + public void UsesMonoAndroidFramework_RuntimeAssemblyWithMonoAndroid_ReturnsTrue() + { + var library = new LockFileTargetLibrary(); + library.RuntimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/a.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + } + + [Fact] + public void UsesMonoAndroidFramework_RefFolderWithMonoAndroid_ReturnsTrue() + { + var library = new LockFileTargetLibrary(); + library.CompileTimeAssemblies.Add(new LockFileItem("ref/monoandroid10.0/a.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + } + + [Fact] + public void UsesMonoAndroidFramework_MonoAndroidCaseInsensitive_ReturnsTrue() + { + var library = new LockFileTargetLibrary(); + library.CompileTimeAssemblies.Add(new LockFileItem("lib/MonoAndroid10.0/a.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + } + + [Fact] + public void UsesMonoAndroidFramework_Net6Android_ReturnsFalse() + { + var library = new LockFileTargetLibrary(); + library.CompileTimeAssemblies.Add(new LockFileItem("lib/net6.0-android31.0/a.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeFalse(); + } + + [Fact] + public void UsesMonoAndroidFramework_NetStandard_ReturnsFalse() + { + var library = new LockFileTargetLibrary(); + library.CompileTimeAssemblies.Add(new LockFileItem("lib/netstandard2.0/a.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeFalse(); + } + + [Fact] + public void UsesMonoAndroidFramework_EmptyAssemblies_ReturnsFalse() + { + var library = new LockFileTargetLibrary(); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeFalse(); + } + + [Fact] + public void UsesMonoAndroidFramework_MonoAndroidWithoutVersion_ReturnsTrue() + { + var library = new LockFileTargetLibrary(); + library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid/a.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + } + + [Fact] + public void UsesMonoAndroidFramework_MultipleAssemblies_OneMonoAndroid_ReturnsTrue() + { + var library = new LockFileTargetLibrary(); + library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/a.dll")); + library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/b.dll")); + + MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + } + + #endregion + + #region Integration tests + + [Fact] + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_EmitsNU1704() + { + // Arrange + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/monoandroid10.0/a.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net11.0-android35.0", + dependencyName: "a"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Should().HaveCount(1); + result.LockFile.LogMessages[0].Code.Should().Be(NuGetLogCode.NU1704); + result.LockFile.LogMessages[0].Level.Should().Be(LogLevel.Warning); + result.LockFile.LogMessages[0].LibraryId.Should().Be("a"); + result.LockFile.LogMessages[0].Message.Should().Contain("MonoAndroid"); + logger.Errors.Should().Be(0); + logger.Warnings.Should().Be(1); + } + + [Fact] + public async Task Restore_Net10Android_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1704() + { + // net10.0-android has version major 10, below the 11 threshold + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/monoandroid10.0/a.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net10.0-android35.0", + dependencyName: "a"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + } + + [Fact] + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel10_DoesNotEmitNU1704() + { + // SDK analysis level too old + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/monoandroid10.0/a.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net11.0-android35.0", + dependencyName: "a"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("10.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + } + + [Fact] + public async Task Restore_Net11Android_NetAndroidPackage_SdkLevel11_DoesNotEmitNU1704() + { + // Package uses net6.0-android, not monoandroid - should not warn + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/net6.0-android31.0/a.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net11.0-android35.0", + dependencyName: "a"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + } + + [Fact] + public async Task Restore_Net11iOS_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1704() + { + // iOS project, not android - should not warn even with monoandroid package + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/monoandroid10.0/a.dll"); + // Also add netstandard so the package resolves for iOS + packageA.AddFile("lib/netstandard2.0/a.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net11.0-ios18.0", + dependencyName: "a"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + } + + [Fact] + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_NU1704_CanBeSuppressed() + { + // NoWarn for NU1704 should suppress the warning + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/monoandroid10.0/a.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net11.0-android35.0", + dependencyName: "a"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + spec.RestoreMetadata.ProjectWideWarningProperties = new WarningProperties( + warningsAsErrors: new System.Collections.Generic.HashSet(), + noWarn: new System.Collections.Generic.HashSet { NuGetLogCode.NU1704 }, + allWarningsAsErrors: false, + warningsNotAsErrors: new System.Collections.Generic.HashSet()); + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + logger.Warnings.Should().Be(0); + } + + [Fact] + public async Task Restore_Net11Android_MultiplePackages_OnlyMonoAndroidPackageGetsNU1704() + { + // One package with monoandroid, one with netstandard - only monoandroid package should warn + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/monoandroid10.0/a.dll"); + + var packageB = new SimpleTestPackageContext("b", "2.0.0"); + packageB.AddFile("lib/netstandard2.0/b.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA, + packageB); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net11.0-android35.0", + dependencyName: "a"); + // Add second dependency + var tfi = spec.TargetFrameworks[0]; + tfi.Dependencies.Add(new LibraryModel.LibraryDependency + { + LibraryRange = new LibraryModel.LibraryRange("b", VersionRange.Parse("2.0.0"), LibraryModel.LibraryDependencyTarget.Package) + }); + + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + result.LockFile.LogMessages.Where(m => m.Code == NuGetLogCode.NU1704).Should().HaveCount(1); + result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1704).LibraryId.Should().Be("a"); + logger.Warnings.Should().Be(1); + } + + [Fact] + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_WarningMessageFormat() + { + // Verify the exact warning message format + using var pathContext = new SimpleTestPathContext(); + + var packageA = new SimpleTestPackageContext("MyPackage", "3.2.1"); + packageA.AddFile("lib/monoandroid10.0/MyPackage.dll"); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + packageA); + + var spec = ProjectTestHelpers.GetPackageSpec("Project1", + pathContext.SolutionRoot, + framework: "net11.0-android35.0", + dependencyName: "MyPackage", + dependencyVersion: "3.2.1"); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); + spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + + var logger = new TestLogger(); + var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); + + // Act + var result = await command.ExecuteAsync(); + + // Assert + result.Success.Should().BeTrue(because: logger.ShowMessages()); + var logMessage = result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1704); + var expectedMessage = string.Format(CultureInfo.CurrentCulture, + Strings.Warning_MonoAndroidFrameworkDeprecated, + "MyPackage", + "3.2.1"); + logMessage.Message.Should().Be(expectedMessage); + } + + #endregion + + #region Helpers + + private static PackageSpec CreatePackageSpec(string framework) + { + var spec = ProjectTestHelpers.GetPackageSpec("TestProject", @"C:\", framework); + return spec; + } + + #endregion + } +} From f1ae34fe10150479c7eb1a302515499d866d8fc4 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 30 Mar 2026 12:42:15 -0700 Subject: [PATCH 2/6] Reuse NU1703 instead of introducing NU1704 for MonoAndroid deprecation warning --- .../RestoreCommand/LockFileBuilder.cs | 4 +-- .../NuGet.Common/Errors/NuGetLogCode.cs | 8 ++--- .../NuGet.Common/PublicAPI.Unshipped.txt | 1 - .../MonoAndroidDeprecationTests.cs | 36 +++++++++---------- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs index f83fb13e319..cffbfb0394d 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs @@ -283,7 +283,7 @@ public LockFile CreateLockFile(LockFile previousLockFile, } } - // Log NU1704 warning if the package uses the deprecated MonoAndroid framework + // Log NU1703 warning if the package uses the deprecated MonoAndroid framework if (checkMonoAndroidDeprecation && !librariesWithWarnings.Contains(library) && MonoAndroidDeprecation.UsesMonoAndroidFramework(targetLibrary)) @@ -294,7 +294,7 @@ public LockFile CreateLockFile(LockFile previousLockFile, library.Version); var logMessage = RestoreLogMessage.CreateWarning( - NuGetLogCode.NU1704, + NuGetLogCode.NU1703, message, library.Name, targetGraph.TargetGraphName); diff --git a/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs b/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs index c67bdc6bcd4..60f9b5b5017 100644 --- a/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs +++ b/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs @@ -376,15 +376,11 @@ public enum NuGetLogCode NU1702 = 1702, /// - /// MacCatalyst platform fell back to xamarin.ios - Added in 6.0, removed in 6.1. + /// Package uses a deprecated legacy Xamarin framework (e.g. MonoAndroid) instead of a modern .NET TFM. + /// Originally added in 6.0 for MacCatalyst/Xamarin.iOS (removed in 6.1), reused for MonoAndroid in 11.0. /// NU1703 = 1703, - /// - /// Package uses the deprecated MonoAndroid framework. - /// - NU1704 = 1704, - /// /// Feed error converted to a warning when ignoreFailedSources is true. /// diff --git a/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt index 8da71e2d45d..eb111715f9b 100644 --- a/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt @@ -1,4 +1,3 @@ #nullable enable NuGet.Common.NuGetLogCode.NU1018 = 1018 -> NuGet.Common.NuGetLogCode -NuGet.Common.NuGetLogCode.NU1704 = 1704 -> NuGet.Common.NuGetLogCode NuGet.Common.NuGetLogCode.NU5051 = 5051 -> NuGet.Common.NuGetLogCode diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs index 2eb91253dae..5586575ab68 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs @@ -232,7 +232,7 @@ public void UsesMonoAndroidFramework_MultipleAssemblies_OneMonoAndroid_ReturnsTr #region Integration tests [Fact] - public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_EmitsNU1704() + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_EmitsNU1703() { // Arrange using var pathContext = new SimpleTestPathContext(); @@ -261,7 +261,7 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); result.LockFile.LogMessages.Should().HaveCount(1); - result.LockFile.LogMessages[0].Code.Should().Be(NuGetLogCode.NU1704); + result.LockFile.LogMessages[0].Code.Should().Be(NuGetLogCode.NU1703); result.LockFile.LogMessages[0].Level.Should().Be(LogLevel.Warning); result.LockFile.LogMessages[0].LibraryId.Should().Be("a"); result.LockFile.LogMessages[0].Message.Should().Contain("MonoAndroid"); @@ -270,7 +270,7 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( } [Fact] - public async Task Restore_Net10Android_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1704() + public async Task Restore_Net10Android_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1703() { // net10.0-android has version major 10, below the 11 threshold using var pathContext = new SimpleTestPathContext(); @@ -298,11 +298,11 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); } [Fact] - public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel10_DoesNotEmitNU1704() + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel10_DoesNotEmitNU1703() { // SDK analysis level too old using var pathContext = new SimpleTestPathContext(); @@ -330,11 +330,11 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); } [Fact] - public async Task Restore_Net11Android_NetAndroidPackage_SdkLevel11_DoesNotEmitNU1704() + public async Task Restore_Net11Android_NetAndroidPackage_SdkLevel11_DoesNotEmitNU1703() { // Package uses net6.0-android, not monoandroid - should not warn using var pathContext = new SimpleTestPathContext(); @@ -362,11 +362,11 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); } [Fact] - public async Task Restore_Net11iOS_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1704() + public async Task Restore_Net11iOS_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1703() { // iOS project, not android - should not warn even with monoandroid package using var pathContext = new SimpleTestPathContext(); @@ -396,13 +396,13 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); } [Fact] - public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_NU1704_CanBeSuppressed() + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_NU1703_CanBeSuppressed() { - // NoWarn for NU1704 should suppress the warning + // NoWarn for NU1703 should suppress the warning using var pathContext = new SimpleTestPathContext(); var packageA = new SimpleTestPackageContext("a", "1.0.0"); @@ -421,7 +421,7 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( spec.RestoreMetadata.UsingMicrosoftNETSdk = true; spec.RestoreMetadata.ProjectWideWarningProperties = new WarningProperties( warningsAsErrors: new System.Collections.Generic.HashSet(), - noWarn: new System.Collections.Generic.HashSet { NuGetLogCode.NU1704 }, + noWarn: new System.Collections.Generic.HashSet { NuGetLogCode.NU1703 }, allWarningsAsErrors: false, warningsNotAsErrors: new System.Collections.Generic.HashSet()); @@ -433,12 +433,12 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1704); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); logger.Warnings.Should().Be(0); } [Fact] - public async Task Restore_Net11Android_MultiplePackages_OnlyMonoAndroidPackageGetsNU1704() + public async Task Restore_Net11Android_MultiplePackages_OnlyMonoAndroidPackageGetsNU1703() { // One package with monoandroid, one with netstandard - only monoandroid package should warn using var pathContext = new SimpleTestPathContext(); @@ -477,8 +477,8 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Where(m => m.Code == NuGetLogCode.NU1704).Should().HaveCount(1); - result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1704).LibraryId.Should().Be("a"); + result.LockFile.LogMessages.Where(m => m.Code == NuGetLogCode.NU1703).Should().HaveCount(1); + result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1703).LibraryId.Should().Be("a"); logger.Warnings.Should().Be(1); } @@ -512,7 +512,7 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - var logMessage = result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1704); + var logMessage = result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1703); var expectedMessage = string.Format(CultureInfo.CurrentCulture, Strings.Warning_MonoAndroidFrameworkDeprecated, "MyPackage", From a7d46673be6d17d434bc24dd5dcb17816f3e66e3 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 3 Apr 2026 10:53:57 -0700 Subject: [PATCH 3/6] Use resolved asset frameworks from content model instead of re-parsing paths Instead of re-parsing assembly paths (e.g. lib/monoandroid10.0/a.dll) to detect MonoAndroid usage, read the NuGetFramework already computed by FindBestItemGroup from ContentItemGroup.Properties["tfm"] during asset selection. Extend CreateLockFileTargetLibrary and LockFileBuilderCache to propagate the compile and runtime asset frameworks, and check IsMonoAndroidFramework in LockFileBuilder where the NU1703 warning is emitted. This removes the path-parsing helpers and simplifies tests. --- .../RestoreCommand/LockFileBuilder.cs | 5 +- .../RestoreCommand/LockFileBuilderCache.cs | 6 +- .../RestoreCommand/MonoAndroidDeprecation.cs | 42 ++--------- .../RestoreCommand/Utility/LockFileUtils.cs | 31 +++++--- .../MonoAndroidDeprecationTests.cs | 73 ++++--------------- 5 files changed, 51 insertions(+), 106 deletions(-) diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs index cffbfb0394d..3335ee59d81 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs @@ -228,7 +228,7 @@ public LockFile CreateLockFile(LockFile previousLockFile, var package = packageInfo.Package; var libraryDependency = tfi.Dependencies.FirstOrDefault(e => e.Name.Equals(library.Name, StringComparison.OrdinalIgnoreCase)); - (LockFileTargetLibrary targetLibrary, bool usedFallbackFramework) = LockFileUtils.CreateLockFileTargetLibrary( + (LockFileTargetLibrary targetLibrary, bool usedFallbackFramework, NuGetFramework compileAssetFramework, NuGetFramework runtimeAssetFramework) = LockFileUtils.CreateLockFileTargetLibrary( libraryDependency?.Aliases, libraries[ValueTuple.Create(library.Name, library.Version)], package, @@ -286,7 +286,8 @@ public LockFile CreateLockFile(LockFile previousLockFile, // Log NU1703 warning if the package uses the deprecated MonoAndroid framework if (checkMonoAndroidDeprecation && !librariesWithWarnings.Contains(library) - && MonoAndroidDeprecation.UsesMonoAndroidFramework(targetLibrary)) + && (MonoAndroidDeprecation.IsMonoAndroidFramework(compileAssetFramework) + || MonoAndroidDeprecation.IsMonoAndroidFramework(runtimeAssetFramework))) { var message = string.Format(CultureInfo.CurrentCulture, Strings.Warning_MonoAndroidFrameworkDeprecated, diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilderCache.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilderCache.cs index eab49cbf8a1..48a00a380af 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilderCache.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilderCache.cs @@ -31,7 +31,7 @@ public class LockFileBuilderCache private readonly ConcurrentDictionary, bool)>> _criteriaSets = new(); - private readonly ConcurrentDictionary<(CriteriaKey, string path, string aliases, LibraryIncludeFlags, int dependencyCount), Lazy<(LockFileTargetLibrary, bool)>> _lockFileTargetLibraryCache = + private readonly ConcurrentDictionary<(CriteriaKey, string path, string aliases, LibraryIncludeFlags, int dependencyCount), Lazy<(LockFileTargetLibrary, bool, NuGetFramework, NuGetFramework)>> _lockFileTargetLibraryCache = new(); /// @@ -106,7 +106,7 @@ public ContentItemCollection GetContentItems(LockFileLibrary library, LocalPacka /// /// Try to get a LockFileTargetLibrary from the cache. /// - internal (LockFileTargetLibrary, bool) GetLockFileTargetLibrary(RestoreTargetGraph graph, NuGetFramework framework, LocalPackageInfo localPackageInfo, string aliases, LibraryIncludeFlags libraryIncludeFlags, List dependencies, Func<(LockFileTargetLibrary, bool)> valueFactory) + internal (LockFileTargetLibrary, bool, NuGetFramework, NuGetFramework) GetLockFileTargetLibrary(RestoreTargetGraph graph, NuGetFramework framework, LocalPackageInfo localPackageInfo, string aliases, LibraryIncludeFlags libraryIncludeFlags, List dependencies, Func<(LockFileTargetLibrary, bool, NuGetFramework, NuGetFramework)> valueFactory) { // Comparing RuntimeGraph for equality is very expensive, // so in case of a request where the RuntimeGraph is not empty we avoid using the cache. @@ -117,7 +117,7 @@ public ContentItemCollection GetContentItems(LockFileLibrary library, LocalPacka var criteriaKey = new CriteriaKey(graph.TargetGraphName, framework); var packagePath = localPackageInfo.ExpandedPath; return _lockFileTargetLibraryCache.GetOrAdd((criteriaKey, packagePath, aliases, libraryIncludeFlags, dependencies.Count), - key => new Lazy<(LockFileTargetLibrary, bool)>(valueFactory)).Value; + key => new Lazy<(LockFileTargetLibrary, bool, NuGetFramework, NuGetFramework)>(valueFactory)).Value; } private class CriteriaKey : IEquatable diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs index d4c5a449a3e..4bbd0b0a61c 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/MonoAndroidDeprecation.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using NuGet.Frameworks; using NuGet.ProjectModel; @@ -44,41 +43,16 @@ internal static bool ShouldCheck(PackageSpec project, NuGetFramework framework) } /// - /// Checks whether the given lock file target library uses the deprecated MonoAndroid framework - /// by inspecting the paths of compile-time and runtime assemblies. + /// Checks whether the given framework is a MonoAndroid framework. /// - /// The lock file target library to check. - /// True if the library uses MonoAndroid framework assets. - internal static bool UsesMonoAndroidFramework(LockFileTargetLibrary library) + /// The framework to check, or null. + /// True if the framework uses the MonoAndroid framework identifier. + internal static bool IsMonoAndroidFramework(NuGetFramework framework) { - return ContainsMonoAndroidItem(library.CompileTimeAssemblies) - || ContainsMonoAndroidItem(library.RuntimeAssemblies); - } - - private static bool ContainsMonoAndroidItem(IList items) - { - for (int i = 0; i < items.Count; i++) - { - string path = items[i].Path; - - // Paths are like "lib/monoandroid10.0/Assembly.dll" or "ref/monoandroid10.0/Assembly.dll" - // Extract the framework folder segment (between first and second '/'). - int firstSlash = path.IndexOf('/'); - if (firstSlash >= 0) - { - int secondSlash = path.IndexOf('/', firstSlash + 1); - if (secondSlash > firstSlash + 1) - { - var folderName = path.AsSpan(firstSlash + 1, secondSlash - firstSlash - 1); - if (folderName.StartsWith("monoandroid".AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - } - } - - return false; + return framework != null + && StringComparer.OrdinalIgnoreCase.Equals( + framework.Framework, + FrameworkConstants.FrameworkIdentifiers.MonoAndroid); } } } diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/LockFileUtils.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/LockFileUtils.cs index 5ad5dcf0ee7..7f604bb7d9c 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/LockFileUtils.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/LockFileUtils.cs @@ -34,7 +34,7 @@ public static LockFileTargetLibrary CreateLockFileTargetLibrary( RestoreTargetGraph targetGraph, LibraryIncludeFlags dependencyType) { - var (lockFileTargetLibrary, _) = CreateLockFileTargetLibrary( + var (lockFileTargetLibrary, _, _, _) = CreateLockFileTargetLibrary( aliases: null, library, package, @@ -57,8 +57,8 @@ public static LockFileTargetLibrary CreateLockFileTargetLibrary( /// The original framework if the asset selection is happening for a fallback framework. /// The dependencies of this package. /// The lock file build cache. - /// The LockFileTargetLibrary, and whether a fallback framework criteria was used to select it. - internal static (LockFileTargetLibrary, bool) CreateLockFileTargetLibrary( + /// The LockFileTargetLibrary, whether a fallback framework criteria was used to select it, the framework selected for compile assets, and the framework selected for runtime assets. + internal static (LockFileTargetLibrary, bool, NuGetFramework, NuGetFramework) CreateLockFileTargetLibrary( string aliases, LockFileLibrary library, LocalPackageInfo package, @@ -75,6 +75,8 @@ internal static (LockFileTargetLibrary, bool) CreateLockFileTargetLibrary( () => { LockFileTargetLibrary lockFileLib = null; + NuGetFramework compileAssetFramework = null; + NuGetFramework runtimeAssetFramework = null; // This will throw an appropriate error if the nuspec is missing var nuspec = package.Nuspec; @@ -86,7 +88,7 @@ internal static (LockFileTargetLibrary, bool) CreateLockFileTargetLibrary( for (var i = 0; i < orderedCriteriaSets.Count; i++) { - lockFileLib = CreateLockFileTargetLibrary(aliases, library, package, targetGraph.Conventions, dependencyType, + (lockFileLib, compileAssetFramework, runtimeAssetFramework) = CreateLockFileTargetLibrary(aliases, library, package, targetGraph.Conventions, dependencyType, framework, runtimeIdentifier, contentItems, nuspec, packageTypes, orderedCriteriaSets[i].orderedCriteria); // Check if compatible assets were found. // If no compatible assets were found and this is the last check @@ -108,7 +110,7 @@ internal static (LockFileTargetLibrary, bool) CreateLockFileTargetLibrary( lockFileLib.Freeze(); - return (lockFileLib, fallbackUsed); + return (lockFileLib, fallbackUsed, compileAssetFramework, runtimeAssetFramework); }); } @@ -165,7 +167,7 @@ private static void ApplyAliases(string aliases, LockFileItem item) /// /// Populate assets for a . /// - internal static LockFileTargetLibrary CreateLockFileTargetLibrary( + internal static (LockFileTargetLibrary lockFileLib, NuGetFramework compileAssetFramework, NuGetFramework runtimeAssetFramework) CreateLockFileTargetLibrary( string aliases, LockFileLibrary library, LocalPackageInfo package, @@ -198,6 +200,7 @@ internal static LockFileTargetLibrary CreateLockFileTargetLibrary( orderedCriteria, contentItems, applyAliases, + out NuGetFramework compileFramework, managedCodeConventions.Patterns.CompileRefAssemblies, managedCodeConventions.Patterns.CompileLibAssemblies); @@ -205,6 +208,8 @@ internal static LockFileTargetLibrary CreateLockFileTargetLibrary( lockFileLib.RuntimeAssemblies = GetLockFileItems( orderedCriteria, contentItems, + additionalAction: null, + out NuGetFramework runtimeFramework, managedCodeConventions.Patterns.RuntimeAssemblies); // Embed @@ -242,7 +247,7 @@ internal static LockFileTargetLibrary CreateLockFileTargetLibrary( // Apply filters from the node in the nuspec ApplyReferenceFilter(lockFileLib, framework, nuspec); - return lockFileLib; + return (lockFileLib, compileFramework, runtimeFramework); } private static void AddMSBuildAssets( @@ -659,15 +664,17 @@ private static List ConvertToProjectPaths( } /// - /// Create lock file items for the best matching group. + /// Create lock file items for the best matching group, and optionally output the selected framework. /// /// Enumerate this once after calling. private static IList GetLockFileItems( List criteria, ContentItemCollection items, Action additionalAction, + out NuGetFramework selectedFramework, params PatternSet[] patterns) { + selectedFramework = null; List result = null; // Loop through each criteria taking the first one that matches one or more items. foreach (var managedCriteria in criteria) @@ -678,6 +685,12 @@ private static IList GetLockFileItems( if (group != null) { + if (group.Properties.TryGetValue( + ManagedCodeConventions.PropertyNames.TargetFrameworkMoniker, out object tfmObj)) + { + selectedFramework = tfmObj as NuGetFramework; + } + result = new(group.Items.Count); foreach (var item in group.Items.NoAllocEnumerate()) { @@ -711,7 +724,7 @@ private static IList GetLockFileItems( ContentItemCollection items, params PatternSet[] patterns) { - return GetLockFileItems(criteria, items, additionalAction: null, patterns); + return GetLockFileItems(criteria, items, additionalAction: null, out _, patterns); } /// diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs index 5586575ab68..7129e643a4f 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs @@ -144,87 +144,44 @@ public void ShouldCheck_Net6Android_SdkLevel11_ReturnsFalse() #endregion - #region UsesMonoAndroidFramework tests + #region IsMonoAndroidFramework tests [Fact] - public void UsesMonoAndroidFramework_CompileTimeAssemblyWithMonoAndroid_ReturnsTrue() + public void IsMonoAndroidFramework_MonoAndroid_ReturnsTrue() { - var library = new LockFileTargetLibrary(); - library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/a.dll")); + var framework = NuGetFramework.Parse("monoandroid10.0"); - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + MonoAndroidDeprecation.IsMonoAndroidFramework(framework).Should().BeTrue(); } [Fact] - public void UsesMonoAndroidFramework_RuntimeAssemblyWithMonoAndroid_ReturnsTrue() + public void IsMonoAndroidFramework_MonoAndroidNoVersion_ReturnsTrue() { - var library = new LockFileTargetLibrary(); - library.RuntimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/a.dll")); + var framework = new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.MonoAndroid); - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + MonoAndroidDeprecation.IsMonoAndroidFramework(framework).Should().BeTrue(); } [Fact] - public void UsesMonoAndroidFramework_RefFolderWithMonoAndroid_ReturnsTrue() + public void IsMonoAndroidFramework_NetCoreApp_ReturnsFalse() { - var library = new LockFileTargetLibrary(); - library.CompileTimeAssemblies.Add(new LockFileItem("ref/monoandroid10.0/a.dll")); - - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); - } - - [Fact] - public void UsesMonoAndroidFramework_MonoAndroidCaseInsensitive_ReturnsTrue() - { - var library = new LockFileTargetLibrary(); - library.CompileTimeAssemblies.Add(new LockFileItem("lib/MonoAndroid10.0/a.dll")); - - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); - } - - [Fact] - public void UsesMonoAndroidFramework_Net6Android_ReturnsFalse() - { - var library = new LockFileTargetLibrary(); - library.CompileTimeAssemblies.Add(new LockFileItem("lib/net6.0-android31.0/a.dll")); - - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeFalse(); - } - - [Fact] - public void UsesMonoAndroidFramework_NetStandard_ReturnsFalse() - { - var library = new LockFileTargetLibrary(); - library.CompileTimeAssemblies.Add(new LockFileItem("lib/netstandard2.0/a.dll")); - - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeFalse(); - } - - [Fact] - public void UsesMonoAndroidFramework_EmptyAssemblies_ReturnsFalse() - { - var library = new LockFileTargetLibrary(); + var framework = NuGetFramework.Parse("net6.0-android31.0"); - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeFalse(); + MonoAndroidDeprecation.IsMonoAndroidFramework(framework).Should().BeFalse(); } [Fact] - public void UsesMonoAndroidFramework_MonoAndroidWithoutVersion_ReturnsTrue() + public void IsMonoAndroidFramework_NetStandard_ReturnsFalse() { - var library = new LockFileTargetLibrary(); - library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid/a.dll")); + var framework = NuGetFramework.Parse("netstandard2.0"); - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + MonoAndroidDeprecation.IsMonoAndroidFramework(framework).Should().BeFalse(); } [Fact] - public void UsesMonoAndroidFramework_MultipleAssemblies_OneMonoAndroid_ReturnsTrue() + public void IsMonoAndroidFramework_Null_ReturnsFalse() { - var library = new LockFileTargetLibrary(); - library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/a.dll")); - library.CompileTimeAssemblies.Add(new LockFileItem("lib/monoandroid10.0/b.dll")); - - MonoAndroidDeprecation.UsesMonoAndroidFramework(library).Should().BeTrue(); + MonoAndroidDeprecation.IsMonoAndroidFramework(null).Should().BeFalse(); } #endregion From a553bef218e8541914550a34db485c1bcf8ab1b3 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 8 Apr 2026 14:03:24 -0700 Subject: [PATCH 4/6] Use separate dedup set for NU1703 so it is not suppressed by NU1701 --- .../NuGet.Commands/RestoreCommand/LockFileBuilder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs index 3335ee59d81..61669587b6e 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs @@ -154,6 +154,7 @@ public LockFile CreateLockFile(LockFile previousLockFile, Dictionary, LockFileLibrary> libraries = EnsureUniqueLockFileLibraries(lockFile); var librariesWithWarnings = new HashSet(); + var librariesWithMonoAndroidWarnings = new HashSet(); var rootProjectStyle = project.RestoreMetadata?.ProjectStyle ?? ProjectStyle.Unknown; @@ -285,7 +286,7 @@ public LockFile CreateLockFile(LockFile previousLockFile, // Log NU1703 warning if the package uses the deprecated MonoAndroid framework if (checkMonoAndroidDeprecation - && !librariesWithWarnings.Contains(library) + && !librariesWithMonoAndroidWarnings.Contains(library) && (MonoAndroidDeprecation.IsMonoAndroidFramework(compileAssetFramework) || MonoAndroidDeprecation.IsMonoAndroidFramework(runtimeAssetFramework))) { @@ -303,7 +304,7 @@ public LockFile CreateLockFile(LockFile previousLockFile, _logger.Log(logMessage); // only log the warning once per library - librariesWithWarnings.Add(library); + librariesWithMonoAndroidWarnings.Add(library); } } } From 3483f408200eb3662bffa5af10019e8de02244d9 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 21 Apr 2026 14:11:58 -0700 Subject: [PATCH 5/6] Address PR feedback: per-framework dedup set and streamlined tests - Move librariesWithMonoAndroidWarnings inside the per-framework loop so NU1703 is emitted per target framework, not deduplicated globally (see NuGet/Home#14815) - Convert ShouldCheck unit tests to Theory with InlineData - Reduce integration tests to: basic emit, multi-package, and package-level suppression (replacing redundant restore tests) - Remove unused System.Globalization using Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../RestoreCommand/LockFileBuilder.cs | 3 +- .../MonoAndroidDeprecationTests.cs | 304 +++--------------- 2 files changed, 40 insertions(+), 267 deletions(-) diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs index 61669587b6e..80572729569 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs @@ -154,7 +154,6 @@ public LockFile CreateLockFile(LockFile previousLockFile, Dictionary, LockFileLibrary> libraries = EnsureUniqueLockFileLibraries(lockFile); var librariesWithWarnings = new HashSet(); - var librariesWithMonoAndroidWarnings = new HashSet(); var rootProjectStyle = project.RestoreMetadata?.ProjectStyle ?? ProjectStyle.Unknown; @@ -163,6 +162,8 @@ public LockFile CreateLockFile(LockFile previousLockFile, .OrderBy(graph => graph.Framework.ToString(), StringComparer.Ordinal) .ThenBy(graph => graph.RuntimeIdentifier, StringComparer.Ordinal)) { + var librariesWithMonoAndroidWarnings = new HashSet(); + var target = lockFile.Version >= LockFileFormat.AliasedVersion ? new LockFileTarget { diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs index 7129e643a4f..c5fd9da1b0d 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Globalization; using System.Linq; using System.Threading.Tasks; using FluentAssertions; @@ -21,78 +20,27 @@ public class MonoAndroidDeprecationTests { #region ShouldCheck tests - [Fact] - public void ShouldCheck_Net11Android_SdkLevel11_ReturnsTrue() - { - // Arrange - var spec = CreatePackageSpec("net11.0-android35.0"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - var framework = NuGetFramework.Parse("net11.0-android35.0"); - - // Act & Assert - MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeTrue(); - } - - [Fact] - public void ShouldCheck_Net12Android_SdkLevel11_ReturnsTrue() - { - // Arrange - var spec = CreatePackageSpec("net12.0-android35.0"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - var framework = NuGetFramework.Parse("net12.0-android35.0"); - - // Act & Assert - MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeTrue(); - } - - [Fact] - public void ShouldCheck_Net10Android_SdkLevel11_ReturnsFalse() - { - // net10.0-android has version major 10, which is < 11 - var spec = CreatePackageSpec("net10.0-android35.0"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - var framework = NuGetFramework.Parse("net10.0-android35.0"); - - MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); - } - - [Fact] - public void ShouldCheck_Net11Android_SdkLevel10_ReturnsFalse() - { - // SDK analysis level 10.0.100 is too old - var spec = CreatePackageSpec("net11.0-android35.0"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("10.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - var framework = NuGetFramework.Parse("net11.0-android35.0"); - - MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); - } - - [Fact] - public void ShouldCheck_Net11iOS_SdkLevel11_ReturnsFalse() + [Theory] + [InlineData("net11.0-android35.0", "11.0.100", true, true, "net11.0-android with SdkLevel 11")] + [InlineData("net12.0-android35.0", "11.0.100", true, true, "net12.0-android with SdkLevel 11")] + [InlineData("net10.0-android35.0", "11.0.100", true, false, "net10.0-android below version threshold")] + [InlineData("net6.0-android31.0", "11.0.100", true, false, "net6.0-android below version threshold")] + [InlineData("net11.0-android35.0", "10.0.100", true, false, "SdkAnalysisLevel too old")] + [InlineData("net11.0-ios18.0", "11.0.100", true, false, "iOS platform, not android")] + [InlineData("net11.0", "11.0.100", true, false, "no platform")] + public void ShouldCheck_WithSdkAnalysisLevel_ReturnsExpected( + string frameworkString, + string sdkAnalysisLevel, + bool usingMicrosoftNETSdk, + bool expected, + string because) { - // iOS platform, not android - var spec = CreatePackageSpec("net11.0-ios18.0"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - var framework = NuGetFramework.Parse("net11.0-ios18.0"); + var spec = CreatePackageSpec(frameworkString); + spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse(sdkAnalysisLevel); + spec.RestoreMetadata.UsingMicrosoftNETSdk = usingMicrosoftNETSdk; + var framework = NuGetFramework.Parse(frameworkString); - MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); - } - - [Fact] - public void ShouldCheck_Net11_NoPlatform_SdkLevel11_ReturnsFalse() - { - // net11.0 without platform - var spec = CreatePackageSpec("net11.0"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - var framework = NuGetFramework.Parse("net11.0"); - - MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); + MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().Be(expected, because); } [Fact] @@ -108,7 +56,6 @@ public void ShouldCheck_NullRestoreMetadata_ReturnsFalse() [Fact] public void ShouldCheck_NullSdkAnalysisLevel_UsingMicrosoftNETSdk_ReturnsFalse() { - // When SdkAnalysisLevel is null and UsingMicrosoftNETSdk is true, IsEnabled returns false var spec = CreatePackageSpec("net11.0-android35.0"); spec.RestoreMetadata.SdkAnalysisLevel = null; spec.RestoreMetadata.UsingMicrosoftNETSdk = true; @@ -120,8 +67,6 @@ public void ShouldCheck_NullSdkAnalysisLevel_UsingMicrosoftNETSdk_ReturnsFalse() [Fact] public void ShouldCheck_NullSdkAnalysisLevel_NotUsingMicrosoftNETSdk_ReturnsTrue() { - // When SdkAnalysisLevel is null and UsingMicrosoftNETSdk is false, IsEnabled returns true - // but the framework check should still apply var spec = CreatePackageSpec("net11.0-android35.0"); spec.RestoreMetadata.SdkAnalysisLevel = null; spec.RestoreMetadata.UsingMicrosoftNETSdk = false; @@ -130,18 +75,6 @@ public void ShouldCheck_NullSdkAnalysisLevel_NotUsingMicrosoftNETSdk_ReturnsTrue MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeTrue(); } - [Fact] - public void ShouldCheck_Net6Android_SdkLevel11_ReturnsFalse() - { - // net6.0-android has version major 6, which is < 11 - var spec = CreatePackageSpec("net6.0-android31.0"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - var framework = NuGetFramework.Parse("net6.0-android31.0"); - - MonoAndroidDeprecation.ShouldCheck(spec, framework).Should().BeFalse(); - } - #endregion #region IsMonoAndroidFramework tests @@ -226,174 +159,6 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( logger.Warnings.Should().Be(1); } - [Fact] - public async Task Restore_Net10Android_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1703() - { - // net10.0-android has version major 10, below the 11 threshold - using var pathContext = new SimpleTestPathContext(); - - var packageA = new SimpleTestPackageContext("a", "1.0.0"); - packageA.AddFile("lib/monoandroid10.0/a.dll"); - - await SimpleTestPackageUtility.CreateFolderFeedV3Async( - pathContext.PackageSource, - PackageSaveMode.Defaultv3, - packageA); - - var spec = ProjectTestHelpers.GetPackageSpec("Project1", - pathContext.SolutionRoot, - framework: "net10.0-android35.0", - dependencyName: "a"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - - var logger = new TestLogger(); - var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); - - // Act - var result = await command.ExecuteAsync(); - - // Assert - result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); - } - - [Fact] - public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel10_DoesNotEmitNU1703() - { - // SDK analysis level too old - using var pathContext = new SimpleTestPathContext(); - - var packageA = new SimpleTestPackageContext("a", "1.0.0"); - packageA.AddFile("lib/monoandroid10.0/a.dll"); - - await SimpleTestPackageUtility.CreateFolderFeedV3Async( - pathContext.PackageSource, - PackageSaveMode.Defaultv3, - packageA); - - var spec = ProjectTestHelpers.GetPackageSpec("Project1", - pathContext.SolutionRoot, - framework: "net11.0-android35.0", - dependencyName: "a"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("10.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - - var logger = new TestLogger(); - var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); - - // Act - var result = await command.ExecuteAsync(); - - // Assert - result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); - } - - [Fact] - public async Task Restore_Net11Android_NetAndroidPackage_SdkLevel11_DoesNotEmitNU1703() - { - // Package uses net6.0-android, not monoandroid - should not warn - using var pathContext = new SimpleTestPathContext(); - - var packageA = new SimpleTestPackageContext("a", "1.0.0"); - packageA.AddFile("lib/net6.0-android31.0/a.dll"); - - await SimpleTestPackageUtility.CreateFolderFeedV3Async( - pathContext.PackageSource, - PackageSaveMode.Defaultv3, - packageA); - - var spec = ProjectTestHelpers.GetPackageSpec("Project1", - pathContext.SolutionRoot, - framework: "net11.0-android35.0", - dependencyName: "a"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - - var logger = new TestLogger(); - var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); - - // Act - var result = await command.ExecuteAsync(); - - // Assert - result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); - } - - [Fact] - public async Task Restore_Net11iOS_MonoAndroidPackage_SdkLevel11_DoesNotEmitNU1703() - { - // iOS project, not android - should not warn even with monoandroid package - using var pathContext = new SimpleTestPathContext(); - - var packageA = new SimpleTestPackageContext("a", "1.0.0"); - packageA.AddFile("lib/monoandroid10.0/a.dll"); - // Also add netstandard so the package resolves for iOS - packageA.AddFile("lib/netstandard2.0/a.dll"); - - await SimpleTestPackageUtility.CreateFolderFeedV3Async( - pathContext.PackageSource, - PackageSaveMode.Defaultv3, - packageA); - - var spec = ProjectTestHelpers.GetPackageSpec("Project1", - pathContext.SolutionRoot, - framework: "net11.0-ios18.0", - dependencyName: "a"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - - var logger = new TestLogger(); - var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); - - // Act - var result = await command.ExecuteAsync(); - - // Assert - result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); - } - - [Fact] - public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_NU1703_CanBeSuppressed() - { - // NoWarn for NU1703 should suppress the warning - using var pathContext = new SimpleTestPathContext(); - - var packageA = new SimpleTestPackageContext("a", "1.0.0"); - packageA.AddFile("lib/monoandroid10.0/a.dll"); - - await SimpleTestPackageUtility.CreateFolderFeedV3Async( - pathContext.PackageSource, - PackageSaveMode.Defaultv3, - packageA); - - var spec = ProjectTestHelpers.GetPackageSpec("Project1", - pathContext.SolutionRoot, - framework: "net11.0-android35.0", - dependencyName: "a"); - spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); - spec.RestoreMetadata.UsingMicrosoftNETSdk = true; - spec.RestoreMetadata.ProjectWideWarningProperties = new WarningProperties( - warningsAsErrors: new System.Collections.Generic.HashSet(), - noWarn: new System.Collections.Generic.HashSet { NuGetLogCode.NU1703 }, - allWarningsAsErrors: false, - warningsNotAsErrors: new System.Collections.Generic.HashSet()); - - var logger = new TestLogger(); - var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); - - // Act - var result = await command.ExecuteAsync(); - - // Assert - result.Success.Should().BeTrue(because: logger.ShowMessages()); - result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); - logger.Warnings.Should().Be(0); - } - [Fact] public async Task Restore_Net11Android_MultiplePackages_OnlyMonoAndroidPackageGetsNU1703() { @@ -440,13 +205,13 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( } [Fact] - public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_WarningMessageFormat() + public async Task Restore_Net11Android_MonoAndroidPackage_SdkLevel11_NU1703_PackageLevelSuppression() { - // Verify the exact warning message format + // NoWarn on the package dependency itself should suppress the warning using var pathContext = new SimpleTestPathContext(); - var packageA = new SimpleTestPackageContext("MyPackage", "3.2.1"); - packageA.AddFile("lib/monoandroid10.0/MyPackage.dll"); + var packageA = new SimpleTestPackageContext("a", "1.0.0"); + packageA.AddFile("lib/monoandroid10.0/a.dll"); await SimpleTestPackageUtility.CreateFolderFeedV3Async( pathContext.PackageSource, @@ -456,11 +221,22 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( var spec = ProjectTestHelpers.GetPackageSpec("Project1", pathContext.SolutionRoot, framework: "net11.0-android35.0", - dependencyName: "MyPackage", - dependencyVersion: "3.2.1"); + dependencyName: "a"); spec.RestoreMetadata.SdkAnalysisLevel = NuGetVersion.Parse("11.0.100"); spec.RestoreMetadata.UsingMicrosoftNETSdk = true; + // Reconstruct the dependency with package-level NoWarn for NU1703 + var oldTfi = spec.TargetFrameworks[0]; + var oldDep = oldTfi.Dependencies[0]; + var newDep = new LibraryModel.LibraryDependency(oldDep) + { + NoWarn = System.Collections.Immutable.ImmutableArray.Create(NuGetLogCode.NU1703) + }; + spec.TargetFrameworks[0] = new TargetFrameworkInformation(oldTfi) + { + Dependencies = System.Collections.Immutable.ImmutableArray.Create(newDep) + }; + var logger = new TestLogger(); var command = new RestoreCommand(ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, spec)); @@ -469,12 +245,8 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( // Assert result.Success.Should().BeTrue(because: logger.ShowMessages()); - var logMessage = result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1703); - var expectedMessage = string.Format(CultureInfo.CurrentCulture, - Strings.Warning_MonoAndroidFrameworkDeprecated, - "MyPackage", - "3.2.1"); - logMessage.Message.Should().Be(expectedMessage); + result.LockFile.LogMessages.Should().NotContain(m => m.Code == NuGetLogCode.NU1703); + logger.Warnings.Should().Be(0); } #endregion From 4520160a63e2c4290b2cfcb7d76f4171e76e8c8c Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 24 Apr 2026 02:07:32 +0800 Subject: [PATCH 6/6] Update test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs Co-authored-by: Nikolche Kolev --- .../RestoreCommandTests/MonoAndroidDeprecationTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs index c5fd9da1b0d..aea50ceabbf 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/MonoAndroidDeprecationTests.cs @@ -155,6 +155,8 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( result.LockFile.LogMessages[0].Level.Should().Be(LogLevel.Warning); result.LockFile.LogMessages[0].LibraryId.Should().Be("a"); result.LockFile.LogMessages[0].Message.Should().Contain("MonoAndroid"); + result.LockFile.LogMessages[0].TargetGraphs.Should().HaveCount(1); + result.LockFile.LogMessages[0].TargetGraphs[0].Should().Be("net11.0-android35.0"); logger.Errors.Should().Be(0); logger.Warnings.Should().Be(1); }