From 10fd0c8d400ec9992cada3ecc7e54a7a68fe9b9b Mon Sep 17 00:00:00 2001 From: Taylor Southwick Date: Tue, 4 May 2021 13:40:41 -0700 Subject: [PATCH] Enable LTS/Current/Preview selection (#469) This change does a couple of things to enable LTS/Current/Preview selection: - Adds a command line argument to allow selection - Maps the selected support model to identify the TFM that is needed - Updates the PackageLoader to limit searches to results that match the TFMs in a project --- CHANGELOG.md | 5 +- Directory.Packages.props | 2 +- README.md | 42 ++- .../ConsoleUtils.cs | 13 + .../Program.cs | 28 +- .../appsettings.json | 7 +- .../IPackageLoader.cs | 6 +- .../IUpgradeContextProperties.cs | 5 - .../UpgradeOptions.cs | 2 +- .../UpgradeTarget.cs | 3 +- .../INuGetPackageSourceFactory.cs | 13 + .../MSBuildWorkspaceUpgradeContext.cs | 1 - .../NuGetDownloaderOptions.cs | 10 + .../NuGetPackageSourceFactory.cs | 43 +++ .../PackageLoader.cs | 178 ++++++------ .../UpgraderMsBuildExtensions.cs | 20 +- .../Microsoft.DotNet.UpgradeAssistant.csproj | 1 + .../TargetFramework/DefaultTfmOptions.cs | 19 ++ ...ncyMinimumTargetFrameworkSelectorFilter.cs | 1 - .../TargetFramework/TFMSelectorOptions.cs | 12 - .../TargetFrameworkSelector.cs | 23 +- ...WindowsSdkTargetFrameworkSelectorFilter.cs | 1 - .../UpgraderExtensions.cs | 6 +- .../Analyzers/NewtonsoftReferenceAnalyzer.cs | 2 +- .../Analyzers/PackageMapReferenceAnalyzer.cs | 2 +- .../TargetCompatibilityReferenceAnalyzer.cs | 34 +-- .../UpgradeAssistantReferenceAnalyzer.cs | 9 +- .../WindowsCompatReferenceAnalyzer.cs | 2 +- .../PackageLoaderTests.cs | 271 ++++++++++++++++++ .../TargetFrameworkSelectorTests.cs | 13 +- .../UpdateContextPropertiesTests.cs | 8 - .../BackupStepTests.cs | 1 - .../NewtonsoftReferenceAnalyzerTests.cs | 11 +- .../ExpectedPackageVersions.json | 2 +- .../csharp/Upgraded/TemplateMvc.csproj | 2 +- .../PCL/Upgraded/SamplePCL.csproj | 2 +- .../csharp/Upgraded/WebLibrary.csproj | 2 +- .../BeanTraderClient/BeanTraderClient.csproj | 2 +- .../BeanTraderCommon.csproj | 2 +- .../vb/Upgraded/WpfApp1/WpfApp1.vbproj | 2 +- .../InterceptingKnownPackageLoader.cs | 12 +- 41 files changed, 576 insertions(+), 244 deletions(-) create mode 100644 src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/INuGetPackageSourceFactory.cs create mode 100644 src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetDownloaderOptions.cs create mode 100644 src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetPackageSourceFactory.cs create mode 100644 src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DefaultTfmOptions.cs delete mode 100644 src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TFMSelectorOptions.cs create mode 100644 tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/PackageLoaderTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index ad97c812..b3477ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## Current +### Added +- A new command line option (`--target-tfm-support` to select the support model of LTS/Preview/Current that is desired [#469](https://github.com/dotnet/upgrade-assistant/pull/469) + ### Fixed - VB Win Forms projects should keep import for 'System.Windows.Forms' [#474](https://github.com/dotnet/upgrade-assistant/pull/474) ## Version 0.2.222702 - 2021-04-27 ([Link](https://www.nuget.org/packages/upgrade-assistant/0.2.222702)) ### Added -- Multiple entrypoints can now be added by using globbing and multiple instances of the `--entry-point` argument [#425](https://github.com/dotnet/upgrade-assistant/pull/425) +- Multiple entrypoints can now be added by using globbing and multiple instances of the `--entry-point` argument [#425](https://github.com/dotnet/upgrade-assistant/pull/425) - NuGet credential providers will now be used, if present (may require running in interactive mode) [#448](https://github.com/dotnet/upgrade-assistant/pull/448) - Source analyzers and code fix providers are now applied to source embedded in Razor documents [#455](https://github.com/dotnet/upgrade-assistant/pull/455) diff --git a/Directory.Packages.props b/Directory.Packages.props index e61a06e3..0aad4469 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -55,7 +55,7 @@ - + diff --git a/README.md b/README.md index e0abbaf9..3cc039fd 100644 --- a/README.md +++ b/README.md @@ -82,29 +82,25 @@ Arguments: Options: - --skip-backup Disables backing up the project. This is - not recommended unless the project is in - source control since this tool will make - large changes to both the project and - source files. - --extension Specifies a .NET Upgrade Assistant - extension package to include. This could - be an ExtensionManifest.json file, a - directory containing an - ExtensionManifest.json file, or a zip - archive containing an extension. This - option can be specified multiple times. - -e, --entry-point Provides the entry-point project to start - the upgrade process. This may include - globbing patterns such as '*' for match. - -v, --verbose Enable verbose diagnostics - --non-interactive Automatically select each first option in - non-interactive mode. - --non-interactive-wait Wait the supplied seconds before moving - on to the next option in non-interactive - mode. - --version Show version information - -?, -h, --help Show help and usage information + --skip-backup Disables backing up the project. This is not recommended unless the + project is in source control since this tool will make large changes + to both the project and source files. + --extension Specifies a .NET Upgrade Assistant extension package to include. This + could be an ExtensionManifest.json file, a directory containing an + ExtensionManifest.json file, or a zip archive containing an extension. + This option can be specified multiple times. + -e, --entry-point Provides the entry-point project to start the upgrade process. This + may include globbing patterns such as '*' for match. + -v, --verbose Enable verbose diagnostics + --non-interactive Automatically select each first option in non-interactive mode. + --non-interactive-wait Wait the supplied seconds before moving on to the next option in + non-interactive mode. + --target-tfm-support Select if you would like the Long Term Support (LTS), Current, or + Preview TFM. See + https://dotnet.microsoft.com/platform/support/policy/dotnet-core for + details for what these mean. + --version Show version information + -?, -h, --help Show help and usage information ``` >**:warning:** The primary usage of upgrade-assistant is to be used in interactive mode, giving users control over changes/upgrades done to their projects. Usage of upgrade-assistant with --non-interactive mode can leave projects in a broken state and users are advised to use at their own discretion. **:warning:** diff --git a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/ConsoleUtils.cs b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/ConsoleUtils.cs index 7c9e7938..30610901 100644 --- a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/ConsoleUtils.cs +++ b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/ConsoleUtils.cs @@ -7,6 +7,19 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli { internal static class ConsoleUtils { + public static int Width + { + get + { + if (Console.IsOutputRedirected) + { + return int.MaxValue; + } + + return Console.WindowWidth; + } + } + public static void Clear() { if (!Console.IsOutputRedirected) diff --git a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Program.cs b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Program.cs index 4caa35f1..8828859e 100644 --- a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Program.cs +++ b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Program.cs @@ -7,7 +7,6 @@ using System.CommandLine; using System.CommandLine.Builder; using System.CommandLine.Help; using System.CommandLine.Invocation; -using System.CommandLine.IO; using System.CommandLine.Parsing; using System.IO; using System.Runtime.InteropServices; @@ -15,6 +14,7 @@ using System.Threading; using System.Threading.Tasks; using Autofac.Extensions.DependencyInjection; using Microsoft.DotNet.UpgradeAssistant.Extensions; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; @@ -133,7 +133,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli services.AddSingleton(); services.AddSingleton(); - services.AddStepManagement(); + services.AddStepManagement(context.Configuration.GetSection("DefaultTargetFrameworks").Bind); }); var host = configure(hostBuilder).UseConsoleLifetime(options => @@ -181,29 +181,18 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli private class HelpWithHeader : HelpBuilder { public HelpWithHeader(IConsole console) - : base(console, maxWidth: 90) + : base(console, maxWidth: ConsoleUtils.Width) { } - public override void Write(ICommand command) + protected override void AddSynopsis(ICommand command) { ShowHeader(); - WriteString("Makes a best-effort attempt to upgrade .NET Framework projects to .NET 5."); - WriteString("This tool does not completely automate the upgrade process and it is expected that projects will have build errors after the tool runs. Manual changes will be required to complete the upgrade to .NET 5."); - WriteString("This tool's purpose is to automate some of the 'routine' upgrade tasks such as changing project file formats and updating APIs with near-equivalents in .NET Core. Analyzers added to the project will highlight the remaining changes needed after the tool runs."); - - base.Write(command); - } - - private void WriteString(string input) - { - foreach (var line in SplitText(input, MaxWidth)) - { - Console.Out.WriteLine(line); - } - - Console.Out.WriteLine(); + const string Title = "Makes a best-effort attempt to upgrade .NET Framework projects to current, preview or LTS versions of .NET.\n\n" + + "This tool does not completely automate the upgrade process and it is expected that projects will have build errors after the tool runs. Manual changes will be required to complete the upgrade to .NET 5.\n\n" + + "This tool's purpose is to automate some of the 'routine' upgrade tasks such as changing project file formats and updating APIs with near-equivalents in the selected target framework. Analyzers added to the project will highlight the remaining changes needed after the tool runs.\n"; + WriteHeading(Title, null); } } @@ -218,6 +207,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Cli command.AddOption(new Option(new[] { "--verbose", "-v" }, "Enable verbose diagnostics")); command.AddOption(new Option(new[] { "--non-interactive" }, "Automatically select each first option in non-interactive mode.")); command.AddOption(new Option(new[] { "--non-interactive-wait" }, "Wait the supplied seconds before moving on to the next option in non-interactive mode.")); + command.AddOption(new Option(new[] { "--target-tfm-support" }, "Select if you would like the Long Term Support (LTS), Current, or Preview TFM. See https://dotnet.microsoft.com/platform/support/policy/dotnet-core for details for what these mean.")); } #if ANALYZE_COMMAND diff --git a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json index 5d7fc982..6dceed47 100644 --- a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json +++ b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json @@ -15,9 +15,10 @@ "Templates\\VisualBasicWebAppTemplates\\WebAppTemplates.json" ] }, - "TFMSelector": { - "CurrentTFMBase": "net5.0", - "LTSTFMBase": "net5.0" + "DefaultTargetFrameworks": { + "Current": "net5.0", + "LTS": "netcoreapp3.1", + "Preview": "net6.0" }, "TryConvertProjectConverter": { "TryConvertPath": "./tools/try-convert.exe" diff --git a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IPackageLoader.cs b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IPackageLoader.cs index b222eb17..33f40d14 100644 --- a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IPackageLoader.cs +++ b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IPackageLoader.cs @@ -9,12 +9,10 @@ namespace Microsoft.DotNet.UpgradeAssistant { public interface IPackageLoader { - IEnumerable PackageSources { get; } - Task DoesPackageSupportTargetFrameworksAsync(NuGetReference packageReference, IEnumerable targetFrameworks, CancellationToken token); - Task> GetNewerVersionsAsync(NuGetReference reference, bool latestMinorAndBuildOnly, CancellationToken token); + Task> GetNewerVersionsAsync(NuGetReference reference, IEnumerable tfms, bool latestMinorAndBuildOnly, CancellationToken token); - Task GetLatestVersionAsync(string packageName, bool includePreRelease, string[]? packageSources, CancellationToken token); + Task GetLatestVersionAsync(string packageName, IEnumerable tfms, bool includePreRelease, CancellationToken token); } } diff --git a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IUpgradeContextProperties.cs b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IUpgradeContextProperties.cs index f562febf..4474ea82 100644 --- a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IUpgradeContextProperties.cs +++ b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IUpgradeContextProperties.cs @@ -1,12 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; namespace Microsoft.DotNet.UpgradeAssistant { diff --git a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeOptions.cs b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeOptions.cs index 655cee0c..b4eff19e 100644 --- a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeOptions.cs +++ b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeOptions.cs @@ -27,6 +27,6 @@ namespace Microsoft.DotNet.UpgradeAssistant public int NonInteractiveWait { get; set; } = 2; - public UpgradeTarget UpgradeTarget { get; set; } = UpgradeTarget.Current; + public UpgradeTarget TargetTfmSupport { get; set; } = UpgradeTarget.Current; } } diff --git a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeTarget.cs b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeTarget.cs index 97db0a77..7b1d91f7 100644 --- a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeTarget.cs +++ b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/UpgradeTarget.cs @@ -6,6 +6,7 @@ namespace Microsoft.DotNet.UpgradeAssistant public enum UpgradeTarget { LTS, - Current + Current, + Preview } } diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/INuGetPackageSourceFactory.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/INuGetPackageSourceFactory.cs new file mode 100644 index 00000000..9df36975 --- /dev/null +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/INuGetPackageSourceFactory.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using NuGet.Configuration; + +namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +{ + public interface INuGetPackageSourceFactory + { + IEnumerable GetPackageSources(string? path); + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs index 0ce40a5f..74376a6c 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetDownloaderOptions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetDownloaderOptions.cs new file mode 100644 index 00000000..d65e5e61 --- /dev/null +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetDownloaderOptions.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +{ + public class NuGetDownloaderOptions + { + public string? CachePath { get; set; } + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetPackageSourceFactory.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetPackageSourceFactory.cs new file mode 100644 index 00000000..8d4c1599 --- /dev/null +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetPackageSourceFactory.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using NuGet.Configuration; + +namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +{ + public class NuGetPackageSourceFactory : INuGetPackageSourceFactory + { + private const string DefaultPackageSource = "https://api.nuget.org/v3/index.json"; + + private readonly ILogger _logger; + + public NuGetPackageSourceFactory(ILogger logger) + { + _logger = logger; + } + + public IEnumerable GetPackageSources(string? path) + { + var packageSources = new List(); + + if (path != null) + { + var nugetSettings = Settings.LoadDefaultSettings(path); + var sourceProvider = new PackageSourceProvider(nugetSettings); + packageSources.AddRange(sourceProvider.LoadPackageSources().Where(e => e.IsEnabled)); + } + + if (packageSources.Count == 0) + { + packageSources.Add(new PackageSource(DefaultPackageSource)); + } + + _logger.LogDebug("Found package sources: {PackageSources}", packageSources); + + return packageSources; + } + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageLoader.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageLoader.cs index cf111d13..aee10e2f 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageLoader.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageLoader.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NuGet.Configuration; using NuGet.Frameworks; using NuGet.Packaging; @@ -20,38 +22,47 @@ namespace Microsoft.DotNet.UpgradeAssistant.MSBuild { public sealed class PackageLoader : IPackageLoader, IDisposable { - private const string DefaultPackageSource = "https://api.nuget.org/v3/index.json"; private const int MaxRetries = 3; private readonly SourceCacheContext _cache; - private readonly List _packageSources; - private readonly ILogger _logger; + private readonly Lazy> _packageSources; + private readonly ILogger _logger; private readonly NuGet.Common.ILogger _nugetLogger; - private readonly string _cachePath; - private readonly IDictionary _sourceRepositoryCache; + private readonly Dictionary _sourceRepositoryCache; + private readonly NuGetDownloaderOptions _options; - public IEnumerable PackageSources => _packageSources.Select(s => s.Source); - - public PackageLoader(UpgradeOptions options, ILogger logger) + public PackageLoader( + UpgradeOptions upgradeOptions, + INuGetPackageSourceFactory sourceFactory, + ILogger logger, + IOptions options) { + if (upgradeOptions is null) + { + throw new ArgumentNullException(nameof(upgradeOptions)); + } + + if (sourceFactory is null) + { + throw new ArgumentNullException(nameof(sourceFactory)); + } + if (options is null) { throw new ArgumentNullException(nameof(options)); } - if (options.ProjectPath is null) + if (upgradeOptions.ProjectPath is null) { - throw new ArgumentException("Project path must be set in UpgradeOptions", nameof(options)); + throw new ArgumentException("Project path must be set in UpgradeOptions", nameof(upgradeOptions)); } _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _nugetLogger = new NuGetLogger(logger); _cache = new SourceCacheContext(); - _packageSources = GetPackageSources(Path.GetDirectoryName(options.ProjectPath)); + _packageSources = new Lazy>(() => sourceFactory.GetPackageSources(Path.GetDirectoryName(upgradeOptions.ProjectPath))); _sourceRepositoryCache = new Dictionary(); - - var settings = Settings.LoadDefaultSettings(null); - _cachePath = SettingsUtility.GetGlobalPackagesFolder(settings); + _options = options.Value; } public async Task DoesPackageSupportTargetFrameworksAsync(NuGetReference packageReference, IEnumerable targetFrameworks, CancellationToken token) @@ -68,23 +79,35 @@ namespace Microsoft.DotNet.UpgradeAssistant.MSBuild return targetFrameworks.All(tfm => packageFrameworks.Any(f => DefaultCompatibilityProvider.Instance.IsCompatible(NuGetFramework.Parse(tfm.Name), f))); } - public async Task> GetNewerVersionsAsync(NuGetReference reference, bool latestMinorAndBuildOnly, CancellationToken token) + public Task> GetNewerVersionsAsync(NuGetReference reference, IEnumerable tfms, bool latestMinorAndBuildOnly, CancellationToken token) { if (reference is null) { throw new ArgumentNullException(nameof(reference)); } - var versions = new List(); + return SearchByNameAsync(reference.Name, tfms, currentVersion: reference.GetNuGetVersion(), latestMinorAndBuildOnly: latestMinorAndBuildOnly, token: token); + } - // Query each package source for listed versions of the given package name - foreach (var source in _packageSources) + public async Task GetLatestVersionAsync(string packageName, IEnumerable tfms, bool includePreRelease, CancellationToken token) + { + var result = await SearchByNameAsync(packageName, tfms, includePreRelease, token: token).ConfigureAwait(false); + + return result.LastOrDefault(); + } + + private async Task> SearchByNameAsync(string name, IEnumerable tfms, bool includePrerelease = false, NuGetVersion? currentVersion = null, bool latestMinorAndBuildOnly = false, CancellationToken token = default) + { + var results = new List(); + + foreach (var source in _packageSources.Value) { try { var metadata = await GetSourceRepository(source).GetResourceAsync(token).ConfigureAwait(false); - var searchResults = await CallWithRetryAsync(() => metadata.GetMetadataAsync(reference.Name, includePrerelease: true, includeUnlisted: false, _cache, _nugetLogger, token)).ConfigureAwait(false); - versions.AddRange(searchResults.Select(r => r.Identity.Version)); + var searchResults = await CallWithRetryAsync(() => metadata.GetMetadataAsync(name, includePrerelease: includePrerelease, includeUnlisted: false, _cache, _nugetLogger, token)).ConfigureAwait(false); + + results.AddRange(searchResults); } catch (NuGetProtocolException) { @@ -96,76 +119,59 @@ namespace Microsoft.DotNet.UpgradeAssistant.MSBuild } } - // Filter to only include versions higher than the user's current version and, - // optionally, only the highest minor/build for each major version - var currentVersion = reference.GetNuGetVersion(); - var filteredVersions = versions.Distinct().Where(v => v > currentVersion); - var versionsToReturn = latestMinorAndBuildOnly - ? filteredVersions.GroupBy(v => v.Major).Select(v => v.Where(v => !v.IsPrerelease).Max() ?? v.Max()!) - : filteredVersions; - - _logger.LogDebug("Found versions for package {PackageName}: {PackageVersions}", reference.Name, versionsToReturn); - - return versionsToReturn.OrderBy(v => v).Select(v => reference with { Version = v.ToNormalizedString() }); + return FilterSearchResults(name, results, tfms, currentVersion, latestMinorAndBuildOnly); } - public async Task GetLatestVersionAsync(string packageName, bool includePreRelease, string[]? packageSources, CancellationToken token) + public static IEnumerable FilterSearchResults( + string name, + IReadOnlyCollection searchResults, + IEnumerable tfms, + NuGetVersion? currentVersion = null, + bool latestMinorAndBuildOnly = false) { - NuGetVersion? highestVersion = null; - - // Query each package source for listed versions of the given package name - foreach (var source in packageSources?.Select(p => new PackageSource(p)) ?? _packageSources) + if (searchResults is null || searchResults.Count == 0) { - try - { - var metadata = await GetSourceRepository(source).GetResourceAsync(token).ConfigureAwait(false); - var searchResults = await CallWithRetryAsync(() => metadata.GetMetadataAsync(packageName, includePrerelease: includePreRelease, includeUnlisted: false, _cache, _nugetLogger, token)).ConfigureAwait(false); - var highestVersionResult = searchResults.Select(r => r.Identity.Version).Max(v => v); - if (highestVersionResult is null) + return Enumerable.Empty(); + } + + var tfmSet = ImmutableHashSet.CreateRange(tfms.Select(t => NuGetFramework.Parse(t.Name))); + + var results = searchResults + .Where(r => currentVersion is null || r.Identity.Version > currentVersion); + + if (latestMinorAndBuildOnly) + { + results = results + .GroupBy(r => r.Identity.Version.Major) + .SelectMany(r => { - continue; + var max = r.Max(t => t.Identity.Version); + + return r.Where(t => t.Identity.Version == max); + }); + } + + return results + .Where(r => + { + var unsupported = tfmSet; + + foreach (var dep in r.DependencySets) + { + foreach (var t in unsupported) + { + if (DefaultCompatibilityProvider.Instance.IsCompatible(t, dep.TargetFramework)) + { + unsupported = unsupported.Remove(t); + } + } } - if (highestVersion is null || highestVersionResult > highestVersion) - { - highestVersion = highestVersionResult; - } - } - catch (NuGetProtocolException) - { - _logger.LogWarning("Failed to get package versions from source {PackageSource} due to a NuGet protocol error", source.Source); - } - catch (HttpRequestException exc) - { - _logger.LogWarning("Failed to get package versions from source {PackageSource} due to an HTTP error ({StatusCode})", source.Source, exc.StatusCode); - } - } - - if (highestVersion is null) - { - return null; - } - - return new NuGetReference(packageName, highestVersion.ToFullString()); - } - - private List GetPackageSources(string? projectDir) - { - var packageSources = new List(); - if (projectDir != null) - { - var nugetSettings = Settings.LoadDefaultSettings(projectDir); - var sourceProvider = new PackageSourceProvider(nugetSettings); - packageSources.AddRange(sourceProvider.LoadPackageSources().Where(e => e.IsEnabled)); - } - - if (packageSources.Count == 0) - { - packageSources.Add(new PackageSource(DefaultPackageSource)); - } - - _logger.LogDebug("Found package sources: {PackageSources}", packageSources); - return packageSources; + return unsupported.IsEmpty; + }) + .Select(r => r.Identity.Version) + .OrderBy(v => v) + .Select(v => new NuGetReference(name, v.ToNormalizedString())); } private async Task CallWithRetryAsync(Func> func) @@ -240,9 +246,9 @@ namespace Microsoft.DotNet.UpgradeAssistant.MSBuild } // First look in the local NuGet cache for the archive - if (_cachePath is not null) + if (_options.CachePath is string cachePath) { - var archivePath = Path.Combine(_cachePath, packageReference.Name, packageReference.Version, $"{packageReference.Name}.{packageReference.Version}.nupkg"); + var archivePath = Path.Combine(cachePath, packageReference.Name, packageReference.Version, $"{packageReference.Name}.{packageReference.Version}.nupkg"); if (File.Exists(archivePath)) { _logger.LogDebug("NuGet package {NuGetPackage} loaded from {PackagePath}", packageReference, archivePath); @@ -256,7 +262,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.MSBuild // Attempt to download the package from the sources var packageVersion = packageReference.GetNuGetVersion(); - foreach (var source in _packageSources) + foreach (var source in _packageSources.Value) { var repo = GetSourceRepository(source); var packageFinder = await repo.GetResourceAsync(token).ConfigureAwait(false); diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs index 63eac457..187c2ef9 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.DotNet.UpgradeAssistant.MSBuild; using Microsoft.Extensions.DependencyInjection; +using NuGet.Configuration; namespace Microsoft.DotNet.UpgradeAssistant { @@ -22,15 +23,28 @@ namespace Microsoft.DotNet.UpgradeAssistant services.AddTransient(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); // Instantiate the upgrade context with a func to avoid needing MSBuild types prior to MSBuild registration services.AddTransient(); services.AddTransient(sp => sp.GetRequiredService()); services.AddTransient>(sp => () => sp.GetRequiredService()); + + services.AddNuGet(); + } + + private static void AddNuGet(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddOptions() + .Configure(options => + { + var settings = Settings.LoadDefaultSettings(null); + options.CachePath = SettingsUtility.GetGlobalPackagesFolder(settings); + }); } // TEMPORARY WORKAROUND diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant/Microsoft.DotNet.UpgradeAssistant.csproj b/src/components/Microsoft.DotNet.UpgradeAssistant/Microsoft.DotNet.UpgradeAssistant.csproj index 6f5a7739..ac581422 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant/Microsoft.DotNet.UpgradeAssistant.csproj +++ b/src/components/Microsoft.DotNet.UpgradeAssistant/Microsoft.DotNet.UpgradeAssistant.csproj @@ -4,6 +4,7 @@ + diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DefaultTfmOptions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DefaultTfmOptions.cs new file mode 100644 index 00000000..1e54ee53 --- /dev/null +++ b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DefaultTfmOptions.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.DotNet.UpgradeAssistant +{ + public class DefaultTfmOptions + { + [Required] + public string Current { get; set; } = null!; + + [Required] + public string LTS { get; set; } = null!; + + [Required] + public string Preview { get; set; } = null!; + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DependencyMinimumTargetFrameworkSelectorFilter.cs b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DependencyMinimumTargetFrameworkSelectorFilter.cs index 6ffe2ca9..7fc9beec 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DependencyMinimumTargetFrameworkSelectorFilter.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/DependencyMinimumTargetFrameworkSelectorFilter.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Linq; namespace Microsoft.DotNet.UpgradeAssistant.TargetFramework diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TFMSelectorOptions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TFMSelectorOptions.cs deleted file mode 100644 index 0c579697..00000000 --- a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TFMSelectorOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.UpgradeAssistant -{ - public class TFMSelectorOptions - { - public string? CurrentTFMBase { get; set; } - - public string? LTSTFMBase { get; set; } - } -} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TargetFrameworkSelector.cs b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TargetFrameworkSelector.cs index cd042272..e8826492 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TargetFrameworkSelector.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/TargetFrameworkSelector.cs @@ -12,32 +12,26 @@ namespace Microsoft.DotNet.UpgradeAssistant.TargetFramework { public class TargetFrameworkSelector : ITargetFrameworkSelector { - private const string DefaultCurrentTFMBase = "net5.0"; - private const string DefaultLTSTFMBase = "net5.0"; - private readonly ITargetFrameworkMonikerComparer _comparer; + private readonly DefaultTfmOptions _selectorOptions; private readonly IEnumerable _selectors; private readonly ILogger _logger; - private readonly string _currentTFMBase; - private readonly string _ltsTFMBase; - private readonly UpgradeTarget _upgradeTarget; public TargetFrameworkSelector( UpgradeOptions options, ITargetFrameworkMonikerComparer comparer, - IOptions selectorOptions, + IOptions selectorOptions, IEnumerable selectors, ILogger logger) { _comparer = comparer; + _selectorOptions = selectorOptions?.Value ?? throw new ArgumentNullException(nameof(selectorOptions)); _selectors = selectors; _logger = logger; - _currentTFMBase = selectorOptions?.Value.CurrentTFMBase ?? DefaultCurrentTFMBase; - _ltsTFMBase = selectorOptions?.Value.LTSTFMBase ?? DefaultLTSTFMBase; - _upgradeTarget = options?.UpgradeTarget ?? throw new ArgumentNullException(nameof(options)); + _upgradeTarget = options?.TargetTfmSupport ?? throw new ArgumentNullException(nameof(options)); } public async ValueTask SelectTargetFrameworkAsync(IProject project, CancellationToken token) @@ -47,7 +41,14 @@ namespace Microsoft.DotNet.UpgradeAssistant.TargetFramework throw new ArgumentNullException(nameof(project)); } - var appBase = _upgradeTarget == UpgradeTarget.Current ? _currentTFMBase : _ltsTFMBase; + var appBase = _upgradeTarget switch + { + UpgradeTarget.Current => _selectorOptions.Current, + UpgradeTarget.Preview => _selectorOptions.Preview, + UpgradeTarget.LTS => _selectorOptions.LTS, + _ => _selectorOptions.LTS, + }; + var current = GetDefaultTargetFrameworkMoniker(project); if (!_comparer.TryParse(appBase, out var appBaseTfm)) diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/WindowsSdkTargetFrameworkSelectorFilter.cs b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/WindowsSdkTargetFrameworkSelectorFilter.cs index a95b6d87..40a60261 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/WindowsSdkTargetFrameworkSelectorFilter.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant/TargetFramework/WindowsSdkTargetFrameworkSelectorFilter.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics.CodeAnalysis; namespace Microsoft.DotNet.UpgradeAssistant.TargetFramework diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant/UpgraderExtensions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant/UpgraderExtensions.cs index d9ef82d3..5c1680d2 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant/UpgraderExtensions.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant/UpgraderExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using Microsoft.DotNet.UpgradeAssistant.Checks; using Microsoft.DotNet.UpgradeAssistant.TargetFramework; using Microsoft.Extensions.DependencyInjection; @@ -9,13 +10,16 @@ namespace Microsoft.DotNet.UpgradeAssistant { public static class UpgraderExtensions { - public static void AddStepManagement(this IServiceCollection services) + public static void AddStepManagement(this IServiceCollection services, Action options) { services.AddScoped(); services.AddTransient(); services.AddTransient(); services.AddReadinessChecks(); services.AddTargetFrameworkSelectors(); + services.AddOptions() + .Configure(options) + .ValidateDataAnnotations(); } private static void AddReadinessChecks(this IServiceCollection services) diff --git a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/NewtonsoftReferenceAnalyzer.cs b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/NewtonsoftReferenceAnalyzer.cs index dbdd73ee..d6e82862 100644 --- a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/NewtonsoftReferenceAnalyzer.cs +++ b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/NewtonsoftReferenceAnalyzer.cs @@ -62,7 +62,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Analyzers if (!packageReferences.Any(r => NewtonsoftPackageName.Equals(r.Name, StringComparison.OrdinalIgnoreCase))) { - var newtonsoftPackage = await _packageLoader.GetLatestVersionAsync(NewtonsoftPackageName, false, null, token).ConfigureAwait(false); + var newtonsoftPackage = await _packageLoader.GetLatestVersionAsync(NewtonsoftPackageName, project.TargetFrameworks, false, token).ConfigureAwait(false); if (newtonsoftPackage is not null) { diff --git a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/PackageMapReferenceAnalyzer.cs b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/PackageMapReferenceAnalyzer.cs index a30f37af..7af36327 100644 --- a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/PackageMapReferenceAnalyzer.cs +++ b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/PackageMapReferenceAnalyzer.cs @@ -109,7 +109,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Analyzers var packageToAdd = newPackage; if (packageToAdd.HasWildcardVersion) { - var reference = await _packageLoader.GetLatestVersionAsync(packageToAdd.Name, false, null, token).ConfigureAwait(false); + var reference = await _packageLoader.GetLatestVersionAsync(packageToAdd.Name, project.TargetFrameworks, false, token).ConfigureAwait(false); if (reference is not null) { diff --git a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/TargetCompatibilityReferenceAnalyzer.cs b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/TargetCompatibilityReferenceAnalyzer.cs index 23099371..9dda90e3 100644 --- a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/TargetCompatibilityReferenceAnalyzer.cs +++ b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/TargetCompatibilityReferenceAnalyzer.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -53,7 +52,9 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Analyzers else { // If the package won't work on the target Framework, check newer versions of the package - var updatedReference = await GetUpdatedPackageVersionAsync(packageReference, project.TargetFrameworks, token).ConfigureAwait(false); + var newerVersions = await _packageLoader.GetNewerVersionsAsync(packageReference, project.TargetFrameworks, true, token).ConfigureAwait(false); + var updatedReference = newerVersions.FirstOrDefault(); + if (updatedReference == null) { _logger.LogWarning("No version of {PackageName} found that supports {TargetFramework}; leaving unchanged", packageReference.Name, project.TargetFrameworks); @@ -82,34 +83,5 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Analyzers return state; } - - private async Task GetUpdatedPackageVersionAsync(NuGetReference packageReference, IEnumerable targetFramework, CancellationToken token) - { - var latestMinorVersions = await _packageLoader.GetNewerVersionsAsync(packageReference, true, token).ConfigureAwait(false); - NuGetReference? prereleaseCandidate = null; - - foreach (var newerPackage in latestMinorVersions) - { - if (await _packageLoader.DoesPackageSupportTargetFrameworksAsync(newerPackage, targetFramework, token).ConfigureAwait(false)) - { - _logger.LogDebug("Package {NuGetPackage} will work on {TargetFramework}", newerPackage, targetFramework); - - // Only return a pre-release version if it's the only newer major version that supports the necessary TFM - if (newerPackage.IsPrerelease) - { - if (prereleaseCandidate is null) - { - prereleaseCandidate = newerPackage; - } - } - else - { - return newerPackage; - } - } - } - - return prereleaseCandidate; - } } } diff --git a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/UpgradeAssistantReferenceAnalyzer.cs b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/UpgradeAssistantReferenceAnalyzer.cs index 92a3475f..dadc2b0b 100644 --- a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/UpgradeAssistantReferenceAnalyzer.cs +++ b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/UpgradeAssistantReferenceAnalyzer.cs @@ -32,18 +32,23 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Analyzers public async Task AnalyzeAsync(IProject project, PackageAnalysisState state, CancellationToken token) { + if (project is null) + { + throw new ArgumentNullException(nameof(project)); + } + if (state is null) { throw new ArgumentNullException(nameof(state)); } - var references = await project.Required().GetNuGetReferencesAsync(token).ConfigureAwait(false); + var references = await project.GetNuGetReferencesAsync(token).ConfigureAwait(false); var packageReferences = references.PackageReferences.Where(r => !state.PackagesToRemove.Contains(r)); // If the project doesn't include a reference to the analyzer package, mark it for addition if (!packageReferences.Any(r => AnalyzerPackageName.Equals(r.Name, StringComparison.OrdinalIgnoreCase))) { - var analyzerPackage = await _packageLoader.GetLatestVersionAsync(AnalyzerPackageName, true, null, token).ConfigureAwait(false); + var analyzerPackage = await _packageLoader.GetLatestVersionAsync(AnalyzerPackageName, project.TargetFrameworks, true, token).ConfigureAwait(false); if (analyzerPackage is not null) { diff --git a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/WindowsCompatReferenceAnalyzer.cs b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/WindowsCompatReferenceAnalyzer.cs index c9652b68..8a50a4c1 100644 --- a/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/WindowsCompatReferenceAnalyzer.cs +++ b/src/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages/Analyzers/WindowsCompatReferenceAnalyzer.cs @@ -54,7 +54,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Analyzers return state; } - var latestVersion = await _loader.GetLatestVersionAsync(PackageName, false, null, token).ConfigureAwait(false); + var latestVersion = await _loader.GetLatestVersionAsync(PackageName, project.TargetFrameworks, false, token).ConfigureAwait(false); if (latestVersion is null) { diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/PackageLoaderTests.cs b/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/PackageLoaderTests.cs new file mode 100644 index 00000000..c586348a --- /dev/null +++ b/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/PackageLoaderTests.cs @@ -0,0 +1,271 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using AutoFixture; +using Moq; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Protocol.Core.Types; +using Xunit; + +namespace Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests +{ + [Collection(MSBuildStepTestCollection.Name)] + public class PackageLoaderTests + { + private readonly Fixture _fixture; + + public PackageLoaderTests() + { + _fixture = new Fixture(); + + _fixture.Customize(o => + o.Without(o => o.PrivateAssets) + .Without(o => o.Version)); + } + + [InlineData(true)] + [InlineData(false)] + [Theory] + public void FilterTestEmptyInput(bool isNull) + { + // Arrange + var name = _fixture.Create(); + var searchResults = isNull ? null : Array.Empty(); + var tfms = _fixture.CreateMany(); + + // Act + var result = PackageLoader.FilterSearchResults(name, searchResults!, tfms); + + // Assert + Assert.Empty(result); + } + + private static IPackageSearchMetadata MockSearchMetadata(NuGetReference reference, params TargetFrameworkMoniker[] tfms) + { + var metadata = new Mock(); + var identity = new PackageIdentity(reference.Name, reference.GetNuGetVersion()); + var groups = tfms.Select(tfm => new PackageDependencyGroup(NuGetFramework.Parse(tfm.Name), Enumerable.Empty())); + + metadata.Setup(m => m.Identity).Returns(identity); + metadata.Setup(m => m.DependencySets).Returns(groups); + + return metadata.Object; + } + + [Fact] + public void FilterExplicitMatch() + { + // Arrange + var item = _fixture.Create() with { Version = "1.0.0" }; + var tfm = TargetFrameworkMoniker.NetStandard20; + var metadata = MockSearchMetadata(item, tfm); + + // Act + var result = PackageLoader.FilterSearchResults(item.Name, new[] { metadata }, new[] { tfm }); + + // Assert + Assert.Collection(result, r => Assert.Equal(r, item)); + } + + [InlineData(true)] + [InlineData(false)] + [Theory] + public void FilterSupported(bool isBackwards) + { + // Arrange + var item1 = _fixture.Create() with { Version = "1.0.0" }; + var item2 = item1 with { Version = "2.0.0" }; + var metadata1 = MockSearchMetadata(item1, TargetFrameworkMoniker.NetStandard20); + var metadata2 = MockSearchMetadata(item2, TargetFrameworkMoniker.Net50); + + var list = isBackwards ? new[] { metadata2, metadata1 } : new[] { metadata1, metadata2 }; + + // Act + var result = PackageLoader.FilterSearchResults(item1.Name, list, new[] { TargetFrameworkMoniker.Net50 }); + + // Assert + Assert.Collection(result, + r => Assert.Equal(r, item1), + r => Assert.Equal(r, item2)); + } + + [Fact] + public void Filter3Only2Supported() + { + // Arrange + var item1 = _fixture.Create() with { Version = "1.0.0" }; + var item2 = item1 with { Version = "2.0.0" }; + var item3 = item1 with { Version = "3.0.0" }; + + var metadata1 = MockSearchMetadata(item1, TargetFrameworkMoniker.NetStandard20); + var metadata2 = MockSearchMetadata(item2, TargetFrameworkMoniker.Net50); + var metadata3 = MockSearchMetadata(item3, TargetFrameworkMoniker.Net60); + + foreach (var metadata in Permute(metadata1, metadata2, metadata3)) + { + // Act + var result = PackageLoader.FilterSearchResults(item1.Name, metadata, new[] { TargetFrameworkMoniker.Net50 }); + + // Assert + Assert.Collection(result, + r => Assert.Equal(r, item1), + r => Assert.Equal(r, item2)); + } + } + + [Fact] + public void Filter3Only1Supported() + { + // Arrange + var item1 = _fixture.Create() with { Version = "1.0.0" }; + var item2 = item1 with { Version = "2.0.0" }; + var item3 = item1 with { Version = "3.0.0" }; + + var metadata1 = MockSearchMetadata(item1, TargetFrameworkMoniker.Net45); + var metadata2 = MockSearchMetadata(item2, TargetFrameworkMoniker.Net50); + var metadata3 = MockSearchMetadata(item3, TargetFrameworkMoniker.Net60); + + foreach (var metadata in Permute(metadata1, metadata2, metadata3)) + { + // Act + var result = PackageLoader.FilterSearchResults(item1.Name, metadata, new[] { TargetFrameworkMoniker.Net50 }); + + // Assert + Assert.Collection(result, r => Assert.Equal(r, item2)); + } + } + + [Fact] + public void Filter3Only1SupportedMultipleTfmsInSearch() + { + // Arrange + var item1 = _fixture.Create() with { Version = "1.0.0" }; + var item2 = item1 with { Version = "2.0.0" }; + var item3 = item1 with { Version = "3.0.0" }; + + var metadata1 = MockSearchMetadata(item1, TargetFrameworkMoniker.NetStandard10, TargetFrameworkMoniker.NetStandard20); + var metadata2 = MockSearchMetadata(item2, TargetFrameworkMoniker.Net45, TargetFrameworkMoniker.Net50); + var metadata3 = MockSearchMetadata(item3, TargetFrameworkMoniker.Net50_Linux, TargetFrameworkMoniker.Net50_Windows); + + foreach (var metadata in Permute(metadata1, metadata2, metadata3)) + { + // Act + var result = PackageLoader.FilterSearchResults(item1.Name, metadata, new[] { TargetFrameworkMoniker.Net50 }); + + // Assert + Assert.Collection(result, + r => Assert.Equal(r, item1), + r => Assert.Equal(r, item2)); + } + } + + [Fact] + public void Filter3Only1SupportedMultipleTfms() + { + // Arrange + var item1 = _fixture.Create() with { Version = "1.0.0" }; + var item2 = item1 with { Version = "2.0.0" }; + var item3 = item1 with { Version = "3.0.0" }; + + var metadata1 = MockSearchMetadata(item1, TargetFrameworkMoniker.NetStandard10, TargetFrameworkMoniker.NetStandard20); + var metadata2 = MockSearchMetadata(item2, TargetFrameworkMoniker.Net45, TargetFrameworkMoniker.Net50); + var metadata3 = MockSearchMetadata(item3, TargetFrameworkMoniker.Net50_Linux, TargetFrameworkMoniker.Net50_Windows); + + foreach (var metadata in Permute(metadata1, metadata2, metadata3)) + { + // Act + var result = PackageLoader.FilterSearchResults(item1.Name, metadata, new[] { TargetFrameworkMoniker.Net50, TargetFrameworkMoniker.NetStandard20 }); + + // Assert + Assert.Collection(result, r => Assert.Equal(r, item1)); + } + } + + [Fact] + public void Filter3Only1SupportedMultipleTfmsNoMatches() + { + // Arrange + var item1 = _fixture.Create() with { Version = "1.0.0" }; + var item2 = item1 with { Version = "2.0.0" }; + var item3 = item1 with { Version = "3.0.0" }; + + var metadata1 = MockSearchMetadata(item1, TargetFrameworkMoniker.Net45, TargetFrameworkMoniker.NetCoreApp21); + var metadata2 = MockSearchMetadata(item2, TargetFrameworkMoniker.Net45, TargetFrameworkMoniker.Net50); + var metadata3 = MockSearchMetadata(item3, TargetFrameworkMoniker.Net50_Linux, TargetFrameworkMoniker.Net50_Windows); + + foreach (var metadata in Permute(metadata1, metadata2, metadata3)) + { + // Act + var result = PackageLoader.FilterSearchResults(item1.Name, metadata, new[] { TargetFrameworkMoniker.Net50, TargetFrameworkMoniker.NetStandard20 }); + + // Assert + Assert.Empty(result); + } + } + + [Fact] + public void FilterOnly() + { + // Arrange + var item1 = _fixture.Create() with { Version = "1.0.0" }; + var item2 = item1 with { Version = "1.1.0" }; + var item3 = item1 with { Version = "1.2.0" }; + var item4 = item1 with { Version = "2.1.0" }; + + var metadata1 = MockSearchMetadata(item1, TargetFrameworkMoniker.NetStandard20); + var metadata2 = MockSearchMetadata(item2, TargetFrameworkMoniker.NetStandard20); + var metadata3 = MockSearchMetadata(item3, TargetFrameworkMoniker.NetStandard20); + var metadata4 = MockSearchMetadata(item4, TargetFrameworkMoniker.NetStandard20); + + foreach (var metadata in Permute(metadata1, metadata2, metadata3, metadata4)) + { + // Act + var result = PackageLoader.FilterSearchResults(item1.Name, metadata, new[] { TargetFrameworkMoniker.Net50 }, latestMinorAndBuildOnly: true); + + // Assert + Assert.Collection(result, + r => Assert.Equal(r, item3), + r => Assert.Equal(r, item4)); + } + } + + private static IReadOnlyCollection> Permute(params T[] nums) + { + var list = new List>(); + return DoPermute(nums, 0, nums.Length - 1, list); + + static List> DoPermute(T[] nums, int start, int end, List> list) + { + if (start == end) + { + // We have one of our possible n! solutions, + // add it to the list. + list.Add(new List(nums)); + } + else + { + for (var i = start; i <= end; i++) + { + Swap(ref nums[start], ref nums[i]); + DoPermute(nums, start + 1, end, list); + Swap(ref nums[start], ref nums[i]); + } + } + + return list; + + static void Swap(ref T a, ref T b) + { + var temp = a; + a = b; + b = temp; + } + } + } + } +} diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/TargetFramework/TargetFrameworkSelectorTests.cs b/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/TargetFramework/TargetFrameworkSelectorTests.cs index 9fcf7ffa..e06105cb 100644 --- a/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/TargetFramework/TargetFrameworkSelectorTests.cs +++ b/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/TargetFramework/TargetFrameworkSelectorTests.cs @@ -16,10 +16,11 @@ namespace Microsoft.DotNet.UpgradeAssistant.Tests { public class TargetFrameworkSelectorTests { - private readonly TFMSelectorOptions _options = new TFMSelectorOptions + private readonly DefaultTfmOptions _options = new DefaultTfmOptions { - CurrentTFMBase = Current, - LTSTFMBase = LTS, + Current = Current, + LTS = LTS, + Preview = Preview, }; [Fact] @@ -61,14 +62,14 @@ namespace Microsoft.DotNet.UpgradeAssistant.Tests project.Setup(p => p.TargetFrameworks).Returns(currentTfms.Select(t => ParseTfm(t)).ToArray()); project.Setup(p => p.GetComponentsAsync(default)).Returns(new ValueTask(components)); - mock.Create().UpgradeTarget = target; - mock.Mock>().Setup(o => o.Value).Returns(_options); + mock.Create().TargetTfmSupport = target; + mock.Mock>().Setup(o => o.Value).Returns(_options); var moniker = mock.Mock(); moniker.Setup(c => c.TryMerge(ParseTfm(current), tfm, out finalTfm)).Returns(true); moniker.SetupTryParse(); - var appBase = target == UpgradeTarget.Current ? _options.CurrentTFMBase : _options.LTSTFMBase; + var appBase = target == UpgradeTarget.Current ? _options.Current : _options.LTS; var selector = mock.Create(); diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/UpdateContextPropertiesTests.cs b/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/UpdateContextPropertiesTests.cs index 58890554..f6f7a09d 100644 --- a/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/UpdateContextPropertiesTests.cs +++ b/tests/components/Microsoft.DotNet.UpgradeAssistant.Tests/UpdateContextPropertiesTests.cs @@ -1,14 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Autofac; -using Autofac.Extras.Moq; -using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.DotNet.UpgradeAssistant.Tests diff --git a/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Backup.Tests/BackupStepTests.cs b/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Backup.Tests/BackupStepTests.cs index e5e7faf7..918148b9 100644 --- a/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Backup.Tests/BackupStepTests.cs +++ b/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Backup.Tests/BackupStepTests.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Autofac; diff --git a/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests/Analyzers/NewtonsoftReferenceAnalyzerTests.cs b/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests/Analyzers/NewtonsoftReferenceAnalyzerTests.cs index 8245d5ac..9ae75819 100644 --- a/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests/Analyzers/NewtonsoftReferenceAnalyzerTests.cs +++ b/tests/steps/Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests/Analyzers/NewtonsoftReferenceAnalyzerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Autofac.Extras.Moq; @@ -36,7 +37,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests.Analyzers // Assert Assert.Contains(actual.PackagesToAdd, (package) => package.Name.Equals(NewtonsoftPackageName, System.StringComparison.Ordinal)); - packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once()); } @@ -66,7 +67,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests.Analyzers // Assert Assert.DoesNotContain(actual.PackagesToAdd, (package) => package.Name.Equals(NewtonsoftPackageName, System.StringComparison.Ordinal)); - packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never()); } @@ -93,7 +94,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests.Analyzers // Assert Assert.DoesNotContain(actual.PackagesToAdd, (package) => package.Name.Equals(NewtonsoftPackageName, System.StringComparison.Ordinal)); - packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never()); } @@ -120,7 +121,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests.Analyzers // Assert Assert.DoesNotContain(actual.PackagesToAdd, (package) => package.Name.Equals(NewtonsoftPackageName, System.StringComparison.Ordinal)); - packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + packageLoader.Verify(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never()); } @@ -155,7 +156,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.Packages.Tests.Analyzers private static Mock CreatePackageLoader(AutoMock mock) { var packageLoader = mock.Mock(); - packageLoader.Setup(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + packageLoader.Setup(pl => pl.GetLatestVersionAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult((NuGetReference?)new NuGetReference(NewtonsoftPackageName, "122.0.0"))); return packageLoader; } diff --git a/tests/tool/Integration.Tests/ExpectedPackageVersions.json b/tests/tool/Integration.Tests/ExpectedPackageVersions.json index 7efe7610..43e7f712 100644 --- a/tests/tool/Integration.Tests/ExpectedPackageVersions.json +++ b/tests/tool/Integration.Tests/ExpectedPackageVersions.json @@ -2,7 +2,7 @@ "Hyak.Common": "1.2.2", "MahApps.Metro": "2.4.4", "Microsoft.AspNetCore.Mvc.NewtonsoftJson": "5.0.5", - "Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers": "1.0.0", + "Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers": "0.2.222702", "Microsoft.Windows.Compatibility": "5.0.2", "Newtonsoft.Json": "9.0.1", "Nito.AsyncEx": "5.1.0" diff --git a/tests/tool/Integration.Tests/IntegrationScenarios/AspNetSample/csharp/Upgraded/TemplateMvc.csproj b/tests/tool/Integration.Tests/IntegrationScenarios/AspNetSample/csharp/Upgraded/TemplateMvc.csproj index 9a142273..e77b987d 100644 --- a/tests/tool/Integration.Tests/IntegrationScenarios/AspNetSample/csharp/Upgraded/TemplateMvc.csproj +++ b/tests/tool/Integration.Tests/IntegrationScenarios/AspNetSample/csharp/Upgraded/TemplateMvc.csproj @@ -28,7 +28,7 @@ - + all diff --git a/tests/tool/Integration.Tests/IntegrationScenarios/PCL/Upgraded/SamplePCL.csproj b/tests/tool/Integration.Tests/IntegrationScenarios/PCL/Upgraded/SamplePCL.csproj index 03bcc244..53e4bdf4 100644 --- a/tests/tool/Integration.Tests/IntegrationScenarios/PCL/Upgraded/SamplePCL.csproj +++ b/tests/tool/Integration.Tests/IntegrationScenarios/PCL/Upgraded/SamplePCL.csproj @@ -11,7 +11,7 @@ - + all diff --git a/tests/tool/Integration.Tests/IntegrationScenarios/WebLibrary/csharp/Upgraded/WebLibrary.csproj b/tests/tool/Integration.Tests/IntegrationScenarios/WebLibrary/csharp/Upgraded/WebLibrary.csproj index 39f0c855..af78f3f5 100644 --- a/tests/tool/Integration.Tests/IntegrationScenarios/WebLibrary/csharp/Upgraded/WebLibrary.csproj +++ b/tests/tool/Integration.Tests/IntegrationScenarios/WebLibrary/csharp/Upgraded/WebLibrary.csproj @@ -9,7 +9,7 @@ - + all diff --git a/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderClient/BeanTraderClient.csproj b/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderClient/BeanTraderClient.csproj index 1d3617f0..272eedb7 100644 --- a/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderClient/BeanTraderClient.csproj +++ b/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderClient/BeanTraderClient.csproj @@ -34,7 +34,7 @@ - + all diff --git a/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderInterfaces/BeanTraderCommon.csproj b/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderInterfaces/BeanTraderCommon.csproj index c5f46b88..120e3a2d 100644 --- a/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderInterfaces/BeanTraderCommon.csproj +++ b/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/csharp/Upgraded/BeanTraderInterfaces/BeanTraderCommon.csproj @@ -12,7 +12,7 @@ - + all diff --git a/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/vb/Upgraded/WpfApp1/WpfApp1.vbproj b/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/vb/Upgraded/WpfApp1/WpfApp1.vbproj index 78d2722a..bc3f3149 100644 --- a/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/vb/Upgraded/WpfApp1/WpfApp1.vbproj +++ b/tests/tool/Integration.Tests/IntegrationScenarios/WpfSample/vb/Upgraded/WpfApp1/WpfApp1.vbproj @@ -46,7 +46,7 @@ - + all diff --git a/tests/tool/Integration.Tests/InterceptingKnownPackageLoader.cs b/tests/tool/Integration.Tests/InterceptingKnownPackageLoader.cs index a035598b..d95fa1fb 100644 --- a/tests/tool/Integration.Tests/InterceptingKnownPackageLoader.cs +++ b/tests/tool/Integration.Tests/InterceptingKnownPackageLoader.cs @@ -26,21 +26,19 @@ namespace Integration.Tests _unknownPackages = unknownPackages ?? throw new ArgumentNullException(nameof(unknownPackages)); } - public IEnumerable PackageSources => _other.PackageSources; - public Task DoesPackageSupportTargetFrameworksAsync(NuGetReference packageReference, IEnumerable targetFrameworks, CancellationToken token) { return _other.DoesPackageSupportTargetFrameworksAsync(packageReference, targetFrameworks, token); } - public async Task GetLatestVersionAsync(string packageName, bool includePreRelease, string[]? packageSources, CancellationToken token) + public async Task GetLatestVersionAsync(string packageName, IEnumerable tfms, bool includePreRelease, CancellationToken token) { if (_packages.TryGetValue(packageName, out var known)) { return known; } - var latest = await _other.GetLatestVersionAsync(packageName, includePreRelease, packageSources, token).ConfigureAwait(false); + var latest = await _other.GetLatestVersionAsync(packageName, tfms, includePreRelease, token).ConfigureAwait(false); if (latest is not null) { @@ -51,14 +49,14 @@ namespace Integration.Tests return latest; } - public async Task> GetNewerVersionsAsync(NuGetReference reference, bool latestMinorAndBuildOnly, CancellationToken token) + public async Task> GetNewerVersionsAsync(NuGetReference reference, IEnumerable tfms, bool latestMinorAndBuildOnly, CancellationToken token) { if (_packages.TryGetValue(reference.Name, out var known)) { return new NuGetReference[] { known }; } - var latest = await _other.GetNewerVersionsAsync(reference, latestMinorAndBuildOnly, token).ConfigureAwait(false); + var latest = await _other.GetNewerVersionsAsync(reference, tfms, latestMinorAndBuildOnly, token).ConfigureAwait(false); if (latest is not null) { @@ -67,7 +65,7 @@ namespace Integration.Tests _logger.LogError("Unexpected check for newer version: {Name}, {Version}", reference.Name, reference.Version); } - return latest ?? Array.Empty(); + return latest ?? Enumerable.Empty(); } } }