Skip to content
Open
24 changes: 24 additions & 0 deletions src/NuGet.Core/NuGet.Commands/RestoreCommand/LockFileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Comment thread
sbomer marked this conversation as resolved.
Outdated
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
internal static class MonoAndroidDeprecation
{
/// <summary>
/// Determines whether the MonoAndroid deprecation check should be performed for the given project and target framework.
/// </summary>
/// <param name="project">The package spec containing restore metadata.</param>
/// <param name="framework">The target framework of the current graph.</param>
/// <returns>True if the deprecation check should be performed.</returns>
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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if NuGet is the right place to check for this. Can this logic be moved to the Android workload instead? It's probably easiest to do in NuGet, but this is code that will run for every restore, even when the Android workload isn't installed.

When I take a binlog of a normal build, the .NET SDK reads NuGet's assets file in the ResolvePackageAssets task. The RuntimeCopyLocalItems and ResolvedCompileFileDefinitions items have PathInPackage metadata, which can be searched for monoandroid, for example a regex something like [lib|ref]/monoandroid*. Checking the runtime assets will be tricker to validate the subdirectory that corresponds to the TFM.

It's not as precise as checking the NuGetFramework for the selected asset, but the question is if it's good enough.

Another design idea is to change the assets file to emit a "framework" metadata for every asset, and change the SDK's ResolvePackageAssets to copy that metadata into the MSBuild item, which can then be checked by the android workload's msbuild targets. However, I don't like this idea as the increased assets file size and additional metadata on every item will cause additional memory allocations and therefore more GC and will probably have worse perf impact than the small benefit in moving the check from NuGet to the android workload.

Given restore almost never breaks backwards compatibility, I just want to make sure that NuGet isn't stuck with this "forever" when it's fundamentally a non-NuGet concern. So, trying to do due diligence in checking if the owning component can feasibly implement the check, rather than NuGet.

Copy link
Copy Markdown
Author

@sbomer sbomer Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar enough with the design of the workloads to know whether it would make sense to handle it there. @jonathanpeppers what do you think?

My instinct is that this should warn during restore, not during build, which is why I thought it made the most sense to handle it in NuGet. Plus there was precedent in the (now removed) Xamarin.iOS warning.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design says to emit this warning here in NuGet (but note the code was NU1701 back then):

I'm not aware of us running any MSBuild logic during restore from a workload.

If we had to do it from a workload, it would be easiest to do during a build -- which is maybe not what we'd want.

}

/// <summary>
/// Checks whether the given lock file target library uses the deprecated MonoAndroid framework
/// by inspecting the paths of compile-time and runtime assemblies.
/// </summary>
/// <param name="library">The lock file target library to check.</param>
/// <returns>True if the library uses MonoAndroid framework assets.</returns>
internal static bool UsesMonoAndroidFramework(LockFileTargetLibrary library)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there are cheaper ways to determine this, that doesn't involve re-parsing.

Do you know how much overhead this is adding?
I guess the fact that it's only on -android frameworks makes it not a hot path.
It's also using span to avoid allocating, but there may be some ways we can utilize the details from the asset selection logic that already parses this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried another approach that reuses the asset selection results, PTAL!

{
return ContainsMonoAndroidItem(library.CompileTimeAssemblies)
|| ContainsMonoAndroidItem(library.RuntimeAssemblies);
}

private static bool ContainsMonoAndroidItem(IList<LockFileItem> 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;
}
}
}
9 changes: 9 additions & 0 deletions src/NuGet.Core/NuGet.Commands/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/NuGet.Core/NuGet.Commands/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@
<data name="Log_ImportsFallbackWarning" xml:space="preserve">
<value>Package '{0}' was restored using '{1}' instead of the project target framework '{2}'. This package may not be fully compatible with your project.</value>
</data>
<data name="Warning_MonoAndroidFrameworkDeprecated" xml:space="preserve">
<value>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.</value>
<comment>{0} - package id
{1} - package version</comment>
</data>
<data name="Log_CycleDetected" xml:space="preserve">
<value>Cycle detected.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ internal static class SdkAnalysisLevelMinimums
/// </summary>
internal static readonly NuGetVersion V10_0_300 = new("10.0.300");

/// <summary>
/// Minimum SDK Analysis Level required for:
/// <list type="bullet">
/// <item>warning when packages use the deprecated MonoAndroid framework</item>
/// </list>
/// </summary>
internal static readonly NuGetVersion V11_0_100 = new("11.0.100");

/// <summary>
/// Determines whether the feature is enabled based on the SDK analysis level.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet vyžaduje zdroje HTTPS. Další informace najdete na https://aka.ms/nuget-
<target state="translated">{0} neposkytuje inkluzivní dolní mez pro závislost {1}. Místo toho bylo přeloženo: {2}.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">Balíček „{0}“ {1} má známé {2} ohrožení zabezpečení závažnosti, {3}</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet erfordert HTTPS-Quellen. Weitere Informationen finden Sie unter https://ak
<target state="translated">{0} stellt keine inklusive untere Grenze für Abhängigkeiten {1} bereit. {2} wurde stattdessen aufgelöst.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">Das Paket "{0}" {1} weist eine bekannte {2} Schweregrad-Sicherheitsanfälligkeit auf, {3}.</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet requiere orígenes HTTPS. Consulte https://aka.ms/nuget-https-everywhere p
<target state="translated">{0} no proporciona un límite inferior inclusivo para la dependencia {1}. {2} se resolvió en su lugar.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">El paquete "{0}" {1} tiene una vulnerabilidad de gravedad {2} conocida, {3}</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet nécessite des sources HTTPS. Reportez-vous à https://aka.ms/nuget-https-
<target state="translated">{0} ne fournit pas de limite inférieure inclusive pour la dépendance {1}. {2} a été résolu à la place.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">Le package '{0}' {1} présente une vulnérabilité de gravité {2} connue, {3}.</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet richiede origini HTTPS. Vedi https://aka.ms/nuget-https-everywhere per alt
<target state="translated">In {0} non è specificato un limite inferiore inclusivo per la dipendenza {1}. {2} è stato risolto.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">Il pacchetto '{0}' {1} presenta una vulnerabilità nota di gravità {2}, {3}</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet には HTTPS ソースが必要です。詳しくは、https://aka.ms/nuge
<target state="translated">{0} では、依存関係 {1} の下限値 (その値を含む) を指定しませんでした。代わりに、{2} が解決されました。</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">パッケージ '{0}' {1} に既知の {2} 重大度の脆弱性があります、{3}</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet에는 HTTPS 원본이 필요합니다. https://aka.ms/nuget-https-everywhe
<target state="translated">{0}은(는) 종속성 {1}에 대한 포괄적인 하한을 제공하지 않습니다. 대신 {2}이(가) 확인되었습니다.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">'{0}' {1} 패키지에 알려진 {2} 심각도 취약성인 {3}이(가) 있습니다.</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.pl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ Menedżer NuGet wymaga źródeł HTTPS. Aby uzyskać więcej informacji, sprawd
<target state="translated">{0} nie zapewnia łącznego dolnego ograniczenia dla zależności {1}. {2} został rozwiązany zamiast tego.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">Pakiet „{0}” {1} ma znane {2} luki w zabezpieczeniach o ważności, {3}</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.pt-BR.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ O NuGet exige fontes HTTPS. Consulte https://aka.ms/nuget-https-everywhere para
<target state="translated">{0} não fornece um limite inferior inclusivo para a dependência {1}. {2} foi resolvido.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">O pacote '{0}' {1} tem uma {2} vulnerabilidade de gravidade conhecida, {3}</target>
Expand Down
6 changes: 6 additions & 0 deletions src/NuGet.Core/NuGet.Commands/xlf/Strings.ru.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,12 @@ NuGet requires HTTPS sources. Refer to https://aka.ms/nuget-https-everywhere for
<target state="translated">{0} не задает включенную нижнюю границу для зависимости {1}. Использовалась версия {2}.</target>
<note />
</trans-unit>
<trans-unit id="Warning_MonoAndroidFrameworkDeprecated">
<source>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.</source>
<target state="new">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.</target>
<note>{0} - package id
{1} - package version</note>
</trans-unit>
<trans-unit id="Warning_PackageWithKnownVulnerability">
<source>Package '{0}' {1} has a known {2} severity vulnerability, {3}</source>
<target state="translated">У пакета "{0}" {1} есть известная уязвимость {3} (уровень серьезности: {2})</target>
Expand Down
Loading
Loading