diff --git a/Microsoft.DotNet.UpgradeAssistant.sln b/Microsoft.DotNet.UpgradeAssistant.sln index 53dd67794..5cd467835 100644 --- a/Microsoft.DotNet.UpgradeAssistant.sln +++ b/Microsoft.DotNet.UpgradeAssistant.sln @@ -172,6 +172,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.UpgradeAss EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.UpgradeAssistant.Steps.Source.Tests", "tests\extensions\default\Microsoft.DotNet.UpgradeAssistant.Steps.Source.Tests\Microsoft.DotNet.UpgradeAssistant.Steps.Source.Tests.csproj", "{134CE4A1-E9A4-49C0-A998-7C531E6207CE}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{021F1610-F2D0-4F00-81C7-AD7397DA296C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet", "src\extensions\nuget\Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet\Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.csproj", "{E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{824AFFC6-40A2-4AD6-8010-B939841CA16E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests", "tests\extensions\nuget\Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests\Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests.csproj", "{58633641-85F3-4F8E-AC18-0EFACC36D142}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -758,6 +766,30 @@ Global {134CE4A1-E9A4-49C0-A998-7C531E6207CE}.Release|x64.Build.0 = Release|Any CPU {134CE4A1-E9A4-49C0-A998-7C531E6207CE}.Release|x86.ActiveCfg = Release|Any CPU {134CE4A1-E9A4-49C0-A998-7C531E6207CE}.Release|x86.Build.0 = Release|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Debug|x64.ActiveCfg = Debug|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Debug|x64.Build.0 = Debug|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Debug|x86.ActiveCfg = Debug|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Debug|x86.Build.0 = Debug|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Release|Any CPU.Build.0 = Release|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Release|x64.ActiveCfg = Release|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Release|x64.Build.0 = Release|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Release|x86.ActiveCfg = Release|Any CPU + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB}.Release|x86.Build.0 = Release|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Debug|x64.ActiveCfg = Debug|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Debug|x64.Build.0 = Debug|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Debug|x86.ActiveCfg = Debug|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Debug|x86.Build.0 = Debug|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Release|Any CPU.Build.0 = Release|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Release|x64.ActiveCfg = Release|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Release|x64.Build.0 = Release|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Release|x86.ActiveCfg = Release|Any CPU + {58633641-85F3-4F8E-AC18-0EFACC36D142}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -831,6 +863,10 @@ Global {7304F25F-0F4D-450F-A529-3C7AFE9D98CA} = {82BC0AAB-94CA-4D5B-BF95-F7329D1150CB} {CEDAAB8D-613C-4A3F-8114-7D6B480F1EE3} = {7304F25F-0F4D-450F-A529-3C7AFE9D98CA} {134CE4A1-E9A4-49C0-A998-7C531E6207CE} = {4E12F429-CEC0-4080-B0FE-39F9B8B9AE4C} + {021F1610-F2D0-4F00-81C7-AD7397DA296C} = {AA22EE67-3BBE-49A2-8868-531FE68FE162} + {E18A3CB0-BFDB-4672-AD53-7EAAF26C52AB} = {021F1610-F2D0-4F00-81C7-AD7397DA296C} + {824AFFC6-40A2-4AD6-8010-B939841CA16E} = {82BC0AAB-94CA-4D5B-BF95-F7329D1150CB} + {58633641-85F3-4F8E-AC18-0EFACC36D142} = {824AFFC6-40A2-4AD6-8010-B939841CA16E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D02F665B-C14D-43C2-955C-9338A23836E0} diff --git a/docs/images/dependency-validation.png b/docs/images/dependency-validation.png index c5b39e3cb..7880b709c 100644 Binary files a/docs/images/dependency-validation.png and b/docs/images/dependency-validation.png differ diff --git a/eng/DependencyValidation/DependencyValidation.modelproj b/eng/DependencyValidation/DependencyValidation.modelproj index 71461f316..7d24d629c 100644 --- a/eng/DependencyValidation/DependencyValidation.modelproj +++ b/eng/DependencyValidation/DependencyValidation.modelproj @@ -134,6 +134,10 @@ Microsoft.DotNet.UpgradeAssistant.Extensions.Maui {a122dd5b-4b80-4558-8db2-8e9ebff12a1a} + + Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet + {e18a3cb0-bfdb-4672-ad53-7eaaf26c52ab} + Microsoft.DotNet.UpgradeAssistant.Extensions.VisualBasic {608e7ce5-5674-49eb-8728-719a468fc62e} diff --git a/eng/DependencyValidation/UpgradeAssistant.layerdiagram b/eng/DependencyValidation/UpgradeAssistant.layerdiagram index 996839f84..428d4845a 100644 --- a/eng/DependencyValidation/UpgradeAssistant.layerdiagram +++ b/eng/DependencyValidation/UpgradeAssistant.layerdiagram @@ -205,6 +205,19 @@ + + + + + + + + + + + + + diff --git a/eng/DependencyValidation/UpgradeAssistant.layerdiagram.layout b/eng/DependencyValidation/UpgradeAssistant.layerdiagram.layout index 1e93f95a5..68340c930 100644 --- a/eng/DependencyValidation/UpgradeAssistant.layerdiagram.layout +++ b/eng/DependencyValidation/UpgradeAssistant.layerdiagram.layout @@ -50,9 +50,12 @@ + + + - + @@ -79,7 +82,7 @@ - + @@ -103,7 +106,7 @@ - + @@ -124,7 +127,7 @@ - + diff --git a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Microsoft.DotNet.UpgradeAssistant.Cli.csproj b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Microsoft.DotNet.UpgradeAssistant.Cli.csproj index 3655bbf31..d75dffe64 100644 --- a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Microsoft.DotNet.UpgradeAssistant.Cli.csproj +++ b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Microsoft.DotNet.UpgradeAssistant.Cli.csproj @@ -108,6 +108,9 @@ windows + + nuget + diff --git a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/UpgradeAssistantHostExtensions.cs b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/UpgradeAssistantHostExtensions.cs index f38104832..f73f6832d 100644 --- a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/UpgradeAssistantHostExtensions.cs +++ b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/UpgradeAssistantHostExtensions.cs @@ -101,14 +101,6 @@ public static IHostBuilder UseUpgradeAssistant(this IHostBuilder host, IUp } }); - services.AddNuGet(optionss => - { - if (upgradeOptions.Project?.FullName is string fullname) - { - optionss.PackageSourcePath = Path.GetDirectoryName(fullname); - } - }); - services.AddUserInput(); services.AddAnalysis(); diff --git a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json index 7679c31ae..5578f6ae0 100644 --- a/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json +++ b/src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/appsettings.json @@ -15,6 +15,10 @@ "Extensions": { "Source": "https://upgradeassistant.blob.core.windows.net/extensions/index.json", + "required": [ + "nuget" + ], + "Default": [ "default", "vb", diff --git a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IProjectFile.cs b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IProjectFile.cs index 7d0f93371..9747527b2 100644 --- a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IProjectFile.cs +++ b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/IProjectFile.cs @@ -15,6 +15,8 @@ public interface IProjectFile string FilePath { get; } + IEnumerable PackageReferences { get; } + void AddFrameworkReferences(IEnumerable frameworkReferences); void RemoveFrameworkReferences(IEnumerable frameworkReferences); diff --git a/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/ITargetFrameworkCollection.cs b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/ITargetFrameworkCollection.cs new file mode 100644 index 000000000..f7da49a0a --- /dev/null +++ b/src/common/Microsoft.DotNet.UpgradeAssistant.Abstractions/ITargetFrameworkCollection.cs @@ -0,0 +1,12 @@ +// 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; + +namespace Microsoft.DotNet.UpgradeAssistant +{ + public interface ITargetFrameworkCollection : IReadOnlyCollection + { + void SetTargetFramework(TargetFrameworkMoniker tfm); + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionAssemblyLoadContext.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionAssemblyLoadContext.cs index afe39ef6c..f4d92e27d 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionAssemblyLoadContext.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionAssemblyLoadContext.cs @@ -64,7 +64,9 @@ private void Load(ExtensionInstance extension, string[] assemblies) protected override Assembly? Load(AssemblyName assemblyName) { // If available in the default, we want to ensure that is used. - var inDefault = Default.Assemblies.FirstOrDefault(a => string.Equals(a.GetName().Name, assemblyName.Name, StringComparison.Ordinal)); + var inDefault = Default.Assemblies + .Where(a => !a.GetName().Name!.Contains("NuGet", StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(a => string.Equals(a.GetName().Name, assemblyName.Name, StringComparison.Ordinal)); if (inDefault is Assembly existing) { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionOptions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionOptions.cs index d57362085..84a8e5458 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionOptions.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionOptions.cs @@ -14,6 +14,8 @@ public class ExtensionOptions public ICollection DefaultExtensions { get; } = new List(); + public ICollection RequiredExtensions { get; } = new List(); + public ICollection ExtensionPaths { get; } = new List(); public IEnumerable AdditionalOptions { get; set; } = Enumerable.Empty(); diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProvider.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProvider.cs index c2555dcfb..c0c790237 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProvider.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProvider.cs @@ -46,14 +46,20 @@ public ExtensionProvider( _extensions = new Lazy>(() => { + var list = new List(); + var opts = options.Value; - if (!opts.LoadExtensions) + foreach (var path in opts.RequiredExtensions) { - return Enumerable.Empty(); + LoadPath(path, isDefault: true); } - var list = new List(); + // Required extensions must load, otherwise they may be turned off + if (!opts.LoadExtensions) + { + return list; + } foreach (var path in opts.ExtensionPaths) { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProviderExtensions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProviderExtensions.cs index f8db82160..a7924c4a2 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProviderExtensions.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.Extensions/ExtensionProviderExtensions.cs @@ -87,16 +87,22 @@ public static OptionsBuilder AddDefaultExtensions(this Options return builder.Configure(options => { const string ExtensionDirectory = "extensions"; - var settings = configuration.GetSection("Extensions").Get(); - var defaultExtensions = settings.Default - .Select(n => Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, ExtensionDirectory, n))); options.DefaultSource = settings.Source; - foreach (var path in defaultExtensions) + AddExtensions(options.DefaultExtensions, settings.Default); + AddExtensions(options.RequiredExtensions, settings.Required); + + static void AddExtensions(ICollection collection, string[] names) { - options.DefaultExtensions.Add(path); + var extensionFullPaths = names + .Select(n => Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, ExtensionDirectory, n))); + + foreach (var path in extensionFullPaths) + { + collection.Add(path); + } } }); } @@ -106,6 +112,8 @@ private class ExtensionSettings public string Source { get; set; } = string.Empty; public string[] Default { get; set; } = Array.Empty(); + + public string[] Required { get; set; } = Array.Empty(); } public static IServiceCollection AddExtensionOption(this IServiceCollection services, TOption option) diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/Factories.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/Factories.cs new file mode 100644 index 000000000..e599770b6 --- /dev/null +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/Factories.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +{ + internal class Factories + { + private readonly Func _nugetReferenceFactory; + private readonly Func _tfmCollectionFactory; + + public Factories( + Func nugetReferenceFactory, + Func tfmCollectionFactory) + { + _nugetReferenceFactory = nugetReferenceFactory; + _tfmCollectionFactory = tfmCollectionFactory; + } + + public INuGetReferences CreateNuGetReferences(IUpgradeContext context, IProject project) => _nugetReferenceFactory(context, project); + + public ITargetFrameworkCollection CreateTfmCollection(IProjectFile project) => _tfmCollectionFactory(project); + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.File.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.File.cs index 12217a737..de10a64a5 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.File.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.File.cs @@ -44,7 +44,7 @@ public ProjectRootElement ProjectRoot public ICollection Sdk => new SdkCollection(ProjectRoot); - public void SetTFM(TargetFrameworkMoniker tfm) => new TargetFrameworkMonikerCollection(this, _comparer).SetTargetFramework(tfm); + public void SetTFM(TargetFrameworkMoniker tfm) => _factories.CreateTfmCollection(this).SetTargetFramework(tfm); public void AddPackages(IEnumerable references) { @@ -61,7 +61,7 @@ public void AddPackages(IEnumerable references) public void RemovePackages(IEnumerable references) { - foreach (var reference in PackageReferences) + foreach (var reference in NuGetReferences.PackageReferences) { if (references.Contains(reference)) { @@ -184,7 +184,7 @@ public void RemoveProperty(string propertyName) { Project.RemoveProperty(property); } - } + } private static string GetPathRelativeToProject(string path, string projectDir) => Path.IsPathFullyQualified(path) diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.NuGetPackages.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.NuGetPackages.cs index d457a4b33..848f942c0 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.NuGetPackages.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.NuGetPackages.cs @@ -1,200 +1,15 @@ // 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.IO; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.Extensions.Logging; -using NuGet.Frameworks; -using NuGet.Packaging.Core; -using NuGet.ProjectModel; namespace Microsoft.DotNet.UpgradeAssistant.MSBuild { - internal partial class MSBuildProject : INuGetReferences + internal partial class MSBuildProject { - public INuGetReferences NuGetReferences => this; + public INuGetReferences NuGetReferences => _factories.CreateNuGetReferences(Context, this); - public NugetPackageFormat PackageReferenceFormat - { - get - { - if (GetPackagesConfigPath() is not null) - { - return NugetPackageFormat.PackageConfig; - } - else if (ProjectRoot.GetAllPackageReferences().ToList() is IEnumerable list && list.Any()) - { - return NugetPackageFormat.PackageReference; - } - else - { - return NugetPackageFormat.None; - } - } - } - - private string? GetPackagesConfigPath() => FindFiles("packages.config", ProjectItemType.Content).FirstOrDefault(); - - public IEnumerable PackageReferences - { - get - { - var packagesConfig = GetPackagesConfigPath(); - - if (packagesConfig is null) - { - var packages = ProjectRoot.GetAllPackageReferences(); - - return packages.Select(p => p.AsNuGetReference()); - } - else - { - return PackageConfig.GetPackages(packagesConfig); - } - } - } - - public IAsyncEnumerable GetTransitivePackageReferencesAsync(TargetFrameworkMoniker tfm, CancellationToken token) - => PackageReferenceFormat switch - { - NugetPackageFormat.PackageConfig => PackageReferences.ToAsyncEnumerable(), - NugetPackageFormat.PackageReference => GetAllPackageReferenceDependenciesAsync(tfm, token).Select(l => new NuGetReference(l.Name, l.Version.ToNormalizedString())), - _ => AsyncEnumerable.Empty() - }; - - public async ValueTask IsTransitivelyAvailableAsync(string packageName, CancellationToken token) - => PackageReferences.Any(p => p.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase)) - || (PackageReferenceFormat == NugetPackageFormat.PackageReference && await TargetFrameworks.ToAsyncEnumerable().AnyAwaitAsync(tfm => ContainsPackageDependencyAsync(tfm, d => string.Equals(packageName, d.Id, StringComparison.OrdinalIgnoreCase), token), cancellationToken: token).ConfigureAwait(false)); - - public async ValueTask IsTransitiveDependencyAsync(NuGetReference nugetReference, CancellationToken token) - => PackageReferenceFormat == NugetPackageFormat.PackageReference && await TargetFrameworks.ToAsyncEnumerable().AnyAwaitAsync(tfm => ContainsPackageDependencyAsync(tfm, d => ReferenceSatisfiesDependency(d, nugetReference, true), token), token).ConfigureAwait(false); - - private static bool ReferenceSatisfiesDependency(PackageDependency dependency, NuGetReference packageReference, bool minVersionMatchOnly) - { - // If the dependency's name doesn't match the reference's name, return false - if (!dependency.Id.Equals(packageReference.Name, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (!packageReference.TryGetNuGetVersion(out var packageVersion)) - { - throw new InvalidOperationException("Package references from a lock file should always have a specific version"); - } - - // Return false if the reference's version falls outside of the dependency range - var versionRange = dependency.VersionRange; - if (versionRange.HasLowerBound && packageVersion < versionRange.MinVersion) - { - return false; - } - - if (versionRange.HasUpperBound && packageVersion > versionRange.MaxVersion) - { - return false; - } - - // In some cases (looking for transitive dependencies), it's interesting to only match packages that are the minimum version - if (minVersionMatchOnly && versionRange.HasLowerBound && packageVersion != versionRange.MinVersion) - { - return false; - } - - // Otherwise, return true - return true; - } - - private ValueTask ContainsPackageDependencyAsync(TargetFrameworkMoniker tfm, Func filter, CancellationToken token) - => GetAllPackageReferenceDependenciesAsync(tfm, token).AnyAsync(l => l.Dependencies.Any(d => filter(d)), token); - - private async IAsyncEnumerable GetAllPackageReferenceDependenciesAsync(TargetFrameworkMoniker tfm, [EnumeratorCancellation] CancellationToken token) - { - if (!IsRestored) - { - throw new InvalidOperationException("Project should have already been restored. Please file an issue at https://github.com/dotnet/upgrade-assistant"); - } - - if (PackageReferenceFormat != NugetPackageFormat.PackageReference) - { - throw new InvalidOperationException("PackageReference restore for transitive dependencies should only happen for PackageReference package reference format"); - } - - var parsedTfm = NuGetFramework.Parse(tfm.Name); - var target = GetLockFileTarget(parsedTfm); - - if (target is null) - { - // Break if there are no packages in the project. Otherwise, we end up performing restores too often. - if (!PackageReferences.Any()) - { - _logger.LogDebug("Skipping restore as no package references exist in project file {Path}", FileInfo.FullName); - yield break; - } - - _logger.LogDebug("Attempting a restore to retrieve missing lock file data {Path}", FileInfo.FullName); - - await _restorer.RestorePackagesAsync(Context, this, token).ConfigureAwait(false); - - // If the LockFilePath is defined but does not exist, there are no libraries - if (!File.Exists(LockFilePath)) - { - yield break; - } - - target = GetLockFileTarget(parsedTfm); - } - - if (target is null) - { - _logger.LogError("NuGet target in project.assets.json is still unavailable after restore. Please verify that the project has been restored."); - throw new UpgradeException("Restore has not restored the expected TFMs. Please review any warnings from dotnet-restore."); - } - - foreach (var library in target.Libraries) - { - yield return library; - } - - LockFileTarget? GetLockFileTarget(NuGetFramework parsedTfm) - { - var lockFile = LockFileUtilities.GetLockFile(LockFilePath, NuGet.Common.NullLogger.Instance); - - if (lockFile?.Targets is null) - { - return null; - } - - return lockFile.Targets - .FirstOrDefault(t => t.TargetFramework.DotNetFrameworkName.Equals(parsedTfm.DotNetFrameworkName, StringComparison.Ordinal)); - } - } - - private bool IsRestored => LockFilePath is not null; - - private string? LockFilePath - { - get - { - var lockFilePath = Path.Combine(GetPropertyValue("MSBuildProjectExtensionsPath"), "project.assets.json"); - - if (string.IsNullOrEmpty(lockFilePath)) - { - return null; - } - - if (!Path.IsPathFullyQualified(lockFilePath)) - { - lockFilePath = Path.Combine(FileInfo.DirectoryName ?? string.Empty, lockFilePath); - } - - return lockFilePath; - } - } + public IEnumerable PackageReferences => ProjectRoot.GetAllPackageReferences().Select(p => p.AsNuGetReference()); } } diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.cs index 9639a5bff..1e5b2220b 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildProject.cs @@ -20,7 +20,7 @@ internal partial class MSBuildProject : IProject private readonly ILogger _logger; private readonly IEnumerable _componentIdentifiers; private readonly IPackageRestorer _restorer; - private readonly ITargetFrameworkMonikerComparer _comparer; + private readonly Factories _factories; public MSBuildWorkspaceUpgradeContext Context { get; } @@ -29,6 +29,7 @@ internal partial class MSBuildProject : IProject public MSBuildProject( MSBuildWorkspaceUpgradeContext context, IEnumerable componentIdentifiers, + Factories factories, IPackageRestorer restorer, ITargetFrameworkMonikerComparer comparer, FileInfo file, @@ -37,9 +38,9 @@ public MSBuildProject( FileInfo = file ?? throw new ArgumentNullException(nameof(file)); Context = context ?? throw new ArgumentNullException(nameof(context)); + _factories = factories ?? throw new ArgumentNullException(nameof(factories)); _componentIdentifiers = componentIdentifiers ?? throw new ArgumentNullException(nameof(componentIdentifiers)); _restorer = restorer ?? throw new ArgumentNullException(nameof(restorer)); - _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -141,7 +142,7 @@ public IEnumerable FindFiles(ProjectItemMatcher matcher, ProjectItemType public IEnumerable References => ProjectRoot.GetAllReferences().Select(r => r.AsReference()).ToList(); - public IReadOnlyCollection TargetFrameworks => new TargetFrameworkMonikerCollection(this, _comparer); + public IReadOnlyCollection TargetFrameworks => _factories.CreateTfmCollection(this); public IEnumerable ProjectTypes => GetPropertyValue("ProjectTypeGuids").Split(';'); diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs index 56af463b3..1534cf076 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/MSBuildWorkspaceUpgradeContext.cs @@ -23,6 +23,7 @@ internal sealed class MSBuildWorkspaceUpgradeContext : IUpgradeContext, IDisposa private readonly ILogger _logger; private readonly Dictionary _projectCache; private readonly IOptions _options; + private readonly Factories _factories; private List? _entryPointPaths; private FileInfo? _projectPath; @@ -58,6 +59,7 @@ public MSBuildWorkspaceUpgradeContext( IOptions options, Func infoGenerator, IPackageRestorer restorer, + Factories factories, ITargetFrameworkMonikerComparer comparer, IEnumerable componentIdentifiers, ILogger logger) @@ -67,6 +69,7 @@ public MSBuildWorkspaceUpgradeContext( throw new ArgumentNullException(nameof(infoGenerator)); } + _factories = factories ?? throw new ArgumentNullException(nameof(factories)); _projectCache = new Dictionary(StringComparer.OrdinalIgnoreCase); _options = options ?? throw new ArgumentNullException(nameof(options)); _restorer = restorer ?? throw new ArgumentNullException(nameof(restorer)); @@ -96,7 +99,7 @@ public IProject GetOrAddProject(FileInfo path) return cached; } - var project = new MSBuildProject(this, _componentIdentifiers, _restorer, _comparer, path, _logger); + var project = new MSBuildProject(this, _componentIdentifiers, _factories, _restorer, _comparer, path, _logger); _projectCache.Add(path.FullName, project); diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs index 3dedd9737..56415f534 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs +++ b/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/UpgraderMsBuildExtensions.cs @@ -8,7 +8,6 @@ using Microsoft.DotNet.UpgradeAssistant.MSBuild; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using NuGet.Configuration; namespace Microsoft.DotNet.UpgradeAssistant { @@ -22,6 +21,8 @@ public static void AddMsBuild(this IServiceCollection services, Action(); + services.AddTransient, VisualStudioFinder>(); services.AddTransient, MSBuildWorkspaceOptionsConfigure>(); services.AddTransient(); @@ -35,24 +36,6 @@ public static void AddMsBuild(this IServiceCollection services, Action>(sp => () => sp.GetRequiredService()); } - public static void AddNuGet(this IServiceCollection services, Action configure) - { - services.AddSingleton(); - services.AddTransient(ctx => ctx.GetRequiredService()); - services.AddTransient(ctx => ctx.GetRequiredService()); - services.AddSingleton(); - services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); - services.AddOptions() - .Configure(configure) - .Configure(options => - { - var settings = Settings.LoadDefaultSettings(null); - options.CachePath = SettingsUtility.GetGlobalPackagesFolder(settings); - }); - } - // TEMPORARY WORKAROUND // https://github.com/dotnet/roslyn/issues/36781 // Adding documents to a project can result in extra "" items diff --git a/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/ExtensionManifest.json b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/ExtensionManifest.json new file mode 100644 index 000000000..5deb1af86 --- /dev/null +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/ExtensionManifest.json @@ -0,0 +1,7 @@ +{ + "ExtensionName": "NuGet", + + "ExtensionServiceProviders": [ + "Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.dll" + ] +} \ No newline at end of file diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/INuGetPackageSourceFactory.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/INuGetPackageSourceFactory.cs similarity index 84% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/INuGetPackageSourceFactory.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/INuGetPackageSourceFactory.cs index 9df369754..5937f1a72 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/INuGetPackageSourceFactory.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/INuGetPackageSourceFactory.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using NuGet.Configuration; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public interface INuGetPackageSourceFactory { diff --git a/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.csproj b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.csproj new file mode 100644 index 000000000..a44e14bb5 --- /dev/null +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.csproj @@ -0,0 +1,48 @@ + + + + net5.0 + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetCredentialsStartup.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetCredentialsStartup.cs similarity index 94% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetCredentialsStartup.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetCredentialsStartup.cs index 37fad51b3..e846c5a64 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetCredentialsStartup.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetCredentialsStartup.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; using NuGet.Credentials; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public class NuGetCredentialsStartup : IUpgradeStartup { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetDownloaderOptions.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetDownloaderOptions.cs similarity index 83% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetDownloaderOptions.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetDownloaderOptions.cs index 759189ef3..420d87615 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetDownloaderOptions.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetDownloaderOptions.cs @@ -1,7 +1,7 @@ // 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 +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public class NuGetDownloaderOptions { diff --git a/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetExtensionBuilder.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetExtensionBuilder.cs new file mode 100644 index 000000000..60324f71d --- /dev/null +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetExtensionBuilder.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.UpgradeAssistant.MSBuild; +using Microsoft.Extensions.DependencyInjection; +using NuGet.Configuration; + +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet +{ + public class NuGetExtensionBuilder : IExtensionServiceProvider + { + public void AddServices(IExtensionServiceCollection services) + => AddNuGet(services.Services); + + private static void AddNuGet(IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + services.AddTransient(ctx => ctx.GetRequiredService()); + services.AddTransient(ctx => ctx.GetRequiredService()); + services.AddSingleton(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddOptions() + .Configure(options => + { + var settings = Settings.LoadDefaultSettings(null); + options.CachePath = SettingsUtility.GetGlobalPackagesFolder(settings); + }); + } + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetExtensions.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetExtensions.cs similarity index 96% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetExtensions.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetExtensions.cs index 46f0594b2..0094dcfb6 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetExtensions.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetExtensions.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using NuGet.Versioning; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public static class NuGetExtensions { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetLogger.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetLogger.cs similarity index 97% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetLogger.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetLogger.cs index e8928ac7b..19149a176 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetLogger.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetLogger.cs @@ -8,7 +8,7 @@ using ILogger = NuGet.Common.ILogger; using LogLevel = NuGet.Common.LogLevel; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public class NuGetLogger : ILogger { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetPackageSourceFactory.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetPackageSourceFactory.cs similarity index 95% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetPackageSourceFactory.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetPackageSourceFactory.cs index 8d4c15995..cf1699e1a 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetPackageSourceFactory.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetPackageSourceFactory.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging; using NuGet.Configuration; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public class NuGetPackageSourceFactory : INuGetPackageSourceFactory { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetTargetFrameworkMonikerComparer.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetTargetFrameworkMonikerComparer.cs similarity index 98% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetTargetFrameworkMonikerComparer.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetTargetFrameworkMonikerComparer.cs index b95603413..79ac6754f 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetTargetFrameworkMonikerComparer.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetTargetFrameworkMonikerComparer.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging; using NuGet.Frameworks; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public class NuGetTargetFrameworkMonikerComparer : ITargetFrameworkMonikerComparer { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetVersionComparer.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetVersionComparer.cs similarity index 95% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetVersionComparer.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetVersionComparer.cs index 2203be4a9..edb34de33 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/NuGetVersionComparer.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/NuGetVersionComparer.cs @@ -3,7 +3,7 @@ using NuGet.Versioning; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public class NuGetVersionComparer : IVersionComparer { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageConfig.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/PackageConfig.cs similarity index 94% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageConfig.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/PackageConfig.cs index 04710ac5e..28ac0ee84 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageConfig.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/PackageConfig.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Xml.Linq; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { internal static class PackageConfig { diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageLoader.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/PackageLoader.cs similarity index 99% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageLoader.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/PackageLoader.cs index 8ab41cf5b..42e319b25 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/PackageLoader.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/PackageLoader.cs @@ -11,7 +11,6 @@ using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; -using Microsoft.DotNet.UpgradeAssistant.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NuGet.Configuration; @@ -21,7 +20,7 @@ using NuGet.Protocol.Core.Types; using NuGet.Versioning; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { public sealed class PackageLoader : IPackageLoader, IPackageDownloader, IDisposable { @@ -30,7 +29,7 @@ public sealed class PackageLoader : IPackageLoader, IPackageDownloader, IDisposa private readonly SourceCacheContext _cache; private readonly Lazy> _packageSources; private readonly ILogger _logger; - private readonly NuGet.Common.ILogger _nugetLogger; + private readonly global::NuGet.Common.ILogger _nugetLogger; private readonly Dictionary _sourceRepositoryCache; private readonly NuGetDownloaderOptions _options; diff --git a/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/ProjectNuGetReferences.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/ProjectNuGetReferences.cs new file mode 100644 index 000000000..ea5c63173 --- /dev/null +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/ProjectNuGetReferences.cs @@ -0,0 +1,213 @@ +// 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.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; +using NuGet.Frameworks; +using NuGet.Packaging.Core; +using NuGet.ProjectModel; + +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet +{ + public class ProjectNuGetReferences : INuGetReferences + { + private readonly IUpgradeContext _context; + private readonly IProject _project; + private readonly IPackageRestorer _restorer; + private readonly ILogger _logger; + + public ProjectNuGetReferences( + IUpgradeContext context, + IProject project, + IPackageRestorer restorer, + ILogger logger) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _project = project ?? throw new ArgumentNullException(nameof(project)); + _restorer = restorer ?? throw new ArgumentNullException(nameof(restorer)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public NugetPackageFormat PackageReferenceFormat + { + get + { + if (GetPackagesConfigPath() is not null) + { + return NugetPackageFormat.PackageConfig; + } + else if (_project.GetFile().PackageReferences.Any()) + { + return NugetPackageFormat.PackageReference; + } + else + { + return NugetPackageFormat.None; + } + } + } + + private string? GetPackagesConfigPath() => _project.FindFiles("packages.config", ProjectItemType.Content).FirstOrDefault(); + + public IEnumerable PackageReferences + { + get + { + var packagesConfig = GetPackagesConfigPath(); + + if (packagesConfig is null) + { + return _project.GetFile().PackageReferences; + } + else + { + return PackageConfig.GetPackages(packagesConfig); + } + } + } + + public IAsyncEnumerable GetTransitivePackageReferencesAsync(TargetFrameworkMoniker tfm, CancellationToken token) + => PackageReferenceFormat switch + { + NugetPackageFormat.PackageConfig => PackageReferences.ToAsyncEnumerable(), + NugetPackageFormat.PackageReference => GetAllPackageReferenceDependenciesAsync(tfm, token).Select(l => new NuGetReference(l.Name, l.Version.ToNormalizedString())), + _ => AsyncEnumerable.Empty() + }; + + public async ValueTask IsTransitivelyAvailableAsync(string packageName, CancellationToken token) + => PackageReferences.Any(p => p.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase)) + || (PackageReferenceFormat == NugetPackageFormat.PackageReference && await _project.TargetFrameworks.ToAsyncEnumerable().AnyAwaitAsync(tfm => ContainsPackageDependencyAsync(tfm, d => string.Equals(packageName, d.Id, StringComparison.OrdinalIgnoreCase), token), cancellationToken: token).ConfigureAwait(false)); + + public async ValueTask IsTransitiveDependencyAsync(NuGetReference nugetReference, CancellationToken token) + => PackageReferenceFormat == NugetPackageFormat.PackageReference && await _project.TargetFrameworks.ToAsyncEnumerable().AnyAwaitAsync(tfm => ContainsPackageDependencyAsync(tfm, d => ReferenceSatisfiesDependency(d, nugetReference, true), token), token).ConfigureAwait(false); + + private static bool ReferenceSatisfiesDependency(PackageDependency dependency, NuGetReference packageReference, bool minVersionMatchOnly) + { + // If the dependency's name doesn't match the reference's name, return false + if (!dependency.Id.Equals(packageReference.Name, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!packageReference.TryGetNuGetVersion(out var packageVersion)) + { + throw new InvalidOperationException("Package references from a lock file should always have a specific version"); + } + + // Return false if the reference's version falls outside of the dependency range + var versionRange = dependency.VersionRange; + if (versionRange.HasLowerBound && packageVersion < versionRange.MinVersion) + { + return false; + } + + if (versionRange.HasUpperBound && packageVersion > versionRange.MaxVersion) + { + return false; + } + + // In some cases (looking for transitive dependencies), it's interesting to only match packages that are the minimum version + if (minVersionMatchOnly && versionRange.HasLowerBound && packageVersion != versionRange.MinVersion) + { + return false; + } + + // Otherwise, return true + return true; + } + + private ValueTask ContainsPackageDependencyAsync(TargetFrameworkMoniker tfm, Func filter, CancellationToken token) + => GetAllPackageReferenceDependenciesAsync(tfm, token).AnyAsync(l => l.Dependencies.Any(d => filter(d)), token); + + private async IAsyncEnumerable GetAllPackageReferenceDependenciesAsync(TargetFrameworkMoniker tfm, [EnumeratorCancellation] CancellationToken token) + { + if (!IsRestored) + { + throw new InvalidOperationException("Project should have already been restored. Please file an issue at https://github.com/dotnet/upgrade-assistant"); + } + + if (PackageReferenceFormat != NugetPackageFormat.PackageReference) + { + throw new InvalidOperationException("PackageReference restore for transitive dependencies should only happen for PackageReference package reference format"); + } + + var parsedTfm = NuGetFramework.Parse(tfm.Name); + var target = GetLockFileTarget(parsedTfm); + + if (target is null) + { + // Break if there are no packages in the project. Otherwise, we end up performing restores too often. + if (!PackageReferences.Any()) + { + _logger.LogDebug("Skipping restore as no package references exist in project file {Path}", _project.FileInfo.FullName); + yield break; + } + + _logger.LogDebug("Attempting a restore to retrieve missing lock file data {Path}", _project.FileInfo.FullName); + + await _restorer.RestorePackagesAsync(_context, _project, token).ConfigureAwait(false); + + // If the LockFilePath is defined but does not exist, there are no libraries + if (!File.Exists(LockFilePath)) + { + yield break; + } + + target = GetLockFileTarget(parsedTfm); + } + + if (target is null) + { + _logger.LogError("NuGet target in project.assets.json is still unavailable after restore. Please verify that the project has been restored."); + throw new UpgradeException("Restore has not restored the expected TFMs. Please review any warnings from dotnet-restore."); + } + + foreach (var library in target.Libraries) + { + yield return library; + } + + LockFileTarget? GetLockFileTarget(NuGetFramework parsedTfm) + { + var lockFile = LockFileUtilities.GetLockFile(LockFilePath, global::NuGet.Common.NullLogger.Instance); + + if (lockFile?.Targets is null) + { + return null; + } + + return lockFile.Targets + .FirstOrDefault(t => t.TargetFramework.DotNetFrameworkName.Equals(parsedTfm.DotNetFrameworkName, StringComparison.Ordinal)); + } + } + + private bool IsRestored => LockFilePath is not null; + + private string? LockFilePath + { + get + { + var lockFilePath = Path.Combine(_project.GetFile().GetPropertyValue("MSBuildProjectExtensionsPath"), "project.assets.json"); + + if (string.IsNullOrEmpty(lockFilePath)) + { + return null; + } + + if (!Path.IsPathFullyQualified(lockFilePath)) + { + lockFilePath = Path.Combine(_project.FileInfo.DirectoryName ?? string.Empty, lockFilePath); + } + + return lockFilePath; + } + } + } +} diff --git a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/TargetFrameworkMonikerCollection.cs b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/TargetFrameworkMonikerCollection.cs similarity index 96% rename from src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/TargetFrameworkMonikerCollection.cs rename to src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/TargetFrameworkMonikerCollection.cs index 32317564c..3c35a0b88 100644 --- a/src/components/Microsoft.DotNet.UpgradeAssistant.MSBuild/TargetFrameworkMonikerCollection.cs +++ b/src/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet/TargetFrameworkMonikerCollection.cs @@ -5,9 +5,9 @@ using System.Collections.Generic; using NuGet.Frameworks; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet { - public class TargetFrameworkMonikerCollection : IReadOnlyCollection + public class TargetFrameworkMonikerCollection : IReadOnlyCollection, ITargetFrameworkCollection { private const string SdkSingleTargetFramework = "TargetFramework"; private const string SdkMultipleTargetFrameworks = "TargetFrameworks"; diff --git a/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests.csproj b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests.csproj new file mode 100644 index 000000000..e5717e1f6 --- /dev/null +++ b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests.csproj @@ -0,0 +1,9 @@ + + + net5.0 + + + + + + \ No newline at end of file diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetExtensionsTests.cs b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetExtensionsTests.cs similarity index 91% rename from tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetExtensionsTests.cs rename to tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetExtensionsTests.cs index 09fe47e92..c350deeae 100644 --- a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetExtensionsTests.cs +++ b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetExtensionsTests.cs @@ -4,9 +4,8 @@ using System; using Xunit; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests { - [Collection(MSBuildStepTestCollection.Name)] public class NuGetExtensionsTests { [Fact] diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetTargetFrameworkMonikerComparerTests.cs b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetTargetFrameworkMonikerComparerTests.cs similarity index 98% rename from tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetTargetFrameworkMonikerComparerTests.cs rename to tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetTargetFrameworkMonikerComparerTests.cs index e89e2c5c8..3c773c22e 100644 --- a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetTargetFrameworkMonikerComparerTests.cs +++ b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetTargetFrameworkMonikerComparerTests.cs @@ -7,9 +7,8 @@ using static Microsoft.DotNet.UpgradeAssistant.TargetFrameworkMonikerParser; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests { - [Collection(MSBuildStepTestCollection.Name)] public class NuGetTargetFrameworkMonikerComparerTests { [InlineData(Net50, NetCoreApp31, true)] diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetVersionComparerTests.cs b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetVersionComparerTests.cs similarity index 92% rename from tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetVersionComparerTests.cs rename to tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetVersionComparerTests.cs index 6ddab9d84..226b91978 100644 --- a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/NuGetVersionComparerTests.cs +++ b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/NuGetVersionComparerTests.cs @@ -3,9 +3,8 @@ using Xunit; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests { - [Collection(MSBuildStepTestCollection.Name)] public class NuGetVersionComparerTests { [InlineData("1.0", "1.0", 0)] diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/PackageLoaderTests.cs b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/PackageLoaderTests.cs similarity index 99% rename from tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/PackageLoaderTests.cs rename to tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/PackageLoaderTests.cs index c586348a8..16619f125 100644 --- a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/PackageLoaderTests.cs +++ b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/PackageLoaderTests.cs @@ -12,9 +12,8 @@ using NuGet.Protocol.Core.Types; using Xunit; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests { - [Collection(MSBuildStepTestCollection.Name)] public class PackageLoaderTests { private readonly Fixture _fixture; diff --git a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/TargetFrameworkMonikerCollectionTests.cs b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/TargetFrameworkMonikerCollectionTests.cs similarity index 98% rename from tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/TargetFrameworkMonikerCollectionTests.cs rename to tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/TargetFrameworkMonikerCollectionTests.cs index d508b0b49..c135a20a1 100644 --- a/tests/components/Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests/TargetFrameworkMonikerCollectionTests.cs +++ b/tests/extensions/nuget/Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests/TargetFrameworkMonikerCollectionTests.cs @@ -8,9 +8,8 @@ using static Microsoft.DotNet.UpgradeAssistant.TargetFrameworkMonikerParser; -namespace Microsoft.DotNet.UpgradeAssistant.MSBuild.Tests +namespace Microsoft.DotNet.UpgradeAssistant.Extensions.NuGet.Tests { - [Collection(MSBuildStepTestCollection.Name)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.", Justification = "Need to verify .Count property")] public class TargetFrameworkMonikerCollectionTests {