Skip to content

Commit

Permalink
allow to install multiple versions of the same packages
Browse files Browse the repository at this point in the history
  • Loading branch information
mkhomutov committed Jan 15, 2025
1 parent b39a5a5 commit c1eedba
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,27 @@ namespace Orc.NuGetExplorer.Messaging
public PackagingDeletemeMessage(Orc.NuGetExplorer.Packaging.PackageOperationInfo content) { }
}
}
namespace Orc.NuGetExplorer.Models
{
[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 System.Threading.CancellationToken CancellationToken { 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; }
}
}
namespace Orc.NuGetExplorer.Packaging
{
public class EmptyPackageDetails : Orc.NuGetExplorer.IPackageDetails
Expand Down Expand Up @@ -1249,6 +1270,7 @@ namespace Orc.NuGetExplorer.Services
public interface IPackageInstallationService
{
NuGet.Packaging.VersionFolderPathResolver InstallerPathResolver { get; }
System.Threading.Tasks.Task<Orc.NuGetExplorer.InstallerResult> InstallAsync(Orc.NuGetExplorer.Models.InstallationContext context);
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
18 changes: 18 additions & 0 deletions src/Orc.NuGetExplorer/Models/InstallationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Orc.NuGetExplorer.Models;

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 CancellationToken CancellationToken { get; set; }
public bool AllowMultipleVersions { get; set; } = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;
using Orc.NuGetExplorer.Models;

public interface IPackageInstallationService
{
Expand All @@ -15,6 +16,8 @@ public interface IPackageInstallationService
/// </summary>
VersionFolderPathResolver InstallerPathResolver { get; }

Task<InstallerResult> InstallAsync(InstallationContext context);

Task<InstallerResult> InstallAsync(
PackageIdentity package,
IExtensibleProject project,
Expand Down
71 changes: 53 additions & 18 deletions src/Orc.NuGetExplorer/Services/PackageInstallationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Catel.IoC;
using Catel.Logging;
using MethodTimer;
using Models;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Frameworks;
Expand Down Expand Up @@ -183,18 +185,17 @@ 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)
{
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 cancellationToken = context.CancellationToken;
var allowMultipleVersions = context.AllowMultipleVersions;

try
{
Expand Down Expand Up @@ -235,7 +236,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 +272,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.Ignore,
(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 All @@ -302,6 +311,32 @@ public async Task<InstallerResult> InstallAsync(
}
}

[Time]
public async 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,
CancellationToken = cancellationToken
};

return await InstallAsync(context);
}

// TODO move to separate class
public async Task<long?> MeasurePackageSizeFromRepositoryAsync(PackageIdentity packageIdentity, SourceRepository sourceRepository)
{
Expand Down

0 comments on commit c1eedba

Please sign in to comment.