diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 07217a9e5a..b7c3258db7 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -32,3 +32,20 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +License notice for NuGet.Client (NuGet.Frameworks) +-------------------------------------------------- +Copyright (c) .NET Foundation and Contributors. + +All rights reserved. + + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index 987c2d8b1c..7d038cd01e 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -6,7 +6,6 @@ - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index fafb1adf99..610c783221 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -24,10 +24,6 @@ 0030d238c7929b0e9b06576837b60ad90037b1d2 - - https://github.com/nuget/nuget.client - 4ba7bfa82f894ec32a554ca8d2df143675c85735 - https://github.com/dotnet/corefx diff --git a/eng/Versions.props b/eng/Versions.props index 06423ba803..dd8ae3364f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -90,10 +90,6 @@ [16.6.1] [16.11.0] [15.9.2] - - - 6.5.0 5.0.0 diff --git a/eng/verify-nupkgs.ps1 b/eng/verify-nupkgs.ps1 index be44eeefb7..8fe256dfe7 100644 --- a/eng/verify-nupkgs.ps1 +++ b/eng/verify-nupkgs.ps1 @@ -16,13 +16,13 @@ function Verify-Nuget-Packages { $expectedNumOfFiles = @{ "Microsoft.CodeCoverage" = 59; "Microsoft.NET.Test.Sdk" = 16; - "Microsoft.TestPlatform" = 607; + "Microsoft.TestPlatform" = 605; "Microsoft.TestPlatform.Build" = 21; - "Microsoft.TestPlatform.CLI" = 472; + "Microsoft.TestPlatform.CLI" = 470; "Microsoft.TestPlatform.Extensions.TrxLogger" = 35; "Microsoft.TestPlatform.ObjectModel" = 93; "Microsoft.TestPlatform.AdapterUtilities" = 34; - "Microsoft.TestPlatform.Portable" = 595; + "Microsoft.TestPlatform.Portable" = 592; "Microsoft.TestPlatform.TestHost" = 63; "Microsoft.TestPlatform.TranslationLayer" = 123; "Microsoft.TestPlatform.Internal.Uwp" = 39; diff --git a/scripts/update-nuget-frameworks.ps1 b/scripts/update-nuget-frameworks.ps1 new file mode 100644 index 0000000000..6d6894b50e --- /dev/null +++ b/scripts/update-nuget-frameworks.ps1 @@ -0,0 +1,117 @@ +param ( + [String] $VersionTag = "6.8.0.117" +) + +$root = Resolve-Path "$PSScriptRoot/.." + +$source = "$root/artifacts/tmp/NuGet.Client" + +if (Test-Path $source) { + Remove-Item -Recurse -Force $source +} + +git clone --depth 1 --branch $VersionTag https://github.com/NuGet/NuGet.Client.git $source +if (0 -ne $LASTEXITCODE) { + throw "Cloning failed." +} + +$commit = git -C $source log -1 --pretty=format:"%h" +if (0 -ne $LASTEXITCODE) { + throw "Getting commit failed." +} + +$destination = "$root/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/" + +$frameworksPath = "$source/src/NuGet.Core/NuGet.Frameworks" +$frameworkItems = @( + "DefaultFrameworkMappings.cs" + "DefaultFrameworkNameProvider.cs" + "DefaultPortableFrameworkMappings.cs" + "DefaultCompatibilityProvider.cs" + "CompatibilityProvider.cs" + "FrameworkConstants.cs" + "FrameworkException.cs" + "FrameworkNameProvider.cs" + "FrameworkRange.cs" + "FrameworkReducer.cs" + "FrameworkNameHelpers.cs", + "FrameworkSpecificMapping.cs" + "FallbackFramework.cs" + "FrameworkExpander.cs" + "CompatibilityCacheKey.cs" + "def/IFrameworkCompatibilityListProvider.cs" + "def/IFrameworkCompatibilityProvider.cs" + "def/IFrameworkMappings.cs" + "def/IFrameworkNameProvider.cs" + "def/IFrameworkSpecific.cs" + "def/IPortableFrameworkMappings.cs" + "NuGetFramework.cs" + "NuGetFrameworkFactory.cs" + "comparers/NuGetFrameworkFullComparer.cs" + "comparers/NuGetFrameworkNameComparer.cs" + "comparers/CompatibilityMappingComparer.cs" + "comparers/FrameworkRangeComparer.cs" + "comparers/NuGetFrameworkSorter.cs" + "comparers/FrameworkPrecedenceSorter.cs" + "NuGetFrameworkUtility.cs" + "OneWayCompatibilityMappingEntry.cs" +) | ForEach-Object { "$frameworksPath/$_" } + +$extraItems = @( + ".editorconfig" + "build/Shared/HashCodeCombiner.cs" + "build/Shared/NoAllocEnumerateExtensions.cs" + "build/Shared/StringBuilderPool.cs" + "build/Shared/SimplePool.cs" +) | ForEach-Object { "$source/$_" } + +if ((Test-Path $destination)) { + Remove-Item $destination -Force -Recurse +} + +New-Item -ItemType Directory $destination -ErrorAction Ignore | Out-Null +foreach ($item in $frameworkItems + $extraItems) { + if (-not (Test-Path $item)) { + throw "File not found $item" + } + $content = Get-Content $item + $name = (Get-Item $item).Name + + $path = "$destination/$name" + + # some types are directly in Nuget namespace, and if we would suffix + # .Clone, then Nuget.Frameworks.Clone is no longer autometicaly using + # Nuget.Clone, and we would have to add more usings into the files. + $finalContent = $content ` + -replace 'public(.*)(class|interface)', 'internal$1$2' ` + -replace 'namespace NuGet', 'namespace NuGetClone' ` + -replace 'using NuGet', 'using NuGetClone' ` + -replace 'NuGet.Frameworks.NuGetFramework', 'NuGetClone.Frameworks.NuGetFramework' + + if ($name -eq ".editorconfig") { + $finalContent += @" + +[*.{cs,vb}] +dotnet_diagnostic.IDE0001.severity = none +dotnet_diagnostic.IDE0005.severity = none +dotnet_diagnostic.IDE1006.severity = none +"@ + } + $finalContent | Set-Content -Path $path -Encoding utf8NoBOM +} + + +@" +This directory contains code that is copied from https://github.com/NuGet/NuGet.Client/tree/dev/src/NuGet.Core/NuGet.Frameworks, with the namespaces changed +and class visibility changed. This is done to ensure we are providing the same functionality as Nuget.Frameworks, without depending on the package explicitly. + +The files in this folder are coming from tag $VersionTag, on commit $commit. + +To update this code, run the script in: $($PSCommandPath -replace [regex]::Escape($root)) , with -VersionTag . + +"@ | Set-Content "$destination/README.md" + +$tpnPath = "$root/src/package/ThirdPartyNotices.txt" +$tpn = Get-Content $tpnPath -Raw +$tpn = $tpn -replace "Nuget.Client version.*\(", "Nuget.Client version $versionTag \(" +$tpn | Set-Content -Path $tpnPath -Encoding utf8NoBOM -NoNewline \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs index 515065126b..d6d7826980 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs @@ -7,8 +7,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; -using NuGet.Frameworks; - namespace Microsoft.TestPlatform.Extensions.BlameDataCollector; internal class CrashDumperFactory : ICrashDumperFactory @@ -18,15 +16,15 @@ public ICrashDumper Create(string targetFramework) ValidateArg.NotNull(targetFramework, nameof(targetFramework)); EqtTrace.Info($"CrashDumperFactory: Creating dumper for {RuntimeInformation.OSDescription} with target framework {targetFramework}."); - var tfm = NuGetFramework.Parse(targetFramework); + var tfm = Framework.FromString(targetFramework); - if (tfm == null || tfm.IsUnsupported) + if (tfm == null) { EqtTrace.Error($"CrashDumperFactory: Could not parse target framework {targetFramework}, to a supported framework version."); throw new NotSupportedException($"Could not parse target framework {targetFramework}, to a supported framework version."); } - var isNet50OrNewer = tfm.Framework == ".NETCoreApp" && tfm.Version >= Version.Parse("5.0.0.0"); + var isNet50OrNewer = tfm.FrameworkName == ".NETCoreApp" && Version.Parse(tfm.Version) >= Version.Parse("5.0.0.0"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs index d9f2352bc5..3ca7beb8c4 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs @@ -7,8 +7,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; -using NuGet.Frameworks; - namespace Microsoft.TestPlatform.Extensions.BlameDataCollector; internal class HangDumperFactory : IHangDumperFactory @@ -24,9 +22,9 @@ public IHangDumper Create(string targetFramework) var netdumpOverride = Environment.GetEnvironmentVariable("VSTEST_DUMP_FORCENETDUMP")?.Trim(); EqtTrace.Verbose($"HangDumperFactory: Overrides for dumpers: VSTEST_DUMP_FORCEPROCDUMP={procdumpOverride};VSTEST_DUMP_FORCENETDUMP={netdumpOverride}"); - var tfm = NuGetFramework.Parse(targetFramework); + var tfm = Framework.FromString(targetFramework); - if (tfm == null || tfm.IsUnsupported) + if (tfm == null) { EqtTrace.Error($"HangDumperFactory: Could not parse target framework {targetFramework}, to a supported framework version."); throw new NotSupportedException($"Could not parse target framework {targetFramework}, to a supported framework version."); @@ -46,15 +44,15 @@ public IHangDumper Create(string targetFramework) var forceUsingNetdump = !netdumpOverride.IsNullOrWhiteSpace() && netdumpOverride != "0"; if (forceUsingNetdump) { - var isLessThan50 = tfm.Framework == ".NETCoreApp" && tfm.Version < Version.Parse("5.0.0.0"); + var isLessThan50 = tfm.FrameworkName == ".NETCoreApp" && Version.Parse(tfm.Version) < Version.Parse("5.0.0.0"); if (!isLessThan50) { - EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.Framework} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, forcing use of .NetClientHangDumper"); + EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.FrameworkName} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, forcing use of .NetClientHangDumper"); return new NetClientHangDumper(); } else { - EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.Framework} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, but only applies to .NET 5.0 and newer. Falling back to default hang dumper."); + EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.FrameworkName} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, but only applies to .NET 5.0 and newer. Falling back to default hang dumper."); } } @@ -64,7 +62,7 @@ public IHangDumper Create(string targetFramework) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - var isLessThan31 = tfm.Framework == ".NETCoreApp" && tfm.Version < Version.Parse("3.1.0.0"); + var isLessThan31 = tfm.FrameworkName == ".NETCoreApp" && Version.Parse(tfm.Version) < Version.Parse("3.1.0.0"); if (isLessThan31) { EqtTrace.Info($"HangDumperFactory: This is Linux on netcoreapp2.1, returning SigtrapDumper."); @@ -78,7 +76,7 @@ public IHangDumper Create(string targetFramework) if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - var isLessThan50 = tfm.Framework == ".NETCoreApp" && tfm.Version < Version.Parse("5.0.0.0"); + var isLessThan50 = tfm.FrameworkName == ".NETCoreApp" && Version.Parse(tfm.Version) < Version.Parse("5.0.0.0"); if (isLessThan50) { EqtTrace.Info($"HangDumperFactory: This is OSX on {targetFramework}, This combination of OS and framework is not supported."); diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs index 6d979825b5..bf994ee5e2 100644 --- a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs @@ -18,8 +18,6 @@ using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; -using NuGet.Frameworks; - using HtmlLoggerConstants = Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.Constants; using HtmlResource = Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.Resources.Resources; @@ -285,7 +283,7 @@ public void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e) var framework = _parametersDictionary[DefaultLoggerParameterNames.TargetFramework]; if (framework != null) { - framework = NuGetFramework.Parse(framework).GetShortFolderName(); + framework = Framework.FromString(framework)?.ShortName ?? framework; logFilePrefixValue = logFilePrefixValue + "_" + framework; } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs index 436f965a2d..bcef64a85f 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs @@ -20,8 +20,6 @@ using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; -using NuGet.Frameworks; - using ObjectModelConstants = Microsoft.VisualStudio.TestPlatform.ObjectModel.Constants; using TrxLoggerConstants = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.Constants; using TrxLoggerObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; @@ -489,7 +487,7 @@ private string AcquireTrxFileNamePath(out bool shouldOverwrite) { if (_parametersDictionary.TryGetValue(DefaultLoggerParameterNames.TargetFramework, out var framework) && framework != null) { - framework = NuGetFramework.Parse(framework).GetShortFolderName(); + framework = Framework.FromString(framework)?.ShortName ?? framework; logFilePrefixValue = logFilePrefixValue + "_" + framework; } diff --git a/src/Microsoft.TestPlatform.ObjectModel/Framework.cs b/src/Microsoft.TestPlatform.ObjectModel/Framework.cs index 9e3b8fe100..64ed5f203c 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Framework.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Framework.cs @@ -5,9 +5,8 @@ using System.Globalization; -using NuGet.Frameworks; - -using static NuGet.Frameworks.FrameworkConstants; +using NuGetClone.Frameworks; +using System; namespace Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -36,6 +35,16 @@ private Framework() /// public string Name { get; private set; } + /// + /// Gets the framework name such as .NETCoreApp. + /// + public string FrameworkName { get; private set; } + + /// + /// Common short name, as well as directory name, such as net5.0. Is null when the framework is not correct. + /// + public string? ShortName { get; private set; } + /// /// Gets the framework version. /// @@ -53,56 +62,71 @@ private Framework() return null; } - string name, version; + string name, frameworkName, version; + string? shortName = null; try { // IDE always sends framework in form of ENUM, which always throws exception // This throws up in first chance exception, refer Bug https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/591142 var formattedFrameworkString = frameworkString.Trim().ToLower(CultureInfo.InvariantCulture); + string? mappedShortName = null; switch (formattedFrameworkString) { case "framework35": - name = CommonFrameworks.Net35.DotNetFrameworkName; - version = CommonFrameworks.Net35.Version.ToString(); + mappedShortName = "net3.5"; break; case "framework40": - name = CommonFrameworks.Net4.DotNetFrameworkName; - version = CommonFrameworks.Net4.Version.ToString(); + mappedShortName = "net4.0"; break; case "framework45": - name = CommonFrameworks.Net45.DotNetFrameworkName; - version = CommonFrameworks.Net45.Version.ToString(); + mappedShortName = "net4.5"; break; case "frameworkcore10": - name = CommonFrameworks.NetCoreApp10.DotNetFrameworkName; - version = CommonFrameworks.NetCoreApp10.Version.ToString(); + mappedShortName = "netcoreapp1.0"; break; case "frameworkuap10": - name = CommonFrameworks.UAP10.DotNetFrameworkName; - version = CommonFrameworks.UAP10.Version.ToString(); + mappedShortName = "uap10.0"; break; + } - default: - var nugetFramework = NuGetFramework.Parse(frameworkString); - if (nugetFramework.IsUnsupported) - return null; + if (mappedShortName != null) + { + frameworkString = mappedShortName; + } - name = nugetFramework.DotNetFrameworkName; - version = nugetFramework.Version.ToString(); + var nugetFramework = NuGetFramework.Parse(frameworkString); + if (nugetFramework.IsUnsupported) + return null; - break; + // e.g. .NETFramework,Version=v3.5 + name = nugetFramework.DotNetFrameworkName; + // e.g. net35 + try + { + // .NETPortable4.5 for example, is not a valid framework + // and this will throw. + shortName = nugetFramework.GetShortFolderName(); } + catch (Exception ex) + { + EqtTrace.Error(ex); + } + // e.g. .NETFramework + frameworkName = nugetFramework.Framework; + // e.g. 3.5.0.0 + version = nugetFramework.Version.ToString(); + } catch { return null; } - return new Framework() { Name = name, Version = version }; + return new Framework() { Name = name, ShortName = shortName, FrameworkName = frameworkName, Version = version }; } /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj index efa090d13b..0fb981d962 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj @@ -4,6 +4,8 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel net7.0;$(NetFrameworkMinimum);$(NetCoreAppMinimum);netstandard2.0; + + $(NoWarn);SYSLIB0051 @@ -32,7 +34,6 @@ - diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec index c4ec7526eb..105050657a 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec @@ -6,17 +6,14 @@ - - - diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/.editorconfig b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/.editorconfig new file mode 100644 index 0000000000..5e94bf7109 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/.editorconfig @@ -0,0 +1,153 @@ +; EditorConfig to support per-solution formatting. +; Use the EditorConfig VS add-in to make this work. +; http://editorconfig.org/ + +; This is the default for the codeline. +root = true + +[*] +; Don't use tabs for indentation. +indent_style = space +; (Please don't specify an indent_size here; that has too many unintended consequences.) +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Spell checker configuration +spelling_exclusion_path = spelling.dic + +; Code files +[*.{cs}] +indent_size = 4 + +; All XML-based file formats +[*.{config,csproj,nuspec,props,resx,ruleset,targets,vsct,vsixmanifest,xaml,xml,vsmanproj,swixproj}] +indent_size = 2 + +; JSON files +[*.json] +indent_size = 2 + +; PowerShell scripts +[*.{ps1}] +indent_size = 4 + +[*.{sh}] +indent_size = 4 + +; Dotnet code style settings +[*.{cs,vb}] +; Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +; IDE0003 Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +; IDE0012 Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +; IDE0013 +dotnet_style_predefined_type_for_member_access = true:warning + +; Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion + +; Licence header +file_header_template = Copyright (c) .NET Foundation. All rights reserved.\nLicensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +; CSharp code style settings +[*.cs] +; IDE0007 'var' preferences +csharp_style_var_for_built_in_types = true:none +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_elsewhere = false:none + +; Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +; Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_accessors = true:suggestion + +; Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:none +csharp_style_pattern_matching_over_as_with_null_check = true:none +csharp_style_inlined_variable_declaration = true:none +csharp_style_throw_expression = true:none +csharp_style_conditional_delegate_call = true:suggestion + +; Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true + +; Naming styles +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +dotnet_naming_style.camel_case_style.capitalization = camel_case + +; Naming rule: async methods end in Async +dotnet_naming_style.async_method_style.capitalization = pascal_case +dotnet_naming_style.async_method_style.required_suffix = Async +dotnet_naming_symbols.async_method_symbols.applicable_kinds = method +dotnet_naming_symbols.async_method_symbols.required_modifiers = async +dotnet_naming_rule.async_methods_rule.severity = suggestion +dotnet_naming_rule.async_methods_rule.symbols = async_method_symbols +dotnet_naming_rule.async_methods_rule.style = async_method_style + +; Naming rule: Interfaces must be pascal-cased prefixed with I +dotnet_naming_style.interface_style.capitalization = pascal_case +dotnet_naming_style.interface_style.required_prefix = I +dotnet_naming_symbols.interface_symbols.applicable_kinds = interface +dotnet_naming_symbols.interface_symbols.applicable_accessibilities = * +dotnet_naming_rule.interfaces_rule.severity = warning +dotnet_naming_rule.interfaces_rule.symbols = interface_symbols +dotnet_naming_rule.interfaces_rule.style = interface_style + +; Naming rule: All methods and properties must be pascal-cased +dotnet_naming_symbols.method_and_property_symbols.applicable_kinds = method,property,class,struct,enum:property,namespace +dotnet_naming_symbols.method_and_property_symbols.applicable_accessibilities = * +dotnet_naming_rule.methods_and_properties_rule.severity = warning +dotnet_naming_rule.methods_and_properties_rule.symbols = method_and_property_symbols +dotnet_naming_rule.methods_and_properties_rule.style = pascal_case_style + +; Naming rule: Static fields must be pascal-cased +dotnet_naming_symbols.static_member_symbols.applicable_kinds = field +dotnet_naming_symbols.static_member_symbols.applicable_accessibilities = * +dotnet_naming_symbols.static_member_symbols.required_modifiers = static +dotnet_naming_symbols.const_member_symbols.applicable_kinds = field +dotnet_naming_symbols.const_member_symbols.applicable_accessibilities = * +dotnet_naming_symbols.const_member_symbols.required_modifiers = const +dotnet_naming_rule.static_fields_rule.severity = warning +dotnet_naming_rule.static_fields_rule.symbols = static_member_symbols +dotnet_naming_rule.static_fields_rule.style = pascal_case_style + +; Naming rule: Private members must be camel-cased and prefixed with underscore +dotnet_naming_style.private_member_style.capitalization = camel_case +dotnet_naming_style.private_member_style.required_prefix = _ +dotnet_naming_symbols.private_field_symbols.applicable_kinds = field,event +dotnet_naming_symbols.private_field_symbols.applicable_accessibilities = private,protected,internal +dotnet_naming_rule.private_field_rule.severity = warning +dotnet_naming_rule.private_field_rule.symbols = private_field_symbols +dotnet_naming_rule.private_field_rule.style = private_member_style + +; Diagnostics rule: Don't leave unnecessary suppressions +dotnet_diagnostic.IDE0076.severity = warning +dotnet_diagnostic.IDE0005.severity = warning + +[*.{cs,vb}] +dotnet_diagnostic.IDE0001.severity = none +dotnet_diagnostic.IDE0005.severity = none +dotnet_diagnostic.IDE1006.severity = none diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityCacheKey.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityCacheKey.cs new file mode 100644 index 0000000000..a3ed7babc2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityCacheKey.cs @@ -0,0 +1,68 @@ +// 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.Globalization; +using NuGetClone.Shared; + +namespace NuGetClone.Frameworks +{ + /// + /// Internal cache key used to store framework compatibility. + /// + internal readonly struct CompatibilityCacheKey : IEquatable + { + public NuGetFramework Target { get; } + + public NuGetFramework Candidate { get; } + + private readonly int _hashCode; + + public CompatibilityCacheKey(NuGetFramework target, NuGetFramework candidate) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (candidate == null) + { + throw new ArgumentNullException(nameof(candidate)); + } + + Target = target; + Candidate = candidate; + + // This is designed to be cached, just get the hash up front + var combiner = new HashCodeCombiner(); + combiner.AddObject(target); + combiner.AddObject(candidate); + _hashCode = combiner.CombinedHash; + } + + public override int GetHashCode() + { + return _hashCode; + } + + public bool Equals(CompatibilityCacheKey other) + { + return Target.Equals(other.Target) + && Candidate.Equals(other.Candidate); + } + + public override bool Equals(object? obj) + { + return obj is CompatibilityCacheKey other && Equals(other); + } + + public override string ToString() + { + return string.Format( + CultureInfo.CurrentCulture, + "{0} -> {1}", + Target.DotNetFrameworkName, + Candidate.DotNetFrameworkName); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityMappingComparer.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityMappingComparer.cs new file mode 100644 index 0000000000..60399ce19f --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityMappingComparer.cs @@ -0,0 +1,56 @@ +// 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 NuGetClone.Shared; + +namespace NuGetClone.Frameworks +{ + internal class CompatibilityMappingComparer : IEqualityComparer + { +#pragma warning disable CS0618 // Type or member is obsolete + public static CompatibilityMappingComparer Instance { get; } = new(); +#pragma warning restore CS0618 // Type or member is obsolete + + [Obsolete("Use singleton CompatibilityMappingComparer.Instance instead")] + public CompatibilityMappingComparer() + { + } + + public bool Equals(OneWayCompatibilityMappingEntry? x, OneWayCompatibilityMappingEntry? y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (ReferenceEquals(x, null) + || ReferenceEquals(y, null)) + { + return false; + } + + var comparer = FrameworkRangeComparer.Instance; + + return comparer.Equals(x.TargetFrameworkRange, y.TargetFrameworkRange) + && comparer.Equals(x.SupportedFrameworkRange, y.SupportedFrameworkRange); + } + + public int GetHashCode(OneWayCompatibilityMappingEntry obj) + { + if (ReferenceEquals(obj, null)) + { + return 0; + } + + var combiner = new HashCodeCombiner(); + var comparer = FrameworkRangeComparer.Instance; + + combiner.AddObject(comparer.GetHashCode(obj.TargetFrameworkRange)); + combiner.AddObject(comparer.GetHashCode(obj.SupportedFrameworkRange)); + + return combiner.CombinedHash; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityProvider.cs new file mode 100644 index 0000000000..332bb9cf35 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/CompatibilityProvider.cs @@ -0,0 +1,260 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace NuGetClone.Frameworks +{ + internal class CompatibilityProvider : IFrameworkCompatibilityProvider + { + private readonly IFrameworkNameProvider _mappings; + private readonly FrameworkExpander _expander; + private static readonly NuGetFrameworkFullComparer FullComparer = NuGetFrameworkFullComparer.Instance; + private readonly ConcurrentDictionary _cache; + + public CompatibilityProvider(IFrameworkNameProvider mappings) + { + _mappings = mappings ?? throw new ArgumentNullException(nameof(mappings)); + _expander = new FrameworkExpander(mappings); + _cache = new ConcurrentDictionary(); + } + + /// + /// Check if the frameworks are compatible. + /// + /// Project framework + /// Other framework to check against the project framework + /// True if framework supports other + public bool IsCompatible(NuGetFramework target, NuGetFramework candidate) + { + if (target == null) throw new ArgumentNullException(nameof(target)); + if (candidate == null) throw new ArgumentNullException(nameof(candidate)); + + // check the cache for a solution + var cacheKey = new CompatibilityCacheKey(target, candidate); + + if (!_cache.TryGetValue(cacheKey, out bool result)) + { + result = IsCompatibleCore(target, candidate) == true; + _cache.TryAdd(cacheKey, result); + } + + return result; + } + + /// + /// Actual compatibility check without caching + /// + private bool? IsCompatibleCore(NuGetFramework target, NuGetFramework candidate) + { + bool? result = null; + + // check if they are the exact same + if (FullComparer.Equals(target, candidate)) + { + return true; + } + + // special cased frameworks + if (!target.IsSpecificFramework + || !candidate.IsSpecificFramework) + { + result = IsSpecialFrameworkCompatible(target, candidate); + } + + if (result == null) + { + if (target.IsPCL || candidate.IsPCL) + { + // PCL compat logic + result = IsPCLCompatible(target, candidate); + } + else + { + // regular framework compat check + result = IsCompatibleWithTarget(target, candidate); + } + } + + return result; + } + + private bool? IsSpecialFrameworkCompatible(NuGetFramework target, NuGetFramework candidate) + { + // TODO: Revist these + if (target.IsAny + || candidate.IsAny) + { + return true; + } + + if (target.IsUnsupported) + { + return false; + } + + if (candidate.IsAgnostic) + { + return true; + } + + if (candidate.IsUnsupported) + { + return false; + } + + return null; + } + + private bool IsPCLCompatible(NuGetFramework target, NuGetFramework candidate) + { + if (target.IsPCL && !candidate.IsPCL) + { + return IsCompatibleWithTarget(target, candidate); + } + + IEnumerable? targetFrameworks; + IEnumerable? candidateFrameworks; + + if (target.IsPCL) + { + // do not include optional frameworks here since we might be unable to tell what is optional on the other framework + if (!_mappings.TryGetPortableFrameworks(target.Profile, includeOptional: false, out targetFrameworks)) + { + targetFrameworks = Array.Empty(); + } + } + else + { + targetFrameworks = new NuGetFramework[] { target }; + } + + if (candidate.IsPCL) + { + // include optional frameworks here, the larger the list the more compatible it is + if (!_mappings.TryGetPortableFrameworks(candidate.Profile, includeOptional: true, out candidateFrameworks)) + { + candidateFrameworks = Array.Empty(); + } + } + else + { + candidateFrameworks = new NuGetFramework[] { candidate }; + } + + // check if we this is a compatible superset + return PCLInnerCompare(targetFrameworks, candidateFrameworks); + } + + private bool PCLInnerCompare(IEnumerable targetFrameworks, IEnumerable candidateFrameworks) + { + // TODO: Does this check need to make sure multiple frameworks aren't matched against a single framework from the other list? + return targetFrameworks.Count() <= candidateFrameworks.Count() && targetFrameworks.All(f => candidateFrameworks.Any(ff => IsCompatible(f, ff))); + } + + private bool IsCompatibleWithTarget(NuGetFramework target, NuGetFramework candidate) + { + // find all possible substitutions + var targetSet = new List() { target }; + targetSet.AddRange(_expander.Expand(target)); + + var candidateSet = new List() { candidate }; + candidateSet.AddRange(GetEquivalentFrameworksClosure(candidate)); + + // check for compat + foreach (var currentCandidate in candidateSet) + { + if (targetSet.Any(framework => IsCompatibleWithTargetCore(framework, currentCandidate))) + { + return true; + } + } + + return false; + } + + private static bool IsCompatibleWithTargetCore(NuGetFramework target, NuGetFramework candidate) + { + bool result = true; + bool isNet6Era = target.IsNet5Era && target.Version.Major >= 6; + if (isNet6Era && target.HasPlatform && !NuGetFramework.FrameworkNameComparer.Equals(target, candidate)) + { + if (candidate.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase)) + { + result = result && StringComparer.OrdinalIgnoreCase.Equals(target.Platform, "android"); + } + else if (candidate.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase)) + { + result = result && StringComparer.OrdinalIgnoreCase.Equals(target.Platform, "tizen"); + } + else + { + result = false; + } + } + else + { + result = NuGetFramework.FrameworkNameComparer.Equals(target, candidate) + && IsVersionCompatible(target.Version, candidate.Version) + && StringComparer.OrdinalIgnoreCase.Equals(target.Profile, candidate.Profile); + + if (target.IsNet5Era && candidate.HasPlatform) + { + result = result + && StringComparer.OrdinalIgnoreCase.Equals(target.Platform, candidate.Platform) + && IsVersionCompatible(target.PlatformVersion, candidate.PlatformVersion); + } + } + + + return result; + } + + private static bool IsVersionCompatible(Version target, Version candidate) + { + return candidate == FrameworkConstants.EmptyVersion || candidate <= target; + } + + /// + /// Find all equivalent frameworks, and their equivalent frameworks. + /// Example: + /// Mappings: + /// A <‒> B + /// B <‒> C + /// C <‒> D + /// For A we need to find B, C, and D so we must retrieve equivalent frameworks for A, B, and C + /// also as we discover them. + /// + private IEnumerable GetEquivalentFrameworksClosure(NuGetFramework framework) + { + // add the current framework to the seen list to avoid returning it later + var seen = new HashSet() { framework }; + + var toExpand = new Stack(); + toExpand.Push(framework); + + while (toExpand.Count > 0) + { + var frameworkToExpand = toExpand.Pop(); + + if (_mappings.TryGetEquivalentFrameworks(frameworkToExpand, out IEnumerable? compatibleFrameworks)) + { + foreach (var curFramework in compatibleFrameworks) + { + if (seen.Add(curFramework)) + { + yield return curFramework; + + toExpand.Push(curFramework); + } + } + } + } + + yield break; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultCompatibilityProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultCompatibilityProvider.cs new file mode 100644 index 0000000000..ecc59c3ab2 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultCompatibilityProvider.cs @@ -0,0 +1,28 @@ +// 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. + +namespace NuGetClone.Frameworks +{ + internal sealed class DefaultCompatibilityProvider : CompatibilityProvider + { + public DefaultCompatibilityProvider() + : base(DefaultFrameworkNameProvider.Instance) + { + } + + private static IFrameworkCompatibilityProvider? _instance; + + public static IFrameworkCompatibilityProvider Instance + { + get + { + if (_instance == null) + { + _instance = new DefaultCompatibilityProvider(); + } + + return _instance; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultFrameworkMappings.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultFrameworkMappings.cs new file mode 100644 index 0000000000..28d6d26f9b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultFrameworkMappings.cs @@ -0,0 +1,664 @@ +// 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 System.Linq; + +namespace NuGetClone.Frameworks +{ + internal sealed class DefaultFrameworkMappings : IFrameworkMappings + { + private static Lazy[]> IdentifierSynonymsLazy = new Lazy[]>(() => + { + return new[]{ + // .NET + new KeyValuePair("NETFramework", FrameworkConstants.FrameworkIdentifiers.Net), + new KeyValuePair(".NET", FrameworkConstants.FrameworkIdentifiers.Net), + + // .NET Core + new KeyValuePair("NETCore", FrameworkConstants.FrameworkIdentifiers.NetCore), + + // Portable + new KeyValuePair("NETPortable", FrameworkConstants.FrameworkIdentifiers.Portable), + + // ASP + new KeyValuePair("asp.net", FrameworkConstants.FrameworkIdentifiers.AspNet), + new KeyValuePair("asp.netcore", FrameworkConstants.FrameworkIdentifiers.AspNetCore), + + // Mono/Xamarin + new KeyValuePair("Xamarin.PlayStationThree", FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation3), + new KeyValuePair("XamarinPlayStationThree", FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation3), + new KeyValuePair("Xamarin.PlayStationFour", FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation4), + new KeyValuePair("XamarinPlayStationFour", FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation4), + new KeyValuePair("XamarinPlayStationVita", FrameworkConstants.FrameworkIdentifiers.XamarinPlayStationVita), + }; + }); + + public IEnumerable> IdentifierSynonyms + { + get + { + return IdentifierSynonymsLazy.Value; + } + } + + private static readonly Lazy[]> IdentifierShortNamesLazy = new Lazy[]>(() => + { + return new[] + { + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.NetCoreApp, "netcoreapp"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.NetStandardApp, "netstandardapp"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.NetStandard, "netstandard"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.NetPlatform, "dotnet"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.Net, "net"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.NetMicro, "netmf"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.Silverlight, "sl"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.Portable, "portable"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.WindowsPhone, "wp"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.WindowsPhoneApp, "wpa"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.Windows, "win"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.AspNet, "aspnet"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.AspNetCore, "aspnetcore"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.Native, "native"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, "monoandroid"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.MonoTouch, "monotouch"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.MonoMac, "monomac"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinIOs, "xamarinios"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinMac, "xamarinmac"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation3, "xamarinpsthree"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation4, "xamarinpsfour"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinPlayStationVita, "xamarinpsvita"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinWatchOS, "xamarinwatchos"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinTVOS, "xamarintvos"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinXbox360, "xamarinxboxthreesixty"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.XamarinXboxOne, "xamarinxboxone"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.Dnx, "dnx"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.DnxCore, "dnxcore"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.NetCore, "netcore"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.WinRT, "winrt"), // legacy + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.UAP, "uap"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.Tizen, "tizen"), + new KeyValuePair(FrameworkConstants.FrameworkIdentifiers.NanoFramework, "netnano"), + }; + }); + + public IEnumerable> IdentifierShortNames + { + get + { + return IdentifierShortNamesLazy.Value; + } + } + + private static readonly Lazy ProfileShortNamesLazy = new Lazy(() => + { + return new[] + { + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Net, "Client", "Client"), + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Net, "CF", "CompactFramework"), + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Net, "Full", string.Empty), + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Silverlight, "WP", "WindowsPhone"), + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Silverlight, "WP71", "WindowsPhone71"), + }; + }); + + public IEnumerable ProfileShortNames + { + get + { + return ProfileShortNamesLazy.Value; + } + } + + private static readonly Lazy[]> EquivalentFrameworksLazy = new Lazy[]>(() => + { + return new[] + { + // UAP 0.0 <-> UAP 10.0 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, FrameworkConstants.EmptyVersion), + FrameworkConstants.CommonFrameworks.UAP10), + + // win <-> win8 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Windows, FrameworkConstants.EmptyVersion), + FrameworkConstants.CommonFrameworks.Win8), + + // win8 <-> netcore45 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.Win8, + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.NetCore, new Version(4, 5, 0, 0))), + + // netcore45 <-> winrt45 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.NetCore, new Version(4, 5, 0, 0)), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WinRT, new Version(4, 5, 0, 0))), + + // netcore <-> netcore45 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.NetCore, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.NetCore, new Version(4, 5, 0, 0))), + + // winrt <-> winrt45 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WinRT, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WinRT, new Version(4, 5, 0, 0))), + + // win81 <-> netcore451 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.Win81, + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.NetCore, new Version(4, 5, 1, 0))), + + // wp <-> wp7 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WindowsPhone, FrameworkConstants.EmptyVersion), + FrameworkConstants.CommonFrameworks.WP7), + + // wp7 <-> f:sl3-wp + new KeyValuePair( + FrameworkConstants.CommonFrameworks.WP7, + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Silverlight, new Version(3, 0, 0, 0), "WindowsPhone")), + + // wp71 <-> f:sl4-wp71 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WindowsPhone, new Version(7, 1, 0, 0)), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Silverlight, new Version(4, 0, 0, 0), "WindowsPhone71")), + + // wp8 <-> f:sl8-wp + new KeyValuePair( + FrameworkConstants.CommonFrameworks.WP8, + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Silverlight, new Version(8, 0, 0, 0), "WindowsPhone")), + + // wp81 <-> f:sl81-wp + new KeyValuePair( + FrameworkConstants.CommonFrameworks.WP81, + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Silverlight, new Version(8, 1, 0, 0), "WindowsPhone")), + + // wpa <-> wpa81 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WindowsPhoneApp, FrameworkConstants.EmptyVersion), + FrameworkConstants.CommonFrameworks.WPA81), + + // tizen <-> tizen3 + new KeyValuePair( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Tizen, FrameworkConstants.EmptyVersion), + FrameworkConstants.CommonFrameworks.Tizen3), + + // dnx <-> dnx45 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.Dnx, + FrameworkConstants.CommonFrameworks.Dnx45), + + // dnxcore <-> dnxcore50 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.DnxCore, + FrameworkConstants.CommonFrameworks.DnxCore50), + + // dotnet <-> dotnet50 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.DotNet, + FrameworkConstants.CommonFrameworks.DotNet50), + + // TODO: remove these rules post-RC + // aspnet <-> aspnet50 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.AspNet, + FrameworkConstants.CommonFrameworks.AspNet50), + + // aspnetcore <-> aspnetcore50 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.AspNetCore, + FrameworkConstants.CommonFrameworks.AspNetCore50), + + // dnx451 <-> aspnet50 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.Dnx45, + FrameworkConstants.CommonFrameworks.AspNet50), + + // dnxcore50 <-> aspnetcore50 + new KeyValuePair( + FrameworkConstants.CommonFrameworks.DnxCore50, + FrameworkConstants.CommonFrameworks.AspNetCore50), + }; + }); + + public IEnumerable> EquivalentFrameworks + { + get + { + return EquivalentFrameworksLazy.Value; + } + } + + private static readonly Lazy EquivalentProfilesLazy = new Lazy(() => + { + return new[] + { + // The client profile, for the purposes of NuGet, is the same as the full framework + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Net, "Client", string.Empty), + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Net, "Full", string.Empty), + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.Silverlight, "WindowsPhone71", "WindowsPhone"), + new FrameworkSpecificMapping(FrameworkConstants.FrameworkIdentifiers.WindowsPhone, "WindowsPhone71", "WindowsPhone"), + }; + }); + + public IEnumerable EquivalentProfiles + { + get + { + return EquivalentProfilesLazy.Value; + } + } + + private static readonly Lazy[]> SubSetFrameworksLazy = new Lazy[]>(() => + { + return new[] + { + // .NET is a subset of DNX + new KeyValuePair( + FrameworkConstants.FrameworkIdentifiers.Net, + FrameworkConstants.FrameworkIdentifiers.Dnx), + + // NetPlatform is a subset of DNXCore + new KeyValuePair( + FrameworkConstants.FrameworkIdentifiers.NetPlatform, + FrameworkConstants.FrameworkIdentifiers.DnxCore), + + // NetStandard is a subset of NetStandardApp + new KeyValuePair( + FrameworkConstants.FrameworkIdentifiers.NetStandard, + FrameworkConstants.FrameworkIdentifiers.NetStandardApp) + }; + }); + + public IEnumerable> SubSetFrameworks + { + get + { + return SubSetFrameworksLazy.Value; + } + } + + private static readonly Lazy CompatibilityMappingsLazy = new Lazy(() => + { + return new[] + { + // UAP supports Win81 + new OneWayCompatibilityMappingEntry(new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, FrameworkConstants.MaxVersion)), + new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Windows, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Windows, new Version(8, 1, 0, 0)))), + + // UAP supports WPA81 + new OneWayCompatibilityMappingEntry(new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, FrameworkConstants.MaxVersion)), + new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WindowsPhoneApp, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WindowsPhoneApp, new Version(8, 1, 0, 0)))), + + // UAP supports NetCore50 + new OneWayCompatibilityMappingEntry(new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, FrameworkConstants.MaxVersion)), + new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.NetCore, FrameworkConstants.Version5), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.NetCore, FrameworkConstants.Version5))), + + // Win projects support WinRT + new OneWayCompatibilityMappingEntry(new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Windows, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Windows, FrameworkConstants.MaxVersion)), + new FrameworkRange( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WinRT, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.WinRT, new Version(4, 5, 0, 0)))), + + // Tizen3 projects support NETStandard1.6 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.Tizen3, + FrameworkConstants.CommonFrameworks.NetStandard16), + + // Tizen4 projects support NETStandard2.0 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.Tizen4, + FrameworkConstants.CommonFrameworks.NetStandard20), + + // Tizen6 projects support NETStandard2.1 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.Tizen6, + FrameworkConstants.CommonFrameworks.NetStandard21), + + // UAP 10.0.15064.0 projects support NETStandard2.0 + CreateStandardMapping( + new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.UAP, new Version(10, 0, 15064, 0)), + FrameworkConstants.CommonFrameworks.NetStandard20), + + // NetCoreApp1.0 projects support NetStandard1.6 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.NetCoreApp10, + FrameworkConstants.CommonFrameworks.NetStandard16), + + // NetCoreApp1.1 projects support NetStandard1.7 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.NetCoreApp11, + FrameworkConstants.CommonFrameworks.NetStandard17), + + // NetCoreApp2.0 projects support NetStandard2.0 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.NetCoreApp20, + FrameworkConstants.CommonFrameworks.NetStandard20), + + // NetCoreApp3.0 projects support NetStandard2.1 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.NetCoreApp30, + FrameworkConstants.CommonFrameworks.NetStandard21), + + // net463 projects support NetStandard2.0 + CreateStandardMapping( + FrameworkConstants.CommonFrameworks.Net463, + FrameworkConstants.CommonFrameworks.NetStandard20) + } + .Concat(new[] + { + // dnxcore50 -> dotnet5.6, netstandard1.5 + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.DnxCore, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard15), + + // uap -> dotnet5.5, netstandard1.4 + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.UAP, + FrameworkConstants.CommonFrameworks.DotNet55, + FrameworkConstants.CommonFrameworks.NetStandard14), + + // netcore50 -> dotnet5.5, netstandard1.4 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.NetCore50, + FrameworkConstants.CommonFrameworks.DotNet55, + FrameworkConstants.CommonFrameworks.NetStandard14), + + // wpa81 -> dotnet5.3, netstandard1.2 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.WPA81, + FrameworkConstants.CommonFrameworks.DotNet53, + FrameworkConstants.CommonFrameworks.NetStandard12), + + // wp8, wp81 -> dotnet5.1, netstandard1.0 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.WP8, + FrameworkConstants.CommonFrameworks.DotNet51, + FrameworkConstants.CommonFrameworks.NetStandard10), + + // net45 -> dotnet5.2, netstandard1.1 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.Net45, + FrameworkConstants.CommonFrameworks.DotNet52, + FrameworkConstants.CommonFrameworks.NetStandard11), + + // net451 -> dotnet5.3, netstandard1.2 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.Net451, + FrameworkConstants.CommonFrameworks.DotNet53, + FrameworkConstants.CommonFrameworks.NetStandard12), + + // net46 -> dotnet5.4, netstandard1.3 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.Net46, + FrameworkConstants.CommonFrameworks.DotNet54, + FrameworkConstants.CommonFrameworks.NetStandard13), + + // net461 -> dotnet5.5, netstandard2.0 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.Net461, + FrameworkConstants.CommonFrameworks.DotNet55, + FrameworkConstants.CommonFrameworks.NetStandard20), + + // net462 -> dotnet5.6, netstandard2.0 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.Net462, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard20), + + // netcore45 -> dotnet5.2, netstandard1.1 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.NetCore45, + FrameworkConstants.CommonFrameworks.DotNet52, + FrameworkConstants.CommonFrameworks.NetStandard11), + + // netcore451 -> dotnet5.3, netstandard1.2 + CreateGenerationAndStandardMapping( + FrameworkConstants.CommonFrameworks.NetCore451, + FrameworkConstants.CommonFrameworks.DotNet53, + FrameworkConstants.CommonFrameworks.NetStandard12), + + // xamarin frameworks + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.MonoAndroid, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard21), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.MonoMac, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard21), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.MonoTouch, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard21), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinIOs, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard21), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinMac, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard21), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation3, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard20), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinPlayStation4, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard20), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinPlayStationVita, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard20), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinXbox360, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard20), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinXboxOne, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard20), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinTVOS, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard21), + + CreateGenerationAndStandardMappingForAllVersions( + FrameworkConstants.FrameworkIdentifiers.XamarinWatchOS, + FrameworkConstants.CommonFrameworks.DotNet56, + FrameworkConstants.CommonFrameworks.NetStandard21) + }.SelectMany(mappings => mappings)) + .ToArray(); + }); + + public IEnumerable CompatibilityMappings + { + get + { + return CompatibilityMappingsLazy.Value; + } + } + + private static OneWayCompatibilityMappingEntry CreateGenerationMapping( + NuGetFramework framework, + NuGetFramework netPlatform) + { + return new OneWayCompatibilityMappingEntry( + new FrameworkRange( + framework, + new NuGetFramework(framework.Framework, FrameworkConstants.MaxVersion)), + new FrameworkRange( + FrameworkConstants.CommonFrameworks.DotNet, + netPlatform)); + } + + private static OneWayCompatibilityMappingEntry CreateStandardMapping( + NuGetFramework framework, + NuGetFramework netPlatform) + { + return new OneWayCompatibilityMappingEntry( + new FrameworkRange( + framework, + new NuGetFramework(framework.Framework, FrameworkConstants.MaxVersion)), + new FrameworkRange( + FrameworkConstants.CommonFrameworks.NetStandard10, + netPlatform)); + } + + private static IEnumerable CreateGenerationAndStandardMapping( + NuGetFramework framework, + NuGetFramework netPlatform, + NuGetFramework netStandard) + { + yield return CreateGenerationMapping(framework, netPlatform); + yield return CreateStandardMapping(framework, netStandard); + } + + private static IEnumerable CreateGenerationAndStandardMappingForAllVersions( + string framework, + NuGetFramework netPlatform, + NuGetFramework netStandard) + { + var lowestFramework = new NuGetFramework(framework, FrameworkConstants.EmptyVersion); + return CreateGenerationAndStandardMapping(lowestFramework, netPlatform, netStandard); + } + + private static readonly Lazy NonPackageBasedFrameworkPrecedenceLazy = new Lazy(() => + { + return new[] + { + FrameworkConstants.FrameworkIdentifiers.Net, + FrameworkConstants.FrameworkIdentifiers.NetCore, + FrameworkConstants.FrameworkIdentifiers.Windows, + FrameworkConstants.FrameworkIdentifiers.WindowsPhoneApp + }; + }); + + public IEnumerable NonPackageBasedFrameworkPrecedence + { + get + { + return NonPackageBasedFrameworkPrecedenceLazy.Value; + } + } + + private static readonly Lazy PackageBasedFrameworkPrecedenceLazy = new Lazy(() => + { + return new[] + { + FrameworkConstants.FrameworkIdentifiers.NetCoreApp, + FrameworkConstants.FrameworkIdentifiers.NetStandardApp, + FrameworkConstants.FrameworkIdentifiers.NetStandard, + FrameworkConstants.FrameworkIdentifiers.NetPlatform + }; + }); + + public IEnumerable PackageBasedFrameworkPrecedence + { + get + { + return PackageBasedFrameworkPrecedenceLazy.Value; + } + } + + private static readonly Lazy EquivalentFrameworkPrecedenceLazy = new Lazy(() => + { + return new[] + { + FrameworkConstants.FrameworkIdentifiers.Windows, + FrameworkConstants.FrameworkIdentifiers.NetCore, + FrameworkConstants.FrameworkIdentifiers.WinRT, + + FrameworkConstants.FrameworkIdentifiers.WindowsPhone, + FrameworkConstants.FrameworkIdentifiers.Silverlight, + + FrameworkConstants.FrameworkIdentifiers.DnxCore, + FrameworkConstants.FrameworkIdentifiers.AspNetCore, + + FrameworkConstants.FrameworkIdentifiers.Dnx, + FrameworkConstants.FrameworkIdentifiers.AspNet + }; + }); + + public IEnumerable EquivalentFrameworkPrecedence + { + get + { + return EquivalentFrameworkPrecedenceLazy.Value; + } + } + + private static readonly Lazy[]> ShortNameReplacementsLazy = new Lazy[]>(() => + { + return new[] + { + new KeyValuePair(FrameworkConstants.CommonFrameworks.DotNet50, FrameworkConstants.CommonFrameworks.DotNet) + }; + }); + + public IEnumerable> ShortNameReplacements + { + get + { + return ShortNameReplacementsLazy.Value; + } + } + + private static readonly Lazy[]> FullNameReplacementsLazy = new Lazy[]>(() => + { + return new[] + { + new KeyValuePair(FrameworkConstants.CommonFrameworks.DotNet, FrameworkConstants.CommonFrameworks.DotNet50) + }; + }); + + public IEnumerable> FullNameReplacements + { + get + { + return FullNameReplacementsLazy.Value; + } + } + + private static readonly Lazy InstanceLazy = new Lazy(() => new DefaultFrameworkMappings()); + + /// + /// Singleton instance of the default framework mappings. + /// + public static IFrameworkMappings Instance + { + get + { + return InstanceLazy.Value; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultFrameworkNameProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultFrameworkNameProvider.cs new file mode 100644 index 0000000000..9bf5f12ecb --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultFrameworkNameProvider.cs @@ -0,0 +1,23 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + internal sealed class DefaultFrameworkNameProvider : FrameworkNameProvider + { + public DefaultFrameworkNameProvider() + : base(new IFrameworkMappings[] { DefaultFrameworkMappings.Instance }, + new IPortableFrameworkMappings[] { DefaultPortableFrameworkMappings.Instance }) + { + } + + private static readonly Lazy InstanceLazy = new Lazy(() => new DefaultFrameworkNameProvider()); + + public static IFrameworkNameProvider Instance + { + get { return InstanceLazy.Value; } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultPortableFrameworkMappings.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultPortableFrameworkMappings.cs new file mode 100644 index 0000000000..79b79a204d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/DefaultPortableFrameworkMappings.cs @@ -0,0 +1,186 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + /// + /// Contains the standard portable framework mappings + /// + internal class DefaultPortableFrameworkMappings : IPortableFrameworkMappings + { + + private static readonly Lazy[]> ProfileFrameworksLazy = new Lazy[]>(() => + { + var net4 = FrameworkConstants.CommonFrameworks.Net4; + var net403 = FrameworkConstants.CommonFrameworks.Net403; + var net45 = FrameworkConstants.CommonFrameworks.Net45; + var net451 = FrameworkConstants.CommonFrameworks.Net451; + + var win8 = FrameworkConstants.CommonFrameworks.Win8; + var win81 = FrameworkConstants.CommonFrameworks.Win81; + + var sl4 = FrameworkConstants.CommonFrameworks.SL4; + var sl5 = FrameworkConstants.CommonFrameworks.SL5; + + var wp7 = FrameworkConstants.CommonFrameworks.WP7; + var wp75 = FrameworkConstants.CommonFrameworks.WP75; + var wp8 = FrameworkConstants.CommonFrameworks.WP8; + var wp81 = FrameworkConstants.CommonFrameworks.WP81; + + var wpa81 = FrameworkConstants.CommonFrameworks.WPA81; + + return new[] + { + // v4.6 + CreateProfileFrameworks(31, win81, wp81), + CreateProfileFrameworks(32, win81, wpa81), + CreateProfileFrameworks(44, net451, win81), + CreateProfileFrameworks(84, wp81, wpa81), + CreateProfileFrameworks(151, net451, win81, wpa81), + CreateProfileFrameworks(157, win81, wp81, wpa81), + + // v4.5 + CreateProfileFrameworks(7, net45, win8), + CreateProfileFrameworks(49, net45, wp8), + CreateProfileFrameworks(78, net45, win8, wp8), + CreateProfileFrameworks(111, net45, win8, wpa81), + CreateProfileFrameworks(259, net45, win8, wpa81, wp8), + + // v4.0 + CreateProfileFrameworks(2, net4, win8, sl4, wp7), + CreateProfileFrameworks(3, net4, sl4), + CreateProfileFrameworks(4, net45, sl4, win8, wp7), + CreateProfileFrameworks(5, net4, win8), + CreateProfileFrameworks(6, net403, win8), + CreateProfileFrameworks(14, net4, sl5), + CreateProfileFrameworks(18, net403, sl4), + CreateProfileFrameworks(19, net403, sl5), + CreateProfileFrameworks(23, net45, sl4), + CreateProfileFrameworks(24, net45, sl5), + CreateProfileFrameworks(36, net4, sl4, win8, wp8), + CreateProfileFrameworks(37, net4, sl5, win8), + CreateProfileFrameworks(41, net403, sl4, win8), + CreateProfileFrameworks(42, net403, sl5, win8), + CreateProfileFrameworks(46, net45, sl4, win8), + CreateProfileFrameworks(47, net45, sl5, win8), + CreateProfileFrameworks(88, net4, sl4, win8, wp75), + CreateProfileFrameworks(92, net4, win8, wpa81), + CreateProfileFrameworks(95, net403, sl4, win8, wp7), + CreateProfileFrameworks(96, net403, sl4, win8, wp75), + CreateProfileFrameworks(102, net403, win8, wpa81), + CreateProfileFrameworks(104, net45, sl4, win8, wp75), + CreateProfileFrameworks(136, net4, sl5, win8, wp8), + CreateProfileFrameworks(143, net403, sl4, win8, wp8), + CreateProfileFrameworks(147, net403, sl5, win8, wp8), + CreateProfileFrameworks(154, net45, sl4, win8, wp8), + CreateProfileFrameworks(158, net45, sl5, win8, wp8), + CreateProfileFrameworks(225, net4, sl5, win8, wpa81), + CreateProfileFrameworks(240, net403, sl5, win8, wpa81), + CreateProfileFrameworks(255, net45, sl5, win8, wpa81), + CreateProfileFrameworks(328, net4, sl5, win8, wpa81, wp8), + CreateProfileFrameworks(336, net403, sl5, win8, wpa81, wp8), + CreateProfileFrameworks(344, net45, sl5, win8, wpa81, wp8), + }; + }); + + public IEnumerable> ProfileFrameworks + { + get + { + return ProfileFrameworksLazy.Value; + } + } + + private static KeyValuePair CreateProfileFrameworks(int profile, params NuGetFramework[] frameworks) + { + return new KeyValuePair(profile, frameworks); + } + + // profiles that also support monotouch1+monoandroid1 + private static readonly int[] ProfilesWithOptionalFrameworks = + { + 5, 6, 7, 14, 19, 24, 37, 42, 44, 47, 49, 78, 92, 102, 111, 136, 147, 151, 158, 225, 255, 259, 328, 336, 344 + }; + + private static readonly Lazy>> ProfileOptionalFrameworksLazy = new Lazy>>(() => + { + var monoandroid = new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, new Version(0, 0)); + var monotouch = new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.MonoTouch, new Version(0, 0)); + var xamarinIOs = new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.XamarinIOs, new Version(0, 0)); + var xamarinMac = new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.XamarinMac, new Version(0, 0)); + var xamarinTVOS = new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.XamarinTVOS, new Version(0, 0)); + var xamarinWatchOS = new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.XamarinWatchOS, new Version(0, 0)); + var monoFrameworks = new NuGetFramework[] { monoandroid, monotouch, xamarinIOs, xamarinMac, xamarinWatchOS, xamarinTVOS }; + + var profileOptionalFrameworks = new List>(ProfilesWithOptionalFrameworks.Length); + + foreach (var profile in ProfilesWithOptionalFrameworks) + { + profileOptionalFrameworks.Add(new KeyValuePair(profile, monoFrameworks)); + } + + return profileOptionalFrameworks; + }); + + public IEnumerable> ProfileOptionalFrameworks + { + get + { + return ProfileOptionalFrameworksLazy.Value; + } + } + + private static readonly Lazy[]> CompatibilityMappingsLazy = new Lazy[]>(() => + { + return new[] + { + CreateStandardMapping(7, FrameworkConstants.CommonFrameworks.NetStandard11), + CreateStandardMapping(31, FrameworkConstants.CommonFrameworks.NetStandard10), + CreateStandardMapping(32, FrameworkConstants.CommonFrameworks.NetStandard12), + CreateStandardMapping(44, FrameworkConstants.CommonFrameworks.NetStandard12), + CreateStandardMapping(49, FrameworkConstants.CommonFrameworks.NetStandard10), + CreateStandardMapping(78, FrameworkConstants.CommonFrameworks.NetStandard10), + CreateStandardMapping(84, FrameworkConstants.CommonFrameworks.NetStandard10), + CreateStandardMapping(111, FrameworkConstants.CommonFrameworks.NetStandard11), + CreateStandardMapping(151, FrameworkConstants.CommonFrameworks.NetStandard12), + CreateStandardMapping(157, FrameworkConstants.CommonFrameworks.NetStandard10), + CreateStandardMapping(259, FrameworkConstants.CommonFrameworks.NetStandard10) + }; + }); + + public IEnumerable> CompatibilityMappings + { + get + { + return CompatibilityMappingsLazy.Value; + } + } + + private static KeyValuePair CreateStandardMapping( + int profileNumber, + NuGetFramework netStandard) + { + var range = new FrameworkRange( + FrameworkConstants.CommonFrameworks.NetStandard10, + netStandard); + + return new KeyValuePair(profileNumber, range); + } + + private static readonly Lazy InstanceLazy = new Lazy(() => new DefaultPortableFrameworkMappings()); + + /// + /// Static instance of the portable framework mappings + /// + public static IPortableFrameworkMappings Instance + { + get + { + return InstanceLazy.Value; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FallbackFramework.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FallbackFramework.cs new file mode 100644 index 0000000000..71f191051a --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FallbackFramework.cs @@ -0,0 +1,82 @@ +// 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.Linq; +using NuGetClone.Shared; + +using FallbackList = System.Collections.Generic.IReadOnlyList; + +namespace NuGetClone.Frameworks +{ + internal class FallbackFramework : NuGetFramework, IEquatable + { + /// + /// List framework to fall back to. + /// + public FallbackList Fallback { get; } + + private int? _hashCode; + + public FallbackFramework(NuGetFramework framework, FallbackList fallbackFrameworks) + : base(framework) + { + if (framework == null) + { + throw new ArgumentNullException(nameof(framework)); + } + + if (fallbackFrameworks == null) + { + throw new ArgumentNullException(nameof(fallbackFrameworks)); + } + + if (fallbackFrameworks.Count == 0) + { + throw new ArgumentException("Empty fallbackFrameworks is invalid", nameof(fallbackFrameworks)); + } + + Fallback = fallbackFrameworks; + } + + public override bool Equals(object? obj) + { + return Equals(obj as FallbackFramework); + } + + public override int GetHashCode() + { + if (_hashCode == null) + { + var combiner = new HashCodeCombiner(); + + combiner.AddObject(Comparer.GetHashCode(this)); + + foreach (var each in Fallback) + { + combiner.AddObject(Comparer.GetHashCode(each)); + } + + _hashCode = combiner.CombinedHash; + } + + return _hashCode.Value; + } + + public bool Equals(FallbackFramework? other) + { + if (other == null) + { + return false; + } + + if (Object.ReferenceEquals(this, other)) + { + return true; + } + + return NuGetFramework.Comparer.Equals(this, other) + && Fallback.SequenceEqual(other.Fallback); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkConstants.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkConstants.cs new file mode 100644 index 0000000000..21cb9c9c1f --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkConstants.cs @@ -0,0 +1,195 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + internal static class FrameworkConstants + { + public static readonly Version EmptyVersion = new Version(0, 0, 0, 0); + public static readonly Version MaxVersion = new Version(int.MaxValue, 0, 0, 0); + public static readonly Version Version5 = new Version(5, 0, 0, 0); + public static readonly Version Version6 = new Version(6, 0, 0, 0); + public static readonly Version Version7 = new Version(7, 0, 0, 0); + public static readonly Version Version10 = new Version(10, 0, 0, 0); + public static readonly FrameworkRange DotNetAll = new FrameworkRange( + new NuGetFramework(FrameworkIdentifiers.NetPlatform, FrameworkConstants.EmptyVersion), + new NuGetFramework(FrameworkIdentifiers.NetPlatform, FrameworkConstants.MaxVersion)); + + internal static class SpecialIdentifiers + { + public const string Any = "Any"; + public const string Agnostic = "Agnostic"; + public const string Unsupported = "Unsupported"; + } + + internal static class PlatformIdentifiers + { + public const string WindowsPhone = "WindowsPhone"; + public const string Windows = "Windows"; + } + + internal static class FrameworkIdentifiers + { + public const string NetCoreApp = ".NETCoreApp"; + public const string NetStandardApp = ".NETStandardApp"; + public const string NetStandard = ".NETStandard"; + public const string NetPlatform = ".NETPlatform"; + public const string DotNet = "dotnet"; + public const string Net = ".NETFramework"; + public const string NetCore = ".NETCore"; + public const string WinRT = "WinRT"; // deprecated + public const string NetMicro = ".NETMicroFramework"; + public const string Portable = ".NETPortable"; + public const string WindowsPhone = "WindowsPhone"; + public const string Windows = "Windows"; + public const string WindowsPhoneApp = "WindowsPhoneApp"; + public const string Dnx = "DNX"; + public const string DnxCore = "DNXCore"; + public const string AspNet = "ASP.NET"; + public const string AspNetCore = "ASP.NETCore"; + public const string Silverlight = "Silverlight"; + public const string Native = "native"; + public const string MonoAndroid = "MonoAndroid"; + public const string MonoTouch = "MonoTouch"; + public const string MonoMac = "MonoMac"; + public const string XamarinIOs = "Xamarin.iOS"; + public const string XamarinMac = "Xamarin.Mac"; + public const string XamarinPlayStation3 = "Xamarin.PlayStation3"; + public const string XamarinPlayStation4 = "Xamarin.PlayStation4"; + public const string XamarinPlayStationVita = "Xamarin.PlayStationVita"; + public const string XamarinWatchOS = "Xamarin.WatchOS"; + public const string XamarinTVOS = "Xamarin.TVOS"; + public const string XamarinXbox360 = "Xamarin.Xbox360"; + public const string XamarinXboxOne = "Xamarin.XboxOne"; + public const string UAP = "UAP"; + public const string Tizen = "Tizen"; + public const string NanoFramework = ".NETnanoFramework"; + } + + /// + /// Interned frameworks that are commonly used in NuGet + /// + internal static class CommonFrameworks + { + public static readonly NuGetFramework Net11 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(1, 1, 0, 0)); + public static readonly NuGetFramework Net2 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(2, 0, 0, 0)); + public static readonly NuGetFramework Net35 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(3, 5, 0, 0)); + public static readonly NuGetFramework Net4 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 0, 0, 0)); + public static readonly NuGetFramework Net403 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 0, 3, 0)); + public static readonly NuGetFramework Net45 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 5, 0, 0)); + public static readonly NuGetFramework Net451 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 5, 1, 0)); + public static readonly NuGetFramework Net452 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 5, 2, 0)); + public static readonly NuGetFramework Net46 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 6, 0, 0)); + public static readonly NuGetFramework Net461 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 6, 1, 0)); + public static readonly NuGetFramework Net462 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 6, 2, 0)); + public static readonly NuGetFramework Net463 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 6, 3, 0)); + public static readonly NuGetFramework Net47 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 7, 0, 0)); + public static readonly NuGetFramework Net471 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 7, 1, 0)); + public static readonly NuGetFramework Net472 = new NuGetFramework(FrameworkIdentifiers.Net, new Version(4, 7, 2, 0)); + + public static readonly NuGetFramework NetCore45 = new NuGetFramework(FrameworkIdentifiers.NetCore, new Version(4, 5, 0, 0)); + public static readonly NuGetFramework NetCore451 = new NuGetFramework(FrameworkIdentifiers.NetCore, new Version(4, 5, 1, 0)); + public static readonly NuGetFramework NetCore50 = new NuGetFramework(FrameworkIdentifiers.NetCore, new Version(5, 0, 0, 0)); + + public static readonly NuGetFramework Win8 = new NuGetFramework(FrameworkIdentifiers.Windows, new Version(8, 0, 0, 0)); + public static readonly NuGetFramework Win81 = new NuGetFramework(FrameworkIdentifiers.Windows, new Version(8, 1, 0, 0)); + public static readonly NuGetFramework Win10 = new NuGetFramework(FrameworkIdentifiers.Windows, new Version(10, 0, 0, 0)); + + public static readonly NuGetFramework SL4 = new NuGetFramework(FrameworkIdentifiers.Silverlight, new Version(4, 0, 0, 0)); + public static readonly NuGetFramework SL5 = new NuGetFramework(FrameworkIdentifiers.Silverlight, new Version(5, 0, 0, 0)); + + public static readonly NuGetFramework WP7 = new NuGetFramework(FrameworkIdentifiers.WindowsPhone, new Version(7, 0, 0, 0)); + public static readonly NuGetFramework WP75 = new NuGetFramework(FrameworkIdentifiers.WindowsPhone, new Version(7, 5, 0, 0)); + public static readonly NuGetFramework WP8 = new NuGetFramework(FrameworkIdentifiers.WindowsPhone, new Version(8, 0, 0, 0)); + public static readonly NuGetFramework WP81 = new NuGetFramework(FrameworkIdentifiers.WindowsPhone, new Version(8, 1, 0, 0)); + public static readonly NuGetFramework WPA81 = new NuGetFramework(FrameworkIdentifiers.WindowsPhoneApp, new Version(8, 1, 0, 0)); + + public static readonly NuGetFramework Tizen3 = new NuGetFramework(FrameworkIdentifiers.Tizen, new Version(3, 0, 0, 0)); + public static readonly NuGetFramework Tizen4 = new NuGetFramework(FrameworkIdentifiers.Tizen, new Version(4, 0, 0, 0)); + public static readonly NuGetFramework Tizen6 = new NuGetFramework(FrameworkIdentifiers.Tizen, new Version(6, 0, 0, 0)); + + public static readonly NuGetFramework AspNet = new NuGetFramework(FrameworkIdentifiers.AspNet, EmptyVersion); + public static readonly NuGetFramework AspNetCore = new NuGetFramework(FrameworkIdentifiers.AspNetCore, EmptyVersion); + public static readonly NuGetFramework AspNet50 = new NuGetFramework(FrameworkIdentifiers.AspNet, Version5); + public static readonly NuGetFramework AspNetCore50 = new NuGetFramework(FrameworkIdentifiers.AspNetCore, Version5); + + public static readonly NuGetFramework Dnx = new NuGetFramework(FrameworkIdentifiers.Dnx, EmptyVersion); + public static readonly NuGetFramework Dnx45 = new NuGetFramework(FrameworkIdentifiers.Dnx, new Version(4, 5, 0, 0)); + public static readonly NuGetFramework Dnx451 = new NuGetFramework(FrameworkIdentifiers.Dnx, new Version(4, 5, 1, 0)); + public static readonly NuGetFramework Dnx452 = new NuGetFramework(FrameworkIdentifiers.Dnx, new Version(4, 5, 2, 0)); + public static readonly NuGetFramework DnxCore = new NuGetFramework(FrameworkIdentifiers.DnxCore, EmptyVersion); + public static readonly NuGetFramework DnxCore50 = new NuGetFramework(FrameworkIdentifiers.DnxCore, Version5); + + public static readonly NuGetFramework DotNet + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, EmptyVersion); + public static readonly NuGetFramework DotNet50 + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, Version5); + public static readonly NuGetFramework DotNet51 + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, new Version(5, 1, 0, 0)); + public static readonly NuGetFramework DotNet52 + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, new Version(5, 2, 0, 0)); + public static readonly NuGetFramework DotNet53 + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, new Version(5, 3, 0, 0)); + public static readonly NuGetFramework DotNet54 + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, new Version(5, 4, 0, 0)); + public static readonly NuGetFramework DotNet55 + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, new Version(5, 5, 0, 0)); + public static readonly NuGetFramework DotNet56 + = new NuGetFramework(FrameworkIdentifiers.NetPlatform, new Version(5, 6, 0, 0)); + + public static readonly NuGetFramework NetStandard + = new NuGetFramework(FrameworkIdentifiers.NetStandard, EmptyVersion); + public static readonly NuGetFramework NetStandard10 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 0, 0, 0)); + public static readonly NuGetFramework NetStandard11 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 1, 0, 0)); + public static readonly NuGetFramework NetStandard12 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 2, 0, 0)); + public static readonly NuGetFramework NetStandard13 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 3, 0, 0)); + public static readonly NuGetFramework NetStandard14 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 4, 0, 0)); + public static readonly NuGetFramework NetStandard15 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 5, 0, 0)); + public static readonly NuGetFramework NetStandard16 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 6, 0, 0)); + public static readonly NuGetFramework NetStandard17 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(1, 7, 0, 0)); + public static readonly NuGetFramework NetStandard20 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(2, 0, 0, 0)); + public static readonly NuGetFramework NetStandard21 + = new NuGetFramework(FrameworkIdentifiers.NetStandard, new Version(2, 1, 0, 0)); + + public static readonly NuGetFramework NetStandardApp15 + = new NuGetFramework(FrameworkIdentifiers.NetStandardApp, new Version(1, 5, 0, 0)); + + public static readonly NuGetFramework UAP10 + = new NuGetFramework(FrameworkIdentifiers.UAP, Version10); + + public static readonly NuGetFramework NetCoreApp10 + = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, new Version(1, 0, 0, 0)); + public static readonly NuGetFramework NetCoreApp11 + = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, new Version(1, 1, 0, 0)); + public static readonly NuGetFramework NetCoreApp20 + = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, new Version(2, 0, 0, 0)); + public static readonly NuGetFramework NetCoreApp21 + = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, new Version(2, 1, 0, 0)); + public static readonly NuGetFramework NetCoreApp22 + = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, new Version(2, 2, 0, 0)); + public static readonly NuGetFramework NetCoreApp30 + = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, new Version(3, 0, 0, 0)); + public static readonly NuGetFramework NetCoreApp31 + = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, new Version(3, 1, 0, 0)); + + // .NET 5.0 and later has NetCoreApp identifier + public static readonly NuGetFramework Net50 = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version5); + public static readonly NuGetFramework Net60 = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version6); + public static readonly NuGetFramework Net70 = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version7); + + public static readonly NuGetFramework Native = new NuGetFramework(FrameworkIdentifiers.Native, new Version(0, 0, 0, 0)); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkException.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkException.cs new file mode 100644 index 0000000000..101b2726cf --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkException.cs @@ -0,0 +1,21 @@ +// 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.Runtime.Serialization; + +namespace NuGetClone.Frameworks +{ + [Serializable] + internal class FrameworkException : Exception + { + public FrameworkException(string message) + : base(message) + { + } + + protected FrameworkException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkExpander.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkExpander.cs new file mode 100644 index 0000000000..59e53543bc --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkExpander.cs @@ -0,0 +1,128 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + /// + /// FrameworkExpander finds all equivalent and compatible frameworks for a NuGetFramework + /// + internal class FrameworkExpander + { + private readonly IFrameworkNameProvider _mappings; + + public FrameworkExpander() + : this(DefaultFrameworkNameProvider.Instance) + { + } + + public FrameworkExpander(IFrameworkNameProvider mappings) + { + _mappings = mappings ?? throw new ArgumentNullException(nameof(mappings)); + } + + /// + /// Return all possible equivalent, subset, and known compatible frameworks. + /// + public IEnumerable Expand(NuGetFramework framework) + { + if (framework == null) throw new ArgumentNullException(nameof(framework)); + + var seen = new HashSet() { framework }; + var toExpand = new Stack(); + toExpand.Push(framework); + + while (toExpand.Count > 0) + { + foreach (var expansion in ExpandInternal(toExpand.Pop())) + { + // only return distinct frameworks + if (seen.Add(expansion)) + { + yield return expansion; + + toExpand.Push(expansion); + } + } + } + + // This PCL check is done outside of the loop because we do not want + // to recurse (via the stack above) on this PCL equivalence. The + // intent here is to ensure that PCL should expand to netstandard, + // but NOT to dotnet (which is deprecated). + if (framework.IsPCL) + { + if (_mappings.TryGetPortableProfileNumber(framework.Profile, out int profileNumber) + && _mappings.TryGetPortableCompatibilityMappings(profileNumber, out IEnumerable? ranges)) + { + foreach (var range in ranges) + { + yield return range.Min; + + if (!range.Min.Equals(range.Max)) + { + yield return range.Max; + } + } + } + } + } + + /// + /// Finds all expansions using the mapping provider + /// + private IEnumerable ExpandInternal(NuGetFramework framework) + { + // check the framework directly, this includes profiles which the range doesn't return + if (_mappings.TryGetEquivalentFrameworks(framework, out IEnumerable? directlyEquivalent)) + { + foreach (var eqFw in directlyEquivalent) + { + yield return eqFw; + } + } + + // 0.0 through the current framework + var frameworkRange = new FrameworkRange( + new NuGetFramework(framework.Framework, new Version(0, 0), framework.Profile), + framework); + + if (_mappings.TryGetEquivalentFrameworks(frameworkRange, out IEnumerable? equivalent)) + { + foreach (var eqFw in equivalent) + { + yield return eqFw; + } + } + + // find all possible sub set frameworks if no profile is used + if (!framework.HasProfile) + { + if (_mappings.TryGetSubSetFrameworks(framework.Framework, out IEnumerable? subSetFrameworks)) + { + foreach (var subFramework in subSetFrameworks) + { + // clone the framework but use the sub framework instead + yield return new NuGetFramework(subFramework, framework.Version, framework.Profile); + } + } + } + + // explicit compatiblity mappings + if (_mappings.TryGetCompatibilityMappings(framework, out IEnumerable? ranges)) + { + foreach (var range in ranges) + { + yield return range.Min; + + if (!range.Min.Equals(range.Max)) + { + yield return range.Max; + } + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkNameHelpers.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkNameHelpers.cs new file mode 100644 index 0000000000..9c6fa9bd1d --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkNameHelpers.cs @@ -0,0 +1,73 @@ +// 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.Globalization; +using System.Linq; + +namespace NuGetClone.Frameworks +{ + internal static class FrameworkNameHelpers + { + public static string GetPortableProfileNumberString(int profileNumber) + { + return String.Format(CultureInfo.InvariantCulture, "Profile{0}", profileNumber); + } + + public static string GetFolderName(string identifierShortName, string versionString, string? profileShortName) + { + return String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}", identifierShortName, versionString, String.IsNullOrEmpty(profileShortName) ? string.Empty : "-", profileShortName); + } + + public static string GetVersionString(Version version) + { + string? versionString = null; + + if (version != null) + { + if (version.Major > 9 + || version.Minor > 9 + || version.Build > 9 + || version.Revision > 9) + { + versionString = version.ToString(); + } + else + { + versionString = version.ToString().Replace(".", "").TrimEnd('0'); + } + } + + return versionString!; + } + + public static Version GetVersion(string? versionString) + { + if (string.IsNullOrEmpty(versionString)) + { + return FrameworkConstants.EmptyVersion; + } + else + { + if (versionString!.IndexOf('.') > -1) + { + // parse the version as a normal dot delimited version + return Version.Parse(versionString); + } + else + { + // make sure we have at least 2 digits + if (versionString.Length < 2) + { + versionString += "0"; + } + + // take only the first 4 digits and add dots + // 451 -> 4.5.1 + // 81233 -> 8123 + return Version.Parse(string.Join(".", versionString.ToCharArray().Take(4))); + } + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkNameProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkNameProvider.cs new file mode 100644 index 0000000000..4c810b6564 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkNameProvider.cs @@ -0,0 +1,1185 @@ +// 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 System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace NuGetClone.Frameworks +{ + internal class FrameworkNameProvider : IFrameworkNameProvider + { + private static readonly HashSet EmptyFrameworkSet = new(); + + /// + /// Legacy frameworks that are allowed to have a single digit for the version number. + /// + private static readonly HashSet SingleDigitVersionFrameworks = new(StringComparer.OrdinalIgnoreCase) + { + FrameworkConstants.FrameworkIdentifiers.Windows, + FrameworkConstants.FrameworkIdentifiers.WindowsPhone, + FrameworkConstants.FrameworkIdentifiers.Silverlight + }; + + /// + /// Frameworks that must always include a decimal point (period) between numerical parts. + /// + private static readonly HashSet DecimalPointFrameworks = new(StringComparer.OrdinalIgnoreCase) + { + FrameworkConstants.FrameworkIdentifiers.NetCoreApp, + FrameworkConstants.FrameworkIdentifiers.NetStandard, + FrameworkConstants.FrameworkIdentifiers.NanoFramework + }; + + /// + /// Contains identifier -> identifier + /// Ex: .NET Framework -> .NET Framework + /// Ex: NET Framework -> .NET Framework + /// This includes self mappings. + /// + private readonly Dictionary _identifierSynonyms; + + private readonly Dictionary _identifierToShortName; + private readonly Dictionary _profilesToShortName; + private readonly Dictionary _identifierShortToLong; + private readonly Dictionary _profileShortToLong; + + // profile -> supported frameworks, optional frameworks + private readonly Dictionary> _portableFrameworks; + private readonly Dictionary> _portableOptionalFrameworks; + + // PCL compatibility mappings + private readonly Dictionary> _portableCompatibilityMappings; + + // equivalent frameworks + private readonly Dictionary> _equivalentFrameworks; + + // equivalent profiles + private readonly Dictionary>> _equivalentProfiles; + + // non-PCL compatibility mappings + private readonly Dictionary> _compatibilityMappings; + + // subsets, net -> netcore + private readonly Dictionary> _subSetFrameworks; + + // framework ordering (for non-package based frameworks) + private readonly Dictionary _nonPackageBasedFrameworkPrecedence; + + // framework ordering (for package based frameworks) + private readonly Dictionary _packageBasedFrameworkPrecedence; + + // framework ordering (when choosing between equivalent frameworks) + private readonly Dictionary _equivalentFrameworkPrecedence; + + // Rewrite mappings + private readonly Dictionary _shortNameRewrites; + private readonly Dictionary _fullNameRewrites; + + // NetStandard information + private readonly List _netStandardVersions; + private readonly List _compatibleCandidates; + + public FrameworkNameProvider(IEnumerable? mappings, IEnumerable? portableMappings) + { + _identifierSynonyms = new Dictionary(StringComparer.OrdinalIgnoreCase); + _identifierToShortName = new Dictionary(StringComparer.OrdinalIgnoreCase); + _profilesToShortName = new Dictionary(StringComparer.OrdinalIgnoreCase); + _identifierShortToLong = new Dictionary(StringComparer.OrdinalIgnoreCase); + _profileShortToLong = new Dictionary(StringComparer.OrdinalIgnoreCase); + _portableFrameworks = new Dictionary>(); + _portableOptionalFrameworks = new Dictionary>(); + _equivalentFrameworks = new Dictionary>(); + _equivalentProfiles = new Dictionary>>(StringComparer.OrdinalIgnoreCase); + _subSetFrameworks = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _nonPackageBasedFrameworkPrecedence = new Dictionary(StringComparer.OrdinalIgnoreCase); + _packageBasedFrameworkPrecedence = new Dictionary(StringComparer.OrdinalIgnoreCase); + _equivalentFrameworkPrecedence = new Dictionary(StringComparer.OrdinalIgnoreCase); + _compatibilityMappings = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _portableCompatibilityMappings = new Dictionary>(); + _shortNameRewrites = new Dictionary(); + _fullNameRewrites = new Dictionary(); + _netStandardVersions = new List(); + _compatibleCandidates = new List(); + + InitMappings(mappings); + + InitPortableMappings(portableMappings); + + InitNetStandard(); + } + + /// + /// Converts a key using the mappings, or if the key is already converted, finds the normalized form. + /// + private static bool TryConvertOrNormalize(string key, IDictionary mappings, IDictionary reverse, [NotNullWhen(true)] out string? value) + { + if (mappings.TryGetValue(key, out value)) + { + return true; + } + else if (reverse.ContainsKey(key)) + { + value = reverse.Where(p => StringComparer.OrdinalIgnoreCase.Equals(p.Key, key)).Select(s => s.Key).Single(); + return true; + } + + value = null; + return false; + } + + public bool TryGetIdentifier(string framework, [NotNullWhen(true)] out string? identifier) + { + return TryConvertOrNormalize(framework, _identifierSynonyms, _identifierToShortName, out identifier); + } + + public bool TryGetProfile(string frameworkIdentifier, string profileShortName, [NotNullWhen(true)] out string? profile) + { + return TryConvertOrNormalize(profileShortName, _profileShortToLong, _profilesToShortName, out profile); + } + + public bool TryGetShortIdentifier(string identifier, [NotNullWhen(true)] out string? identifierShortName) + { + return TryConvertOrNormalize(identifier, _identifierToShortName, _identifierShortToLong, out identifierShortName); + } + + public bool TryGetShortProfile(string frameworkIdentifier, string profile, [NotNullWhen(true)] out string? profileShortName) + { + return TryConvertOrNormalize(profile, _profilesToShortName, _profileShortToLong, out profileShortName); + } + + public bool TryGetVersion(string versionString, [NotNullWhen(true)] out Version? version) + { + if (string.IsNullOrEmpty(versionString)) + { + version = null; + return false; + } + else + { + if (versionString.IndexOf('.') > -1) + { + // parse the version as a normal dot delimited version + return Version.TryParse(versionString, out version); + } + else + { + // make sure we have at least 2 digits + if (versionString.Length < 2) + { + versionString += "0"; + } + + // take only the first 4 digits and add dots + // 451 -> 4.5.1 + // 81233 -> 8123 + return Version.TryParse(string.Join(".", versionString.ToCharArray().Take(4)), out version); + } + } + } + + public bool TryGetPlatformVersion(string versionString, [NotNullWhen(true)] out Version? version) + { + if (string.IsNullOrEmpty(versionString)) + { + version = null; + return false; + } + else + { + if (versionString.IndexOf('.') < 0) + { + versionString += ".0"; + } + return Version.TryParse(versionString, out version); + } + } + + public string GetVersionString(string framework, Version version) + { + if (version is null || IsZero(version)) + { + return string.Empty; + } + + int major = version.Major > 0 ? version.Major : 0; + int minor = version.Minor > 0 ? version.Minor : 0; + int build = version.Build > 0 ? version.Build : 0; + int revision = version.Revision > 0 ? version.Revision : 0; + + // Remove all trailing zeros beyond the minor version. + int partCount = (minor == 0, build == 0, revision == 0) switch + { + (true, true, true) => 1, + (false, true, true) => 2, + (_, false, true) => 3, + (_, _, false) => 4 + }; + + // Only some legacy frameworks are allowed to have one part in their version. + if (partCount == 1 && !SingleDigitVersionFrameworks.Contains(framework)) + { + partCount = 2; + } + + StringBuilder sb = StringBuilderPool.Shared.Rent(256); + + // Some frameworks require a decimal point between parts. + // If any part is greater than 9 (requiring multiple digits), we add decimal points. + if (DecimalPointFrameworks.Contains(framework) || HasGreaterThanNinePart()) + { + // An additional zero is needed for decimals. + if (partCount == 1) + partCount = 2; + + sb.Append(major); + if (partCount > 1) + sb.Append('.').Append(minor); + if (partCount > 2) + sb.Append('.').Append(build); + if (partCount > 3) + sb.Append('.').Append(revision); + } + else + { + sb.Append(major); + if (partCount > 1) + sb.Append(minor); + if (partCount > 2) + sb.Append(build); + if (partCount > 3) + sb.Append(revision); + } + + return StringBuilderPool.Shared.ToStringAndReturn(sb); + + bool HasGreaterThanNinePart() + { + return major > 9 || minor > 9 || build > 9 || revision > 9; + } + + static bool IsZero(Version version) + { + // Build and Revision can be -1 when only major & minor are specified. + // Out of caution, check all values for zero or less. + return version.Major <= 0 + && version.Minor <= 0 + && version.Build <= 0 + && version.Revision <= 0; + } + } + + public bool TryGetPortableProfile(IEnumerable supportedFrameworks, out int profileNumber) + { + if (supportedFrameworks == null) + { + throw new ArgumentNullException(nameof(supportedFrameworks)); + } + + profileNumber = -1; + + // Remove duplicate frameworks, ex: win+win8 -> win + var profileFrameworks = RemoveDuplicateFramework(supportedFrameworks); + + var reduced = new HashSet(); + foreach (var pair in _portableFrameworks) + { + // to match the required set must be less than or the same count as the input + // if we knew which frameworks were optional in the input we could rule out the lesser ones also + if (pair.Value.Count <= profileFrameworks.Count) + { + foreach (var curFw in profileFrameworks) + { + var isOptional = false; + + foreach (var optional in GetOptionalFrameworks(pair.Key)) + { + // TODO: profile check? Is the version check correct here? + if (NuGetFramework.FrameworkNameComparer.Equals(optional, curFw) + && StringComparer.OrdinalIgnoreCase.Equals(optional.Profile, curFw.Profile) + && curFw.Version >= optional.Version) + { + isOptional = true; + } + } + + if (!isOptional) + { + reduced.Add(curFw); + } + } + + // check all frameworks while taking into account equivalent variations + foreach (var permutation in GetEquivalentPermutations(pair.Value)) + { + if (SetEquals(reduced, permutation)) + { + // found a match + profileNumber = pair.Key; + return true; + } + } + } + + reduced.Clear(); + } + + return false; + } + + private HashSet RemoveDuplicateFramework(IEnumerable supportedFrameworks) + { + var result = new HashSet(); + var existingFrameworks = new HashSet(); + + foreach (var framework in supportedFrameworks) + { + if (!existingFrameworks.Contains(framework)) + { + result.Add(framework); + + // Add in the existing framework (included here) and all equivalent frameworks + var equivalentFrameworks = GetAllEquivalentFrameworks(framework); + + UnionWith(existingFrameworks, equivalentFrameworks); + } + } + + return result; + } + + /// + /// Get all equivalent frameworks including the given framework + /// + private HashSet GetAllEquivalentFrameworks(NuGetFramework framework) + { + // Loop through the frameworks, all frameworks that are not in results yet + // will be added to toProcess to get the equivalent frameworks + var toProcess = new Stack(); + var results = new HashSet(); + + toProcess.Push(framework); + results.Add(framework); + + while (toProcess.Count > 0) + { + var current = toProcess.Pop(); + + if (_equivalentFrameworks.TryGetValue(current, out HashSet? currentEquivalent)) + { + foreach (var equalFramework in currentEquivalent) + { + if (results.Add(equalFramework)) + { + toProcess.Push(equalFramework); + } + } + } + } + + return results; + } + + // find all combinations that are equivalent + // ex: net4+win8 <-> net4+netcore45 + private IEnumerable> GetEquivalentPermutations(HashSet frameworks) + { + if (frameworks.Count > 0) + { + NuGetFramework? current = null; + var remaining = frameworks.Count == 1 ? null : new HashSet(); + + var isFirst = true; + foreach (var fw in frameworks) + { + if (isFirst) + { + current = fw; + isFirst = false; + continue; + } + + remaining!.Add(fw); + } + + var equalFrameworks = new HashSet(); + // include ourselves + equalFrameworks.Add(current!); + + // find all equivalent frameworks for the current one + if (_equivalentFrameworks.TryGetValue(current!, out HashSet? curFrameworks)) + { + UnionWith(equalFrameworks, curFrameworks); + } + + foreach (var fw in equalFrameworks) + { + if (remaining != null && remaining.Count > 0) + { + foreach (var result in GetEquivalentPermutations(remaining)) + { + // work backwards adding the frameworks into the sets + result.Add(fw); + yield return result; + } + } + else + { + var singleFramework = new HashSet(); + singleFramework.Add(fw); + yield return singleFramework; + } + } + } + + yield break; + } + + private HashSet GetOptionalFrameworks(int profile) + { + if (_portableOptionalFrameworks.TryGetValue(profile, out HashSet? frameworks)) + { + return frameworks; + } + + return EmptyFrameworkSet; + } + + public bool TryGetPortableFrameworks(int profile, [NotNullWhen(true)] out IEnumerable? frameworks) + { + return TryGetPortableFrameworks(profile, true, out frameworks); + } + + public bool TryGetPortableFrameworks(int profile, bool includeOptional, [NotNullWhen(true)] out IEnumerable? frameworks) + { + var result = new HashSet(); + if (_portableFrameworks.TryGetValue(profile, out HashSet? tmpFrameworks)) + { + foreach (var fw in tmpFrameworks) + { + result.Add(fw); + } + } + + if (includeOptional) + { + if (_portableOptionalFrameworks.TryGetValue(profile, out HashSet? optional)) + { + foreach (var fw in optional) + { + result.Add(fw); + } + } + } + + frameworks = result; + return result.Count > 0; + } + + public bool TryGetPortableFrameworks(string shortPortableProfiles, [NotNullWhen(true)] out IEnumerable? frameworks) + { + if (shortPortableProfiles == null) + { + throw new ArgumentNullException(nameof(shortPortableProfiles)); + } + + var shortNames = shortPortableProfiles.Split(new char[] { '+' }, StringSplitOptions.RemoveEmptyEntries); + + var result = new List(); + foreach (var name in shortNames) + { + var framework = NuGetFramework.Parse(name, this); + if (framework.HasProfile) + { + // Frameworks within the portable profile are not allowed + // to have profiles themselves #1869 + throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + Strings.InvalidPortableFrameworksDueToHyphen, + shortPortableProfiles)); + } + + result.Add(framework); + } + + frameworks = result; + return result.Count > 0; + } + + public bool TryGetPortableCompatibilityMappings(int profile, [NotNullWhen(true)] out IEnumerable? supportedFrameworkRanges) + { + if (_portableCompatibilityMappings.TryGetValue(profile, out HashSet? entries)) + { + supportedFrameworkRanges = entries; + return supportedFrameworkRanges.Any(); + } + + supportedFrameworkRanges = null; + return false; + } + + public bool TryGetPortableProfileNumber(string profile, out int profileNumber) + { + // attempt to parse the profile for a number + if (profile.StartsWith("Profile", StringComparison.OrdinalIgnoreCase)) + { + var trimmed = profile.Substring(7, profile.Length - 7); + return int.TryParse(trimmed, out profileNumber); + } + + profileNumber = -1; + return false; + } + + public bool TryGetPortableFrameworks(string profile, bool includeOptional, [NotNullWhen(true)] out IEnumerable? frameworks) + { + // attempt to parse the profile for a number + int profileNum; + if (TryGetPortableProfileNumber(profile, out profileNum)) + { + if (TryGetPortableFrameworks(profileNum, includeOptional, out frameworks)) + { + return true; + } + + frameworks = Enumerable.Empty(); + return false; + } + + // treat the profile as a list of frameworks + return TryGetPortableFrameworks(profile, out frameworks); + } + + public bool TryGetEquivalentFrameworks(NuGetFramework framework, [NotNullWhen(true)] out IEnumerable? frameworks) + { + var result = new HashSet(); + + // add in all framework aliases + if (_equivalentFrameworks.TryGetValue(framework, out HashSet? eqFrameworks)) + { + foreach (var eqFw in eqFrameworks) + { + result.Add(eqFw); + } + } + + var baseFrameworks = new List(result); + baseFrameworks.Add(framework); + + // add in all profile aliases + foreach (var fw in baseFrameworks) + { + if (_equivalentProfiles.TryGetValue(fw.Framework, out Dictionary>? eqProfiles)) + { + if (eqProfiles.TryGetValue(fw.Profile, out HashSet? matchingProfiles)) + { + foreach (var eqProfile in matchingProfiles) + { + result.Add(new NuGetFramework(fw.Framework, fw.Version, eqProfile)); + } + } + } + } + + // do not include the original framework + result.Remove(framework); + + frameworks = result; + return result.Count > 0; + } + + public bool TryGetEquivalentFrameworks(FrameworkRange range, [NotNullWhen(true)] out IEnumerable? frameworks) + { + if (range == null) + { + throw new ArgumentNullException(nameof(range)); + } + + var relevant = new HashSet(); + + foreach (var framework in _equivalentFrameworks.Keys.Where(f => range.Satisfies(f))) + { + relevant.Add(framework); + } + + var results = new HashSet(); + + foreach (var framework in relevant) + { + if (TryGetEquivalentFrameworks(framework, out IEnumerable? values)) + { + foreach (var val in values) + { + results.Add(val); + } + } + } + + frameworks = results; + return results.Count > 0; + } + + private void InitMappings(IEnumerable? mappings) + { + if (mappings != null) + { + foreach (var mapping in mappings) + { + // eq profiles + AddEquivalentProfiles(mapping.EquivalentProfiles); + + // equivalent frameworks + AddEquivalentFrameworks(mapping.EquivalentFrameworks); + + // add synonyms + AddFrameworkSynonyms(mapping.IdentifierSynonyms); + + // populate short <-> long + AddIdentifierShortNames(mapping.IdentifierShortNames); + + // official profile short names + AddProfileShortNames(mapping.ProfileShortNames); + + // add compatibility mappings + AddCompatibilityMappings(mapping.CompatibilityMappings); + + // add subset frameworks + AddSubSetFrameworks(mapping.SubSetFrameworks); + + // add framework ordering rules + AddFrameworkPrecedenceMappings(_nonPackageBasedFrameworkPrecedence, mapping.NonPackageBasedFrameworkPrecedence); + AddFrameworkPrecedenceMappings(_packageBasedFrameworkPrecedence, mapping.PackageBasedFrameworkPrecedence); + AddFrameworkPrecedenceMappings(_equivalentFrameworkPrecedence, mapping.EquivalentFrameworkPrecedence); + + // add rewrite rules + AddShortNameRewriteMappings(mapping.ShortNameReplacements); + AddFullNameRewriteMappings(mapping.FullNameReplacements); + } + } + } + + private void InitPortableMappings(IEnumerable? portableMappings) + { + if (portableMappings != null) + { + foreach (var portableMapping in portableMappings) + { + // populate portable framework names + AddPortableProfileMappings(portableMapping.ProfileFrameworks); + + // populate portable optional frameworks + AddPortableOptionalFrameworks(portableMapping.ProfileOptionalFrameworks); + + // populate portable compatibility mappings + AddPortableCompatibilityMappings(portableMapping.CompatibilityMappings); + } + } + } + + private void InitNetStandard() + { + // populate the list of frameworks that could be compatible with NetStandard + AddCompatibleCandidates(); + + // populate the list of NetStandard versions + AddNetStandardVersions(); + } + + private void AddShortNameRewriteMappings(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var mapping in mappings) + { + if (!_shortNameRewrites.ContainsKey(mapping.Key)) + { + _shortNameRewrites.Add(mapping.Key, mapping.Value); + } + } + } + } + + private void AddFullNameRewriteMappings(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var mapping in mappings) + { + if (!_fullNameRewrites.ContainsKey(mapping.Key)) + { + _fullNameRewrites.Add(mapping.Key, mapping.Value); + } + } + } + } + + private void AddCompatibilityMappings(IEnumerable mappings) + { + if (mappings != null) + { + foreach (var mapping in mappings) + { + if (!_compatibilityMappings.TryGetValue(mapping.TargetFrameworkRange.Min.Framework, out HashSet? entries)) + { + entries = new HashSet(OneWayCompatibilityMappingEntry.Comparer); + _compatibilityMappings.Add(mapping.TargetFrameworkRange.Min.Framework, entries); + } + + entries.Add(mapping); + } + } + } + + private void AddSubSetFrameworks(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var mapping in mappings) + { + if (!_subSetFrameworks.TryGetValue(mapping.Value, out HashSet? subSets)) + { + subSets = new HashSet(StringComparer.OrdinalIgnoreCase); + _subSetFrameworks.Add(mapping.Value, subSets); + } + + subSets.Add(mapping.Key); + } + } + } + + /// + /// 2 way per framework profile equivalence + /// + /// + private void AddEquivalentProfiles(IEnumerable mappings) + { + if (mappings != null) + { + foreach (var profileMapping in mappings) + { + var frameworkIdentifier = profileMapping.FrameworkIdentifier; + var profile1 = profileMapping.Mapping.Key; + var profile2 = profileMapping.Mapping.Value; + + if (!_equivalentProfiles.TryGetValue(frameworkIdentifier, out Dictionary>? profileMappings)) + { + profileMappings = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _equivalentProfiles.Add(frameworkIdentifier, profileMappings); + } + + if (!profileMappings.TryGetValue(profile1, out HashSet? innerMappings1)) + { + innerMappings1 = new HashSet(StringComparer.OrdinalIgnoreCase); + profileMappings.Add(profile1, innerMappings1); + } + + if (!profileMappings.TryGetValue(profile2, out HashSet? innerMappings2)) + { + innerMappings2 = new HashSet(StringComparer.OrdinalIgnoreCase); + profileMappings.Add(profile2, innerMappings2); + } + + innerMappings1.Add(profile2); + innerMappings2.Add(profile1); + } + } + } + + /// + /// 2 way framework equivalence + /// + /// + private void AddEquivalentFrameworks(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var pair in mappings) + { + var remaining = new Stack(); + remaining.Push(pair.Key); + remaining.Push(pair.Value); + + var seen = new HashSet(); + while (remaining.Any()) + { + var next = remaining.Pop(); + if (!seen.Add(next)) + { + continue; + } + + if (!_equivalentFrameworks.TryGetValue(next, out HashSet? eqFrameworks)) + { + // initialize set + eqFrameworks = new HashSet(); + _equivalentFrameworks.Add(next, eqFrameworks); + } + else + { + // explore all equivalent + foreach (var framework in eqFrameworks) + { + remaining.Push(framework); + } + } + } + + // add this equivalency rule, enforcing transitivity + foreach (var framework in seen) + { + foreach (var other in seen) + { + if (!NuGetFramework.Comparer.Equals(framework, other)) + { + _equivalentFrameworks[framework].Add(other); + } + } + } + + } + } + } + + private void AddFrameworkSynonyms(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var pair in mappings) + { + if (!_identifierSynonyms.ContainsKey(pair.Key)) + { + _identifierSynonyms.Add(pair.Key, pair.Value); + } + } + } + } + + private void AddIdentifierShortNames(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var pair in mappings) + { + var shortName = pair.Value; + var longName = pair.Key; + + if (!_identifierSynonyms.ContainsKey(pair.Value)) + { + _identifierSynonyms.Add(pair.Value, pair.Key); + } + + _identifierShortToLong.Add(shortName, longName); + + _identifierToShortName.Add(longName, shortName); + } + } + } + + private void AddProfileShortNames(IEnumerable mappings) + { + if (mappings != null) + { + foreach (var profileMapping in mappings) + { + _profilesToShortName.Add(profileMapping.Mapping.Value, profileMapping.Mapping.Key); + _profileShortToLong.Add(profileMapping.Mapping.Key, profileMapping.Mapping.Value); + } + } + } + + // Add supported frameworks for each portable profile number + private void AddPortableProfileMappings(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var pair in mappings) + { + if (!_portableFrameworks.TryGetValue(pair.Key, out HashSet? frameworks)) + { + frameworks = new HashSet(); + _portableFrameworks.Add(pair.Key, frameworks); + } + + foreach (var fw in pair.Value) + { + frameworks.Add(fw); + } + } + } + } + + // Add optional frameworks for each portable profile number + private void AddPortableOptionalFrameworks(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var pair in mappings) + { + if (!_portableOptionalFrameworks.TryGetValue(pair.Key, out HashSet? frameworks)) + { + frameworks = new HashSet(); + _portableOptionalFrameworks.Add(pair.Key, frameworks); + } + + foreach (var fw in pair.Value) + { + frameworks.Add(fw); + } + } + } + } + + private void AddPortableCompatibilityMappings(IEnumerable> mappings) + { + if (mappings != null) + { + foreach (var mapping in mappings) + { + if (!_portableCompatibilityMappings.TryGetValue(mapping.Key, out HashSet? entries)) + { + entries = new HashSet(FrameworkRangeComparer.Instance); + _portableCompatibilityMappings.Add(mapping.Key, entries); + } + + entries.Add(mapping.Value); + } + } + } + + // Ordered lists of framework identifiers + public void AddFrameworkPrecedenceMappings(IDictionary destination, IEnumerable mappings) + { + if (mappings != null) + { + foreach (var framework in mappings) + { + if (!destination.ContainsKey(framework)) + { + destination.Add(framework, destination.Count); + } + } + } + } + + public bool TryGetCompatibilityMappings(NuGetFramework framework, [NotNullWhen(true)] out IEnumerable? supportedFrameworkRanges) + { + if (_compatibilityMappings.TryGetValue(framework.Framework, out HashSet? entries)) + { + supportedFrameworkRanges = entries.Where(m => m.TargetFrameworkRange.Satisfies(framework)).Select(m => m.SupportedFrameworkRange); + return supportedFrameworkRanges.Any(); + } + + supportedFrameworkRanges = null; + return false; + } + + public bool TryGetSubSetFrameworks(string frameworkIdentifier, [NotNullWhen(true)] out IEnumerable? subSetFrameworks) + { + if (_subSetFrameworks.TryGetValue(frameworkIdentifier, out HashSet? values)) + { + subSetFrameworks = values; + return true; + } + + subSetFrameworks = null; + return false; + } + + public int CompareFrameworks(NuGetFramework? x, NuGetFramework? y) + { + if (x is null && y is null) return 0; + if (x is null) return -1; + if (y is null) return 1; + + // For the purposes of this compare do not treat netcore50 as packages based + var xPackagesBased = x.IsPackageBased && !NuGetFrameworkUtility.IsNetCore50AndUp(x); + var yPackagesBased = y.IsPackageBased && !NuGetFrameworkUtility.IsNetCore50AndUp(y); + + if (xPackagesBased != yPackagesBased) + { + // non-package based always come before package based + return xPackagesBased.CompareTo(yPackagesBased); + } + + var precedence = xPackagesBased ? _packageBasedFrameworkPrecedence : _nonPackageBasedFrameworkPrecedence; + + return CompareUsingPrecedence(x, y, precedence); + } + + public int CompareEquivalentFrameworks(NuGetFramework? x, NuGetFramework? y) + { + return CompareUsingPrecedence(x, y, _equivalentFrameworkPrecedence); + } + + private static int CompareUsingPrecedence(NuGetFramework? x, NuGetFramework? y, Dictionary precedence) + { + if (x is null && y is null) return 0; + if (x is null) return -1; + if (y is null) return 1; + + if (StringComparer.OrdinalIgnoreCase.Equals(x.Framework, y.Framework)) + { + return 0; + } + + int xIndex; + if (!precedence.TryGetValue(x.Framework, out xIndex)) + { + xIndex = int.MaxValue; + } + + int yIndex; + if (!precedence.TryGetValue(y.Framework, out yIndex)) + { + yIndex = int.MaxValue; + } + + return xIndex.CompareTo(yIndex); + } + + + public NuGetFramework GetShortNameReplacement(NuGetFramework framework) + { + // Replace the framework name if a rewrite exists + if (!_shortNameRewrites.TryGetValue(framework, out NuGetFramework? result)) + { + result = framework; + } + + return result; + } + + public NuGetFramework GetFullNameReplacement(NuGetFramework framework) + { + // Replace the framework name if a rewrite exists + if (!_fullNameRewrites.TryGetValue(framework, out NuGetFramework? result)) + { + result = framework; + } + + return result; + } + + public IEnumerable GetNetStandardVersions() + { + return _netStandardVersions.AsReadOnly(); + } + + public IEnumerable GetCompatibleCandidates() + { + return _compatibleCandidates.AsReadOnly(); + } + + private void AddNetStandardVersions() + { + foreach (var framework in _compatibleCandidates) + { + if (StringComparer.OrdinalIgnoreCase.Equals(framework.Framework, FrameworkConstants.FrameworkIdentifiers.NetStandard)) + { + _netStandardVersions.Add(framework); + } + } + + _netStandardVersions.Sort(NuGetFrameworkSorter.Instance); + } + + private void AddCompatibleCandidates() + { + var set = new HashSet(); + + // equivalent + foreach (var framework in _equivalentFrameworks.Values.SelectMany(x => x)) + { + set.Add(framework); + } + + // compatible + foreach (var mapping in _compatibilityMappings.SelectMany(p => p.Value)) + { + set.Add(mapping.TargetFrameworkRange.Min); + set.Add(mapping.TargetFrameworkRange.Max); + set.Add(mapping.SupportedFrameworkRange.Min); + set.Add(mapping.SupportedFrameworkRange.Max); + } + + // portable compatible + foreach (var pair in _portableCompatibilityMappings) + { + var portable = new NuGetFramework( + FrameworkConstants.FrameworkIdentifiers.Portable, + FrameworkConstants.EmptyVersion, + string.Format(NumberFormatInfo.InvariantInfo, "Profile{0}", pair.Key)); + + set.Add(portable); + foreach (var range in pair.Value) + { + set.Add(range.Min); + set.Add(range.Max); + } + } + + // subset and superset + var superSetFrameworks = _subSetFrameworks + .SelectMany(p => p.Value.Select(subset => new { Superset = p.Key, Subset = subset })) + .GroupBy(p => p.Subset, p => p.Superset, StringComparer.OrdinalIgnoreCase) + .ToDictionary(g => g.Key, g => new HashSet(g, StringComparer.OrdinalIgnoreCase)); + + foreach (var framework in set.ToArray()) + { + if (framework.HasProfile) + { + continue; + } + + if (_subSetFrameworks.TryGetValue(framework.Framework, out HashSet? subset)) + { + foreach (var subFramework in subset) + { + set.Add(new NuGetFramework(subFramework, framework.Version, framework.Profile)); + } + } + + if (superSetFrameworks.TryGetValue(framework.Framework, out HashSet? superset)) + { + foreach (var superFramework in superset) + { + set.Add(new NuGetFramework(superFramework, framework.Version, framework.Profile)); + } + } + } + + _compatibleCandidates.AddRange(set); + _compatibleCandidates.Sort(NuGetFrameworkSorter.Instance); + } + + // Strong typed non-IEnumerator based HashSet functions + private static bool SetEquals(HashSet left, HashSet right) + { + if (left.Count != right.Count) + { + return false; + } + + foreach (var fw in left) + { + if (!right.Contains(fw)) + { + return false; + } + } + + return true; + } + + private static void UnionWith(HashSet toAccumulate, HashSet toAdd) + { + foreach (var fw in toAdd) + { + toAccumulate.Add(fw); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkPrecedenceSorter.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkPrecedenceSorter.cs new file mode 100644 index 0000000000..7c72d24b07 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkPrecedenceSorter.cs @@ -0,0 +1,28 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + /// + /// Sorts frameworks according to the framework mappings + /// + internal class FrameworkPrecedenceSorter : IComparer + { + private readonly IFrameworkNameProvider _mappings; + private readonly bool _allEquivalent; + + public FrameworkPrecedenceSorter(IFrameworkNameProvider mappings, bool allEquivalent) + { + _mappings = mappings ?? throw new ArgumentNullException(nameof(mappings)); + _allEquivalent = allEquivalent; + } + + public int Compare(NuGetFramework? x, NuGetFramework? y) + { + return _allEquivalent ? _mappings.CompareEquivalentFrameworks(x, y) : _mappings.CompareFrameworks(x, y); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkRange.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkRange.cs new file mode 100644 index 0000000000..1ef6c58d6b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkRange.cs @@ -0,0 +1,119 @@ +// 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.Globalization; + +namespace NuGetClone.Frameworks +{ + /// + /// An inclusive range of frameworks + /// + internal class FrameworkRange : IEquatable + { + private readonly bool _includeMin; + private readonly bool _includeMax; + + public FrameworkRange(NuGetFramework min, NuGetFramework max) + : this(min, max, true, true) + { + + } + + public FrameworkRange(NuGetFramework min, NuGetFramework max, bool includeMin, bool includeMax) + { + if (min == null) throw new ArgumentNullException(nameof(min)); + if (max == null) throw new ArgumentNullException(nameof(max)); + + if (!SameExceptForVersion(min, max)) + { + throw new FrameworkException(Strings.FrameworkMismatch); + } + + Min = min; + Max = max; + _includeMin = includeMin; + _includeMax = includeMax; + } + + /// + /// Minimum Framework + /// + public NuGetFramework Min { get; } + + /// + /// Maximum Framework + /// + public NuGetFramework Max { get; } + + /// + /// Minimum version inclusiveness. + /// + public bool IncludeMin + { + get + { + return _includeMin; + } + } + + /// + /// Maximum version inclusiveness. + /// + public bool IncludeMax + { + get + { + return _includeMax; + } + } + + /// + /// Framework Identifier of both the Min and Max + /// + public string FrameworkIdentifier => Min.Framework; + + /// + /// True if the framework version falls between the min and max + /// + public bool Satisfies(NuGetFramework framework) + { + return SameExceptForVersion(Min, framework) + && (_includeMin ? Min.Version <= framework.Version : Min.Version < framework.Version) + && (_includeMax ? Max.Version >= framework.Version : Max.Version > framework.Version); + } + + private static bool SameExceptForVersion(NuGetFramework x, NuGetFramework y) + { + return StringComparer.OrdinalIgnoreCase.Equals(x.Framework, y.Framework) + && (StringComparer.OrdinalIgnoreCase.Equals(x.Profile, y.Profile)); + } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "[{0}, {1}]", Min.ToString(), Max.ToString()); + } + + public bool Equals(FrameworkRange? other) + { + return FrameworkRangeComparer.Instance.Equals(this, other); + } + + public override bool Equals(object? obj) + { + var other = obj as FrameworkRange; + + if (other != null) + { + return Equals(other); + } + + return false; + } + + public override int GetHashCode() + { + return FrameworkRangeComparer.Instance.GetHashCode(this); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkRangeComparer.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkRangeComparer.cs new file mode 100644 index 0000000000..a615279fc0 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkRangeComparer.cs @@ -0,0 +1,57 @@ +// 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 NuGetClone.Shared; + +namespace NuGetClone.Frameworks +{ + internal class FrameworkRangeComparer : IEqualityComparer + { +#pragma warning disable CS0618 // Type or member is obsolete + public static FrameworkRangeComparer Instance { get; } = new(); +#pragma warning restore CS0618 // Type or member is obsolete + + [Obsolete("Use singleton FrameworkRangeComparer.Instance instead")] + public FrameworkRangeComparer() + { + } + + public bool Equals(FrameworkRange? x, FrameworkRange? y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (ReferenceEquals(x, null) + || ReferenceEquals(y, null)) + { + return false; + } + + return StringComparer.OrdinalIgnoreCase.Equals(x.FrameworkIdentifier, y.FrameworkIdentifier) && + NuGetFramework.Comparer.Equals(x.Min, y.Min) && NuGetFramework.Comparer.Equals(x.Max, y.Max) + && x.IncludeMin == y.IncludeMin && x.IncludeMax == y.IncludeMax; + } + + public int GetHashCode(FrameworkRange obj) + { + if (ReferenceEquals(obj, null)) + { + return 0; + } + + var combiner = new HashCodeCombiner(); + + combiner.AddStringIgnoreCase(obj.FrameworkIdentifier); + combiner.AddObject(obj.Min); + combiner.AddObject(obj.Max); + combiner.AddObject(obj.IncludeMin); + combiner.AddObject(obj.IncludeMax); + + return combiner.CombinedHash; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkReducer.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkReducer.cs new file mode 100644 index 0000000000..097ee2f7ac --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkReducer.cs @@ -0,0 +1,561 @@ +// 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 System.Diagnostics; +using System.Linq; + +namespace NuGetClone.Frameworks +{ + /// + /// Reduces a list of frameworks into the smallest set of frameworks required. + /// + internal class FrameworkReducer + { + private readonly IFrameworkNameProvider _mappings; + private readonly IFrameworkCompatibilityProvider _compat; + + /// + /// Creates a FrameworkReducer using the default framework mappings. + /// + public FrameworkReducer() + : this(DefaultFrameworkNameProvider.Instance, DefaultCompatibilityProvider.Instance) + { + } + + /// + /// Creates a FrameworkReducer using custom framework mappings. + /// + public FrameworkReducer(IFrameworkNameProvider mappings, IFrameworkCompatibilityProvider compat) + { + _mappings = mappings ?? throw new ArgumentNullException(nameof(mappings)); + _compat = compat ?? throw new ArgumentNullException(nameof(compat)); + } + + /// + /// Returns the nearest matching framework that is compatible. + /// + /// Project target framework + /// Possible frameworks to narrow down + /// Nearest compatible framework. If no frameworks are compatible null is returned. + public NuGetFramework? GetNearest(NuGetFramework framework, IEnumerable possibleFrameworks) + { + if (framework == null) throw new ArgumentNullException(nameof(framework)); + if (possibleFrameworks == null) throw new ArgumentNullException(nameof(possibleFrameworks)); + + var nearest = GetNearestInternal(framework, possibleFrameworks); + + var fallbackFramework = framework as FallbackFramework; + + if (fallbackFramework != null) + { + if (nearest == null || nearest.IsAny) + { + foreach (var supportFramework in fallbackFramework.Fallback) + { + nearest = GetNearestInternal(supportFramework, possibleFrameworks); + if (nearest != null) + { + break; + } + } + } + } + + return nearest; + } + + private NuGetFramework? GetNearestInternal(NuGetFramework framework, IEnumerable possibleFrameworks) + { + NuGetFramework? nearest = null; + + // Unsupported frameworks always lose, throw them out unless it's all we were given + if (possibleFrameworks.Any(e => e != NuGetFramework.UnsupportedFramework)) + { + possibleFrameworks = possibleFrameworks.Where(e => e != NuGetFramework.UnsupportedFramework); + } + + // Try exact matches first + nearest = possibleFrameworks.Where(f => NuGetFrameworkFullComparer.Instance.Equals(framework, f)).FirstOrDefault(); + + if (nearest == null) + { + // Elimate non-compatible frameworks + var compatible = possibleFrameworks.Where(f => _compat.IsCompatible(framework, f)); + + // Remove lower versions of compatible frameworks + var reduced = ReduceUpwards(compatible); + + bool isNet6Era = framework.IsNet5Era && framework.Version.Major >= 6; + + // Reduce to the same framework name if possible, with an exception for Xamarin, MonoAndroid and Tizen when net6.0+ + if (reduced.Count() > 1 && reduced.Any(f => NuGetFrameworkNameComparer.Instance.Equals(f, framework))) + { + reduced = reduced.Where(f => + { + if (isNet6Era && framework.HasPlatform && ( + f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase) + || f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase) + )) + { + return true; + } + else + { + return NuGetFrameworkNameComparer.Instance.Equals(f, framework); + } + }); + } + + // PCL reduce + if (reduced.Count() > 1) + { + // if we have a pcl and non-pcl mix, throw out the pcls + if (reduced.Any(f => f.IsPCL) + && reduced.Any(f => !f.IsPCL)) + { + reduced = reduced.Where(f => !f.IsPCL); + } + else if (reduced.All(f => f.IsPCL)) + { + // decide between PCLs + if (framework.IsPCL) + { + reduced = GetNearestPCLtoPCL(framework, reduced); + } + else + { + reduced = GetNearestNonPCLtoPCL(framework, reduced); + } + + if (reduced.Count() > 1) + { + // For scenarios where we are unable to decide between PCLs, choose the PCL with the + // least frameworks. Less frameworks means less compatibility which means it is nearer to the target. + reduced = new NuGetFramework[] { GetBestPCL(reduced)! }; + } + } + } + + // Packages based framework reduce, only if the project is not packages based + if (reduced.Count() > 1 + && !framework.IsPackageBased + && reduced.Any(f => f.IsPackageBased) + && reduced.Any(f => !f.IsPackageBased)) + { + // If we have a packages based and non-packages based mix, throw out the packages based frameworks. + // This situation is unlikely but it could happen with framework mappings that do not provide + // a relationship between the frameworks and the compatible packages based frameworks. + // Ex: net46, dotnet -> net46 + reduced = reduced.Where(f => !f.IsPackageBased); + } + + // Profile reduce + if (reduced.Count() > 1 + && !reduced.Any(f => f.IsPCL)) + { + // Prefer the same framework and profile + if (framework.HasProfile) + { + var sameProfile = reduced.Where(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f) + && StringComparer.OrdinalIgnoreCase.Equals(framework.Profile, f.Profile)); + + if (sameProfile.Any()) + { + reduced = sameProfile; + } + } + + // Prefer frameworks without profiles + if (reduced.Count() > 1 + && reduced.Any(f => f.HasProfile) + && reduced.Any(f => !f.HasProfile)) + { + reduced = reduced.Where(f => !f.HasProfile); + } + } + + // Platforms reduce + if (reduced.Count() > 1 + && framework.HasPlatform) + { + if (!isNet6Era || reduced.Any(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f) && f.Version.Major >= 6)) + { + // Prefer the highest framework version, likely to be the non-platform specific option. + reduced = reduced.Where(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f)).GroupBy(f => f.Version).OrderByDescending(f => f.Key).First(); + } + else if (isNet6Era && reduced.Any(f => + { + return f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase) + || f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase); + })) + { + // We have a special case for *some* Xamarin-related frameworks here. For specific precedence rules, please see: + // https://github.com/dotnet/designs/blob/main/accepted/2021/net6.0-tfms/net6.0-tfms.md#compatibility-rules + reduced = reduced.GroupBy(f => f.Framework).OrderByDescending(f => f.Key).First(f => + { + NuGetFramework first = f.First(); + return first.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase) + || first.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase); + }); + } + } + + // if we have reduced down to a single framework, use that + if (reduced.Count() == 1) + { + nearest = reduced.Single(); + } + + // this should be a very rare occurrence + // at this point we are unable to decide between the remaining frameworks in any useful way + // just take the first one by rev alphabetical order if we can't narrow it down at all + if (nearest == null && reduced.Any()) + { + // Sort by precedence rules, then by name in the case of a tie + nearest = reduced + .OrderBy(f => f, new FrameworkPrecedenceSorter(_mappings, false)) + .ThenByDescending(f => f, NuGetFrameworkSorter.Instance) + .ThenBy(f => f.GetHashCode()) + .First(); + } + } + + return nearest; + } + + /// + /// Remove duplicates found in the equivalence mappings. + /// + public IEnumerable ReduceEquivalent(IEnumerable frameworks) + { + if (frameworks == null) throw new ArgumentNullException(nameof(frameworks)); + + // order first so we get consistent results for equivalent frameworks + var input = frameworks + .OrderBy(f => f, new FrameworkPrecedenceSorter(_mappings, true)) + .ThenByDescending(f => f, NuGetFrameworkSorter.Instance) + .ToArray(); + + var duplicates = new HashSet(); + foreach (var framework in input) + { + if (duplicates.Contains(framework)) + { + continue; + } + + yield return framework; + + duplicates.Add(framework); + + if (_mappings.TryGetEquivalentFrameworks(framework, out IEnumerable? eqFrameworks)) + { + foreach (var eqFramework in eqFrameworks) + { + duplicates.Add(eqFramework); + } + } + } + } + + /// + /// Reduce to the highest framework + /// Ex: net45, net403, net40 -> net45 + /// + public IEnumerable ReduceUpwards(IEnumerable frameworks) + { + if (frameworks is null) throw new ArgumentNullException(nameof(frameworks)); + + // NuGetFramework.AnyFramework is a special case + if (frameworks.Any(e => e != NuGetFramework.AnyFramework)) + { + // Remove all instances of Any unless it is the only one in the list + frameworks = frameworks.Where(e => e != NuGetFramework.AnyFramework); + } + + // x: net40 j: net45 -> remove net40 + // x: wp8 j: win8 -> keep wp8 + return ReduceCore(frameworks, (x, y) => _compat.IsCompatible(y, x)).ToArray(); + } + + /// + /// Reduce to the lowest framework + /// Ex: net45, net403, net40 -> net40 + /// + public IEnumerable ReduceDownwards(IEnumerable frameworks) + { + if (frameworks is null) throw new ArgumentNullException(nameof(frameworks)); + + // NuGetFramework.AnyFramework is a special case + if (frameworks.Any(e => e == NuGetFramework.AnyFramework)) + { + // Any is always the lowest + return new[] { NuGetFramework.AnyFramework }; + } + + return ReduceCore(frameworks, (x, y) => _compat.IsCompatible(x, y)).ToArray(); + } + + private IEnumerable ReduceCore(IEnumerable frameworks, Func isCompat) + { + // remove duplicate frameworks + var input = frameworks.Distinct(NuGetFrameworkFullComparer.Instance).ToArray(); + + var results = new List(input.Length); + + for (var i = 0; i < input.Length; i++) + { + var dupe = false; + + var x = input[i]; + + for (var j = 0; !dupe && j < input.Length; j++) + { + if (j != i) + { + var y = input[j]; + + // remove frameworks that are compatible with other frameworks in the list + // do not remove frameworks which tie with others, for example: net40 and net40-client + // these equivalent frameworks should both be returned to let the caller decide between them + if (isCompat(x, y)) + { + var revCompat = isCompat(y, x); + + dupe = !revCompat; + + // for scenarios where the framework identifiers are the same dupe the zero version + // Ex: win, win8 - these are equivalent, but only one is needed + if (revCompat && NuGetFrameworkNameComparer.Instance.Equals(x, y)) + { + // Throw out the zero version + // Profile, Platform, and all other aspects should have been covered by the compat check already + dupe = x.Version == FrameworkConstants.EmptyVersion && y.Version != FrameworkConstants.EmptyVersion; + } + } + } + } + + if (!dupe) + { + results.Add(input[i]); + } + } + + // sort the results just to make this more deterministic for the callers + return results.OrderBy(f => f.Framework, StringComparer.OrdinalIgnoreCase).ThenBy(f => f.ToString()); + } + + private IEnumerable GetNearestNonPCLtoPCL(NuGetFramework framework, IEnumerable reduced) + { + // If framework is not a PCL, find the PCL with the sub framework nearest to framework + // Collect all frameworks from all PCLs we are considering + var pclToFrameworks = ExplodePortableFrameworks(reduced); + var allPclFrameworks = pclToFrameworks.Values.SelectMany(f => f); + + // Find the nearest (no PCLs are involved at this point) + Debug.Assert(allPclFrameworks.All(f => !f.IsPCL), "a PCL returned a PCL as its profile framework"); + var nearestProfileFramework = GetNearest(framework, allPclFrameworks); + + // Reduce to only PCLs that include the nearest match + reduced = pclToFrameworks.Where(pair => + pair.Value.Contains(nearestProfileFramework)) + .Select(pair => pair.Key); + + return reduced; + } + + private IEnumerable GetNearestPCLtoPCL(NuGetFramework framework, IEnumerable reduced) + { + // Compare each framework in the target framework individually + // against the list of possible PCLs. This effectively lets + // each sub-framework vote on which PCL is nearest. + var subFrameworks = ExplodePortableFramework(framework); + + // reduce the sub frameworks - this would only have an effect if the PCL is + // poorly formed and contains duplicates such as portable-win8+win81 + subFrameworks = ReduceEquivalent(subFrameworks); + + // Find all frameworks in all PCLs + var pclToFrameworks = ExplodePortableFrameworks(reduced); + var allPclFrameworks = pclToFrameworks.Values.SelectMany(f => f).Distinct(NuGetFrameworkFullComparer.Instance); + + var scores = new Dictionary(); + + // find the nearest PCL for each framework + foreach (var sub in subFrameworks) + { + Debug.Assert(!sub.IsPCL, "a PCL returned a PCL as its profile framework"); + + // from all possible frameworks, find the best match + var nearestForSub = GetNearest(sub, allPclFrameworks); + + if (nearestForSub != null) + { + // +1 each framework containing the best match + foreach (KeyValuePair> pair in pclToFrameworks) + { + if (pair.Value.Contains(nearestForSub, NuGetFrameworkFullComparer.Instance)) + { + if (!scores.ContainsKey(pair.Key)) + { + scores.Add(pair.Key, 1); + } + else + { + scores[pair.Key]++; + } + } + } + } + } + + // take the highest vote count, this will be at least one + reduced = scores.GroupBy(pair => pair.Value).OrderByDescending(g => g.Key).First().Select(e => e.Key); + + return reduced; + } + + /// + /// Create lookup of the given PCLs to their actual frameworks + /// + private Dictionary> ExplodePortableFrameworks(IEnumerable pcls) + { + var result = new Dictionary>(); + + foreach (var pcl in pcls) + { + var frameworks = ExplodePortableFramework(pcl); + result.Add(pcl, frameworks); + } + + return result; + } + + /// + /// portable-net45+win8 -> net45, win8 + /// + private IEnumerable ExplodePortableFramework(NuGetFramework pcl, bool includeOptional = true) + { + if (!_mappings.TryGetPortableFrameworks(pcl.Profile, includeOptional, out IEnumerable? frameworks)) + { + Debug.Fail("Unable to get portable frameworks from: " + pcl.ToString()); + frameworks = Enumerable.Empty(); + } + + return frameworks; + } + + /// + /// Order PCLs when there is no other way to decide. + /// + private NuGetFramework? GetBestPCL(IEnumerable reduced) + { + NuGetFramework? current = null; + + foreach (var considering in reduced) + { + if (current == null + || IsBetterPCL(current, considering)) + { + current = considering; + } + } + + return current; + } + + /// + /// Sort PCLs using these criteria + /// 1. Lowest number of frameworks (highest surface area) wins first + /// 2. Profile with the highest version numbers wins next + /// 3. String compare is used as a last resort + /// + private bool IsBetterPCL(NuGetFramework current, NuGetFramework considering) + { + Debug.Assert(considering.IsPCL && current.IsPCL, "This method should be used only to compare PCLs"); + + // Find all frameworks in the profile + var consideringFrameworks = ExplodePortableFramework(considering, false); + + var currentFrameworks = ExplodePortableFramework(current, false); + + // The PCL with the least frameworks (highest surface area) goes first + if (consideringFrameworks.Count() < currentFrameworks.Count()) + { + return true; + } + else if (currentFrameworks.Count() < consideringFrameworks.Count()) + { + return false; + } + + // If both frameworks have the same number of frameworks take the framework that has the highest + // overall set of framework versions + + // Find Frameworks that both profiles have in common + var sharedFrameworkIds = consideringFrameworks.Select(f => f.Framework) + .Where(f => + currentFrameworks.Any(consideringFramework => StringComparer.OrdinalIgnoreCase.Equals(f, consideringFramework.Framework))); + + var consideringHighest = 0; + var currentHighest = 0; + + // Determine which framework has the highest version of each shared framework + foreach (var sharedId in sharedFrameworkIds) + { + var consideringFramework = consideringFrameworks.First(f => StringComparer.OrdinalIgnoreCase.Equals(f.Framework, sharedId)); + var currentFramework = currentFrameworks.First(f => StringComparer.OrdinalIgnoreCase.Equals(f.Framework, sharedId)); + + if (consideringFramework.Version < currentFramework.Version) + { + currentHighest++; + } + else if (currentFramework.Version < consideringFramework.Version) + { + consideringHighest++; + } + } + + // Prefer the highest count + if (currentHighest < consideringHighest) + { + return true; + } + else if (consideringHighest < currentHighest) + { + return false; + } + + // Take the highest version of .NET if no winner could be determined, this is usually a good indicator of which is newer + var consideringNet = consideringFrameworks.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Framework, FrameworkConstants.FrameworkIdentifiers.Net)); + var currentNet = currentFrameworks.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Framework, FrameworkConstants.FrameworkIdentifiers.Net)); + + // Compare using .NET only if both frameworks have it. PCLs should always have .NET, but since users can make these strings up we should + // try to handle that as best as possible. + if (consideringNet != null + && currentNet != null) + { + if (currentNet.Version < consideringNet.Version) + { + return true; + } + else if (consideringNet.Version < currentNet.Version) + { + return false; + } + } + + // In the very rare case that both frameworks are still equal, we have to pick one. + // There is nothing but we need to be deterministic, so compare the profiles as strings. + if (StringComparer.OrdinalIgnoreCase.Compare(considering.GetShortFolderName(_mappings), current.GetShortFolderName(_mappings)) < 0) + { + return true; + } + + return false; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkSpecificMapping.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkSpecificMapping.cs new file mode 100644 index 0000000000..5b8ebdcb39 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/FrameworkSpecificMapping.cs @@ -0,0 +1,29 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + /// + /// A keyvalue pair specific to a framework identifier + /// + internal class FrameworkSpecificMapping + { + public FrameworkSpecificMapping(string frameworkIdentifier, string key, string value) + : this(frameworkIdentifier, new KeyValuePair(key, value)) + { + } + + public FrameworkSpecificMapping(string frameworkIdentifier, KeyValuePair mapping) + { + FrameworkIdentifier = frameworkIdentifier ?? throw new ArgumentNullException(nameof(frameworkIdentifier)); + Mapping = mapping; + } + + public string FrameworkIdentifier { get; } + + public KeyValuePair Mapping { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/HashCodeCombiner.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/HashCodeCombiner.cs new file mode 100644 index 0000000000..c762592f70 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/HashCodeCombiner.cs @@ -0,0 +1,220 @@ +// 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 enable + +using System; +using System.Collections.Generic; + +namespace NuGetClone.Shared +{ + /// + /// Hash code creator, based on the original NuGet hash code combiner/ASP hash code combiner implementations + /// + internal ref struct HashCodeCombiner + { + // seed from String.GetHashCode() + private const long Seed = 0x1505L; + + private long _combinedHash = Seed; + + public HashCodeCombiner() + { + } + + internal int CombinedHash => _combinedHash.GetHashCode(); + + private void AddHashCode(int i) + { + _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + } + + internal void AddObject(int i) + { + AddHashCode(i); + } + + internal void AddObject(bool b) + { + AddHashCode(b ? 1 : 0); + } + + internal void AddObject(T? o, IEqualityComparer comparer) + where T : class + { + if (o != null) + { + AddHashCode(comparer.GetHashCode(o)); + } + } + + internal void AddObject(T? o) + where T : class + { + if (o != null) + { + AddHashCode(o.GetHashCode()); + } + } + + // Optimization: for value types, we can avoid boxing "o" by skipping the null check + internal void AddStruct(T? o) + where T : struct + { + if (o.HasValue) + { + AddHashCode(o.GetHashCode()); + } + } + + // Optimization: for value types, we can avoid boxing "o" by skipping the null check + internal void AddStruct(T o) + where T : struct + { + AddHashCode(o.GetHashCode()); + } + + internal void AddStringIgnoreCase(string? s) + { + if (s != null) + { + AddHashCode(StringComparer.OrdinalIgnoreCase.GetHashCode(s)); + } + } + + internal void AddSequence(IEnumerable? sequence) where T : notnull + { + if (sequence != null) + { + foreach (var item in sequence.NoAllocEnumerate()) + { + AddHashCode(item.GetHashCode()); + } + } + } + + internal void AddSequence(T[]? array) where T : notnull + { + if (array != null) + { + foreach (var item in array) + { + AddHashCode(item.GetHashCode()); + } + } + } + + internal void AddSequence(IList? list) where T : notnull + { + if (list != null) + { + foreach (var item in list.NoAllocEnumerate()) + { + AddHashCode(item.GetHashCode()); + } + } + } + + internal void AddSequence(IReadOnlyList? list) where T : notnull + { + if (list != null) + { + var count = list.Count; + for (var i = 0; i < count; i++) + { + AddHashCode(list[i].GetHashCode()); + } + } + } + + internal void AddUnorderedSequence(IEnumerable? list) where T : notnull + { + if (list != null) + { + int count = 0; + int hashCode = 0; + foreach (var item in list.NoAllocEnumerate()) + { + // XOR is commutative -- the order of operations doesn't matter + hashCode ^= item.GetHashCode(); + count++; + } + AddHashCode(hashCode); + AddHashCode(count); + } + } + + internal void AddUnorderedSequence(IEnumerable? list, IEqualityComparer comparer) where T : notnull + { + if (list != null) + { + int count = 0; + int hashCode = 0; + foreach (var item in list.NoAllocEnumerate()) + { + // XOR is commutative -- the order of operations doesn't matter + hashCode ^= comparer.GetHashCode(item); + count++; + } + AddHashCode(hashCode); + AddHashCode(count); + } + } + + internal void AddDictionary(IEnumerable>? dictionary) + where TKey : notnull + where TValue : notnull + { + if (dictionary != null) + { + int count = 0; + int dictionaryHash = 0; + + foreach (var pair in dictionary.NoAllocEnumerate()) + { + int keyHash = pair.Key.GetHashCode(); + int valHash = pair.Value.GetHashCode(); + int pairHash = ((keyHash << 5) + keyHash) ^ valHash; + + // XOR is commutative -- the order of operations doesn't matter + dictionaryHash ^= pairHash; + count++; + } + + AddHashCode(dictionaryHash + count); + } + } + + /// + /// Create a unique hash code for the given set of items + /// + internal static int GetHashCode(T1 o1, T2 o2) + where T1 : notnull + where T2 : notnull + { + var combiner = new HashCodeCombiner(); + + combiner.AddHashCode(o1.GetHashCode()); + combiner.AddHashCode(o2.GetHashCode()); + + return combiner.CombinedHash; + } + + /// + /// Create a unique hash code for the given set of items + /// + internal static int GetHashCode(T1 o1, T2 o2, T3 o3) + where T1 : notnull + where T2 : notnull + where T3 : notnull + { + var combiner = new HashCodeCombiner(); + + combiner.AddHashCode(o1.GetHashCode()); + combiner.AddHashCode(o2.GetHashCode()); + combiner.AddHashCode(o3.GetHashCode()); + + return combiner.CombinedHash; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkCompatibilityListProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkCompatibilityListProvider.cs new file mode 100644 index 0000000000..5ba144c766 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkCompatibilityListProvider.cs @@ -0,0 +1,17 @@ +// 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.Collections.Generic; + +namespace NuGetClone.Frameworks +{ + internal interface IFrameworkCompatibilityListProvider + { + /// + /// Get a list of frameworks supporting the provided framework. This list + /// is not meant to be exhaustive but is instead meant to be human-readable. + /// Ex: netstandard1.5 -> netstandardapp1.5, net462, dnxcore50, ... + /// + IEnumerable GetFrameworksSupporting(NuGetFramework target); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkCompatibilityProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkCompatibilityProvider.cs new file mode 100644 index 0000000000..b0df62e651 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkCompatibilityProvider.cs @@ -0,0 +1,17 @@ +// 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. + +namespace NuGetClone.Frameworks +{ + internal interface IFrameworkCompatibilityProvider + { + /// + /// Ex: IsCompatible(net45, net40) -> true + /// Ex: IsCompatible(net40, net45) -> false + /// + /// Project target framework + /// Library framework that is going to be installed + /// True if framework supports other + bool IsCompatible(NuGetFramework framework, NuGetFramework other); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkMappings.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkMappings.cs new file mode 100644 index 0000000000..b3989a7288 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkMappings.cs @@ -0,0 +1,89 @@ +// 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.Collections.Generic; + +namespace NuGetClone.Frameworks +{ + /// + /// A raw list of framework mappings. These are indexed by the framework name provider and in most cases all + /// mappings are + /// mirrored so that the IFrameworkMappings implementation only needs to provide the minimum amount of + /// mappings. + /// + internal interface IFrameworkMappings + { + /// + /// Synonym ‒> Identifier + /// Ex: NET Framework ‒> .NET Framework + /// + IEnumerable> IdentifierSynonyms { get; } + + /// + /// Ex: .NET Framework ‒> net + /// + IEnumerable> IdentifierShortNames { get; } + + /// + /// Ex: WindowsPhone ‒> wp + /// + IEnumerable ProfileShortNames { get; } + + /// + /// Equal frameworks. Used for legacy conversions. + /// ex: Framework: Win8 <‒> Framework: NetCore45 Platform: Win8 + /// + IEnumerable> EquivalentFrameworks { get; } + + /// + /// Framework, EquivalentProfile1, EquivalentProfile2 + /// Ex: Silverlight, WindowsPhone71, WindowsPhone + /// + IEnumerable EquivalentProfiles { get; } + + /// + /// Frameworks which are subsets of others. + /// Ex: .NETCore ‒> .NET + /// Everything in .NETCore maps to .NET and is one way compatible. Version numbers follow the same format. + /// + IEnumerable> SubSetFrameworks { get; } + + /// + /// Additional framework compatibility rules beyond name and version matching. + /// Ex: .NETFramework supports ‒> Native + /// + IEnumerable CompatibilityMappings { get; } + + /// + /// Ordered list of framework identifiers. The first framework in the list will be preferred over other + /// framework identifiers. This is enable better tie breaking in scenarios where legacy frameworks are + /// equivalently compatible to a new framework. + /// Example: UAP10.0 ‒> win81, wpa81 + /// + IEnumerable NonPackageBasedFrameworkPrecedence { get; } + + /// + /// Same as but is only referred to if all of the packages + /// in consideration are package based (determined by ). + /// + IEnumerable PackageBasedFrameworkPrecedence { get; } + + /// + /// Only used to choose between frameworks that are equivalent. This favors more human-readable target + /// frameworks identifiers. + /// + IEnumerable EquivalentFrameworkPrecedence { get; } + + /// + /// Rewrite folder short names to the given value. + /// Ex: dotnet50 ‒> dotnet + /// + IEnumerable> ShortNameReplacements { get; } + + /// + /// Rewrite full framework names to the given value. + /// Ex: .NETPlatform,Version=v0.0 ‒> .NETPlatform,Version=v5.0 + /// + IEnumerable> FullNameReplacements { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkNameProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkNameProvider.cs new file mode 100644 index 0000000000..b35a2dd6ac --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkNameProvider.cs @@ -0,0 +1,157 @@ +// 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 System.Diagnostics.CodeAnalysis; + +namespace NuGetClone.Frameworks +{ + internal interface IFrameworkNameProvider + { + /// + /// Returns the official framework identifier for an alias or short name. + /// + bool TryGetIdentifier(string identifierShortName, [NotNullWhen(true)] out string? identifier); + + /// + /// Gives the short name used for folders in NuGet + /// + bool TryGetShortIdentifier(string identifier, [NotNullWhen(true)] out string? identifierShortName); + + /// + /// Get the official profile name from the short name. + /// + bool TryGetProfile(string frameworkIdentifier, string profileShortName, [NotNullWhen(true)] out string? profile); + + /// + /// Returns the shortened version of the profile name. + /// + bool TryGetShortProfile(string frameworkIdentifier, string profile, [NotNullWhen(true)] out string? profileShortName); + + /// + /// Parses a version string using single digit rules if no dots exist + /// + bool TryGetVersion(string versionString, [NotNullWhen(true)] out Version? version); + + /// + /// Parses a version string. If no dots exist, all digits are treated + /// as semver-major, instead of inserting dots. + /// + bool TryGetPlatformVersion(string versionString, [NotNullWhen(true)] out Version? version); + + /// + /// Returns a shortened version. If all digits are single digits no dots will be used. + /// + string GetVersionString(string framework, Version version); + + /// + /// Tries to parse the portable profile number out of a profile. + /// + bool TryGetPortableProfileNumber(string profile, out int profileNumber); + + /// + /// Looks up the portable profile number based on the framework list. + /// + bool TryGetPortableProfile(IEnumerable supportedFrameworks, out int profileNumber); + + /// + /// Returns the frameworks based on a portable profile number. + /// + bool TryGetPortableFrameworks(int profile, [NotNullWhen(true)] out IEnumerable? frameworks); + + /// + /// Returns the frameworks based on a portable profile number. + /// + bool TryGetPortableFrameworks(int profile, bool includeOptional, [NotNullWhen(true)] out IEnumerable? frameworks); + + /// + /// Returns the frameworks based on a profile string. + /// Profile can be either the number in format: Profile=7, or the shortened NuGet version: net45+win8 + /// + bool TryGetPortableFrameworks(string profile, bool includeOptional, [NotNullWhen(true)] out IEnumerable? frameworks); + + /// + /// Parses a shortened portable framework profile list. + /// Ex: net45+win8 + /// + bool TryGetPortableFrameworks(string shortPortableProfiles, [NotNullWhen(true)] out IEnumerable? frameworks); + + /// + /// Returns ranges of frameworks that are known to be supported by the given portable profile number. + /// Ex: Profile7 -> netstandard1.1 + /// + bool TryGetPortableCompatibilityMappings(int profile, [NotNullWhen(true)] out IEnumerable? supportedFrameworkRanges); + + /// + /// Returns a list of all possible substitutions where the framework name + /// have equivalents. + /// Ex: sl3 -> wp8 + /// + bool TryGetEquivalentFrameworks(NuGetFramework framework, [NotNullWhen(true)] out IEnumerable? frameworks); + + /// + /// Gives all substitutions for a framework range. + /// + bool TryGetEquivalentFrameworks(FrameworkRange range, [NotNullWhen(true)] out IEnumerable? frameworks); + + /// + /// Returns ranges of frameworks that are known to be supported by the given framework. + /// Ex: net45 -> native + /// + bool TryGetCompatibilityMappings(NuGetFramework framework, [NotNullWhen(true)] out IEnumerable? supportedFrameworkRanges); + + /// + /// Returns all sub sets of the given framework. + /// Ex: .NETFramework -> .NETCore + /// These will have the same version, but a different framework + /// + bool TryGetSubSetFrameworks(string frameworkIdentifier, [NotNullWhen(true)] out IEnumerable? subSetFrameworkIdentifiers); + + /// + /// The ascending order of frameworks should be based on the following ordered groups: + /// + /// 1. Non-package-based frameworks in . + /// 2. Other non-package-based frameworks. + /// 3. Package-based frameworks in . + /// 4. Other package-based frameworks. + /// + /// For group #1 and #3, the order within the group is based on the order of the respective precedence list. + /// For group #2 and #4, the order is the original order in the incoming list. This should later be made + /// consistent between different input orderings by using the . + /// + /// netcore50 is a special case since netcore451 is not packages based, but netcore50 is. + /// This sort will treat all versions of netcore as non-packages based. + int CompareFrameworks(NuGetFramework? x, NuGetFramework? y); + + /// + /// Used to pick between two equivalent frameworks. This is meant to favor the more human-readable + /// framework. Note that this comparison does not validate that the provided frameworks are indeed + /// equivalent (e.g. with + /// ). + /// + int CompareEquivalentFrameworks(NuGetFramework? x, NuGetFramework? y); + + /// + /// Returns folder short names rewrites. + /// Ex: dotnet50 -> dotnet + /// + NuGetFramework GetShortNameReplacement(NuGetFramework framework); + + /// + /// Returns full name rewrites. + /// Ex: .NETPlatform,Version=v0.0 -> .NETPlatform,Version=v5.0 + /// + NuGetFramework GetFullNameReplacement(NuGetFramework framework); + + /// + /// Returns all versions of .NETStandard in ascending order. + /// + IEnumerable GetNetStandardVersions(); + + /// + /// Returns a list of frameworks that could be compatible with .NETStandard. + /// + IEnumerable GetCompatibleCandidates(); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkSpecific.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkSpecific.cs new file mode 100644 index 0000000000..7f6f4fe876 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IFrameworkSpecific.cs @@ -0,0 +1,16 @@ +// 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. + +namespace NuGetClone.Frameworks +{ + /// + /// A group or object that is specific to a single target framework + /// + internal interface IFrameworkSpecific + { + /// + /// Target framework + /// + NuGetFramework TargetFramework { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IPortableFrameworkMappings.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IPortableFrameworkMappings.cs new file mode 100644 index 0000000000..4b027fef01 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/IPortableFrameworkMappings.cs @@ -0,0 +1,27 @@ +// 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.Collections.Generic; + +namespace NuGetClone.Frameworks +{ + internal interface IPortableFrameworkMappings + { + /// + /// Ex: 5 -> net4, win8 + /// + IEnumerable> ProfileFrameworks { get; } + + /// + /// Additional optional frameworks supported in a portable profile. + /// Ex: 5 -> MonoAndroid1+MonoTouch1 + /// + IEnumerable> ProfileOptionalFrameworks { get; } + + /// + /// Compatibility mapping for portable profiles. This is a separate compatibility from that in + /// . + /// + IEnumerable> CompatibilityMappings { get; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NoAllocEnumerateExtensions.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NoAllocEnumerateExtensions.cs new file mode 100644 index 0000000000..d726723f99 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NoAllocEnumerateExtensions.cs @@ -0,0 +1,404 @@ +// 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 enable + +using System; +using System.Collections.Generic; + +namespace NuGetClone; + +internal static class NoAllocEnumerateExtensions +{ + #region IList + + /// + /// Avoids allocating an enumerator when enumerating an . + /// + /// + /// + /// Returns a struct-based enumerator that avoids heap allocation during enumeration. + /// If the underlying type is then this method will delegate to , + /// otherwise the collection's items are accessed by index via 's indexer directly. + /// + /// + /// When using a struct-based enumerator, no heap allocation occurs during enumeration via . + /// This is in contrast to the interface-based enumerator which will + /// always be allocated on the heap. + /// + /// + /// + /// + /// list = ...; + /// + /// foreach (string item in list.NoAllocEnumerate()) + /// { + /// // ... + /// }]]> + /// + /// + public static OptimisticallyNonAllocatingListEnumerable NoAllocEnumerate(this IList list) + where T : notnull + { +#pragma warning disable CS0618 // Type or member is obsolete + return new(list); +#pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// Provides a struct-based enumerator for use with . + /// Do not use this type directly. Use instead. + /// + public readonly ref struct OptimisticallyNonAllocatingListEnumerable + where T : notnull + { + private readonly IList _list; + + [Obsolete("Do not construct directly. Use internal static method NoAllocEnumerateExtensions.NoAllocEnumerate instead.")] + internal OptimisticallyNonAllocatingListEnumerable(IList list) => _list = list; + + public Enumerator GetEnumerator() => new(_list); + + /// + /// A struct-based enumerator for use with . + /// Do not use this type directly. Use instead. + /// + public struct Enumerator : IDisposable + { + private enum EnumeratorKind : byte { Empty, List, IList }; + + private readonly EnumeratorKind _kind; + private List.Enumerator _listEnumerator; + private readonly IList? _iList; + private int _iListIndex; + + internal Enumerator(IList list) + { + if (list.Count == 0) + { + // The collection is empty, just return false from MoveNext. + _kind = EnumeratorKind.Empty; + } + else if (list is List concreteList) + { + _kind = EnumeratorKind.List; + _listEnumerator = concreteList.GetEnumerator(); + } + else + { + _kind = EnumeratorKind.IList; + _iList = list; + _iListIndex = -1; + } + } + + public T Current + { + get + { + return _kind switch + { + EnumeratorKind.List => _listEnumerator.Current, + EnumeratorKind.IList => _iList![_iListIndex], + _ => default! + }; + } + } + + public bool MoveNext() + { + return _kind switch + { + EnumeratorKind.List => _listEnumerator.MoveNext(), + EnumeratorKind.IList => ++_iListIndex < _iList!.Count, + _ => false + }; + } + + public void Dispose() + { + switch (_kind) + { + case EnumeratorKind.List: + _listEnumerator.Dispose(); + break; + } + } + } + } + + #endregion + + #region IEnumerable + + /// + /// Avoids allocating an enumerator when enumerating an where the concrete type + /// has a well known struct enumerator, such as for , or when index-based access is possible via + /// . + /// + /// + /// + /// Several collection types (e.g. ) provide a struct-based enumerator type + /// (e.g. ) which the compiler can use in statements. + /// When using a struct-based enumerator, no heap allocation occurs during such enumeration. + /// This is in contrast to the interface-based enumerator which will + /// always be allocated on the heap. + /// + /// + /// This method returns a custom struct enumerator that will avoid any heap allocation if + /// (which is declared via interface ) is actually of known concrete type that + /// provides its own struct enumerator. If so, it delegates to that type's enumerator without any boxing + /// or other heap allocation. + /// + /// + /// If is not of a known concrete type, the returned enumerator falls back to the + /// interface-based enumerator, which will be allocated on the heap. Benchmarking shows the overhead in + /// such cases is low enough to be within the measurement error, meaning this is an inexpensive optimization + /// that won't regress behavior and with low downside for cases where it cannot apply an optimization. + /// + /// + /// + /// + /// source = ...; + /// + /// foreach (string item in source.NoAllocEnumerate()) + /// { + /// // ... + /// }]]> + /// + /// + public static OptimisticallyNonAllocatingEnumerable NoAllocEnumerate(this IEnumerable source) + where T : notnull + { +#pragma warning disable CS0618 // Type or member is obsolete + return new(source); +#pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// Provides a struct-based enumerator for use with . + /// Do not use this type directly. Use instead. + /// + public readonly ref struct OptimisticallyNonAllocatingEnumerable + where T : notnull + { + private readonly IEnumerable _source; + + [Obsolete("Do not construct directly. Use internal static method NoAllocEnumerateExtensions.NoAllocEnumerate instead.")] + internal OptimisticallyNonAllocatingEnumerable(IEnumerable source) => _source = source; + + public Enumerator GetEnumerator() => new(_source); + + /// + /// A struct-based enumerator for use with . + /// Do not use this type directly. Use instead. + /// + public struct Enumerator : IDisposable + { + private enum EnumeratorKind : byte { Empty, List, IList, Fallback }; + + private readonly EnumeratorKind _kind; + private readonly IEnumerator? _fallbackEnumerator; + private List.Enumerator _listEnumerator; + private readonly IList? _iList; + private int _iListIndex; + + internal Enumerator(IEnumerable source) + { + if (source is ICollection { Count: 0 } or IReadOnlyCollection { Count: 0 }) + { + // The collection is empty, just return false from MoveNext. + _kind = EnumeratorKind.Empty; + } + else if (source is List list) + { + _kind = EnumeratorKind.List; + _listEnumerator = list.GetEnumerator(); + } + else if (source is IList iList) + { + _kind = EnumeratorKind.IList; + _iList = iList; + _iListIndex = -1; + } + else + { + _kind = EnumeratorKind.Fallback; + _fallbackEnumerator = source.GetEnumerator(); + } + } + + public T Current + { + get + { + return _kind switch + { + EnumeratorKind.List => _listEnumerator.Current, + EnumeratorKind.IList => _iList![_iListIndex], + EnumeratorKind.Fallback => _fallbackEnumerator!.Current, + _ => default!, + }; + } + } + + public bool MoveNext() + { + return _kind switch + { + EnumeratorKind.List => _listEnumerator.MoveNext(), + EnumeratorKind.IList => ++_iListIndex < _iList!.Count, + EnumeratorKind.Fallback => _fallbackEnumerator!.MoveNext(), + _ => false + }; + } + + public void Dispose() + { + switch (_kind) + { + case EnumeratorKind.List: + _listEnumerator.Dispose(); + break; + case EnumeratorKind.Fallback: + _fallbackEnumerator!.Dispose(); + break; + } + } + } + } + + #endregion + + #region IDictionary + + /// + /// Avoids allocating an enumerator when enumerating an where the concrete type + /// has a well known struct enumerator, such as for . + /// + /// + /// + /// Several collection types (e.g. ) provide a struct-based enumerator type + /// (e.g. ) which the compiler can use in statements. + /// When using a struct-based enumerator, no heap allocation occurs during such enumeration. + /// This is in contrast to the interface-based enumerator which will + /// always be allocated on the heap. + /// + /// + /// This method returns a custom struct enumerator that will avoid any heap allocation if + /// (which is declared via interface ) is actually of known concrete type that + /// provides its own struct enumerator. If so, it delegates to that type's enumerator without any boxing + /// or other heap allocation. + /// + /// + /// If is not of a known concrete type, the returned enumerator falls back to the + /// interface-based enumerator, which will be allocated on the heap. Benchmarking shows the overhead in + /// such cases is low enough to be within the measurement error, meaning this is an inexpensive optimization + /// that won't regress behavior and with low downside for cases where it cannot apply an optimization. + /// + /// + /// + /// + /// dictionary = ...; + /// + /// foreach ((string key, string value) in dictionary.NoAllocEnumerate()) + /// { + /// // ... + /// }]]> + /// + /// + public static OptimisticallyNonAllocatingDictionaryEnumerable NoAllocEnumerate(this IDictionary dictionary) + where TKey : notnull + where TValue : notnull + { +#pragma warning disable CS0618 // Type or member is obsolete + return new(dictionary); +#pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// Provides a struct-based enumerator for use with . + /// Do not use this type directly. Use instead. + /// + public readonly ref struct OptimisticallyNonAllocatingDictionaryEnumerable + where TKey : notnull + where TValue : notnull + { + private readonly IDictionary _dictionary; + + [Obsolete("Do not construct directly. Use internal static method NoAllocEnumerateExtensions.NoAllocEnumerate instead.")] + internal OptimisticallyNonAllocatingDictionaryEnumerable(IDictionary dictionary) => _dictionary = dictionary; + + public Enumerator GetEnumerator() => new(_dictionary); + + /// + /// A struct-based enumerator for use with . + /// Do not use this type directly. Use instead. + /// + public struct Enumerator : IDisposable + { + private enum EnumeratorKind : byte { Empty, Dictionary, Fallback }; + + private readonly EnumeratorKind _kind; + private readonly IEnumerator>? _fallbackEnumerator; + private Dictionary.Enumerator _concreteEnumerator; + + internal Enumerator(IDictionary dictionary) + { + if (dictionary.Count == 0) + { + // The collection is empty, just return false from MoveNext. + _kind = EnumeratorKind.Empty; + } + else if (dictionary is Dictionary concrete) + { + _kind = EnumeratorKind.Dictionary; + _concreteEnumerator = concrete.GetEnumerator(); + } + else + { + _kind = EnumeratorKind.Fallback; + _fallbackEnumerator = dictionary.GetEnumerator(); + } + } + + public KeyValuePair Current + { + get + { + return _kind switch + { + EnumeratorKind.Dictionary => _concreteEnumerator.Current, + EnumeratorKind.Fallback => _fallbackEnumerator!.Current, + _ => default!, + }; + } + } + + public bool MoveNext() + { + return _kind switch + { + EnumeratorKind.Dictionary => _concreteEnumerator.MoveNext(), + EnumeratorKind.Fallback => _fallbackEnumerator!.MoveNext(), + _ => false, + }; + } + + public void Dispose() + { + switch (_kind) + { + case EnumeratorKind.Dictionary: + _concreteEnumerator.Dispose(); + break; + case EnumeratorKind.Fallback: + _fallbackEnumerator!.Dispose(); + break; + } + } + } + } + + #endregion +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFramework.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFramework.cs new file mode 100644 index 0000000000..03716cb015 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFramework.cs @@ -0,0 +1,500 @@ +// 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 System.Globalization; +using System.Linq; +using System.Text; + +namespace NuGetClone.Frameworks +{ + /// + /// A portable implementation of the .NET FrameworkName type with added support for NuGet folder names. + /// + internal partial class NuGetFramework : IEquatable + { + private readonly string _frameworkIdentifier; + private readonly Version _frameworkVersion; + private readonly string _frameworkProfile; + private string? _targetFrameworkMoniker; + private string? _targetPlatformMoniker; + private int? _hashCode; + + public NuGetFramework(NuGetFramework framework) + : this(framework.Framework, framework.Version, framework.Profile, framework.Platform, framework.PlatformVersion) + { + } + + public NuGetFramework(string framework) + : this(framework, FrameworkConstants.EmptyVersion) + { + } + + public NuGetFramework(string framework, Version version) + : this(framework, version, string.Empty, FrameworkConstants.EmptyVersion) + { + } + + private const int Version5 = 5; + + /// + /// Creates a new NuGetFramework instance, with an optional profile (only available for netframework) + /// + public NuGetFramework(string frameworkIdentifier, Version frameworkVersion, string? frameworkProfile) + : this(frameworkIdentifier, frameworkVersion, profile: frameworkProfile ?? string.Empty, platform: string.Empty, platformVersion: FrameworkConstants.EmptyVersion) + { + } + + /// + /// Creates a new NuGetFramework instance, with an optional platform and platformVersion (only available for net5.0+) + /// + public NuGetFramework(string frameworkIdentifier, Version frameworkVersion, string platform, Version platformVersion) + : this(frameworkIdentifier, frameworkVersion, profile: string.Empty, platform: platform, platformVersion: platformVersion) + { + } + + internal NuGetFramework(string frameworkIdentifier, Version frameworkVersion, string profile, string platform, Version platformVersion) + { + if (frameworkIdentifier == null) throw new ArgumentNullException(nameof(frameworkIdentifier)); + if (frameworkVersion == null) throw new ArgumentNullException(nameof(frameworkVersion)); + if (platform == null) throw new ArgumentNullException(nameof(platform)); + if (platformVersion == null) throw new ArgumentNullException(nameof(platformVersion)); + + _frameworkIdentifier = frameworkIdentifier; + _frameworkVersion = NormalizeVersion(frameworkVersion); + _frameworkProfile = profile; + + IsNet5Era = (_frameworkVersion.Major >= Version5 && StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.NetCoreApp, _frameworkIdentifier)); + Platform = IsNet5Era ? platform : string.Empty; + PlatformVersion = IsNet5Era ? NormalizeVersion(platformVersion) : FrameworkConstants.EmptyVersion; + } + + /// + /// Target framework + /// + public string Framework => _frameworkIdentifier; + + /// + /// Target framework version + /// + public Version Version => _frameworkVersion; + + /// + /// Framework Platform (net5.0+) + /// + public string Platform { get; } + + /// + /// Framework Platform Version (net5.0+) + /// + public Version PlatformVersion { get; } + + /// + /// True if the platform is non-empty + /// + public bool HasPlatform + { + get { return !string.IsNullOrEmpty(Platform); } + } + + /// + /// True if the profile is non-empty + /// + public bool HasProfile + { + get { return !string.IsNullOrEmpty(Profile); } + } + + /// + /// Target framework profile + /// + public string Profile => _frameworkProfile; + + /// The TargetFrameworkMoniker identifier of the current NuGetFramework. + /// Formatted to a System.Versioning.FrameworkName + public string DotNetFrameworkName + { + get + { + if (_targetFrameworkMoniker == null) + { + _targetFrameworkMoniker = GetDotNetFrameworkName(DefaultFrameworkNameProvider.Instance); + } + return _targetFrameworkMoniker; + } + } + + /// The TargetFrameworkMoniker identifier of the current NuGetFramework. + /// Formatted to a System.Versioning.FrameworkName + public string GetDotNetFrameworkName(IFrameworkNameProvider mappings) + { + if (mappings == null) + { + throw new ArgumentNullException(nameof(mappings)); + } + + // Check for rewrites + var framework = mappings.GetFullNameReplacement(this); + + if (framework.IsSpecificFramework) + { + var parts = new List(3) { Framework }; + + parts.Add(string.Format(CultureInfo.InvariantCulture, "Version=v{0}", GetDisplayVersion(framework.Version))); + + if (!string.IsNullOrEmpty(framework.Profile)) + { + parts.Add(string.Format(CultureInfo.InvariantCulture, "Profile={0}", framework.Profile)); + } + + return string.Join(",", parts); + } + else + { + return string.Format(CultureInfo.InvariantCulture, "{0},Version=v0.0", framework.Framework); + } + } + + /// The TargetPlatformMoniker identifier of the current NuGetFramework. + /// Similar to a System.Versioning.FrameworkName, but missing the v at the beginning of the version. + public string DotNetPlatformName + { + get + { + if (_targetPlatformMoniker == null) + { + _targetPlatformMoniker = string.IsNullOrEmpty(Platform) + ? string.Empty + : Platform + ",Version=" + GetDisplayVersion(PlatformVersion); + } + + return _targetPlatformMoniker; + } + } + + /// + /// Creates the shortened version of the framework using the default mappings. + /// Ex: net45 + /// + public string GetShortFolderName() + { + return GetShortFolderName(DefaultFrameworkNameProvider.Instance); + } + + /// + /// Helper that is .NET 5 Era aware to replace identifier when appropriate + /// + private string GetFrameworkIdentifier() + { + return IsNet5Era ? FrameworkConstants.FrameworkIdentifiers.Net : Framework; + } + + /// + /// Creates the shortened version of the framework using the given mappings. + /// + public virtual string GetShortFolderName(IFrameworkNameProvider mappings) + { + // Check for rewrites + var framework = mappings.GetShortNameReplacement(this); + + var sb = new StringBuilder(); + + if (IsSpecificFramework) + { + var shortFramework = string.Empty; + + // get the framework + if (!mappings.TryGetShortIdentifier( + GetFrameworkIdentifier(), + out shortFramework)) + { + shortFramework = GetLettersAndDigitsOnly(framework.Framework); + } + + if (string.IsNullOrEmpty(shortFramework)) + { + throw new FrameworkException(string.Format( + CultureInfo.CurrentCulture, + Strings.InvalidFrameworkIdentifier, + shortFramework)); + } + + // add framework + sb.Append(shortFramework); + + // add the version if it is non-empty + if (!AllFrameworkVersions) + { + sb.Append(mappings.GetVersionString(framework.Framework, framework.Version)); + } + + if (IsPCL) + { + sb.Append("-"); + + if (framework.HasProfile + && mappings.TryGetPortableFrameworks(framework.Profile, includeOptional: false, out IEnumerable? frameworks) + && frameworks.Any()) + { + var required = new HashSet(frameworks, Comparer); + + // Normalize by removing all optional frameworks + mappings.TryGetPortableFrameworks(framework.Profile, includeOptional: false, out frameworks); + + // sort the PCL frameworks by alphabetical order + var sortedFrameworks = required.Select(e => e.GetShortFolderName(mappings)).OrderBy(e => e, StringComparer.OrdinalIgnoreCase); + + sb.Append(string.Join("+", sortedFrameworks)); + } + else + { + throw new FrameworkException(string.Format( + CultureInfo.CurrentCulture, + Strings.MissingPortableFrameworks, + framework.DotNetFrameworkName)); + } + } + else if (IsNet5Era) + { + if (!string.IsNullOrEmpty(framework.Platform)) + { + sb.Append("-"); + sb.Append(framework.Platform.ToLowerInvariant()); + + if (framework.PlatformVersion != FrameworkConstants.EmptyVersion) + { + sb.Append(mappings.GetVersionString(framework.Framework, framework.PlatformVersion)); + } + } + } + else + { + // add the profile + var shortProfile = string.Empty; + + if (framework.HasProfile && !mappings.TryGetShortProfile(framework.Framework, framework.Profile, out shortProfile)) + { + // if we have a profile, but can't get a mapping, just use the profile as is + shortProfile = framework.Profile; + } + + if (!string.IsNullOrEmpty(shortProfile)) + { + sb.Append("-"); + sb.Append(shortProfile); + } + } + } + else + { + // unsupported, any, agnostic + sb.Append(Framework); + } + + return sb.ToString().ToLowerInvariant(); + } + + private static string GetDisplayVersion(Version version) + { + var sb = new StringBuilder(string.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor)); + + if (version.Build > 0 + || version.Revision > 0) + { + sb.AppendFormat(CultureInfo.InvariantCulture, ".{0}", version.Build); + + if (version.Revision > 0) + { + sb.AppendFormat(CultureInfo.InvariantCulture, ".{0}", version.Revision); + } + } + + return sb.ToString(); + } + + private static string GetLettersAndDigitsOnly(string s) + { + var sb = new StringBuilder(); + + foreach (var c in s.ToCharArray()) + { + if (char.IsLetterOrDigit(c)) + { + sb.Append(c); + } + } + + return sb.ToString(); + } + + /// + /// Portable class library check + /// + public bool IsPCL + { + get { return StringComparer.OrdinalIgnoreCase.Equals(Framework, FrameworkConstants.FrameworkIdentifiers.Portable) && Version.Major < 5; } + } + + /// + /// True if the framework is packages based. + /// Ex: dotnet, dnxcore, netcoreapp, netstandard, uap, netcore50 + /// + public bool IsPackageBased + { + get + { + // For these frameworks all versions are packages based. + if (PackagesBased.Contains(Framework)) + { + return true; + } + + // NetCore 5.0 and up are packages based. + // Everything else is not packages based. + return NuGetFrameworkUtility.IsNetCore50AndUp(this); + } + } + + /// + /// True if this framework matches for all versions. + /// Ex: net + /// + public bool AllFrameworkVersions + { + get { return Version.Major == 0 && Version.Minor == 0 && Version.Build == 0 && Version.Revision == 0; } + } + + /// + /// True if this framework was invalid or unknown. This framework is only compatible with Any and Agnostic. + /// + public bool IsUnsupported + { + get { return UnsupportedFramework.Equals(this); } + } + + /// + /// True if this framework is non-specific. Always compatible. + /// + public bool IsAgnostic + { + get { return AgnosticFramework.Equals(this); } + } + + /// + /// True if this is the any framework. Always compatible. + /// + public bool IsAny + { + get { return AnyFramework.Equals(this); } + } + + /// + /// True if this framework is real and not one of the special identifiers. + /// + public bool IsSpecificFramework + { + get { return !IsAgnostic && !IsAny && !IsUnsupported; } + } + + /// + /// True if this framework is Net5 or later, until we invent something new. + /// + internal bool IsNet5Era { get; private set; } + + /// + /// Full framework comparison of the identifier, version, profile, platform, and platform version + /// + public static readonly IEqualityComparer Comparer = NuGetFrameworkFullComparer.Instance; + + /// + /// Framework name only comparison. + /// + public static readonly IEqualityComparer FrameworkNameComparer = NuGetFrameworkNameComparer.Instance; + + public override string ToString() + { + return IsNet5Era + ? GetShortFolderName() + : DotNetFrameworkName; + } + + public bool Equals(NuGetFramework? other) + { +#pragma warning disable CS8604 // Possible null reference argument. + // Nullable annotations were added to the BCL for IEqualityComparer in .NET 5 + return Comparer.Equals(this, other); +#pragma warning restore CS8604 // Possible null reference argument. + } + + public static bool operator ==(NuGetFramework? left, NuGetFramework? right) + { +#pragma warning disable CS8604 // Possible null reference argument. + // Nullable annotations were added to the BCL for IEqualityComparer in .NET 5 + return Comparer.Equals(left, right); +#pragma warning restore CS8604 // Possible null reference argument. + } + + public static bool operator !=(NuGetFramework? left, NuGetFramework? right) + { + return !(left == right); + } + + public override int GetHashCode() + { + if (_hashCode == null) + { + _hashCode = Comparer.GetHashCode(this); + } + + return _hashCode.Value; + } + + public override bool Equals(object? obj) + { + var other = obj as NuGetFramework; + + if (other != null) + { + return Equals(other); + } + else + { + return base.Equals(obj); + } + } + + private static Version NormalizeVersion(Version version) + { + var normalized = version; + + if (version.Build < 0 + || version.Revision < 0) + { + normalized = new Version( + version.Major, + version.Minor, + Math.Max(version.Build, 0), + Math.Max(version.Revision, 0)); + } + + return normalized; + } + + /// + /// Frameworks that are packages based across all versions. + /// + private static readonly SortedSet PackagesBased = new SortedSet( + new[] + { + FrameworkConstants.FrameworkIdentifiers.DnxCore, + FrameworkConstants.FrameworkIdentifiers.NetPlatform, + FrameworkConstants.FrameworkIdentifiers.NetStandard, + FrameworkConstants.FrameworkIdentifiers.NetStandardApp, + FrameworkConstants.FrameworkIdentifiers.NetCoreApp, + FrameworkConstants.FrameworkIdentifiers.UAP, + FrameworkConstants.FrameworkIdentifiers.Tizen, + }, + StringComparer.OrdinalIgnoreCase); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkFactory.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkFactory.cs new file mode 100644 index 0000000000..c2f3206850 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkFactory.cs @@ -0,0 +1,668 @@ +// 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 System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; + +namespace NuGetClone.Frameworks +{ + internal partial class NuGetFramework + { + /// + /// An unknown or invalid framework + /// + public static readonly NuGetFramework UnsupportedFramework = new NuGetFramework(FrameworkConstants.SpecialIdentifiers.Unsupported); + + /// + /// A framework with no specific target framework. This can be used for content only packages. + /// + public static readonly NuGetFramework AgnosticFramework = new NuGetFramework(FrameworkConstants.SpecialIdentifiers.Agnostic); + + /// + /// A wildcard matching all frameworks + /// + public static readonly NuGetFramework AnyFramework = new NuGetFramework(FrameworkConstants.SpecialIdentifiers.Any); + + /// + /// Creates a NuGetFramework from a folder name using the default mappings. + /// + public static NuGetFramework Parse(string folderName) + { + return Parse(folderName, DefaultFrameworkNameProvider.Instance); + } + + /// + /// Creates a NuGetFramework from a folder name using the given mappings. + /// + public static NuGetFramework Parse(string folderName, IFrameworkNameProvider mappings) + { + if (folderName == null) throw new ArgumentNullException(nameof(folderName)); + if (mappings == null) throw new ArgumentNullException(nameof(mappings)); + + Debug.Assert(folderName.IndexOf(";", StringComparison.Ordinal) < 0, "invalid folder name, this appears to contain multiple frameworks"); + + NuGetFramework framework = folderName.IndexOf(',') > -1 + ? ParseFrameworkName(folderName, mappings) + : ParseFolder(folderName, mappings); + + return framework; + } + + /// + /// Creates a NuGetFramework from individual components + /// + public static NuGetFramework ParseComponents(string targetFrameworkMoniker, string? targetPlatformMoniker) + { + return ParseComponents(targetFrameworkMoniker, targetPlatformMoniker, DefaultFrameworkNameProvider.Instance); + } + + /// + /// Creates a NuGetFramework from individual components, using the given mappings. + /// This method may have individual component preference, as described in the remarks. + /// + /// + /// Profiles and TargetPlatforms can't mix. As such the precedence order is profile over target platforms (TPI, TPV). + /// .NETCoreApp,Version=v5.0 and later do not support profiles. + /// Target Platforms are ignored for any frameworks not supporting them. + /// This allows to handle the old project scenarios where the TargetPlatformIdentifier and TargetPlatformVersion may be set to Windows and v7.0 respectively. + /// + internal static NuGetFramework ParseComponents(string targetFrameworkMoniker, string? targetPlatformMoniker, IFrameworkNameProvider mappings) + { + if (string.IsNullOrEmpty(targetFrameworkMoniker)) throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(targetFrameworkMoniker)); + if (mappings == null) throw new ArgumentNullException(nameof(mappings)); + + NuGetFramework? result; + string targetFrameworkIdentifier; + Version targetFrameworkVersion; + var parts = GetParts(targetFrameworkMoniker); + + // if the first part is a special framework, ignore the rest + if (TryParseSpecialFramework(parts[0], out result)) + { + return result; + } + + string? profile; + string? targetFrameworkProfile; + ParseFrameworkNameParts(mappings, parts, out targetFrameworkIdentifier, out targetFrameworkVersion, out targetFrameworkProfile); + if (!mappings.TryGetProfile(targetFrameworkIdentifier, targetFrameworkProfile ?? string.Empty, out profile)) + { + profile = targetFrameworkProfile; + } + + if (StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.Portable, targetFrameworkIdentifier)) + { + if (profile != null && mappings.TryGetPortableFrameworks(profile, out IEnumerable? clientFrameworks)) + { + if (mappings.TryGetPortableProfile(clientFrameworks, out int profileNumber)) + { + profile = FrameworkNameHelpers.GetPortableProfileNumberString(profileNumber); + } + } + else + { + return UnsupportedFramework; + } + } + + var isNet5EraTfm = targetFrameworkVersion.Major >= 5 && + StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.NetCoreApp, targetFrameworkIdentifier); + + if (!string.IsNullOrEmpty(profile) && isNet5EraTfm) + { + throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + Strings.FrameworkDoesNotSupportProfiles, + profile + )); + } + + if (!string.IsNullOrEmpty(targetPlatformMoniker) && isNet5EraTfm) + { + string targetPlatformIdentifier; + Version platformVersion; + ParsePlatformParts(GetParts(targetPlatformMoniker!), out targetPlatformIdentifier, out platformVersion); + result = new NuGetFramework(targetFrameworkIdentifier, targetFrameworkVersion, targetPlatformIdentifier ?? string.Empty, platformVersion); + } + else + { + result = new NuGetFramework(targetFrameworkIdentifier, targetFrameworkVersion, profile); + } + + return result; + } + + private static readonly char[] CommaSeparator = new char[] { ',' }; + + private static string[] GetParts(string targetPlatformMoniker) + { + return targetPlatformMoniker.Split(CommaSeparator, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); + } + + /// + /// Creates a NuGetFramework from a .NET FrameworkName + /// + public static NuGetFramework ParseFrameworkName(string frameworkName, IFrameworkNameProvider mappings) + { + if (frameworkName == null) throw new ArgumentNullException(nameof(frameworkName)); + if (mappings == null) throw new ArgumentNullException(nameof(mappings)); + + string[] parts = frameworkName.Split(CommaSeparator, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); + + // if the first part is a special framework, ignore the rest + if (!TryParseSpecialFramework(parts[0], out NuGetFramework? result)) + { + ParseFrameworkNameParts(mappings, parts, out string? framework, out Version version, out string? profile); + + if (version.Major >= 5 + && StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.NetCoreApp, framework)) + { + result = new NuGetFramework(framework, version, string.Empty, FrameworkConstants.EmptyVersion); + } + else + { + result = new NuGetFramework(framework, version, profile); + } + } + + return result; + } + + private static void ParseFrameworkNameParts(IFrameworkNameProvider mappings, string[] parts, out string framework, out Version version, out string? profile) + { + framework = mappings.TryGetIdentifier(parts[0], out string? mappedFramework) + ? mappedFramework + : parts[0]; + + version = new Version(0, 0); + profile = null; + var versionPart = SingleOrDefaultSafe(parts.Where(s => s.IndexOf("Version=", StringComparison.OrdinalIgnoreCase) == 0)); + var profilePart = SingleOrDefaultSafe(parts.Where(s => s.IndexOf("Profile=", StringComparison.OrdinalIgnoreCase) == 0)); + if (!string.IsNullOrEmpty(versionPart)) + { + var versionString = versionPart!.Split('=')[1].TrimStart('v'); + + if (versionString.IndexOf('.') < 0) + { + versionString += ".0"; + } + + version = Version.TryParse(versionString, out Version? parsedVersion) + ? parsedVersion + : throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + Strings.InvalidFrameworkVersion, + versionString)); + } + + if (!string.IsNullOrEmpty(profilePart)) + { + profile = profilePart!.Split('=')[1]; + } + + if (StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.Portable, framework) + && !string.IsNullOrEmpty(profile) + && profile!.Contains("-")) + { + // Frameworks within the portable profile are not allowed + // to have profiles themselves #1869 + throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + Strings.InvalidPortableFrameworksDueToHyphen, + profile)); + } + } + + private static void ParsePlatformParts(string[] parts, out string targetPlatformIdentifier, out Version platformVersion) + { + targetPlatformIdentifier = parts[0]; + platformVersion = new Version(0, 0); + var versionPart = SingleOrDefaultSafe(parts.Where(s => s.IndexOf("Version=", StringComparison.OrdinalIgnoreCase) == 0)); + if (!string.IsNullOrEmpty(versionPart)) + { + var versionString = versionPart!.Split('=')[1].TrimStart('v'); + + if (versionString.IndexOf('.') < 0) + { + versionString += ".0"; + } + + platformVersion = Version.TryParse(versionString, out Version? parsedVersion) + ? parsedVersion + : throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + Strings.InvalidPlatformVersion, + versionString)); + } + } + + /// + /// Creates a NuGetFramework from a folder name using the default mappings. + /// + public static NuGetFramework ParseFolder(string folderName) + { + return ParseFolder(folderName, DefaultFrameworkNameProvider.Instance); + } + + /// + /// Creates a NuGetFramework from a folder name using the given mappings. + /// + public static NuGetFramework ParseFolder(string folderName, IFrameworkNameProvider mappings) + { + if (folderName == null) + { + throw new ArgumentNullException(nameof(folderName)); + } + + if (mappings == null) + { + throw new ArgumentNullException(nameof(mappings)); + } + + if (folderName.IndexOf('%') > -1) + { + folderName = Uri.UnescapeDataString(folderName); + } + + NuGetFramework? result; + // first check if we have a special or common framework + if (!TryParseSpecialFramework(folderName, out result) + && !TryParseCommonFramework(folderName, out result)) + { + // assume this is unsupported unless we find a match + result = UnsupportedFramework; + + var parts = RawParse(folderName); + + if (parts != null) + { + if (mappings.TryGetIdentifier(parts.Item1, out string? framework)) + { + var version = FrameworkConstants.EmptyVersion; + + if (parts.Item2 == null + || mappings.TryGetVersion(parts.Item2, out version)) + { + string profileShort = parts.Item3; + + if (version.Major >= 5 + && (StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.Net, framework) + || StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.NetCoreApp, framework) + ) + ) + { + // net should be treated as netcoreapp in 5.0 and later + framework = FrameworkConstants.FrameworkIdentifiers.NetCoreApp; + if (!string.IsNullOrEmpty(profileShort)) + { + // Find a platform version if it exists and yank it out + var platformChars = profileShort; + var versionStart = 0; + while (versionStart < platformChars.Length + && IsLetterOrDot(platformChars[versionStart])) + { + versionStart++; + } + string platform = versionStart > 0 ? profileShort.Substring(0, versionStart) : profileShort; + string? platformVersionString = versionStart > 0 ? profileShort.Substring(versionStart, profileShort.Length - versionStart) : null; + + // Parse the version if it's there. + Version? platformVersion = FrameworkConstants.EmptyVersion; + if ((string.IsNullOrEmpty(platformVersionString) || mappings.TryGetPlatformVersion(platformVersionString!, out platformVersion))) + { + result = new NuGetFramework(framework, version, platform ?? string.Empty, platformVersion ?? FrameworkConstants.EmptyVersion); + } + else + { + return result; // with result == UnsupportedFramework + } + } + else + { + result = new NuGetFramework(framework, version, string.Empty, FrameworkConstants.EmptyVersion); + } + } + else + { + if (!mappings.TryGetProfile(framework, profileShort, out string? profile)) + { + profile = profileShort ?? string.Empty; + } + + if (StringComparer.OrdinalIgnoreCase.Equals(FrameworkConstants.FrameworkIdentifiers.Portable, framework)) + { + if (!mappings.TryGetPortableFrameworks(profileShort!, out IEnumerable? clientFrameworks)) + { + result = UnsupportedFramework; + } + else + { + var profileNumber = -1; + if (mappings.TryGetPortableProfile(clientFrameworks, out profileNumber)) + { + var portableProfileNumber = FrameworkNameHelpers.GetPortableProfileNumberString(profileNumber); + result = new NuGetFramework(framework, version, portableProfileNumber); + } + else + { + result = new NuGetFramework(framework, version, profileShort); + } + } + } + else + { + result = new NuGetFramework(framework, version, profile); + } + } + } + } + } + else + { + // If the framework was not recognized check if it is a deprecated framework + if (TryParseDeprecatedFramework(folderName, out NuGetFramework? deprecated)) + { + result = deprecated; + } + } + } + + return result; + } + + /// + /// Attempt to parse a common but deprecated framework using an exact string match + /// Support for these should be dropped as soon as possible. + /// + private static bool TryParseDeprecatedFramework(string s, [NotNullWhen(true)] out NuGetFramework? framework) + { + framework = null; + + switch (s) + { + case "45": + case "4.5": + framework = FrameworkConstants.CommonFrameworks.Net45; + break; + case "40": + case "4.0": + case "4": + framework = FrameworkConstants.CommonFrameworks.Net4; + break; + case "35": + case "3.5": + framework = FrameworkConstants.CommonFrameworks.Net35; + break; + case "20": + case "2": + case "2.0": + framework = FrameworkConstants.CommonFrameworks.Net2; + break; + } + + return framework != null; + } + + private static Tuple? RawParse(string s) + { + string identifier; + var profile = string.Empty; + string? version = null; + + var chars = s.ToCharArray(); + + var versionStart = 0; + + while (versionStart < chars.Length + && IsLetterOrDot(chars[versionStart])) + { + versionStart++; + } + + if (versionStart > 0) + { + identifier = s.Substring(0, versionStart); + } + else + { + // invalid, we no longer support names like: 40 + return null; + } + + var profileStart = versionStart; + + while (profileStart < chars.Length + && IsDigitOrDot(chars[profileStart])) + { + profileStart++; + } + + var versionLength = profileStart - versionStart; + + if (versionLength > 0) + { + version = s.Substring(versionStart, versionLength); + } + + if (profileStart < chars.Length) + { + if (chars[profileStart] == '-') + { + var actualProfileStart = profileStart + 1; + + if (actualProfileStart == chars.Length) + { + // empty profiles are not allowed + return null; + } + + profile = s.Substring(actualProfileStart, s.Length - actualProfileStart); + + foreach (var c in profile.ToArray()) + { + // validate the profile string to AZaz09-+. + if (!IsValidProfileChar(c)) + { + return null; + } + } + } + else + { + // invalid profile + return null; + } + } + + return new Tuple(identifier, version, profile); + } + + private static bool IsLetterOrDot(char c) + { + var x = (int)c; + + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + return (x >= 65 && x <= 90) || (x >= 97 && x <= 122) || x == 46; + } + + private static bool IsDigitOrDot(char c) + { + var x = (int)c; + + // "0123456789" + return (x >= 48 && x <= 57) || x == 46; + } + + private static bool IsValidProfileChar(char c) + { + var x = (int)c; + + // letter, digit, dot, dash, or plus + return (x >= 48 && x <= 57) || (x >= 65 && x <= 90) || (x >= 97 && x <= 122) || x == 46 || x == 43 || x == 45; + } + + private static bool TryParseSpecialFramework(string frameworkString, [NotNullWhen(true)] out NuGetFramework? framework) + { + framework = null; + + if (StringComparer.OrdinalIgnoreCase.Equals(frameworkString, FrameworkConstants.SpecialIdentifiers.Any)) + { + framework = AnyFramework; + } + else if (StringComparer.OrdinalIgnoreCase.Equals(frameworkString, FrameworkConstants.SpecialIdentifiers.Agnostic)) + { + framework = AgnosticFramework; + } + else if (StringComparer.OrdinalIgnoreCase.Equals(frameworkString, FrameworkConstants.SpecialIdentifiers.Unsupported)) + { + framework = UnsupportedFramework; + } + + return framework != null; + } + + /// + /// A set of special and common frameworks that can be returned from the list of constants without parsing + /// Using the interned frameworks here optimizes comparisons since they can be checked by reference. + /// This is designed to optimize + /// + private static bool TryParseCommonFramework(string frameworkString, [NotNullWhen(true)] out NuGetFramework? framework) + { + framework = null; + + frameworkString = frameworkString.ToLowerInvariant(); + + switch (frameworkString) + { + case "dotnet": + case "dotnet50": + case "dotnet5.0": + framework = FrameworkConstants.CommonFrameworks.DotNet50; + break; + case "net40": + case "net4": + framework = FrameworkConstants.CommonFrameworks.Net4; + break; + case "net45": + framework = FrameworkConstants.CommonFrameworks.Net45; + break; + case "net451": + framework = FrameworkConstants.CommonFrameworks.Net451; + break; + case "net46": + framework = FrameworkConstants.CommonFrameworks.Net46; + break; + case "net461": + framework = FrameworkConstants.CommonFrameworks.Net461; + break; + case "net462": + framework = FrameworkConstants.CommonFrameworks.Net462; + break; + case "net47": + framework = FrameworkConstants.CommonFrameworks.Net47; + break; + case "net471": + framework = FrameworkConstants.CommonFrameworks.Net471; + break; + case "net472": + framework = FrameworkConstants.CommonFrameworks.Net472; + break; + case "win8": + framework = FrameworkConstants.CommonFrameworks.Win8; + break; + case "win81": + framework = FrameworkConstants.CommonFrameworks.Win81; + break; + case "netstandard": + framework = FrameworkConstants.CommonFrameworks.NetStandard; + break; + case "netstandard1.0": + case "netstandard10": + framework = FrameworkConstants.CommonFrameworks.NetStandard10; + break; + case "netstandard1.1": + case "netstandard11": + framework = FrameworkConstants.CommonFrameworks.NetStandard11; + break; + case "netstandard1.2": + case "netstandard12": + framework = FrameworkConstants.CommonFrameworks.NetStandard12; + break; + case "netstandard1.3": + case "netstandard13": + framework = FrameworkConstants.CommonFrameworks.NetStandard13; + break; + case "netstandard1.4": + case "netstandard14": + framework = FrameworkConstants.CommonFrameworks.NetStandard14; + break; + case "netstandard1.5": + case "netstandard15": + framework = FrameworkConstants.CommonFrameworks.NetStandard15; + break; + case "netstandard1.6": + case "netstandard16": + framework = FrameworkConstants.CommonFrameworks.NetStandard16; + break; + case "netstandard1.7": + case "netstandard17": + framework = FrameworkConstants.CommonFrameworks.NetStandard17; + break; + case "netstandard2.0": + case "netstandard20": + framework = FrameworkConstants.CommonFrameworks.NetStandard20; + break; + case "netstandard2.1": + case "netstandard21": + framework = FrameworkConstants.CommonFrameworks.NetStandard21; + break; + case "netcoreapp2.1": + case "netcoreapp21": + framework = FrameworkConstants.CommonFrameworks.NetCoreApp21; + break; + case "netcoreapp3.0": + case "netcoreapp30": + framework = FrameworkConstants.CommonFrameworks.NetCoreApp30; + break; + case "netcoreapp3.1": + case "netcoreapp31": + framework = FrameworkConstants.CommonFrameworks.NetCoreApp31; + break; + case "netcoreapp5.0": + case "netcoreapp50": + case "net5.0": + case "net50": + framework = FrameworkConstants.CommonFrameworks.Net50; + break; + case "netcoreapp6.0": + case "netcoreapp60": + case "net6.0": + case "net60": + framework = FrameworkConstants.CommonFrameworks.Net60; + break; + case "netcoreapp7.0": + case "netcoreapp70": + case "net7.0": + case "net70": + framework = FrameworkConstants.CommonFrameworks.Net70; + break; + } + + return framework != null; + } + + private static string? SingleOrDefaultSafe(IEnumerable items) + { + if (items.Count() == 1) + { + return items.Single(); + } + + return null; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkFullComparer.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkFullComparer.cs new file mode 100644 index 0000000000..d7f0744f5e --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkFullComparer.cs @@ -0,0 +1,59 @@ +// 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 NuGetClone.Shared; + +namespace NuGetClone.Frameworks +{ + /// + /// A case insensitive compare of the framework, version, and profile + /// + internal class NuGetFrameworkFullComparer : IEqualityComparer + { +#pragma warning disable CS0618 // Type or member is obsolete + public static NuGetFrameworkFullComparer Instance { get; } = new(); +#pragma warning restore CS0618 // Type or member is obsolete + + [Obsolete("Use singleton via NuGetFrameworkFullComparer.Instance instead")] + public NuGetFrameworkFullComparer() { } + + public bool Equals(NuGetFramework? x, NuGetFramework? y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (ReferenceEquals(x, null) + || ReferenceEquals(y, null)) + { + return false; + } + + return x.Version == y.Version + && StringComparer.OrdinalIgnoreCase.Equals(x.Framework, y.Framework) + && StringComparer.OrdinalIgnoreCase.Equals(x.Profile, y.Profile) + && StringComparer.OrdinalIgnoreCase.Equals(x.Platform, y.Platform) + && x.PlatformVersion == y.PlatformVersion + && !x.IsUnsupported; + } + + public int GetHashCode(NuGetFramework obj) + { + if (ReferenceEquals(obj, null)) + { + return 0; + } + + var combiner = new HashCodeCombiner(); + + combiner.AddStringIgnoreCase(obj.Framework); + combiner.AddObject(obj.Version); + combiner.AddStringIgnoreCase(obj.Profile); + + return combiner.CombinedHash; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkNameComparer.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkNameComparer.cs new file mode 100644 index 0000000000..fb8744985e --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkNameComparer.cs @@ -0,0 +1,50 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + /// + /// A case insensitive compare of the framework name only + /// + internal class NuGetFrameworkNameComparer : IEqualityComparer + { +#pragma warning disable CS0618 // Type or member is obsolete + public static NuGetFrameworkNameComparer Instance { get; } = new(); +#pragma warning restore CS0618 // Type or member is obsolete + + [Obsolete("Use singleton NuGetFrameworkNameComparer instead")] + public NuGetFrameworkNameComparer() + { + } + + public bool Equals(NuGetFramework? x, NuGetFramework? y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (ReferenceEquals(x, null) + || ReferenceEquals(y, null)) + { + return false; + } + + return StringComparer.OrdinalIgnoreCase.Equals(x.Framework, y.Framework); + } + + public int GetHashCode(NuGetFramework obj) + { + if (ReferenceEquals(obj, null) + || ReferenceEquals(obj.Framework, null)) + { + return 0; + } + + return obj.Framework.ToUpperInvariant().GetHashCode(); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkSorter.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkSorter.cs new file mode 100644 index 0000000000..1a4e9f5da9 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkSorter.cs @@ -0,0 +1,107 @@ +// 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; + +namespace NuGetClone.Frameworks +{ + /// + /// Sorts NuGet Frameworks in a consistent way for package readers. + /// The order is not particularly useful here beyond making things deterministic + /// since it compares completely different frameworks. + /// + internal class NuGetFrameworkSorter : IComparer + { +#pragma warning disable CS0618 // Type or member is obsolete + public static NuGetFrameworkSorter Instance { get; } = new(); +#pragma warning restore CS0618 // Type or member is obsolete + + [Obsolete("Use Instance singleton instead")] + public NuGetFrameworkSorter() + { + } + + public int Compare(NuGetFramework? x, NuGetFramework? y) + { + if (ReferenceEquals(x, y)) + { + return 0; + } + + if (ReferenceEquals(x, null)) + { + return -1; + } + + if (ReferenceEquals(y, null)) + { + return 1; + } + + // Any goes first + if (x.IsAny + && !y.IsAny) + { + return -1; + } + + if (!x.IsAny + && y.IsAny) + { + return 1; + } + + // Unsupported goes last + if (x.IsUnsupported + && !y.IsUnsupported) + { + return 1; + } + + if (!x.IsUnsupported + && y.IsUnsupported) + { + return -1; + } + + // Compare on Framework, Version, Profile, Platform, and PlatformVersion + var result = StringComparer.OrdinalIgnoreCase.Compare(x.Framework, y.Framework); + + if (result != 0) + { + return result; + } + + result = x.Version.CompareTo(y.Version); + + if (result != 0) + { + return result; + } + + result = StringComparer.OrdinalIgnoreCase.Compare(x.Profile, y.Profile); + + if (result != 0) + { + return result; + } + + result = StringComparer.OrdinalIgnoreCase.Compare(x.Platform, y.Platform); + + if (result != 0) + { + return result; + } + + result = x.PlatformVersion.CompareTo(y.PlatformVersion); + + if (result != 0) + { + return result; + } + + return 0; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkUtility.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkUtility.cs new file mode 100644 index 0000000000..b5367a43b5 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/NuGetFrameworkUtility.cs @@ -0,0 +1,134 @@ +// 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 System.Linq; + +namespace NuGetClone.Frameworks +{ + internal static class NuGetFrameworkUtility + { + /// + /// Find the most compatible group based on target framework + /// + /// framework specific groups or items + /// project target framework + /// retrieves the framework from the group + internal static T? GetNearest(IEnumerable items, NuGetFramework framework, Func selector) where T : class + { + return GetNearest(items, framework, DefaultFrameworkNameProvider.Instance, DefaultCompatibilityProvider.Instance, selector); + } + + /// + /// Find the most compatible group based on target framework + /// + /// framework specific groups or items + /// project target framework + /// retrieves the framework from the group + /// framework mappings + public static T? GetNearest(IEnumerable items, + NuGetFramework framework, + IFrameworkNameProvider frameworkMappings, + IFrameworkCompatibilityProvider compatibilityProvider, + Func selector) where T : class + { + if (framework == null) throw new ArgumentNullException(nameof(framework)); + if (frameworkMappings == null) throw new ArgumentNullException(nameof(frameworkMappings)); + if (compatibilityProvider == null) throw new ArgumentNullException(nameof(compatibilityProvider)); + + if (items != null) + { + var reducer = new FrameworkReducer(frameworkMappings, compatibilityProvider); + + var mostCompatibleFramework = reducer.GetNearest(framework, items.Select(selector)); + if (mostCompatibleFramework != null) + { + return items.FirstOrDefault(item => NuGetFramework.Comparer.Equals(selector(item), mostCompatibleFramework)); + } + } + + return null; + } + + /// + /// Find the most compatible group based on target framework + /// + /// framework specific groups or items + /// project target framework + public static T? GetNearest(IEnumerable items, NuGetFramework framework) where T : IFrameworkSpecific + { + return GetNearest(items, framework, DefaultFrameworkNameProvider.Instance, DefaultCompatibilityProvider.Instance); + } + + /// + /// Find the most compatible group based on target framework + /// + /// framework specific groups or items + /// project target framework + public static T? GetNearest(IEnumerable items, + NuGetFramework framework, + IFrameworkNameProvider frameworkMappings, + IFrameworkCompatibilityProvider compatibilityProvider) where T : IFrameworkSpecific + { + if (framework == null) throw new ArgumentNullException(nameof(framework)); + if (frameworkMappings == null) throw new ArgumentNullException(nameof(frameworkMappings)); + if (compatibilityProvider == null) throw new ArgumentNullException(nameof(compatibilityProvider)); + + if (items != null) + { + var reducer = new FrameworkReducer(frameworkMappings, compatibilityProvider); + + var mostCompatibleFramework = reducer.GetNearest(framework, items.Select(item => item.TargetFramework)); + if (mostCompatibleFramework != null) + { + return items.FirstOrDefault(item => NuGetFramework.Comparer.Equals(item.TargetFramework, mostCompatibleFramework)); + } + } + + return default(T); + } + + /// + /// Check compatibility with additional checks for the fallback framework. + /// + public static bool IsCompatibleWithFallbackCheck(NuGetFramework projectFramework, NuGetFramework candidate) + { + if (projectFramework is null) throw new ArgumentNullException(nameof(projectFramework)); + if (candidate is null) throw new ArgumentNullException(nameof(candidate)); + + var compatible = DefaultCompatibilityProvider.Instance.IsCompatible(projectFramework, candidate); + + if (!compatible) + { + var fallbackFramework = projectFramework as FallbackFramework; + + if (fallbackFramework != null && fallbackFramework.Fallback != null) + { + foreach (var supportFramework in fallbackFramework.Fallback) + { + compatible = DefaultCompatibilityProvider.Instance.IsCompatible(supportFramework, candidate); + if (compatible) + { + break; + } + } + } + } + + return compatible; + } + + /// + /// True if the framework is netcore50 or higher. This is where the framework + /// becomes packages based. + /// + public static bool IsNetCore50AndUp(NuGetFramework framework) + { + return (framework.Version.Major >= 5 + && StringComparer.OrdinalIgnoreCase.Equals( + framework.Framework, + FrameworkConstants.FrameworkIdentifiers.NetCore)); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/OneWayCompatibilityMappingEntry.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/OneWayCompatibilityMappingEntry.cs new file mode 100644 index 0000000000..5a35dad4bb --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/OneWayCompatibilityMappingEntry.cs @@ -0,0 +1,57 @@ +// 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.Globalization; + +namespace NuGetClone.Frameworks +{ + internal class OneWayCompatibilityMappingEntry : IEquatable + { + private readonly FrameworkRange _targetFramework; + private readonly FrameworkRange _supportedFramework; + + /// + /// Creates a one way compatibility mapping. + /// Ex: net -supports-> native + /// + /// Project framework + /// Framework that is supported by the project framework + public OneWayCompatibilityMappingEntry(FrameworkRange targetFramework, FrameworkRange supportedFramework) + { + _targetFramework = targetFramework; + _supportedFramework = supportedFramework; + } + + /// + /// Primary framework range or project target framework that supports the SuppportedFrameworkRange + /// + public FrameworkRange TargetFrameworkRange + { + get { return _targetFramework; } + } + + /// + /// Framework range that is supported by the TargetFrameworkRange + /// + public FrameworkRange SupportedFrameworkRange + { + get { return _supportedFramework; } + } + + public static CompatibilityMappingComparer Comparer + { + get { return CompatibilityMappingComparer.Instance; } + } + + public bool Equals(OneWayCompatibilityMappingEntry? other) + { + return Comparer.Equals(this, other); + } + + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", TargetFrameworkRange.ToString(), SupportedFrameworkRange.ToString()); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/README.md b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/README.md new file mode 100644 index 0000000000..6f38b67bec --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/README.md @@ -0,0 +1,10 @@ +This directory contains code that is copied from https://github.com/NuGet/NuGet.Client/tree/dev/src/NuGet.Core/NuGet.Frameworks, with the namespaces changed +and class visibility changed. This is done to ensure we are providing the same functionality as Nuget.Frameworks, without depending on the package explicitly. + +The files in this folder are coming from tag 6.8.0.117, on commit 7fb5ed8. + +To update this code, run the script in: \scripts\update-nuget-frameworks.ps1 , with -VersionTag . + +Exception to this is the Strings.cs, this is coming from Strings.Designer.cs because we don't localize those messages here. These messages +go only into logs. + diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/SimplePool.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/SimplePool.cs new file mode 100644 index 0000000000..1d72d34acf --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/SimplePool.cs @@ -0,0 +1,36 @@ +// 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 enable + +using System; +using System.Collections.Concurrent; + +namespace NuGetClone +{ + internal class SimplePool where T : class + { + private readonly ConcurrentStack _values = new(); + private readonly Func _allocate; + + public SimplePool(Func allocate) + { + _allocate = allocate; + } + + public T Allocate() + { + if (_values.TryPop(out T? result)) + { + return result; + } + + return _allocate(); + } + + public void Free(T value) + { + _values.Push(value); + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/StringBuilderPool.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/StringBuilderPool.cs new file mode 100644 index 0000000000..1848471e45 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/StringBuilderPool.cs @@ -0,0 +1,89 @@ +// 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 enable + +using System.Text; + +namespace NuGetClone +{ + /// + /// Provides a resource pool that enables reusing instances of instances. + /// + /// + /// + /// Renting and returning buffers with an can increase performance + /// in situations where instances are created and destroyed frequently, + /// resulting in significant memory pressure on the garbage collector. + /// + /// + /// This class is thread-safe. All members may be used by multiple threads concurrently. + /// + /// + internal class StringBuilderPool + { + private const int MaxPoolSize = 256; + private readonly SimplePool _pool = new(() => new StringBuilder(MaxPoolSize)); + + /// + /// Retrieves a shared instance. + /// + public static readonly StringBuilderPool Shared = new(); + + private StringBuilderPool() + { + } + + /// + /// Retrieves a that is at least the requested length. + /// + /// The minimum capacity of the needed. + /// + /// A that is at least in length. + /// + /// + /// This buffer is loaned to the caller and should be returned to the same pool via + /// so that it may be reused in subsequent usage of . + /// It is not a fatal error to not return a rented string builder, but failure to do so may lead to + /// decreased application performance, as the pool may need to create a new instance to replace + /// the one lost. + /// + public StringBuilder Rent(int minimumCapacity) + { + if (minimumCapacity <= MaxPoolSize) + { + return _pool.Allocate(); + } + + return new StringBuilder(minimumCapacity); + } + + /// + /// Returns to the pool an array that was previously obtained via on the same + /// instance, returning the built string. + /// + /// + /// The previously obtained from to return to the pool. + /// + /// + /// Once a has been returned to the pool, the caller gives up all ownership + /// of the instance and must not use it. The reference returned from a given call to + /// must only be returned via once. The default + /// may hold onto the returned instance in order to rent it again, or it may release the returned instance + /// if it's determined that the pool already has enough instances stored. + /// + /// The string, built from . + public string ToStringAndReturn(StringBuilder builder) + { + string result = builder.ToString(); + + if (builder.Capacity <= MaxPoolSize) + { + builder.Clear(); + _pool.Free(builder); + } + + return result; + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/Strings.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/Strings.cs new file mode 100644 index 0000000000..a99c6176b8 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/Strings.cs @@ -0,0 +1,54 @@ + +namespace NuGetClone.Frameworks { + + internal class Strings { + + internal static string ArgumentCannotBeNullOrEmpty { + get { + return "The argument cannot be null or empty."; + } + } + + internal static string FrameworkDoesNotSupportProfiles { + get { + return ".NET 5.0 and above does not support profiles."; + } + } + + internal static string FrameworkMismatch { + get { + return "Frameworks must have the same identifier, profile, and platform."; + } + } + + internal static string InvalidFrameworkIdentifier { + get { + return "{0} is the invalid framework identifier."; + } + } + + internal static string InvalidFrameworkVersion { + get { + return "{0} is the invalid framework version."; + } + } + + internal static string InvalidPlatformVersion { + get { + return "{0} is the invalid platform version."; + } + } + + internal static string InvalidPortableFrameworksDueToHyphen { + get { + return "{0} is the invalid portable framework string."; + } + } + + internal static string MissingPortableFrameworks { + get { + return "{0} is the invalid portable framework string."; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/spelling.dic b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/spelling.dic new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt index e56a11ad0d..617987321a 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt @@ -968,3 +968,5 @@ virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.TestObject.ProtectedSetP ~static Microsoft.VisualStudio.TestPlatform.ObjectModel.Resources.CommonResources.SourceIncompatible.get -> string Microsoft.VisualStudio.TestPlatform.ObjectModel.DirectoryBasedTestDiscovererAttribute Microsoft.VisualStudio.TestPlatform.ObjectModel.DirectoryBasedTestDiscovererAttribute.DirectoryBasedTestDiscovererAttribute() -> void +Microsoft.VisualStudio.TestPlatform.ObjectModel.Framework.FrameworkName.get -> string! +Microsoft.VisualStudio.TestPlatform.ObjectModel.Framework.ShortName.get -> string? diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj index 48fda235ad..b8f6849b74 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj @@ -71,7 +71,6 @@ - @@ -83,7 +82,6 @@ - @@ -94,7 +92,6 @@ - diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec index 05c7823f3d..cf42d91a2a 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec @@ -33,7 +33,6 @@ - @@ -122,7 +121,6 @@ - diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec index 4de0d6f867..8e0541e09b 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec @@ -32,7 +32,6 @@ - diff --git a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj index 5455e51e3c..73d7b35d15 100644 --- a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj +++ b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppMinimum);$(NetFrameworkMinimum);net47;net471;net472;net48;netstandard2.0 @@ -61,7 +61,6 @@ - @@ -73,7 +72,6 @@ - @@ -83,7 +81,6 @@ - diff --git a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec index 010bd396b8..ceaecfc3ac 100644 --- a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec +++ b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec @@ -88,7 +88,6 @@ - @@ -329,7 +328,6 @@ - @@ -478,7 +476,6 @@ - diff --git a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj index 593c47b856..d6dc433cf9 100644 --- a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj +++ b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj @@ -68,7 +68,6 @@ - @@ -98,7 +97,6 @@ - @@ -123,7 +121,6 @@ - diff --git a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec index 43d19aef20..5885ff0af0 100644 --- a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec +++ b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec @@ -60,7 +60,6 @@ - @@ -530,7 +529,6 @@ - diff --git a/src/package/ThirdPartyNotices.txt b/src/package/ThirdPartyNotices.txt index 4c85ae8285..5d98c81cc4 100644 --- a/src/package/ThirdPartyNotices.txt +++ b/src/package/ThirdPartyNotices.txt @@ -8,9 +8,9 @@ and the licenses under which Microsoft received such components are set forth be informational purposes only. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. -1. Newtonsoft version 13.0.1 (https://github.com/JamesNK/Newtonsoft.Json) -2. Mono.Cecil version 0.11.3 (https://github.com/jbevain/cecil) - +1. Newtonsoft version 13.0.1 (https://github.com/JamesNK/Newtonsoft.Json) +2. Mono.Cecil version 0.11.3 (https://github.com/jbevain/cecil) +3. Nuget.Client version 6.8.0.117 \(https://github.com/NuGet/NuGet.Client) %% Newtonsoft NOTICES AND INFORMATION BEGIN HERE @@ -62,3 +62,24 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF Mono.Cecil NOTICES AND INFORMATION + + +%% NuGet.Client NOTICES AND INFORMATION BEGIN HERE +================================================== +Copyright (c) .NET Foundation and Contributors. + +All rights reserved. + + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +========================================= +END OF NuGet.Client NOTICES AND INFORMATION diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs index a687280755..7743e61a1d 100644 --- a/src/vstest.console/Internal/ConsoleLogger.cs +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -16,8 +16,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestPlatform.Utilities; -using NuGet.Frameworks; - using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; @@ -224,7 +222,7 @@ public void Initialize(TestLoggerEvents events, Dictionary para } parameters.TryGetValue(DefaultLoggerParameterNames.TargetFramework, out _targetFramework); - _targetFramework = !_targetFramework.IsNullOrEmpty() ? NuGetFramework.Parse(_targetFramework).GetShortFolderName() : _targetFramework; + _targetFramework = Framework.FromString(_targetFramework)?.ShortName ?? _targetFramework; Initialize(events, string.Empty); } diff --git a/temp/testhost/testhost.deps.json b/temp/testhost/testhost.deps.json index 5ba110a58f..2563b5da42 100644 --- a/temp/testhost/testhost.deps.json +++ b/temp/testhost/testhost.deps.json @@ -15,8 +15,7 @@ "Microsoft.TestPlatform.Utilities": "15.0.0.0", "Microsoft.VisualStudio.TestPlatform.Common": "15.0.0.0", "Microsoft.VisualStudio.TestPlatform.ObjectModel": "15.0.0.0", - "Newtonsoft.Json": "13.0.0.0", - "NuGet.Frameworks": "5.0.0.6" + "Newtonsoft.Json": "13.0.0.0" }, "runtime": { "testhost.dll": {} @@ -85,14 +84,6 @@ "fileVersion": "13.0.1.25517" } } - }, - "NuGet.Frameworks/5.0.0.6": { - "runtime": { - "NuGet.Frameworks.dll": { - "assemblyVersion": "5.0.0.6", - "fileVersion": "5.0.0.5923" - } - } } } }, @@ -150,12 +141,6 @@ "serviceable": false, "sha512": "", "path": "/" - }, - "NuGet.Frameworks/5.0.0.6": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" } } } \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs index 8648d497c6..18b43def79 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs @@ -135,6 +135,23 @@ public void GetDefaultTestHostManagerReturnsANonSharedManagerIfDisableAppDomainI public void TestHostProviderManagerShouldReturnNullIfTargetFrameworkIsPortable() { string runSettingsXml = @" + + + 0 + x64 + .NETPortable,Version=v4.5,Profile=Profile44 + + "; + + var manager = TestRuntimeProviderManager.Instance; + Assert.IsNull(manager.GetTestHostManagerByRunConfiguration(runSettingsXml, null)); + } + + [TestMethod] + public void TestHostProviderManagerShouldReturnNullIfTargetFrameworkIsInvalidFramework() + { + // Portable 4.5 is not valid when it does not mention which profile it is. + string runSettingsXml = @" 0