diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs index 794d44ce53e..e766f14aed6 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs @@ -511,6 +511,8 @@ private async Task GetPackageSpecAsync(ISettings settings) string centralPackageTransitivePinningEnabled = GetPropertySafe(_vsProjectAdapter.BuildProperties, ProjectBuildProperties.CentralPackageTransitivePinningEnabled); // Do not add new properties here. Use BuildProperties.GetPropertyValue instead, without DTE fallback. #pragma warning restore CS0618 // Type or member is obsolete + string skdAnalysisLevelString = _vsProjectAdapter.BuildProperties.GetPropertyValue(ProjectBuildProperties.SdkAnalysisLevel); + string usingNetSdk = _vsProjectAdapter.BuildProperties.GetPropertyValue(ProjectBuildProperties.UsingMicrosoftNETSdk); return new PackageSpec(tfis) { @@ -555,6 +557,8 @@ private async Task GetPackageSpecAsync(ISettings settings) CentralPackageFloatingVersionsEnabled = MSBuildStringUtility.IsTrue(_vsProjectAdapter.BuildProperties.GetPropertyValue(ProjectBuildProperties.CentralPackageFloatingVersionsEnabled)), CentralPackageTransitivePinningEnabled = MSBuildStringUtility.IsTrue(centralPackageTransitivePinningEnabled), RestoreAuditProperties = auditProperties, + SdkAnalysisLevel = MSBuildRestoreUtility.GetSdkAnalysisLevel(skdAnalysisLevelString), + UsingMicrosoftNETSdk = MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(usingNetSdk), } }; } diff --git a/src/NuGet.Clients/NuGet.SolutionRestoreManager/VSNominationUtilities.cs b/src/NuGet.Clients/NuGet.SolutionRestoreManager/VSNominationUtilities.cs index 80458f3bee5..d17c49e82ea 100644 --- a/src/NuGet.Clients/NuGet.SolutionRestoreManager/VSNominationUtilities.cs +++ b/src/NuGet.Clients/NuGet.SolutionRestoreManager/VSNominationUtilities.cs @@ -712,5 +712,26 @@ private static string[] HandleClear(string[] input) return input; } + + internal static NuGetVersion? GetSdkAnalysisLevel(IReadOnlyList targetFrameworks) + { + string? skdAnalysisLevelString = GetSingleNonEvaluatedPropertyOrNull(targetFrameworks, nameof(ProjectBuildProperties.SdkAnalysisLevel), v => v); + + return skdAnalysisLevelString is null + ? null + : MSBuildRestoreUtility.GetSdkAnalysisLevel(skdAnalysisLevelString); + } + + internal static bool GetUsingMicrosoftNETSdk(IReadOnlyList targetFrameworks) + { + string? usingNetSdk = GetSingleNonEvaluatedPropertyOrNull(targetFrameworks, nameof(ProjectBuildProperties.UsingMicrosoftNETSdk), v => v); + + if (usingNetSdk is not null) + { + return MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(usingNetSdk); + } + + return false; + } } } diff --git a/src/NuGet.Clients/NuGet.SolutionRestoreManager/VsSolutionRestoreService.cs b/src/NuGet.Clients/NuGet.SolutionRestoreManager/VsSolutionRestoreService.cs index 51a98f5cf84..10f2cbb1884 100644 --- a/src/NuGet.Clients/NuGet.SolutionRestoreManager/VsSolutionRestoreService.cs +++ b/src/NuGet.Clients/NuGet.SolutionRestoreManager/VsSolutionRestoreService.cs @@ -387,9 +387,11 @@ internal static PackageSpec ToPackageSpec(ProjectNames projectNames, IVsProjectR CentralPackageVersionOverrideDisabled = VSNominationUtilities.IsCentralPackageVersionOverrideDisabled(targetFrameworks), CentralPackageTransitivePinningEnabled = VSNominationUtilities.IsCentralPackageTransitivePinningEnabled(targetFrameworks), RestoreAuditProperties = VSNominationUtilities.GetRestoreAuditProperties(targetFrameworks), + SdkAnalysisLevel = VSNominationUtilities.GetSdkAnalysisLevel(targetFrameworks), + UsingMicrosoftNETSdk = VSNominationUtilities.GetUsingMicrosoftNETSdk(targetFrameworks), }, RuntimeGraph = VSNominationUtilities.GetRuntimeGraph(targetFrameworks), - RestoreSettings = new ProjectRestoreSettings() { HideWarningsAndErrors = true } + RestoreSettings = new ProjectRestoreSettings() { HideWarningsAndErrors = true }, }; return packageSpec; diff --git a/src/NuGet.Core/NuGet.Build.Tasks.Console/MSBuildStaticGraphRestore.cs b/src/NuGet.Core/NuGet.Build.Tasks.Console/MSBuildStaticGraphRestore.cs index 62d4dd73bcf..4559c06be0d 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks.Console/MSBuildStaticGraphRestore.cs +++ b/src/NuGet.Core/NuGet.Build.Tasks.Console/MSBuildStaticGraphRestore.cs @@ -892,6 +892,8 @@ private PackageSpec GetPackageSpec(IMSBuildProject project, IReadOnlyDictionary< restoreMetadata.RestoreLockProperties = new RestoreLockProperties(project.GetProperty("RestorePackagesWithLockFile"), project.GetProperty("NuGetLockFilePath"), project.IsPropertyTrue("RestoreLockedMode")); restoreMetadata.Sources = GetSources(project, innerNodes, settings); restoreMetadata.TargetFrameworks = GetProjectRestoreMetadataFrameworkInfos(targetFrameworkInfos, projectsByTargetFramework); + restoreMetadata.UsingMicrosoftNETSdk = MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(project.GetProperty("UsingMicrosoftNETSdk")); + restoreMetadata.SdkAnalysisLevel = MSBuildRestoreUtility.GetSdkAnalysisLevel(project.GetProperty("SdkAnalysisLevel")); return (restoreMetadata, targetFrameworkInfos); diff --git a/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets b/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets index 82aa7b37d61..57a8f761f8d 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets +++ b/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets @@ -920,6 +920,8 @@ Copyright (c) .NET Foundation. All rights reserved. $(NuGetAudit) $(NuGetAuditLevel) $(NuGetAuditMode) + $(SdkAnalysisLevel) + $(UsingMicrosoftNETSdk) diff --git a/src/NuGet.Core/NuGet.Build.Tasks/Strings.resx b/src/NuGet.Core/NuGet.Build.Tasks/Strings.resx index 1a8fd6a3297..4f248c1270b 100644 --- a/src/NuGet.Core/NuGet.Build.Tasks/Strings.resx +++ b/src/NuGet.Core/NuGet.Build.Tasks/Strings.resx @@ -229,4 +229,4 @@ An error occurred starting static graph-based restore. {0}. Please file an issue at https://github.com/NuGet/Home 0 - The exception message - + \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt index 9f87456d854..3263dfd0ec1 100644 --- a/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Commands/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -3,4 +3,6 @@ NuGet.Commands.UpdateSourceArgs.AllowInsecureConnections.set -> void NuGet.Commands.UpdateSourceArgs.AllowInsecureConnections.get -> bool NuGet.Commands.AddSourceArgs.AllowInsecureConnections.set -> void NuGet.Commands.AddSourceArgs.AllowInsecureConnections.get -> bool +~static NuGet.Commands.MSBuildRestoreUtility.GetSdkAnalysisLevel(string sdkAnalysisLevel) -> NuGet.Versioning.NuGetVersion +~static NuGet.Commands.MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(string usingMicrosoftNETSdk) -> bool NuGet.Commands.RestoreCommandProvidersCache.GetOrCreate(string! globalPackagesPath, System.Collections.Generic.IReadOnlyList! fallbackPackagesPaths, System.Collections.Generic.IReadOnlyList! packageSources, System.Collections.Generic.IReadOnlyList! auditSources, NuGet.Protocol.Core.Types.SourceCacheContext! cacheContext, NuGet.Common.ILogger! log, bool updateLastAccess) -> NuGet.Commands.RestoreCommandProviders! diff --git a/src/NuGet.Core/NuGet.Commands/PublicAPI/netcoreapp5.0/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Commands/PublicAPI/netcoreapp5.0/PublicAPI.Unshipped.txt index 9f87456d854..3263dfd0ec1 100644 --- a/src/NuGet.Core/NuGet.Commands/PublicAPI/netcoreapp5.0/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Commands/PublicAPI/netcoreapp5.0/PublicAPI.Unshipped.txt @@ -3,4 +3,6 @@ NuGet.Commands.UpdateSourceArgs.AllowInsecureConnections.set -> void NuGet.Commands.UpdateSourceArgs.AllowInsecureConnections.get -> bool NuGet.Commands.AddSourceArgs.AllowInsecureConnections.set -> void NuGet.Commands.AddSourceArgs.AllowInsecureConnections.get -> bool +~static NuGet.Commands.MSBuildRestoreUtility.GetSdkAnalysisLevel(string sdkAnalysisLevel) -> NuGet.Versioning.NuGetVersion +~static NuGet.Commands.MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(string usingMicrosoftNETSdk) -> bool NuGet.Commands.RestoreCommandProvidersCache.GetOrCreate(string! globalPackagesPath, System.Collections.Generic.IReadOnlyList! fallbackPackagesPaths, System.Collections.Generic.IReadOnlyList! packageSources, System.Collections.Generic.IReadOnlyList! auditSources, NuGet.Protocol.Core.Types.SourceCacheContext! cacheContext, NuGet.Common.ILogger! log, bool updateLastAccess) -> NuGet.Commands.RestoreCommandProviders! diff --git a/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 9f87456d854..3263dfd0ec1 100644 --- a/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Commands/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -3,4 +3,6 @@ NuGet.Commands.UpdateSourceArgs.AllowInsecureConnections.set -> void NuGet.Commands.UpdateSourceArgs.AllowInsecureConnections.get -> bool NuGet.Commands.AddSourceArgs.AllowInsecureConnections.set -> void NuGet.Commands.AddSourceArgs.AllowInsecureConnections.get -> bool +~static NuGet.Commands.MSBuildRestoreUtility.GetSdkAnalysisLevel(string sdkAnalysisLevel) -> NuGet.Versioning.NuGetVersion +~static NuGet.Commands.MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(string usingMicrosoftNETSdk) -> bool NuGet.Commands.RestoreCommandProvidersCache.GetOrCreate(string! globalPackagesPath, System.Collections.Generic.IReadOnlyList! fallbackPackagesPaths, System.Collections.Generic.IReadOnlyList! packageSources, System.Collections.Generic.IReadOnlyList! auditSources, NuGet.Protocol.Core.Types.SourceCacheContext! cacheContext, NuGet.Common.ILogger! log, bool updateLastAccess) -> NuGet.Commands.RestoreCommandProviders! diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs index 4e68bea613a..5b8aa903c14 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/MSBuildRestoreUtility.cs @@ -308,6 +308,8 @@ public static PackageSpec GetPackageSpec(IEnumerable items) result.RestoreMetadata.CentralPackageVersionOverrideDisabled = isCentralPackageVersionOverrideDisabled; result.RestoreMetadata.CentralPackageFloatingVersionsEnabled = isCentralPackageFloatingVersionsEnabled; result.RestoreMetadata.CentralPackageTransitivePinningEnabled = isCentralPackageTransitivePinningEnabled; + result.RestoreMetadata.UsingMicrosoftNETSdk = MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(specItem.GetProperty("UsingMicrosoftNETSdk")); + result.RestoreMetadata.SdkAnalysisLevel = MSBuildRestoreUtility.GetSdkAnalysisLevel(specItem.GetProperty("SdkAnalysisLevel")); } return result; @@ -941,6 +943,36 @@ public static RestoreAuditProperties GetRestoreAuditProperties(IMSBuildItem spec return null; } + public static NuGetVersion GetSdkAnalysisLevel(string sdkAnalysisLevel) + { + if (string.IsNullOrEmpty(sdkAnalysisLevel)) + { + return null; + } + + if (!NuGetVersion.TryParse(sdkAnalysisLevel, out NuGetVersion version)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Invalid_AttributeValue, "SdkAnalysisLevel", sdkAnalysisLevel, "9.0.100")); + } + + return version; + } + + public static bool GetUsingMicrosoftNETSdk(string usingMicrosoftNETSdk) + { + if (string.IsNullOrEmpty(usingMicrosoftNETSdk)) + { + return false; + } + + if (!bool.TryParse(usingMicrosoftNETSdk, out var result)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Invalid_AttributeValue, "UsingMicrosoftNETSdk", usingMicrosoftNETSdk, "false")); + } + + return result; + } + private static HashSet GetAuditSuppressions(IEnumerable items) { IEnumerable suppressions = GetItemByType(items, "NuGetAuditSuppress") diff --git a/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs b/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs index 5ad97708186..ac6a326af50 100644 --- a/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs +++ b/src/NuGet.Core/NuGet.Commands/Strings.Designer.cs @@ -1014,6 +1014,15 @@ internal static string FoundVersionsInSourceWithoutMatch { } } + /// + /// Looks up a localized string similar to The {0} value: `{1}` is invalid. Valid example: `{2}`. + /// + internal static string Invalid_AttributeValue { + get { + return ResourceManager.GetString("Invalid_AttributeValue", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid restore input. {0}. /// diff --git a/src/NuGet.Core/NuGet.Commands/Strings.resx b/src/NuGet.Core/NuGet.Commands/Strings.resx index e6eb62111fb..d434ff44c8b 100644 --- a/src/NuGet.Core/NuGet.Commands/Strings.resx +++ b/src/NuGet.Core/NuGet.Commands/Strings.resx @@ -1117,6 +1117,12 @@ NuGet requires HTTPS sources. Please refer to https://aka.ms/nuget-https-everywh NuGet requires HTTPS sources. Please refer to https://aka.ms/nuget-https-everywhere for more information. {0} - list of server URIs + + The {0} value: `{1}` is invalid. Valid example: `{2}` + {0} - is the attribute +{1} is the invalid value of the attribute. +{2} a valid attriibute value example + Audit source '{0}' did not provide any vulnerability data. {0} is the source name diff --git a/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectBuildProperties.cs b/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectBuildProperties.cs index ff578892656..258c5b1e317 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectBuildProperties.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectBuildProperties.cs @@ -63,5 +63,7 @@ public static class ProjectBuildProperties public const string NuGetAuditLevel = nameof(NuGetAuditLevel); public const string NuGetAuditMode = nameof(NuGetAuditMode); public const string CentralPackageFloatingVersionsEnabled = nameof(CentralPackageFloatingVersionsEnabled); + public const string SdkAnalysisLevel = nameof(SdkAnalysisLevel); + public const string UsingMicrosoftNETSdk = nameof(UsingMicrosoftNETSdk); } } diff --git a/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt index f5bd21fac2a..6ca11fd0e83 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.PackageManagement/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +~const NuGet.ProjectManagement.ProjectBuildProperties.SdkAnalysisLevel = "SdkAnalysisLevel" -> string +~const NuGet.ProjectManagement.ProjectBuildProperties.UsingMicrosoftNETSdk = "UsingMicrosoftNETSdk" -> string NuGet.PackageManagement.AuditChecker.AuditChecker(System.Collections.Generic.List! packageSources, System.Collections.Generic.IReadOnlyList? auditSources, NuGet.Protocol.Core.Types.SourceCacheContext! sourceCacheContext, NuGet.Common.ILogger! logger) -> void ~NuGet.PackageManagement.PackageRestoreContext.AuditSources.get -> System.Collections.Generic.IReadOnlyList ~NuGet.PackageManagement.PackageRestoreContext.PackageRestoreContext(NuGet.PackageManagement.NuGetPackageManager nuGetPackageManager, System.Collections.Generic.IEnumerable packages, System.Threading.CancellationToken token, System.EventHandler packageRestoredEvent, System.EventHandler packageRestoreFailedEvent, System.Collections.Generic.IEnumerable sourceRepositories, System.Collections.Generic.IReadOnlyList auditSources, int maxNumberOfParallelTasks, bool enableNuGetAudit, System.Collections.Generic.Dictionary restoreAuditProperties, NuGet.Common.ILogger logger) -> void diff --git a/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.Utf8JsonStreamReader.cs b/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.Utf8JsonStreamReader.cs index 04fee0babc1..280df74791e 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.Utf8JsonStreamReader.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.Utf8JsonStreamReader.cs @@ -109,6 +109,8 @@ public partial class JsonPackageSpecReader private static readonly byte[] HashTagImportPropertyName = Encoding.UTF8.GetBytes("#import"); private static readonly byte[] ProjectReferencesPropertyName = Encoding.UTF8.GetBytes("projectReferences"); private static readonly byte[] EmptyStringPropertyName = Encoding.UTF8.GetBytes(string.Empty); + private static readonly byte[] SdkAnalysisLevel = Encoding.UTF8.GetBytes("SdkAnalysisLevel"); + private static readonly byte[] UsingMicrosoftNETSdk = Encoding.UTF8.GetBytes("UsingMicrosoftNETSdk"); internal static PackageSpec GetPackageSpecUtf8JsonStreamReader(Stream stream, string name, string packageSpecPath, IEnvironmentVariableReader environmentVariableReader, string snapshotValue = null) { @@ -945,6 +947,8 @@ private static void ReadMSBuildMetadata(ref Utf8JsonStreamReader jsonReader, Pac RestoreAuditProperties auditProperties = null; bool useMacros = MSBuildStringUtility.IsTrue(environmentVariableReader.GetEnvironmentVariable(MacroStringsUtility.NUGET_ENABLE_EXPERIMENTAL_MACROS)); var userSettingsDirectory = NuGetEnvironment.GetFolderPath(NuGetFolderPath.UserSettingsDirectory); + bool usingMicrosoftNetSdk = true; + NuGetVersion sdkAnalysisLevel = null; if (jsonReader.Read() && jsonReader.TokenType == JsonTokenType.StartObject) { @@ -1169,6 +1173,32 @@ private static void ReadMSBuildMetadata(ref Utf8JsonStreamReader jsonReader, Pac warningProperties = new WarningProperties(warnAsError, noWarn, allWarningsAsErrors, warningsNotAsErrors); } + else if (jsonReader.ValueTextEquals(UsingMicrosoftNETSdk)) + { + usingMicrosoftNetSdk = jsonReader.ReadNextTokenAsBoolOrThrowAnException(UsingMicrosoftNETSdk); + } + else if (jsonReader.ValueTextEquals(SdkAnalysisLevel)) + { + string sdkAnalysisLevelString = jsonReader.ReadNextTokenAsString(); + + if (!string.IsNullOrEmpty(sdkAnalysisLevelString)) + { + try + { + sdkAnalysisLevel = new NuGetVersion(sdkAnalysisLevelString); + } + catch (ArgumentException ex) + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, + Strings.Invalid_AttributeValue, + Encoding.UTF8.GetString(SdkAnalysisLevel), + sdkAnalysisLevelString, + "9.0.100"), + ex); + } + } + } else { jsonReader.Skip(); @@ -1193,6 +1223,8 @@ private static void ReadMSBuildMetadata(ref Utf8JsonStreamReader jsonReader, Pac msbuildMetadata.CentralPackageVersionOverrideDisabled = centralPackageVersionOverrideDisabled; msbuildMetadata.CentralPackageTransitivePinningEnabled = CentralPackageTransitivePinningEnabled; msbuildMetadata.RestoreAuditProperties = auditProperties; + msbuildMetadata.SdkAnalysisLevel = sdkAnalysisLevel; + msbuildMetadata.UsingMicrosoftNETSdk = usingMicrosoftNetSdk; if (configFilePaths != null) { diff --git a/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.cs b/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.cs index 78eafce0a5f..3c8690fa648 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/JsonPackageSpecReader.cs @@ -953,6 +953,8 @@ private static void ReadMSBuildMetadata(JsonTextReader jsonReader, PackageSpec p RestoreAuditProperties auditProperties = null; bool useMacros = MSBuildStringUtility.IsTrue(environmentVariableReader.GetEnvironmentVariable(MacroStringsUtility.NUGET_ENABLE_EXPERIMENTAL_MACROS)); var userSettingsDirectory = NuGetEnvironment.GetFolderPath(NuGetFolderPath.UserSettingsDirectory); + bool usingMicrosoftNetSdk = true; + NuGetVersion sdkAnalysisLevel = null; jsonReader.ReadObject(propertyName => { @@ -1154,6 +1156,39 @@ private static void ReadMSBuildMetadata(JsonTextReader jsonReader, PackageSpec p warningProperties = new WarningProperties(warnAsError, noWarn, allWarningsAsErrors, warningsNotAsErrors); break; + case "SdkAnalysisLevel": + string skdAnalysisLevelString = jsonReader.ReadNextTokenAsString(); + + if (!string.IsNullOrEmpty(skdAnalysisLevelString)) + { + try + { + sdkAnalysisLevel = new NuGetVersion(skdAnalysisLevelString); + } + catch (ArgumentException ex) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Invalid_AttributeValue, "SdkAnalysisLevel", skdAnalysisLevelString, "9.0.100"), ex); + } + } + break; + + case "UsingMicrosoftNETSdk": + + try + { + usingMicrosoftNetSdk = jsonReader.ReadAsBoolean() ?? usingMicrosoftNetSdk; + } + catch (ArgumentException ex) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + Strings.Invalid_AttributeValue, + "UsingMicrosoftNETSdk", + jsonReader.ReadNextTokenAsString(), + "false"), ex); + } + break; } }); @@ -1174,6 +1209,8 @@ private static void ReadMSBuildMetadata(JsonTextReader jsonReader, PackageSpec p msbuildMetadata.CentralPackageVersionOverrideDisabled = centralPackageVersionOverrideDisabled; msbuildMetadata.CentralPackageTransitivePinningEnabled = CentralPackageTransitivePinningEnabled; msbuildMetadata.RestoreAuditProperties = auditProperties; + msbuildMetadata.UsingMicrosoftNETSdk = usingMicrosoftNetSdk; + msbuildMetadata.SdkAnalysisLevel = sdkAnalysisLevel; if (configFilePaths != null) { diff --git a/src/NuGet.Core/NuGet.ProjectModel/PackageSpecWriter.cs b/src/NuGet.Core/NuGet.ProjectModel/PackageSpecWriter.cs index fed438dd567..1b0eeb7352c 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/PackageSpecWriter.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/PackageSpecWriter.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices.ComTypes; using Newtonsoft.Json; using NuGet.Common; using NuGet.Frameworks; @@ -65,7 +64,6 @@ internal static void Write(PackageSpec packageSpec, IObjectWriter writer, bool h SetDictionaryValues(writer, "scripts", packageSpec.Scripts); #pragma warning restore CS0612 // Type or member is obsolete - if (packageSpec.Dependencies.Count > 0) { SetDependencies(writer, packageSpec.Dependencies); @@ -188,6 +186,11 @@ private static void SetMSBuildMetadata(IObjectWriter writer, PackageSpec package SetValue(writer, "packagesConfigPath", pcMsbuildMetadata.PackagesConfigPath); } + if (packageSpec.RestoreMetadata.SdkAnalysisLevel is not null) + { + SetValue(writer, "SdkAnalysisLevel", packageSpec.RestoreMetadata.SdkAnalysisLevel.ToString()); + } + writer.WriteObjectEnd(); } @@ -210,6 +213,7 @@ private static void WriteMetadataBooleans(IObjectWriter writer, ProjectRestoreMe SetValueIfTrue(writer, "centralPackageFloatingVersionsEnabled", msbuildMetadata.CentralPackageFloatingVersionsEnabled); SetValueIfTrue(writer, "centralPackageVersionOverrideDisabled", msbuildMetadata.CentralPackageVersionOverrideDisabled); SetValueIfTrue(writer, "CentralPackageTransitivePinningEnabled", msbuildMetadata.CentralPackageTransitivePinningEnabled); + SetValueIfFalse(writer, "UsingMicrosoftNETSdk", msbuildMetadata.UsingMicrosoftNETSdk); } @@ -694,6 +698,14 @@ private static void SetValueIfTrue(IObjectWriter writer, string name, bool value } } + private static void SetValueIfFalse(IObjectWriter writer, string name, bool value) + { + if (!value) + { + writer.WriteNameValue(name, value); + } + } + private static void SetValueIfNotNull(IObjectWriter writer, string name, string value) { if (!string.IsNullOrEmpty(value)) diff --git a/src/NuGet.Core/NuGet.ProjectModel/ProjectRestoreMetadata.cs b/src/NuGet.Core/NuGet.ProjectModel/ProjectRestoreMetadata.cs index 406002aaf39..f2f2973715c 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/ProjectRestoreMetadata.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/ProjectRestoreMetadata.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using NuGet.Common; using NuGet.Configuration; using NuGet.Shared; +using NuGet.Versioning; namespace NuGet.ProjectModel { @@ -132,6 +134,18 @@ public class ProjectRestoreMetadata : IEquatable public RestoreAuditProperties RestoreAuditProperties { get; set; } + /// + /// A unified flag to help users manage their SDK warning levels. Example: 9.0.100. + /// When introducing a new warning or error use this property to + /// allow users to tell the sdk "treat me as if I were SDK x.y.z" and manage breaking changes + /// + public NuGetVersion SdkAnalysisLevel { get; set; } + + /// + /// Indicates that Microsoft.NET.Sdk is being used. + /// + public bool UsingMicrosoftNETSdk { get; set; } + public override int GetHashCode() { StringComparer osStringComparer = PathUtility.GetStringComparerBasedOnOS(); @@ -162,6 +176,8 @@ public override int GetHashCode() hashCode.AddObject(CentralPackageVersionOverrideDisabled); hashCode.AddObject(CentralPackageTransitivePinningEnabled); hashCode.AddObject(RestoreAuditProperties); + hashCode.AddObject(UsingMicrosoftNETSdk); + hashCode.AddObject(SdkAnalysisLevel); return hashCode.CombinedHash; } @@ -207,7 +223,9 @@ public bool Equals(ProjectRestoreMetadata other) EqualityUtility.EqualsWithNullCheck(CentralPackageFloatingVersionsEnabled, other.CentralPackageFloatingVersionsEnabled) && EqualityUtility.EqualsWithNullCheck(CentralPackageVersionOverrideDisabled, other.CentralPackageVersionOverrideDisabled) && EqualityUtility.EqualsWithNullCheck(CentralPackageTransitivePinningEnabled, other.CentralPackageTransitivePinningEnabled) && - RestoreAuditProperties == other.RestoreAuditProperties; + RestoreAuditProperties == other.RestoreAuditProperties && + UsingMicrosoftNETSdk == other.UsingMicrosoftNETSdk && + EqualityUtility.EqualsWithNullCheck(SdkAnalysisLevel, other.SdkAnalysisLevel); } private HashSet GetSources(IList sources) @@ -258,6 +276,8 @@ protected void FillClone(ProjectRestoreMetadata clone) clone.CentralPackageVersionOverrideDisabled = CentralPackageVersionOverrideDisabled; clone.CentralPackageTransitivePinningEnabled = CentralPackageTransitivePinningEnabled; clone.RestoreAuditProperties = RestoreAuditProperties?.Clone(); + clone.SdkAnalysisLevel = SdkAnalysisLevel; + clone.UsingMicrosoftNETSdk = UsingMicrosoftNETSdk; } } } diff --git a/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt index 5ea8bc8519d..b1de3e531dd 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.ProjectModel/PublicAPI.Unshipped.txt @@ -9,3 +9,7 @@ NuGet.ProjectModel.LockFileReadFlags.PackageSpec = 16 -> NuGet.ProjectModel.Lock NuGet.ProjectModel.LockFileReadFlags.ProjectFileDependencyGroups = 4 -> NuGet.ProjectModel.LockFileReadFlags NuGet.ProjectModel.LockFileReadFlags.Targets = 2 -> NuGet.ProjectModel.LockFileReadFlags ~static NuGet.ProjectModel.LockFileUtilities.GetLockFile(string lockFilePath, NuGet.Common.ILogger logger, NuGet.ProjectModel.LockFileReadFlags flags) -> NuGet.ProjectModel.LockFile +~NuGet.ProjectModel.ProjectRestoreMetadata.SdkAnalysisLevel.get -> NuGet.Versioning.NuGetVersion +~NuGet.ProjectModel.ProjectRestoreMetadata.SdkAnalysisLevel.set -> void +NuGet.ProjectModel.ProjectRestoreMetadata.UsingMicrosoftNETSdk.get -> bool +NuGet.ProjectModel.ProjectRestoreMetadata.UsingMicrosoftNETSdk.set -> void diff --git a/src/NuGet.Core/NuGet.ProjectModel/Strings.Designer.cs b/src/NuGet.Core/NuGet.ProjectModel/Strings.Designer.cs index 819bfba5ac7..6a50cc51063 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/Strings.Designer.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/Strings.Designer.cs @@ -69,6 +69,15 @@ internal static string ArgumentNullOrEmpty { } } + /// + /// Looks up a localized string similar to The {0} value: `{1}` is invalid. Valid example: `{2}`. + /// + internal static string Invalid_AttributeValue { + get { + return ResourceManager.GetString("Invalid_AttributeValue", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid dependency target value '{0}'.. /// diff --git a/src/NuGet.Core/NuGet.ProjectModel/Strings.resx b/src/NuGet.Core/NuGet.ProjectModel/Strings.resx index b67f31dcf04..af762a14549 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/Strings.resx +++ b/src/NuGet.Core/NuGet.ProjectModel/Strings.resx @@ -127,6 +127,12 @@ The pack options package type must be a string or array of strings in '{0}'. {0} is the project.json file name. + + The {0} value: `{1}` is invalid. Valid example: `{2}` + {0} - is the attribute +{1} is the invalid value of the attribute. +{2} a valid attriibute value example + Error loading lock file '{0}' : {1} diff --git a/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReader.cs b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReader.cs index 0fb6b35f40b..f328fcca429 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReader.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/Utf8JsonStreamReader.cs @@ -4,8 +4,10 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Text.Json; namespace NuGet.ProjectModel @@ -220,6 +222,25 @@ internal bool ReadNextTokenAsBoolOrFalse() return false; } + internal bool ReadNextTokenAsBoolOrThrowAnException(byte[] propertyName) + { + ThrowExceptionIfDisposed(); + + if (Read() && (TokenType == JsonTokenType.False || TokenType == JsonTokenType.True)) + { + return GetBoolean(); + } + else + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, + Strings.Invalid_AttributeValue, + Encoding.UTF8.GetString(propertyName), + _reader.ReadTokenAsString(), + "false")); + } + } + internal IReadOnlyList ReadNextStringOrArrayOfStringsAsReadOnlyList() { ThrowExceptionIfDisposed(); diff --git a/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/ProjectSystems/LegacyPackageReferenceProjectTests.cs b/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/ProjectSystems/LegacyPackageReferenceProjectTests.cs index ea3b2b30b72..d6f35f99d38 100644 --- a/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/ProjectSystems/LegacyPackageReferenceProjectTests.cs +++ b/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/ProjectSystems/LegacyPackageReferenceProjectTests.cs @@ -1621,6 +1621,174 @@ public async Task GetPackageSpec_WithNuGetAuditSuppress() auditProperties.SuppressedAdvisories.Should().BeEquivalentTo(["https://cve.test/1"]); } + [Fact] + public async Task GetPackageSpec_WithValidSdkAnalysisLevel_ReadsSdkAnalysisLevelValue() + { + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // Arrange + string sdkAnalysisLevel = "9.0.809"; + using var testDirectory = TestDirectory.Create(); + NuGetVersion expectedVersion = new NuGetVersion(sdkAnalysisLevel); + var projectBuildProperties = new Mock(); + projectBuildProperties.Setup(b => b.GetPropertyValue(ProjectBuildProperties.SdkAnalysisLevel)) + .Returns(sdkAnalysisLevel); + var projectAdapter = CreateProjectAdapter(testDirectory, projectBuildProperties); + + Mock projectAdapterMock = Mock.Get(projectAdapter); + + var projectServices = new TestProjectSystemServices(); + var testProject = new LegacyPackageReferenceProject( + projectAdapter, + Guid.NewGuid().ToString(), + projectServices, + _threadingService); + + var settings = NullSettings.Instance; + var testDependencyGraphCacheContext = new DependencyGraphCacheContext(NullLogger.Instance, settings); + + // Act + var packageSpecs = await testProject.GetPackageSpecsAsync(testDependencyGraphCacheContext); + + // Assert + Assert.NotNull(packageSpecs); + var actualRestoreSpec = packageSpecs.Single(); + SpecValidationUtility.ValidateProjectSpec(actualRestoreSpec); + actualRestoreSpec.RestoreMetadata.SdkAnalysisLevel.Should().Be(expectedVersion); + } + + [Theory] + [InlineData("False")] + [InlineData("FaLse")] + [InlineData("false")] + public async Task GetPackageSpec_WithFalseUsingMicrosoftNetSdk_ReadsFalse(string usingSdk) + { + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // Arrange + using var testDirectory = TestDirectory.Create(); + var projectBuildProperties = new Mock(); + projectBuildProperties.Setup(b => b.GetPropertyValue(ProjectBuildProperties.UsingMicrosoftNETSdk)) + .Returns(usingSdk); + var projectAdapter = CreateProjectAdapter(testDirectory, projectBuildProperties); + + Mock projectAdapterMock = Mock.Get(projectAdapter); + + var projectServices = new TestProjectSystemServices(); + var testProject = new LegacyPackageReferenceProject( + projectAdapter, + Guid.NewGuid().ToString(), + projectServices, + _threadingService); + + var settings = NullSettings.Instance; + var testDependencyGraphCacheContext = new DependencyGraphCacheContext(NullLogger.Instance, settings); + + // Act + var packageSpecs = await testProject.GetPackageSpecsAsync(testDependencyGraphCacheContext); + + // Assert + Assert.NotNull(packageSpecs); + var actualRestoreSpec = packageSpecs.Single(); + SpecValidationUtility.ValidateProjectSpec(actualRestoreSpec); + + Assert.False(actualRestoreSpec.RestoreMetadata.UsingMicrosoftNETSdk); + } + + [Theory] + [InlineData("True")] + [InlineData("true")] + [InlineData("TrUe")] + public async Task GetPackageSpec_WithTrueUsingMicrosoftNetSdk_ReadsTrue(string usingSdk) + { + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // Arrange + using var testDirectory = TestDirectory.Create(); + var projectBuildProperties = new Mock(); + projectBuildProperties.Setup(b => b.GetPropertyValue(ProjectBuildProperties.UsingMicrosoftNETSdk)) + .Returns(usingSdk); + var projectAdapter = CreateProjectAdapter(testDirectory, projectBuildProperties); + + Mock projectAdapterMock = Mock.Get(projectAdapter); + + var projectServices = new TestProjectSystemServices(); + var testProject = new LegacyPackageReferenceProject( + projectAdapter, + Guid.NewGuid().ToString(), + projectServices, + _threadingService); + + var settings = NullSettings.Instance; + var testDependencyGraphCacheContext = new DependencyGraphCacheContext(NullLogger.Instance, settings); + + // Act + var packageSpecs = await testProject.GetPackageSpecsAsync(testDependencyGraphCacheContext); + + // Assert + Assert.NotNull(packageSpecs); + var actualRestoreSpec = packageSpecs.Single(); + SpecValidationUtility.ValidateProjectSpec(actualRestoreSpec); + + Assert.True(actualRestoreSpec.RestoreMetadata.UsingMicrosoftNETSdk); + } + + [Fact] + public async Task GetPackageSpec_WithInvalidSdkAnalysisLevel_ThrowsAnException() + { + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // Arrange + using var testDirectory = TestDirectory.Create(); + var projectBuildProperties = new Mock(); + projectBuildProperties.Setup(b => b.GetPropertyValue(ProjectBuildProperties.SdkAnalysisLevel)) + .Returns("9.ainvlaid"); + var projectAdapter = CreateProjectAdapter(testDirectory, projectBuildProperties); + + Mock projectAdapterMock = Mock.Get(projectAdapter); + + var projectServices = new TestProjectSystemServices(); + var testProject = new LegacyPackageReferenceProject( + projectAdapter, + Guid.NewGuid().ToString(), + projectServices, + _threadingService); + + var settings = NullSettings.Instance; + var testDependencyGraphCacheContext = new DependencyGraphCacheContext(NullLogger.Instance, settings); + + // Act & Assert + await Assert.ThrowsAsync(async () => await testProject.GetPackageSpecsAsync(testDependencyGraphCacheContext)); + } + + [Fact] + public async Task GetPackageSpec_WithInvalidUsingMicrosoftNetSdk_ThrowsAnException() + { + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // Arrange + using var testDirectory = TestDirectory.Create(); + var projectBuildProperties = new Mock(); + projectBuildProperties.Setup(b => b.GetPropertyValue(ProjectBuildProperties.UsingMicrosoftNETSdk)) + .Returns("falsetrue"); + var projectAdapter = CreateProjectAdapter(testDirectory, projectBuildProperties); + + Mock projectAdapterMock = Mock.Get(projectAdapter); + + var projectServices = new TestProjectSystemServices(); + var testProject = new LegacyPackageReferenceProject( + projectAdapter, + Guid.NewGuid().ToString(), + projectServices, + _threadingService); + + var settings = NullSettings.Instance; + var testDependencyGraphCacheContext = new DependencyGraphCacheContext(NullLogger.Instance, settings); + + // Act & Assert + await Assert.ThrowsAsync(async () => await testProject.GetPackageSpecsAsync(testDependencyGraphCacheContext)); + } + private LegacyPackageReferenceProject CreateLegacyPackageReferenceProject(TestDirectory testDirectory, string range) { return ProjectFactories.CreateLegacyPackageReferenceProject(testDirectory, Guid.NewGuid().ToString(), range, _threadingService); diff --git a/test/NuGet.Clients.Tests/NuGet.SolutionRestoreManager.Test/VSNominationUtilitiesTests.cs b/test/NuGet.Clients.Tests/NuGet.SolutionRestoreManager.Test/VSNominationUtilitiesTests.cs index ae78e09cca9..7e341915b1c 100644 --- a/test/NuGet.Clients.Tests/NuGet.SolutionRestoreManager.Test/VSNominationUtilitiesTests.cs +++ b/test/NuGet.Clients.Tests/NuGet.SolutionRestoreManager.Test/VSNominationUtilitiesTests.cs @@ -7,6 +7,7 @@ using Xunit; using FluentAssertions; using System.Collections.Immutable; +using NuGet.Versioning; namespace NuGet.SolutionRestoreManager.Test { @@ -252,5 +253,103 @@ public void GetRestoreAuditProperties_TwoTfmWithDifferentSuppressions_Throws() exception.Message.Should().Contain(ProjectItems.NuGetAuditSuppress); } + private VsTargetFrameworkInfo4[] TargetFrameworkWithSdkAnalysisLevel(string sdkAnalysisLevel) + { + Dictionary keyValuePairs = new Dictionary(); + keyValuePairs["SdkAnalysisLevel"] = sdkAnalysisLevel; + var targetFrameworks = new VsTargetFrameworkInfo4[] + { + new VsTargetFrameworkInfo4( + items: new Dictionary>(StringComparer.OrdinalIgnoreCase), + properties: new Dictionary + { + { ProjectBuildProperties.SdkAnalysisLevel, sdkAnalysisLevel } + }) + }; + + return targetFrameworks; + } + + private VsTargetFrameworkInfo4[] TargetFrameworkWithUsingMicrosoftNetSdk(string UsingMicrosoftNetSdk) + { + Dictionary keyValuePairs = new Dictionary(); + keyValuePairs["UsingMicrosoftNETSdk"] = UsingMicrosoftNetSdk; + var targetFrameworks = new VsTargetFrameworkInfo4[] + { + new VsTargetFrameworkInfo4( + items: new Dictionary>(StringComparer.OrdinalIgnoreCase), + properties: new Dictionary + { + { ProjectBuildProperties.UsingMicrosoftNETSdk, UsingMicrosoftNetSdk } + }) + }; + + return targetFrameworks; + } + + [Theory] + [InlineData("9.0.100")] + [InlineData("7.0.100")] + [InlineData("9.1.100")] + [InlineData("9.2.101")] + public void GetSdkAnalysisLevel_WithValidVersions_ReturnsNuGetVersion(string sdkAnalysisLevel) + { + // Arrange + var targetFrameworks = TargetFrameworkWithSdkAnalysisLevel(sdkAnalysisLevel); + NuGetVersion expected = new NuGetVersion(sdkAnalysisLevel); + + //Act + NuGetVersion actual = VSNominationUtilities.GetSdkAnalysisLevel(targetFrameworks); + + //Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("invalid")] + [InlineData("1.3e")] + public void GetSdkAnalysisLevel_WithInvalidVersions_ThrowsException(string sdkAnalysisLevel) + { + // Act & Assert + Assert.Throws(() => VSNominationUtilities.GetSdkAnalysisLevel(TargetFrameworkWithSdkAnalysisLevel(sdkAnalysisLevel))); + } + + [Theory] + [InlineData("true")] + [InlineData("True")] + [InlineData("trUe")] + [InlineData("TrUe")] + public void GetUsingMicrosoftNETSdk_WithTrueValue_ReturnsTrue(string usingMicrosoftNETSdk) + { + // Act + bool actual = VSNominationUtilities.GetUsingMicrosoftNETSdk(TargetFrameworkWithUsingMicrosoftNetSdk(usingMicrosoftNETSdk)); + + // Assert + Assert.True(actual); + } + + [Theory] + [InlineData("false")] + [InlineData("False")] + [InlineData("falSe")] + [InlineData("FalsE")] + public void GetUsingMicrosoftNETSdk_WithFalseValue_ReturnsFalse(string usingMicrosoftNETSdk) + { + // Act + bool actual = VSNominationUtilities.GetUsingMicrosoftNETSdk(TargetFrameworkWithUsingMicrosoftNetSdk(usingMicrosoftNETSdk)); + + // Assert + Assert.False(actual); + } + + [Theory] + [InlineData("t")] + [InlineData("1.3e")] + [InlineData("1")] + public void GetUsingMicrosoftNETSdk_WithInvalidValue_ThrowsException(string usingMicrosoftNETSdk) + { + Assert.Throws(() => VSNominationUtilities.GetUsingMicrosoftNETSdk(TargetFrameworkWithUsingMicrosoftNetSdk(usingMicrosoftNETSdk))); + } + } } diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/MSBuildRestoreUtilityTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/MSBuildRestoreUtilityTests.cs index 818c53cd464..d316a468050 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/MSBuildRestoreUtilityTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/MSBuildRestoreUtilityTests.cs @@ -4737,6 +4737,69 @@ public void IsPropertyFalse_ReturnsExpectedValue(string value, bool expected) MSBuildRestoreUtility.IsPropertyFalse(item, propertyName).Should().Be(expected); } + [Theory] + [InlineData("9.0.100")] + [InlineData("7.0.100")] + [InlineData("9.1.100")] + [InlineData("9.2.101")] + public void GetSdkAnalysisLevel_WithValidVersions_ReturnsNuGetVersion(string sdkAnalysisLevel) + { + // Arrange + NuGetVersion expected = new NuGetVersion(sdkAnalysisLevel); + + //Act + NuGetVersion actual = MSBuildRestoreUtility.GetSdkAnalysisLevel(sdkAnalysisLevel); + + //Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("invalid")] + [InlineData("1.3e")] + public void GetSdkAnalysisLevel_WithInvalidVersions_ThrowsException(string sdkAnalysisLevel) + { + // Act & Assert + Assert.Throws(() => MSBuildRestoreUtility.GetSdkAnalysisLevel(sdkAnalysisLevel)); + } + + [Theory] + [InlineData("true")] + [InlineData("True")] + [InlineData("trUe")] + [InlineData("TrUe")] + public void GetUsingMicrosoftNETSdk_WithTrueValue_ReturnsTrue(string usingMicrosoftNETSdk) + { + // Act + bool actual = MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(usingMicrosoftNETSdk); + + // Assert + Assert.True(actual); + } + + [Theory] + [InlineData("false")] + [InlineData("False")] + [InlineData("falSe")] + [InlineData("FalsE")] + public void GetUsingMicrosoftNETSdk_WithFalseValue_ReturnsFalse(string usingMicrosoftNETSdk) + { + // Act + bool actual = MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(usingMicrosoftNETSdk); + + // Assert + Assert.False(actual); + } + + [Theory] + [InlineData("t")] + [InlineData("1.3e")] + [InlineData("1")] + public void GetUsingMicrosoftNETSdk_WithInvalidValue_ThrowsException(string usingMicrosoftNETSdk) + { + Assert.Throws(() => MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(usingMicrosoftNETSdk)); + } + private static IDictionary CreateProject(string root, string uniqueName) { var project1Path = Path.Combine(root, "a.csproj"); diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/JsonPackageSpecReaderTests.cs b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/JsonPackageSpecReaderTests.cs index e8f21a205ad..c8004484ef0 100644 --- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/JsonPackageSpecReaderTests.cs +++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/JsonPackageSpecReaderTests.cs @@ -4054,6 +4054,83 @@ public void GetPackageSpec_WithRestoreAuditPropertiesAndSuppressions_ReturnsRest packageSpec.RestoreMetadata.RestoreAuditProperties.SuppressedAdvisories.Last().Should().Be("e"); } + [Theory] + [MemberData(nameof(TestEnvironmentVariableReader), "9.0.100", MemberType = typeof(LockFileParsingEnvironmentVariable))] + [MemberData(nameof(TestEnvironmentVariableReader), "10.0.100", MemberType = typeof(LockFileParsingEnvironmentVariable))] + [MemberData(nameof(TestEnvironmentVariableReader), "8.1.100", MemberType = typeof(LockFileParsingEnvironmentVariable))] + public void GetPackageSpec_WithSdkAnalysisLevelValue_ReturnsSdkAnalysisLevel( + IEnvironmentVariableReader environmentVariableReader, + string version) + { + // Arrange + NuGetVersion expectedNugetVersion = new NuGetVersion(version); + var json = $"{{\"restore\":{{\"SdkAnalysisLevel\":\"{version}\"}}}}"; + + // Act + PackageSpec packageSpec = GetPackageSpec(json, environmentVariableReader); + + // Assert + Assert.Equal(expectedNugetVersion, packageSpec.RestoreMetadata.SdkAnalysisLevel); + } + + [Theory] + [MemberData(nameof(TestEnvironmentVariableReader), "notGood", MemberType = typeof(LockFileParsingEnvironmentVariable))] + [MemberData(nameof(TestEnvironmentVariableReader), "10invalid", MemberType = typeof(LockFileParsingEnvironmentVariable))] + public void GetPackageSpec_WithAnInvalidSdkAnalysisLevelValue_ThrowsAnException( + IEnvironmentVariableReader environmentVariableReader, + string version) + { + // Arrange + var json = $"{{\"restore\":{{\"SdkAnalysisLevel\":\"{version}\"}}}}"; + + // Act & Assert + var ex = Assert.Throws(() => GetPackageSpec(json, environmentVariableReader)); + Assert.Contains("SdkAnalysisLevel", ex.Message); + Assert.Contains(version, ex.Message); + } + + [Theory] + [MemberData(nameof(TestEnvironmentVariableReader), true, MemberType = typeof(LockFileParsingEnvironmentVariable))] + [MemberData(nameof(TestEnvironmentVariableReader), false, MemberType = typeof(LockFileParsingEnvironmentVariable))] + public void GetPackageSpec_WithUsingMicrosoftNetSdk_ReturnsUsingMicrosoftNetSdk( + IEnvironmentVariableReader environmentVariableReader, + bool isSdk) + { + // Arrange + var json = $"{{\"restore\":{{\"UsingMicrosoftNETSdk\":{isSdk.ToString().ToLower()}}}}}"; + + // Act + PackageSpec packageSpec = GetPackageSpec(json, environmentVariableReader); + + // Assert + Assert.Equal(isSdk, packageSpec.RestoreMetadata.UsingMicrosoftNETSdk); + } + + [Fact] + public void GetPackageSpec_WithInvalidUsingMicrosoftNetSdk_ThrowsAnException() + { + // Arrange + var json = $"{{\"restore\":{{\"UsingMicrosoftNETSdk\":1}}}}"; + + // Act & Assert + var ex = Assert.Throws(() => GetPackageSpec(json)); + Assert.Contains("UsingMicrosoftNETSdk", ex.Message); + Assert.Contains("1", ex.Message); + } + + [Fact] + public void GetPackageSpec_WithNoUsingMicrosoftNetSdkValuePassed_defaultsTrue() + { + // Arrange + var json = $"{{\"restore\":{{}}}}"; + + // Act + PackageSpec packageSpec = GetPackageSpec(json); + + // Assert + Assert.True(packageSpec.RestoreMetadata.UsingMicrosoftNETSdk); + } + [Fact] public void GetPackageSpec_RestoreMetadataWithoutMacros_WithMacrosEnabled() { diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs index 9ac68a36e34..59c6aecbaa3 100644 --- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs +++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/LockFileFormatTests.cs @@ -684,6 +684,7 @@ public void LockFileFormat_ReadsPackageSpec() ""projectPath"": ""X:\\ProjectPath\\ProjectPath.csproj"", ""outputPath"": ""X:\\ProjectPath\\obj\\"", ""projectStyle"": ""PackageReference"", + ""UsingMicrosoftNETSdk"": false, ""originalTargetFrameworks"": [ ""netcoreapp10"" ], @@ -1144,7 +1145,8 @@ public void LockFileFormat_WritesErrorMessageWithFilePathSameAsProjectPath() }, ""project"": { ""restore"": { - ""projectPath"": ""kung\\fu\\fighting.csproj"" + ""projectPath"": ""kung\\fu\\fighting.csproj"", + ""UsingMicrosoftNETSdk"": false, } }, ""logs"": [ diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecTests.cs b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecTests.cs index 67c8af29f46..dfa63f50f59 100644 --- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecTests.cs +++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecTests.cs @@ -385,6 +385,8 @@ private ProjectRestoreMetadata CreateProjectRestoreMetadata() originalProjectRestoreMetadata.OriginalTargetFrameworks = new List() { "net45" }; originalProjectRestoreMetadata.Files = new List() { new ProjectRestoreMetadataFile("packagePath", "absolutePath") }; originalProjectRestoreMetadata.ProjectWideWarningProperties = warningProperties; + originalProjectRestoreMetadata.SdkAnalysisLevel = new NuGetVersion("9.0.100"); + originalProjectRestoreMetadata.UsingMicrosoftNETSdk = false; return originalProjectRestoreMetadata; } diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecWriterTests.cs b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecWriterTests.cs index 4271d9385ad..1d0235bdf7d 100644 --- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecWriterTests.cs +++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/PackageSpecWriterTests.cs @@ -727,6 +727,36 @@ public void Write_RestoreAuditProperties_RoundTrips() VerifyJsonPackageSpecRoundTrip(json); } + [Fact] + public void Write_RestoreSdkAnalysisLevel_RoundTrips() + { + // Arrange + var json = @"{ + ""restore"": { + ""projectUniqueName"": ""projectUniqueName"", + ""SdkAnalysisLevel"": ""9.0.100"" + } + }"; + + // Act & Assert + VerifyJsonPackageSpecRoundTrip(json); + } + + [Fact] + public void Write_RestoreUsingMicrosoftNetSdk_RoundTrips() + { + // Arrange + var json = @"{ + ""restore"": { + ""projectUniqueName"": ""projectUniqueName"", + ""UsingMicrosoftNETSdk"": false + } + }"; + + // Act & Assert + VerifyJsonPackageSpecRoundTrip(json); + } + [Fact] public void Write_RestoreAuditPropertiesWithSuppressions_RoundTrips() { diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_CentralVersionDependencies.json b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_CentralVersionDependencies.json index 7b789762bf9..d57c4f1a443 100644 --- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_CentralVersionDependencies.json +++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_CentralVersionDependencies.json @@ -7,7 +7,8 @@ "a": { "restore": { "projectUniqueName": "a", - "centralPackageVersionsManagementEnabled": true + "centralPackageVersionsManagementEnabled": true, + "UsingMicrosoftNETSdk": false }, "frameworks": { "net40": { diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_Save_SerializesMembersAsJson.json b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_Save_SerializesMembersAsJson.json index d25ced5436b..fd4d51aeead 100644 --- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_Save_SerializesMembersAsJson.json +++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/DependencyGraphSpec_Save_SerializesMembersAsJson.json @@ -8,17 +8,20 @@ "projects": { "a": { "restore": { - "projectUniqueName": "a" + "projectUniqueName": "a", + "UsingMicrosoftNETSdk": false } }, "b": { "restore": { - "projectUniqueName": "b" + "projectUniqueName": "b", + "UsingMicrosoftNETSdk": false } }, "c": { "restore": { - "projectUniqueName": "c" + "projectUniqueName": "c", + "UsingMicrosoftNETSdk": false } } } diff --git a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/PackageSpecWriter_Write_SerializesMembersAsJson.json b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/PackageSpecWriter_Write_SerializesMembersAsJson.json index 72cb173ef79..96ec2c5c311 100644 --- a/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/PackageSpecWriter_Write_SerializesMembersAsJson.json +++ b/test/NuGet.Core.Tests/NuGet.ProjectModel.Test/compiler/resources/PackageSpecWriter_Write_SerializesMembersAsJson.json @@ -46,6 +46,7 @@ "outputPath": "outputPath", "projectStyle": "PackageReference", "crossTargeting": true, + "UsingMicrosoftNETSdk": false, "fallbackFolders": [ "b", "a",