diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs index d46e8702cb0..cffbfb0394d 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 NU1703 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.NU1703, + 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 861fd1d7ac5..60f9b5b5017 100644 --- a/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs +++ b/src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs @@ -376,7 +376,8 @@ 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, 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..5586575ab68 --- /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_EmitsNU1703() + { + // 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.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"); + logger.Errors.Should().Be(0); + 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() + { + // 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.NU1703).Should().HaveCount(1); + result.LockFile.LogMessages.Single(m => m.Code == NuGetLogCode.NU1703).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.NU1703); + 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 + } +}