Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow to install multiple versions of the same packages #891

Merged
merged 4 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,23 @@ namespace Orc.NuGetExplorer
string GetDirectory(string relativeDirectoryName);
string GetFile(string relativeFilePath);
}
[System.Runtime.CompilerServices.RequiredMember]
public class InstallationContext
{
[System.Obsolete(("Constructors of types with required members are not supported in this version of " +
"your compiler."), true)]
[System.Runtime.CompilerServices.CompilerFeatureRequired("RequiredMembers")]
public InstallationContext() { }
public bool AllowMultipleVersions { get; set; }
public bool IgnoreMissingPackages { get; set; }
[System.Runtime.CompilerServices.RequiredMember]
public NuGet.Packaging.Core.PackageIdentity Package { get; set; }
public System.Func<NuGet.Packaging.Core.PackageIdentity, bool>? PackagePredicate { get; set; }
[System.Runtime.CompilerServices.RequiredMember]
public Orc.NuGetExplorer.IExtensibleProject Project { get; set; }
[System.Runtime.CompilerServices.RequiredMember]
public System.Collections.Generic.IReadOnlyList<NuGet.Protocol.Core.Types.SourceRepository> Repositories { get; set; }
}
public class InstallerResult
{
public InstallerResult(System.Collections.Generic.IDictionary<NuGet.Protocol.Core.Types.SourcePackageDependencyInfo, NuGet.Protocol.Core.Types.DownloadResourceResult> downloadResult) { }
Expand Down Expand Up @@ -1249,6 +1266,10 @@ namespace Orc.NuGetExplorer.Services
public interface IPackageInstallationService
{
NuGet.Packaging.VersionFolderPathResolver InstallerPathResolver { get; }
System.Threading.Tasks.Task<Orc.NuGetExplorer.InstallerResult> InstallAsync(Orc.NuGetExplorer.InstallationContext context, System.Threading.CancellationToken cancellationToken = default);
[System.Obsolete(("Use `InstallAsync(InstallationContext context, CancellationToken cancellationToke" +
"n = default)` instead. Will be treated as an error from version 6.0.0. Will be r" +
"emoved in version 7.0.0."), false)]
System.Threading.Tasks.Task<Orc.NuGetExplorer.InstallerResult> InstallAsync(NuGet.Packaging.Core.PackageIdentity package, Orc.NuGetExplorer.IExtensibleProject project, System.Collections.Generic.IReadOnlyList<NuGet.Protocol.Core.Types.SourceRepository> repositories, bool ignoreMissingPackages = false, System.Func<NuGet.Packaging.Core.PackageIdentity, bool>? packagePredicate = null, System.Threading.CancellationToken cancellationToken = default);
System.Threading.Tasks.Task<long?> MeasurePackageSizeFromRepositoryAsync(NuGet.Packaging.Core.PackageIdentity packageIdentity, NuGet.Protocol.Core.Types.SourceRepository sourceRepository);
System.Threading.Tasks.Task UninstallAsync(NuGet.Packaging.Core.PackageIdentity package, Orc.NuGetExplorer.IExtensibleProject project, System.Collections.Generic.IEnumerable<NuGet.Packaging.PackageReference> installedPackageReferences, System.Func<NuGet.Packaging.Core.PackageIdentity, bool>? packagePredicate = null, System.Threading.CancellationToken cancellationToken = default);
Expand Down
12 changes: 10 additions & 2 deletions src/Orc.NuGetExplorer/Management/NuGetProjectPackageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,16 @@ public async Task<bool> InstallPackageForProjectAsync(IExtensibleProject project

return false;
}

var installerResults = await _packageInstallationService.InstallAsync(package, project, repositories, project.IgnoreDependencies, packagePredicate, token);
var context = new InstallationContext
{
Project = project,
Repositories = repositories,
Package = package,
PackagePredicate = packagePredicate,
AllowMultipleVersions = true
};

var installerResults = await _packageInstallationService.InstallAsync(context, token);
if (!installerResults.Result.Any())
{
if (showErrors)
Expand Down
17 changes: 17 additions & 0 deletions src/Orc.NuGetExplorer/Models/InstallationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Orc.NuGetExplorer;

using System;
using System.Collections.Generic;
using System.Threading;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;

public class InstallationContext
{
public required PackageIdentity Package { get; set; }
public required IExtensibleProject Project { get; set; }
public required IReadOnlyList<SourceRepository> Repositories { get; set; }
public bool IgnoreMissingPackages { get; set; }
public Func<PackageIdentity, bool>? PackagePredicate { get; set; }
public bool AllowMultipleVersions { get; set; } = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ public interface IPackageInstallationService
/// </summary>
VersionFolderPathResolver InstallerPathResolver { get; }

Task<InstallerResult> InstallAsync(
Task<InstallerResult> InstallAsync(InstallationContext context, CancellationToken cancellationToken = default);

[ObsoleteEx(ReplacementTypeOrMember = "InstallAsync(InstallationContext context, CancellationToken cancellationToken = default)", TreatAsErrorFromVersion = "6", RemoveInVersion = "7")]
public Task<InstallerResult> InstallAsync(
PackageIdentity package,
IExtensibleProject project,
IReadOnlyList<SourceRepository> repositories,
Expand Down
59 changes: 42 additions & 17 deletions src/Orc.NuGetExplorer/Services/PackageInstallationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -90,6 +91,23 @@ public PackageInstallationService(IServiceLocator serviceLocator,

public VersionFolderPathResolver InstallerPathResolver => _installerPathResolver;

[ObsoleteEx(ReplacementTypeOrMember = "InstallAsync(InstallationContext context, CancellationToken cancellationToken = default)", TreatAsErrorFromVersion = "6", RemoveInVersion = "7")]
public Task<InstallerResult> InstallAsync(PackageIdentity package, IExtensibleProject project, IReadOnlyList<SourceRepository> repositories, bool ignoreMissingPackages = false, Func<PackageIdentity, bool>? packagePredicate = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(package);
ArgumentNullException.ThrowIfNull(project);
ArgumentNullException.ThrowIfNull(repositories);
var context = new InstallationContext
{
Package = package,
Project = project,
Repositories = repositories,
IgnoreMissingPackages = ignoreMissingPackages,
PackagePredicate = packagePredicate,
};
return InstallAsync(context, cancellationToken);
}

public async Task UninstallAsync(PackageIdentity package, IExtensibleProject project, IEnumerable<PackageReference> installedPackageReferences,
Func<PackageIdentity, bool>? packagePredicate = null, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -184,17 +202,16 @@ public async Task UninstallAsync(PackageIdentity package, IExtensibleProject pro
}

[Time]
public async Task<InstallerResult> InstallAsync(
PackageIdentity package,
IExtensibleProject project,
IReadOnlyList<SourceRepository> repositories,
bool ignoreMissingPackages = false,
Func<PackageIdentity, bool>? packagePredicate = null,
CancellationToken cancellationToken = default)
public async Task<InstallerResult> InstallAsync(InstallationContext context, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(package);
ArgumentNullException.ThrowIfNull(project);
ArgumentNullException.ThrowIfNull(repositories);
ArgumentNullException.ThrowIfNull(context);

var project = context.Project;
var package = context.Package;
var repositories = context.Repositories;
var ignoreMissingPackages = context.IgnoreMissingPackages;
var packagePredicate = context.PackagePredicate;
var allowMultipleVersions = context.AllowMultipleVersions;

try
{
Expand Down Expand Up @@ -235,7 +252,7 @@ public async Task<InstallerResult> InstallAsync(

var dependencyInfoResources = new DependencyInfoResourceCollection(dependencyResources);

resolverContext = await ResolveDependenciesAsync(package, targetFramework, PackageIdentityComparer.Default, dependencyInfoResources,
resolverContext = await ResolveDependenciesAsync(package, targetFramework, PackageIdentityComparer.Default, dependencyInfoResources,
sourceCacheContext, project, ignoreMissingPackages, packagePredicate, cancellationToken);

if (resolverContext is null ||
Expand Down Expand Up @@ -271,12 +288,20 @@ public async Task<InstallerResult> InstallAsync(
throw Log.ErrorAndCreateException<IncompatiblePackageException>($"Package {package} incompatible with project target platform {targetFramework}");
}

// Step 5. Build install list using NuGet Resolver and select available resources.
// Track packages which already installed and make sure only one version of package exists
var resolver = new Resolver.PackageResolver();
var availablePackagesToInstall = await resolver.ResolveWithVersionOverrideAsync(resolverContext, project, DependencyBehavior.Highest,
(project, conflict) => _fileSystemService.CreateDeleteme(conflict.PackageIdentity.Id, project.GetInstallPath(conflict.PackageIdentity)),
cancellationToken);
// Step 5. Build install list using NuGet Resolver and select available resources.
List<SourcePackageDependencyInfo> availablePackagesToInstall;
if (allowMultipleVersions)
{
availablePackagesToInstall = resolverContext.AvailablePackages.ToList();
}
else
{
// Track packages which already installed and make sure only one version of package exists
var resolver = new Resolver.PackageResolver();
availablePackagesToInstall = await resolver.ResolveWithVersionOverrideAsync(resolverContext, project, DependencyBehavior.Highest,
(project, conflict) => _fileSystemService.CreateDeleteme(conflict.PackageIdentity.Id, project.GetInstallPath(conflict.PackageIdentity)),
cancellationToken);
}

// Step 6. Download everything except main package and extract all
availablePackagesToInstall.Remove(mainPackageInfo);
Expand Down