|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Commands;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.PackageManagement.Utility;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Packaging.Signing;
using NuGet.ProjectManagement;
using NuGet.ProjectManagement.Projects;
using NuGet.ProjectModel;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Resolver;
using NuGet.Shared;
using NuGet.Versioning;
namespace NuGet.PackageManagement
{
/// <summary>
/// NuGetPackageManager orchestrates a nuget package operation such as an install or uninstall
/// It is to be called by various NuGet Clients including the custom third-party ones
/// </summary>
public class NuGetPackageManager
{
private IReadOnlyList<SourceRepository> _globalPackageFolderRepositories;
private ISourceRepositoryProvider SourceRepositoryProvider { get; }
private ISolutionManager SolutionManager { get; }
private Configuration.ISettings Settings { get; }
private HashSet<string> _buildIntegratedProjectsUpdateSet;
private DependencyGraphSpec _buildIntegratedProjectsCache;
private RestoreCommandProvidersCache _restoreProviderCache;
public IDeleteOnRestartManager DeleteOnRestartManager { get; }
public FolderNuGetProject PackagesFolderNuGetProject { get; set; }
public SourceRepository PackagesFolderSourceRepository { get; set; }
public IInstallationCompatibility InstallationCompatibility { get; set; }
private IRestoreProgressReporter RestoreProgressReporter { get; }
/// <summary>
/// Event to be raised when batch processing of install/ uninstall packages starts at a project level
/// </summary>
public event EventHandler<PackageProjectEventArgs> BatchStart;
/// <summary>
/// Event to be raised when batch processing of install/ uninstall packages ends at a project level
/// </summary>
public event EventHandler<PackageProjectEventArgs> BatchEnd;
/// <summary>
/// The telemetry service to use for telemetry events. The setter is exposed for test purposes.
/// </summary>
internal INuGetTelemetryService NuGetTelemetryService { get; set; }
/// <summary>
/// To construct a NuGetPackageManager that does not need a SolutionManager like NuGet.exe
/// </summary>
public NuGetPackageManager(
ISourceRepositoryProvider sourceRepositoryProvider,
ISettings settings,
string packagesFolderPath)
: this(sourceRepositoryProvider, settings, packagesFolderPath, excludeVersion: false)
{
}
public NuGetPackageManager(
ISourceRepositoryProvider sourceRepositoryProvider,
ISettings settings,
string packagesFolderPath,
bool excludeVersion)
{
if (packagesFolderPath == null)
{
throw new ArgumentNullException(nameof(packagesFolderPath));
}
SourceRepositoryProvider = sourceRepositoryProvider ?? throw new ArgumentNullException(nameof(sourceRepositoryProvider));
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
InstallationCompatibility = PackageManagement.InstallationCompatibility.Instance;
InitializePackagesFolderInfo(packagesFolderPath, excludeVersion);
NuGetTelemetryService = TelemetryActivity.NuGetTelemetryService;
}
/// <summary>
/// To construct a NuGetPackageManager with a mandatory SolutionManager lke VS
/// </summary>
public NuGetPackageManager(
ISourceRepositoryProvider sourceRepositoryProvider,
ISettings settings,
ISolutionManager solutionManager,
IDeleteOnRestartManager deleteOnRestartManager)
: this(sourceRepositoryProvider, settings, solutionManager, deleteOnRestartManager, excludeVersion: false)
{
}
public NuGetPackageManager(
ISourceRepositoryProvider sourceRepositoryProvider,
ISettings settings,
ISolutionManager solutionManager,
IDeleteOnRestartManager deleteOnRestartManager,
bool excludeVersion)
: this(sourceRepositoryProvider, settings, solutionManager, deleteOnRestartManager, reporter: null, excludeVersion: excludeVersion)
{
}
public NuGetPackageManager(
ISourceRepositoryProvider sourceRepositoryProvider,
ISettings settings,
ISolutionManager solutionManager,
IDeleteOnRestartManager deleteOnRestartManager,
IRestoreProgressReporter reporter) :
this(sourceRepositoryProvider, settings, solutionManager, deleteOnRestartManager, reporter, excludeVersion: false)
{
_ = reporter ?? throw new ArgumentNullException(nameof(reporter));
}
public NuGetPackageManager(
ISourceRepositoryProvider sourceRepositoryProvider,
ISettings settings,
ISolutionManager solutionManager,
IDeleteOnRestartManager deleteOnRestartManager,
IRestoreProgressReporter reporter,
bool excludeVersion)
{
SourceRepositoryProvider = sourceRepositoryProvider ?? throw new ArgumentNullException(nameof(sourceRepositoryProvider));
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
SolutionManager = solutionManager ?? throw new ArgumentNullException(nameof(solutionManager));
InstallationCompatibility = PackageManagement.InstallationCompatibility.Instance;
InitializePackagesFolderInfo(PackagesFolderPathUtility.GetPackagesFolderPath(SolutionManager, Settings), excludeVersion);
DeleteOnRestartManager = deleteOnRestartManager ?? throw new ArgumentNullException(nameof(deleteOnRestartManager));
RestoreProgressReporter = reporter;
NuGetTelemetryService = TelemetryActivity.NuGetTelemetryService;
}
/// <summary>
/// SourceRepositories for the user global package folder and all fallback package folders.
/// </summary>
public IReadOnlyList<SourceRepository> GlobalPackageFolderRepositories
{
get
{
if (_globalPackageFolderRepositories == null)
{
var sources = new List<SourceRepository>();
// Read package folders from settings
var pathContext = NuGetPathContext.Create(Settings);
// count = FallbackPackageFolders.Count + 1 for UserPackageFolder
var count = pathContext.FallbackPackageFolders.Count + 1;
var folders = new List<string>(count)
{
pathContext.UserPackageFolder
};
folders.AddRange(pathContext.FallbackPackageFolders);
foreach (var folder in folders)
{
// Create a repo for each folder
var source = SourceRepositoryProvider.CreateRepository(new PackageSource(folder), FeedType.FileSystemV3);
sources.Add(source);
}
_globalPackageFolderRepositories = sources;
}
return _globalPackageFolderRepositories;
}
}
private void InitializePackagesFolderInfo(string packagesFolderPath, bool excludeVersion = false)
{
// FileSystemPackagesConfig supports id.version formats, if the version is excluded use the normal v2 format
var feedType = excludeVersion ? FeedType.FileSystemV2 : FeedType.FileSystemPackagesConfig;
var resolver = new PackagePathResolver(packagesFolderPath, !excludeVersion);
PackagesFolderNuGetProject = new FolderNuGetProject(packagesFolderPath, resolver);
// Capturing it locally is important since it allows for the instance to cache packages for the lifetime
// of the closure \ NuGetPackageManager.
PackagesFolderSourceRepository = SourceRepositoryProvider.CreateRepository(
new PackageSource(packagesFolderPath),
feedType);
}
/// <summary>
/// Installs the latest version of the given <paramref name="packageId" /> to NuGetProject
/// <paramref name="nuGetProject" /> <paramref name="resolutionContext" /> and
/// <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public Task InstallPackageAsync(
NuGetProject nuGetProject,
string packageId,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
SourceRepository primarySourceRepository,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
if (resolutionContext == null)
{
throw new ArgumentNullException(nameof(resolutionContext));
}
var logger = new LoggerAdapter(nuGetProjectContext);
var downloadContext = new PackageDownloadContext(resolutionContext.SourceCacheContext)
{
ParentId = nuGetProjectContext.OperationId,
ClientPolicyContext = ClientPolicyContext.GetClientPolicy(Settings, logger)
};
return InstallPackageAsync(
nuGetProject,
packageId,
resolutionContext,
nuGetProjectContext,
downloadContext,
new List<SourceRepository> { primarySourceRepository },
secondarySources,
token);
}
/// <summary>
/// Installs the latest version of the given <paramref name="packageId" /> to NuGetProject
/// <paramref name="nuGetProject" /> <paramref name="resolutionContext" /> and
/// <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public Task InstallPackageAsync(
NuGetProject nuGetProject,
string packageId,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
PackageDownloadContext downloadContext,
SourceRepository primarySourceRepository,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return InstallPackageAsync(
nuGetProject,
packageId,
resolutionContext,
nuGetProjectContext,
downloadContext,
new List<SourceRepository> { primarySourceRepository },
secondarySources,
token);
}
/// <summary>
/// Installs the latest version of the given
/// <paramref name="packageId" /> to NuGetProject <paramref name="nuGetProject" />
/// <paramref name="resolutionContext" /> and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public async Task InstallPackageAsync(NuGetProject nuGetProject, string packageId, ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext, IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources, CancellationToken token)
{
if (resolutionContext == null)
{
throw new ArgumentNullException(nameof(resolutionContext));
}
var logger = new LoggerAdapter(nuGetProjectContext);
var downloadContext = new PackageDownloadContext(resolutionContext.SourceCacheContext)
{
ParentId = nuGetProjectContext.OperationId,
ClientPolicyContext = ClientPolicyContext.GetClientPolicy(Settings, logger)
};
await InstallPackageAsync(
nuGetProject,
packageId,
resolutionContext,
nuGetProjectContext,
downloadContext,
primarySources,
secondarySources,
token);
}
/// <summary>
/// Installs the latest version of the given
/// <paramref name="packageId" /> to NuGetProject <paramref name="nuGetProject" />
/// <paramref name="resolutionContext" /> and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public async Task InstallPackageAsync(
NuGetProject nuGetProject,
string packageId,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
PackageDownloadContext downloadContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
var log = new LoggerAdapter(nuGetProjectContext);
// Step-1 : Get latest version for packageId
var resolvedPackage = await GetLatestVersionAsync(
packageId,
nuGetProject,
resolutionContext,
primarySources,
log,
token);
if (resolvedPackage == null || resolvedPackage.LatestVersion == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.NoLatestVersionFound, packageId));
}
// Step-2 : Call InstallPackageAsync(project, packageIdentity)
await InstallPackageAsync(
nuGetProject,
new PackageIdentity(packageId, resolvedPackage.LatestVersion),
resolutionContext,
nuGetProjectContext,
downloadContext,
primarySources,
secondarySources,
token);
}
/// <summary>
/// Installs given <paramref name="packageIdentity" /> to NuGetProject <paramref name="nuGetProject" />
/// <paramref name="resolutionContext" /> and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public Task InstallPackageAsync(
NuGetProject nuGetProject,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
SourceRepository primarySourceRepository,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return InstallPackageAsync(
nuGetProject,
packageIdentity,
resolutionContext,
nuGetProjectContext,
new List<SourceRepository> { primarySourceRepository },
secondarySources,
token);
}
/// <summary>
/// Installs given <paramref name="packageIdentity" /> to NuGetProject <paramref name="nuGetProject" />
/// <paramref name="resolutionContext" /> and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public Task InstallPackageAsync(
NuGetProject nuGetProject,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
PackageDownloadContext downloadContext,
SourceRepository primarySourceRepository,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return InstallPackageAsync(
nuGetProject,
packageIdentity,
resolutionContext,
nuGetProjectContext,
downloadContext,
new List<SourceRepository> { primarySourceRepository },
secondarySources,
token);
}
/// <summary>
/// Installs given <paramref name="packageIdentity" /> to NuGetProject <paramref name="nuGetProject" />
/// <paramref name="resolutionContext" /> and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public async Task InstallPackageAsync(
NuGetProject nuGetProject,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
if (resolutionContext == null)
{
throw new ArgumentNullException(nameof(resolutionContext));
}
var logger = new LoggerAdapter(nuGetProjectContext);
var downloadContext = new PackageDownloadContext(resolutionContext.SourceCacheContext)
{
ParentId = nuGetProjectContext.OperationId,
ClientPolicyContext = ClientPolicyContext.GetClientPolicy(Settings, logger)
};
await InstallPackageAsync(
nuGetProject,
packageIdentity,
resolutionContext,
nuGetProjectContext,
downloadContext,
primarySources,
secondarySources,
token);
}
/// <summary>
/// Installs given <paramref name="packageIdentity" /> to NuGetProject <paramref name="nuGetProject" />
/// <paramref name="resolutionContext" /> and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public async Task InstallPackageAsync(
NuGetProject nuGetProject,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
PackageDownloadContext downloadContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
ActivityCorrelationId.StartNew();
// Step-1 : Call PreviewInstallPackageAsync to get all the nuGetProjectActions
var nuGetProjectActions = await PreviewInstallPackageAsync(nuGetProject, packageIdentity, resolutionContext,
nuGetProjectContext, primarySources, secondarySources, token);
SetDirectInstall(packageIdentity, nuGetProjectContext);
// Step-2 : Execute all the nuGetProjectActions
await ExecuteNuGetProjectActionsAsync(
nuGetProject,
nuGetProjectActions,
nuGetProjectContext,
downloadContext,
token);
ClearDirectInstall(nuGetProjectContext);
}
public async Task UninstallPackageAsync(NuGetProject nuGetProject, string packageId, UninstallationContext uninstallationContext,
INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
ActivityCorrelationId.StartNew();
// Step-1 : Call PreviewUninstallPackagesAsync to get all the nuGetProjectActions
var nuGetProjectActions = await PreviewUninstallPackageAsync(nuGetProject, packageId, uninstallationContext, nuGetProjectContext, token);
// Step-2 : Execute all the nuGetProjectActions
await ExecuteNuGetProjectActionsAsync(nuGetProject, nuGetProjectActions, nuGetProjectContext, NullSourceCacheContext.Instance, token);
}
/// <summary>
/// Gives the preview as a list of NuGetProjectActions that will be performed to install
/// <paramref name="packageId" /> into <paramref name="nuGetProject" /> <paramref name="resolutionContext" />
/// and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public Task<IEnumerable<NuGetProjectAction>> PreviewInstallPackageAsync(
NuGetProject nuGetProject,
string packageId,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
SourceRepository primarySourceRepository,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return PreviewInstallPackageAsync(nuGetProject, packageId, resolutionContext, nuGetProjectContext, new[] { primarySourceRepository }, secondarySources, token);
}
/// <summary>
/// Gives the preview as a list of NuGetProjectActions that will be performed to install
/// <paramref name="packageId" /> into <paramref name="nuGetProject" /> <paramref name="resolutionContext" />
/// and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public async Task<IEnumerable<NuGetProjectAction>> PreviewInstallPackageAsync(
NuGetProject nuGetProject,
string packageId,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
if (nuGetProject == null)
{
throw new ArgumentNullException(nameof(nuGetProject));
}
if (packageId == null)
{
throw new ArgumentNullException(nameof(packageId));
}
if (resolutionContext == null)
{
throw new ArgumentNullException(nameof(resolutionContext));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
var log = new LoggerAdapter(nuGetProjectContext);
// Step-1 : Get latest version for packageId
var resolvedPackage = await GetLatestVersionAsync(
packageId,
nuGetProject,
resolutionContext,
primarySources,
log,
token);
if (resolvedPackage == null || resolvedPackage.LatestVersion == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.UnknownPackage, packageId));
}
var projectInstalledPackageReferences = await nuGetProject.GetInstalledPackagesAsync(token);
var installedPackageReference = projectInstalledPackageReferences.FirstOrDefault(pr => StringComparer.OrdinalIgnoreCase.Equals(pr.PackageIdentity.Id, packageId));
if (installedPackageReference != null
&& installedPackageReference.PackageIdentity.Version > resolvedPackage.LatestVersion)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.NewerVersionAlreadyReferenced, packageId));
}
// Step-2 : Call InstallPackage(project, packageIdentity)
return await PreviewInstallPackageAsync(nuGetProject, new PackageIdentity(packageId, resolvedPackage.LatestVersion), resolutionContext,
nuGetProjectContext, primarySources, secondarySources, token);
}
public Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesAsync(
IEnumerable<NuGetProject> nuGetProjects,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return PreviewUpdatePackagesAsync(
packageId: null,
packageIdentities: new List<PackageIdentity>(),
nuGetProjects: nuGetProjects,
resolutionContext: resolutionContext,
nuGetProjectContext: nuGetProjectContext,
primarySources: primarySources,
secondarySources: secondarySources,
token: token);
}
public Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesAsync(
string packageId,
IEnumerable<NuGetProject> nuGetProjects,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return PreviewUpdatePackagesAsync(
packageId: packageId,
packageIdentities: new List<PackageIdentity>(),
nuGetProjects: nuGetProjects,
resolutionContext: resolutionContext,
nuGetProjectContext: nuGetProjectContext,
primarySources: primarySources,
secondarySources: secondarySources,
token: token);
}
public Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesAsync(
PackageIdentity packageIdentity,
IEnumerable<NuGetProject> nuGetProjects,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return PreviewUpdatePackagesAsync(
packageId: null,
packageIdentities: new List<PackageIdentity> { packageIdentity },
nuGetProjects: nuGetProjects,
resolutionContext: resolutionContext,
nuGetProjectContext: nuGetProjectContext,
primarySources: primarySources,
secondarySources: secondarySources,
token: token);
}
public Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesAsync(
List<PackageIdentity> packageIdentities,
IEnumerable<NuGetProject> nuGetProjects,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return PreviewUpdatePackagesAsync(
packageId: null,
packageIdentities: packageIdentities,
nuGetProjects: nuGetProjects,
resolutionContext: resolutionContext,
nuGetProjectContext: nuGetProjectContext,
primarySources: primarySources,
secondarySources: secondarySources,
token: token);
}
private async Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesAsync(
string packageId,
List<PackageIdentity> packageIdentities,
IEnumerable<NuGetProject> nuGetProjects,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
if (packageIdentities == null)
{
throw new ArgumentNullException(nameof(packageIdentities));
}
if (nuGetProjects == null)
{
throw new ArgumentNullException(nameof(nuGetProjects));
}
if (resolutionContext == null)
{
throw new ArgumentNullException(nameof(resolutionContext));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
if (primarySources == null)
{
throw new ArgumentNullException(nameof(primarySources));
}
if (secondarySources == null)
{
throw new ArgumentNullException(nameof(secondarySources));
}
var maxTasks = 4;
var tasks = new List<Task<IEnumerable<NuGetProjectAction>>>(maxTasks);
var nugetActions = new List<NuGetProjectAction>();
var nuGetProjectList = nuGetProjects.ToList();
var buildIntegratedProjects = nuGetProjectList.OfType<BuildIntegratedNuGetProject>().ToList();
var nonBuildIntegratedProjects = nuGetProjectList.Except(buildIntegratedProjects).ToList();
var shouldFilterProjectsForUpdate = false;
if (packageIdentities.Count > 0)
{
shouldFilterProjectsForUpdate = true;
}
// Update http source cache context MaxAge so that it can always go online to fetch
// latest version of packages.
resolutionContext.SourceCacheContext.MaxAge = DateTimeOffset.UtcNow;
// add tasks for all build integrated projects
foreach (var project in buildIntegratedProjects)
{
var packagesToUpdateInProject = packageIdentities;
var updatedResolutionContext = resolutionContext;
if (shouldFilterProjectsForUpdate)
{
packagesToUpdateInProject = await GetPackagesToUpdateInProjectAsync(project, packageIdentities, token);
if (packagesToUpdateInProject.Count > 0)
{
var includePrerelease = packagesToUpdateInProject.Any(
package => package.Version.IsPrerelease) || resolutionContext.IncludePrerelease;
updatedResolutionContext = new ResolutionContext(
dependencyBehavior: resolutionContext.DependencyBehavior,
includePrelease: includePrerelease,
includeUnlisted: resolutionContext.IncludeUnlisted,
versionConstraints: resolutionContext.VersionConstraints,
gatherCache: resolutionContext.GatherCache,
sourceCacheContext: resolutionContext.SourceCacheContext);
}
else
{
// skip running update preview for this project, since it doesn't have any package installed
// which is being updated.
continue;
}
}
// if tasks count reachs max then wait until an existing task is completed
if (tasks.Count >= maxTasks)
{
var actions = await CompleteTaskAsync(tasks);
nugetActions.AddRange(actions);
}
tasks.Add(Task.Run(async ()
=> await PreviewUpdatePackagesForBuildIntegratedAsync(
packageId,
packagesToUpdateInProject,
project,
updatedResolutionContext,
nuGetProjectContext,
primarySources,
token)));
}
// Wait for all restores to finish
var allActions = await Task.WhenAll(tasks);
nugetActions.AddRange(allActions.SelectMany(action => action));
foreach (var project in nonBuildIntegratedProjects)
{
var packagesToUpdateInProject = packageIdentities;
var updatedResolutionContext = resolutionContext;
if (shouldFilterProjectsForUpdate)
{
packagesToUpdateInProject = await GetPackagesToUpdateInProjectAsync(project, packageIdentities, token);
if (packagesToUpdateInProject.Count > 0)
{
var includePrerelease = packagesToUpdateInProject.Any(
package => package.HasVersion && package.Version.IsPrerelease) || resolutionContext.IncludePrerelease;
updatedResolutionContext = new ResolutionContext(
dependencyBehavior: resolutionContext.DependencyBehavior,
includePrelease: includePrerelease,
includeUnlisted: resolutionContext.IncludeUnlisted,
versionConstraints: resolutionContext.VersionConstraints,
gatherCache: resolutionContext.GatherCache,
sourceCacheContext: resolutionContext.SourceCacheContext);
}
else
{
// skip running update preview for this project, since it doesn't have any package installed
// which is being updated.
continue;
}
}
// packages.config based projects are handled here
nugetActions.AddRange(await PreviewUpdatePackagesForClassicAsync(
packageId,
packagesToUpdateInProject,
project,
updatedResolutionContext,
nuGetProjectContext,
primarySources,
secondarySources,
token));
}
return nugetActions;
}
private async Task<List<PackageIdentity>> GetPackagesToUpdateInProjectAsync(
NuGetProject project,
List<PackageIdentity> packages,
CancellationToken token)
{
var installedPackages = await project.GetInstalledPackagesAsync(token);
var packageIds = new HashSet<string>(
installedPackages.Select(p => p.PackageIdentity.Id), StringComparer.OrdinalIgnoreCase);
// We need to filter out packages from packagesToUpdate that are not installed
// in the current project. Otherwise, we'll incorrectly install a
// package that is not installed before.
var packagesToUpdateInProject = packages.Where(
package => packageIds.Contains(package.Id)).ToList();
return packagesToUpdateInProject;
}
private async Task<IEnumerable<T>> CompleteTaskAsync<T>(
List<Task<IEnumerable<T>>> updateTasks)
{
var doneTask = await Task.WhenAny(updateTasks);
updateTasks.Remove(doneTask);
return await doneTask;
}
/// <summary>
/// Update Package logic specific to build integrated style NuGet projects
/// </summary>
private async Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesForBuildIntegratedAsync(
string packageId,
IReadOnlyList<PackageIdentity> packageIdentities,
NuGetProject nuGetProject,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
CancellationToken token)
{
var projectInstalledPackageReferences = await nuGetProject.GetInstalledPackagesAsync(token);
if (!nuGetProject.TryGetMetadata(NuGetProjectMetadataKeys.TargetFramework, out NuGetFramework framework))
{
// Default to the any framework if the project does not specify a framework.
framework = NuGetFramework.AnyFramework;
}
var log = new LoggerAdapter(nuGetProjectContext);
var actions = new List<NuGetProjectAction>();
if (packageIdentities.Count == 0 && packageId == null)
{
// Update All
var lowLevelActions = new List<NuGetProjectAction>();
foreach (var installedPackage in projectInstalledPackageReferences)
{
// Skip auto referenced packages during update all.
var autoReferenced = IsPackageReferenceAutoReferenced(installedPackage);
if (!autoReferenced)
{
var resolvedPackage = await GetLatestVersionAsync(
installedPackage,
framework,
resolutionContext,
primarySources,
log,
token);
if (resolvedPackage != null && resolvedPackage.LatestVersion != null && resolvedPackage.LatestVersion > installedPackage.PackageIdentity.Version)
{
lowLevelActions.Add(NuGetProjectAction.CreateInstallProjectAction(
new PackageIdentity(installedPackage.PackageIdentity.Id, resolvedPackage.LatestVersion),
primarySources.FirstOrDefault(),
nuGetProject));
}
}
}
// If the update operation is a no-op there will be no project actions.
if (lowLevelActions.Any())
{
var buildIntegratedProject = nuGetProject as BuildIntegratedNuGetProject;
if (buildIntegratedProject != null)
{
// Create a build integrated action
var buildIntegratedAction =
await PreviewBuildIntegratedProjectActionsAsync(buildIntegratedProject, lowLevelActions, nuGetProjectContext, token);
actions.Add(buildIntegratedAction);
}
else
{
// Use the low level actions for projectK
actions = lowLevelActions;
}
}
}
else
{
// either we have a packageId or a list of specific PackageIdentities to work with
// first lets normalize this input so we are just dealing with a list
if (packageIdentities.Count == 0)
{
var installedPackageReference = projectInstalledPackageReferences
.FirstOrDefault(pr => StringComparer.OrdinalIgnoreCase.Equals(pr.PackageIdentity.Id, packageId));
// Skip autoreferenced update when we have only a package ID.
var autoReferenced = IsPackageReferenceAutoReferenced(installedPackageReference);
if (installedPackageReference != null && !autoReferenced)
{
var resolvedPackage = await GetLatestVersionAsync(
installedPackageReference,
framework,
resolutionContext,
primarySources,
log,
token);
if (resolvedPackage == null || !resolvedPackage.Exists)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.UnknownPackage, packageId));
}
else if (resolvedPackage.LatestVersion != null)
{
if (installedPackageReference.PackageIdentity.Version > resolvedPackage.LatestVersion)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
Strings.NewerVersionAlreadyReferenced, packageId));
}
else if (installedPackageReference.PackageIdentity.Version < resolvedPackage.LatestVersion)
{
// The same instance of packageIdentities might be used by multiple tasks in parallel,
// so it's unsafe to modify, hence which it's IReadOnlyList and not just List.
// Therefore, treat the list as immutable (need a new instance with changes).
packageIdentities = packageIdentities.Concat(new[] { new PackageIdentity(packageId, resolvedPackage.LatestVersion) }).ToList();
}
}
}
}
// process the list of PackageIdentities
var lowLevelActions = new List<NuGetProjectAction>();
foreach (var packageIdentity in packageIdentities)
{
var installed = projectInstalledPackageReferences
.FirstOrDefault(pr => StringComparer.OrdinalIgnoreCase.Equals(pr.PackageIdentity.Id, packageIdentity.Id));
var autoReferenced = IsPackageReferenceAutoReferenced(installed);
// if the package is not currently installed, or the installed one is auto referenced ignore it
if (installed != null && !autoReferenced)
{
lowLevelActions.Add(NuGetProjectAction.CreateInstallProjectAction(packageIdentity,
primarySources.FirstOrDefault(), nuGetProject));
}
}
if (lowLevelActions.Count > 0)
{
var buildIntegratedProject = nuGetProject as BuildIntegratedNuGetProject;
if (buildIntegratedProject != null)
{
// Create a build integrated action
var buildIntegratedAction = await PreviewBuildIntegratedProjectActionsAsync(
buildIntegratedProject,
lowLevelActions,
nuGetProjectContext,
token);
actions.Add(buildIntegratedAction);
}
else
{
// Use the low level actions for projectK
actions.AddRange(lowLevelActions);
}
}
}
if (actions.Count == 0)
{
var projectName = NuGetProject.GetUniqueNameOrName(nuGetProject);
nuGetProjectContext.Log(MessageLevel.Info, Strings.NoPackageUpdates, projectName);
}
return actions;
}
private static bool IsPackageReferenceAutoReferenced(PackageReference package)
{
var buildPackageReference = package as BuildIntegratedPackageReference;
return buildPackageReference?.Dependency?.AutoReferenced == true;
}
/// <summary>
/// Update Package logic specific to classic style NuGet projects
/// </summary>
private async Task<IEnumerable<NuGetProjectAction>> PreviewUpdatePackagesForClassicAsync(
string packageId,
IReadOnlyList<PackageIdentity> packageIdentities,
NuGetProject nuGetProject,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
var log = new LoggerAdapter(nuGetProjectContext);
var stopWatch = Stopwatch.StartNew();
var projectInstalledPackageReferences = await nuGetProject.GetInstalledPackagesAsync(token);
var oldListOfInstalledPackages = projectInstalledPackageReferences.Select(p => p.PackageIdentity);
var isUpdateAll = (packageId == null && packageIdentities.Count == 0);
var preferredVersions = new Dictionary<string, PackageIdentity>(StringComparer.OrdinalIgnoreCase);
// By default we start by preferring everything we already have installed
foreach (var installedPackage in oldListOfInstalledPackages)
{
preferredVersions[installedPackage.Id] = installedPackage;
}
var primaryTargetIds = new List<string>();
var primaryTargets = new List<PackageIdentity>();
// We have been given the exact PackageIdentities (id and version) to update to e.g. from PMC update-package -Id <id> -Version <version>
if (packageIdentities.Count > 0)
{
// If we have been given explicit PackageIdentities to install then we will naturally prefer that
foreach (var packageIdentity in packageIdentities)
{
// Just a check to make sure the preferredVersions created from the existing package list actually contains the target
if (preferredVersions.ContainsKey(packageIdentity.Id))
{
primaryTargetIds.Add(packageIdentity.Id);
// If there was a version specified we will prefer that version
if (packageIdentity.HasVersion)
{
preferredVersions[packageIdentity.Id] = packageIdentity;
primaryTargets.Add(packageIdentity);
}
// Otherwise we just have the Id and so we wil explicitly not prefer the one currently installed
else
{
preferredVersions.Remove(packageIdentity.Id);
}
}
}
}
// We have just been given the package id, in which case we will look for the highest version and attempt to move to that
else if (packageId != null)
{
if (preferredVersions.ContainsKey(packageId))
{
if (PrunePackageTree.IsExactVersion(resolutionContext.VersionConstraints))
{
primaryTargets = new List<PackageIdentity> { preferredVersions[packageId] };
}
else
{
primaryTargetIds = new List<string> { packageId };
// If we have been given just a package Id we certainly don't want the one installed - pruning will be significant
preferredVersions.Remove(packageId);
}
}
else
{
// This is the scenario where a specific package is being updated in a project with a -reinstall flag and the -projectname has not been specified.
// In this case, NuGet should bail out if the package does not exist in this project instead of re-installing all packages of the project.
// Bug: https://github.com/NuGet/Home/issues/737
return Enumerable.Empty<NuGetProjectAction>();
}
}
// We are apply update logic to the complete project - attempting to resolver all updates together
else
{
primaryTargetIds = projectInstalledPackageReferences.Select(p => p.PackageIdentity.Id).ToList();
// We are performing a global project-wide update - nothing is preferred - again pruning will be significant
preferredVersions.Clear();
}
// Note: resolver needs all the installed packages as targets too. And, metadata should be gathered for the installed packages as well
var packageTargetIdsForResolver = new HashSet<string>(oldListOfInstalledPackages.Select(p => p.Id), StringComparer.OrdinalIgnoreCase);
foreach (var packageIdToInstall in primaryTargetIds)
{
packageTargetIdsForResolver.Add(packageIdToInstall);
}
var projectName = NuGetProject.GetUniqueNameOrName(nuGetProject);
var nuGetProjectActions = new List<NuGetProjectAction>();
if (!packageTargetIdsForResolver.Any())
{
nuGetProjectContext.Log(NuGet.ProjectManagement.MessageLevel.Info, Strings.NoPackagesInProject, projectName);
return nuGetProjectActions;
}
try
{
// If any targets are prerelease we should gather with prerelease on and filter afterwards
var includePrereleaseInGather = resolutionContext.IncludePrerelease || (projectInstalledPackageReferences.Any(p => (p.PackageIdentity.HasVersion && p.PackageIdentity.Version.IsPrerelease)));
// Step-1 : Get metadata resources using gatherer
var targetFramework = nuGetProject.GetMetadata<NuGetFramework>(NuGetProjectMetadataKeys.TargetFramework);
nuGetProjectContext.Log(MessageLevel.Info, Environment.NewLine);
nuGetProjectContext.Log(MessageLevel.Info, Strings.AttemptingToGatherDependencyInfoForMultiplePackages, projectName, targetFramework);
var allSources = new List<SourceRepository>(primarySources);
var primarySourcesSet = new HashSet<string>(primarySources.Select(s => s.PackageSource.Source));
foreach (var secondarySource in secondarySources)
{
if (!primarySourcesSet.Contains(secondarySource.PackageSource.Source))
{
allSources.Add(secondarySource);
}
}
foreach (SourceRepository enabledSource in allSources)
{
PackageSource source = enabledSource.PackageSource;
if (source.IsHttp && !source.IsHttps && !source.AllowInsecureConnections)
{
nuGetProjectContext.Log(MessageLevel.Error, Strings.Error_HttpSource_Single, "update", source.Source);
}
}
// Unless the packageIdentity was explicitly asked for we should remove any potential downgrades
var allowDowngrades = false;
if (packageIdentities.Count == 1)
{
// Get installed package version
var packageTargetsForResolver = new HashSet<PackageIdentity>(oldListOfInstalledPackages, PackageIdentity.Comparer);
var installedPackageWithSameId = packageTargetsForResolver.FirstOrDefault(p => p.Id.Equals(packageIdentities[0].Id, StringComparison.OrdinalIgnoreCase));
if (installedPackageWithSameId != null)
{
if (installedPackageWithSameId.Version > packageIdentities[0].Version)
{
// Looks like the installed package is of higher version than one being installed. So, we take it that downgrade is allowed
allowDowngrades = true;
}
}
}
var packageSourceMapping = PackageSourceMapping.GetPackageSourceMapping(Settings);
var gatherContext = new GatherContext(packageSourceMapping)
{
InstalledPackages = oldListOfInstalledPackages.ToList(),
PrimaryTargetIds = primaryTargetIds.ToList(),
PrimaryTargets = primaryTargets.ToList(),
TargetFramework = targetFramework,
PrimarySources = primarySources.ToList(),
AllSources = allSources.ToList(),
PackagesFolderSource = PackagesFolderSourceRepository,
ResolutionContext = resolutionContext,
AllowDowngrades = allowDowngrades,
ProjectContext = nuGetProjectContext,
IsUpdateAll = isUpdateAll
};
var availablePackageDependencyInfoWithSourceSet = await ResolverGather.GatherAsync(gatherContext, token);
// emit gather dependency telemetry event and restart timer
stopWatch.Stop();
var gatherTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.GatherDependencyStepName,
stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(gatherTelemetryEvent);
stopWatch.Restart();
if (!availablePackageDependencyInfoWithSourceSet.Any())
{
throw new InvalidOperationException(Strings.UnableToGatherDependencyInfoForMultiplePackages);
}
// Update-Package ALL packages scenarios must always include the packages in the current project
// Scenarios include: (1) a package havign been deleted from a feed (2) a source being removed from nuget config (3) an explicitly specified source
if (isUpdateAll)
{
// BUG #1181 VS2015 : Updating from one feed fails for packages from different feed.
var packagesFolderResource = await PackagesFolderSourceRepository.GetResourceAsync<DependencyInfoResource>(token);
foreach (var installedPackage in projectInstalledPackageReferences)
{
var packageInfo = await packagesFolderResource.ResolvePackage(installedPackage.PackageIdentity, targetFramework, resolutionContext.SourceCacheContext, log, token);
if (packageInfo != null)
{
availablePackageDependencyInfoWithSourceSet.Add(packageInfo);
}
}
}
// Prune the results down to only what we would allow to be installed
IEnumerable<SourcePackageDependencyInfo> prunedAvailablePackages = availablePackageDependencyInfoWithSourceSet;
if (!resolutionContext.IncludePrerelease)
{
prunedAvailablePackages = PrunePackageTree.PrunePrereleaseExceptAllowed(
prunedAvailablePackages,
oldListOfInstalledPackages,
isUpdateAll);
}
// Prune unlisted versions if IncludeUnlisted flag is not set.
if (!resolutionContext.IncludeUnlisted)
{
prunedAvailablePackages = prunedAvailablePackages.Where(p => p.Listed);
}
// Remove packages that do not meet the constraints specified in the UpdateConstrainst
prunedAvailablePackages = PrunePackageTree.PruneByUpdateConstraints(prunedAvailablePackages, projectInstalledPackageReferences, resolutionContext.VersionConstraints);
// Verify that the target is allowed by packages.config
GatherExceptionHelpers.ThrowIfVersionIsDisallowedByPackagesConfig(primaryTargetIds, projectInstalledPackageReferences, prunedAvailablePackages, log);
// Remove versions that do not satisfy 'allowedVersions' attribute in packages.config, if any
prunedAvailablePackages = PrunePackageTree.PruneDisallowedVersions(prunedAvailablePackages, projectInstalledPackageReferences);
// Remove all but the highest packages that are of the same Id as a specified packageId
if (packageId != null)
{
prunedAvailablePackages = PrunePackageTree.PruneAllButHighest(prunedAvailablePackages, packageId);
// And then verify that the installed package is not already of a higher version - this check here ensures the user get's the right error message
GatherExceptionHelpers.ThrowIfNewerVersionAlreadyReferenced(packageId, projectInstalledPackageReferences, prunedAvailablePackages);
}
// Remove packages that are of the same Id but different version than the primartTargets
prunedAvailablePackages = PrunePackageTree.PruneByPrimaryTargets(prunedAvailablePackages, primaryTargets);
// Unless the packageIdentity was explicitly asked for we should remove any potential downgrades
if (!allowDowngrades)
{
prunedAvailablePackages = PrunePackageTree.PruneDowngrades(prunedAvailablePackages, projectInstalledPackageReferences);
}
// Step-2 : Call PackageResolver.Resolve to get new list of installed packages
var packageResolver = new PackageResolver();
var packageResolverContext = new PackageResolverContext(
resolutionContext.DependencyBehavior,
primaryTargetIds,
packageTargetIdsForResolver,
projectInstalledPackageReferences,
preferredVersions.Values,
prunedAvailablePackages,
SourceRepositoryProvider.GetRepositories().Select(s => s.PackageSource),
log);
nuGetProjectContext.Log(NuGet.ProjectManagement.MessageLevel.Info, Strings.AttemptingToResolveDependenciesForMultiplePackages);
var newListOfInstalledPackages = packageResolver.Resolve(packageResolverContext, token);
// emit resolve dependency telemetry event and restart timer
stopWatch.Stop();
var resolveTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.ResolveDependencyStepName,
stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(resolveTelemetryEvent);
stopWatch.Restart();
if (newListOfInstalledPackages == null)
{
throw new InvalidOperationException(Strings.UnableToResolveDependencyInfoForMultiplePackages);
}
// if we have been asked for exact versions of packages then we should also force the uninstall/install of those packages (this corresponds to a -Reinstall)
var isReinstall = PrunePackageTree.IsExactVersion(resolutionContext.VersionConstraints);
var targetIds = Enumerable.Empty<string>();
if (!isUpdateAll)
{
targetIds = (isReinstall ? primaryTargets.Select(p => p.Id) : primaryTargetIds);
}
var installedPackagesInDependencyOrder = await GetInstalledPackagesInDependencyOrder(nuGetProject, token);
var isDependencyBehaviorIgnore = resolutionContext.DependencyBehavior == DependencyBehavior.Ignore;
nuGetProjectActions = GetProjectActionsForUpdate(
nuGetProject,
newListOfInstalledPackages,
installedPackagesInDependencyOrder,
prunedAvailablePackages,
nuGetProjectContext,
isReinstall,
targetIds,
isDependencyBehaviorIgnore);
// emit resolve actions telemetry event
stopWatch.Stop();
var actionTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.ResolvedActionsStepName,
stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(actionTelemetryEvent);
if (nuGetProjectActions.Count == 0)
{
nuGetProjectContext.Log(MessageLevel.Info, Strings.ResolutionSuccessfulNoAction);
nuGetProjectContext.Log(MessageLevel.Info, Strings.NoUpdatesAvailable);
}
}
catch (InvalidOperationException)
{
throw;
}
catch (AggregateException aggregateEx)
{
throw new InvalidOperationException(aggregateEx.Message, aggregateEx);
}
catch (Exception ex)
{
if (string.IsNullOrEmpty(ex.Message))
{
throw new InvalidOperationException(Strings.PackagesCouldNotBeInstalled, ex);
}
throw new InvalidOperationException(ex.Message, ex);
}
if (nuGetProjectActions.Count == 0)
{
nuGetProjectContext.Log(MessageLevel.Info, Strings.NoPackageUpdates, projectName);
}
return nuGetProjectActions;
}
/// <summary>
/// The package dependency info for the given project. The project needs to be packages.config, otherwise returns an empty list.
/// </summary>
/// <param name="nuGetProject">The project is question</param>
/// <param name="token">cancellation token</param>
/// <param name="includeUnresolved">Whether to include the unresolved packages. The unresolved packages include packages that are not restored and cannot be found on disk.</param>
/// <returns></returns>
public async Task<IEnumerable<PackageDependencyInfo>> GetInstalledPackagesDependencyInfo(NuGetProject nuGetProject, CancellationToken token, bool includeUnresolved = false)
{
if (nuGetProject is MSBuildNuGetProject)
{
var targetFramework = nuGetProject.GetMetadata<NuGetFramework>(NuGetProjectMetadataKeys.TargetFramework);
var installedPackageIdentities = (await nuGetProject.GetInstalledPackagesAsync(token)).Select(pr => pr.PackageIdentity);
return await GetDependencyInfoFromPackagesFolderAsync(installedPackageIdentities, targetFramework, includeUnresolved, token);
}
else
{
return Enumerable.Empty<PackageDependencyInfo>();
}
}
/// <summary>
/// Returns all installed packages in order of dependency. Packages with no dependencies come first.
/// </summary>
/// <remarks>Packages with unresolved dependencies are NOT returned since they are not valid.</remarks>
public async Task<IEnumerable<PackageIdentity>> GetInstalledPackagesInDependencyOrder(NuGetProject nuGetProject,
CancellationToken token)
{
var targetFramework = nuGetProject.GetMetadata<NuGetFramework>(NuGetProjectMetadataKeys.TargetFramework);
var installedPackages = await nuGetProject.GetInstalledPackagesAsync(token);
var installedPackageIdentities = installedPackages.Select(pr => pr.PackageIdentity);
var dependencyInfoFromPackagesFolder = await GetDependencyInfoFromPackagesFolderAsync(installedPackageIdentities,
targetFramework,
includeUnresolved: false,
token);
// dependencyInfoFromPackagesFolder can be null when NuGetProtocolException is thrown
var resolverPackages = dependencyInfoFromPackagesFolder?.Select(package =>
new ResolverPackage(package.Id, package.Version, package.Dependencies, true, false));
// Use the resolver sort to find the order. Packages with no dependencies
// come first, then each package that has satisfied dependencies.
// Packages with missing dependencies will not be returned.
if (resolverPackages != null)
{
return ResolverUtility.TopologicalSort(resolverPackages);
}
return Enumerable.Empty<PackageIdentity>();
}
private static List<NuGetProjectAction> GetProjectActionsForUpdate(
NuGetProject project,
IEnumerable<PackageIdentity> newListOfInstalledPackages,
IEnumerable<PackageIdentity> oldListOfInstalledPackages,
IEnumerable<SourcePackageDependencyInfo> availablePackageDependencyInfoWithSourceSet,
INuGetProjectContext nuGetProjectContext,
bool isReinstall,
IEnumerable<string> targetIds,
bool isDependencyBehaviorIgnore)
{
// Step-3 : Get the list of nuGetProjectActions to perform, install/uninstall on the nugetproject
// based on newPackages obtained in Step-2 and project.GetInstalledPackages
var nuGetProjectActions = new List<NuGetProjectAction>();
nuGetProjectContext.Log(MessageLevel.Info, Strings.ResolvingActionsToInstallOrUpdateMultiplePackages);
// we are reinstalling everything so we just take the ordering directly from the Resolver
var newPackagesToUninstall = oldListOfInstalledPackages;
var newPackagesToInstall = newListOfInstalledPackages;
// we are doing a reinstall of a specific package - we will also want to generate Project Actions for the dependencies
if (isReinstall && targetIds.Any())
{
var packageIdsToReinstall = new HashSet<string>(targetIds, StringComparer.OrdinalIgnoreCase);
// Avoid getting dependencies if dependencyBehavior is set to ignore
if (!isDependencyBehaviorIgnore)
{
packageIdsToReinstall = GetDependencies(targetIds, newListOfInstalledPackages, availablePackageDependencyInfoWithSourceSet);
}
newPackagesToUninstall = oldListOfInstalledPackages.Where(p => packageIdsToReinstall.Contains(p.Id));
newPackagesToInstall = newListOfInstalledPackages.Where(p => packageIdsToReinstall.Contains(p.Id));
}
if (!isReinstall)
{
if (targetIds.Any())
{
// we are targeting a particular package - there is no need therefore to alter other aspects of the project
// specifically an unrelated package may have been force removed in which case we should be happy to leave things that way
// It will get the list of packages which are being uninstalled to get a new version
newPackagesToUninstall = oldListOfInstalledPackages.Where(oldPackage =>
newListOfInstalledPackages.Any(newPackage =>
StringComparer.OrdinalIgnoreCase.Equals(oldPackage.Id, newPackage.Id) && !oldPackage.Version.Equals(newPackage.Version)));
// this will be the new set of target ids which includes current target ids as well as packages which are being updated
//It fixes the issue where we were only getting dependencies for target ids ignoring other packages which are also being updated. #2724
var newTargetIds = new HashSet<string>(newPackagesToUninstall.Select(p => p.Id), StringComparer.OrdinalIgnoreCase);
newTargetIds.AddRange(targetIds);
var allowed = newTargetIds;
// Avoid getting dependencies if dependencyBehavior is set to ignore
if (!isDependencyBehaviorIgnore)
{
// first, we will allow all the dependencies of the package(s) beging targeted
allowed = GetDependencies(newTargetIds, newListOfInstalledPackages, availablePackageDependencyInfoWithSourceSet);
}
// second, any package that is currently in the solution will also be allowed to change
// (note this logically doesn't include packages that have been force uninstalled from the project
// because we wouldn't want to just add those back in)
foreach (var p in oldListOfInstalledPackages)
{
allowed.Add(p.Id);
}
newListOfInstalledPackages = newListOfInstalledPackages.Where(p => allowed.Contains(p.Id));
newPackagesToInstall = newListOfInstalledPackages.Where(p => !oldListOfInstalledPackages.Contains(p));
}
else
{
newPackagesToUninstall = oldListOfInstalledPackages.Where(p => !newListOfInstalledPackages.Contains(p));
newPackagesToInstall = newListOfInstalledPackages.Where(p => !oldListOfInstalledPackages.Contains(p));
}
}
foreach (var newPackageToUninstall in newPackagesToUninstall.Reverse())
{
nuGetProjectActions.Add(NuGetProjectAction.CreateUninstallProjectAction(newPackageToUninstall, project));
}
foreach (var newPackageToInstall in newPackagesToInstall)
{
// find the package match based on identity
var sourceDepInfo = availablePackageDependencyInfoWithSourceSet.SingleOrDefault(p => PackageIdentity.Comparer.Equals(p, newPackageToInstall));
if (sourceDepInfo == null)
{
// this really should never happen
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.PackageNotFound, newPackageToInstall));
}
nuGetProjectActions.Add(NuGetProjectAction.CreateInstallProjectAction(newPackageToInstall, sourceDepInfo.Source, project));
}
return nuGetProjectActions;
}
/// <summary>
/// Filter down the reinstall list to just the ones we need to reinstall (i.e. the dependencies)
/// </summary>
private static HashSet<string> GetDependencies(IEnumerable<string> targetIds, IEnumerable<PackageIdentity> newListOfInstalledPackages, IEnumerable<SourcePackageDependencyInfo> available)
{
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var targetId in targetIds)
{
CollectDependencies(result, targetId, newListOfInstalledPackages, available, 0);
}
return result;
}
/// <summary>
/// A walk through the dependencies to collect the additional package identities that are involved in the current set of packages to be installed
/// </summary>
private static void CollectDependencies(HashSet<string> result,
string id,
IEnumerable<PackageIdentity> packages,
IEnumerable<SourcePackageDependencyInfo> available, int depth)
{
// check if we've already added dependencies for current id
if (!result.Add(id))
{
return;
}
// we want the exact PackageIdentity for this id
var packageIdentity = packages.FirstOrDefault(p => p.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
if (packageIdentity == null)
{
throw new ArgumentException(
message: string.Format(CultureInfo.CurrentCulture, Strings.PackageNotFound, id),
paramName: nameof(packages));
}
// now look up the dependencies of this exact package identity
var sourceDepInfo = available.SingleOrDefault(p => packageIdentity.Equals(p));
if (sourceDepInfo == null)
{
throw new ArgumentException(
message: string.Format(CultureInfo.CurrentCulture, Strings.PackageNotFound, id),
paramName: nameof(available));
}
// iterate through all the dependencies and call recursively to collect dependencies
foreach (var dependency in sourceDepInfo.Dependencies)
{
// check we don't fall into an infinite loop caused by bad dependency data in the packages
if (depth < packages.Count())
{
CollectDependencies(result, dependency.Id, packages, available, depth + 1);
}
}
}
/// <summary>
/// Gives the preview as a list of NuGetProjectActions that will be performed to install
/// <paramref name="packageIdentity" /> into <paramref name="nuGetProject" />
/// <paramref name="resolutionContext" /> and <paramref name="nuGetProjectContext" /> are used in the process.
/// </summary>
public async Task<IEnumerable<NuGetProjectAction>> PreviewInstallPackageAsync(NuGetProject nuGetProject, PackageIdentity packageIdentity,
ResolutionContext resolutionContext, INuGetProjectContext nuGetProjectContext,
SourceRepository primarySourceRepository, IEnumerable<SourceRepository> secondarySources, CancellationToken token)
{
if (nuGetProject is INuGetIntegratedProject)
{
var action = NuGetProjectAction.CreateInstallProjectAction(packageIdentity, primarySourceRepository, nuGetProject);
var actions = new[] { action };
var buildIntegratedProject = nuGetProject as BuildIntegratedNuGetProject;
if (buildIntegratedProject != null)
{
actions = new[] {
await PreviewBuildIntegratedProjectActionsAsync(buildIntegratedProject, actions, nuGetProjectContext, token)
};
}
return actions;
}
var primarySources = new List<SourceRepository> { primarySourceRepository };
return await PreviewInstallPackageAsync(nuGetProject, packageIdentity, resolutionContext,
nuGetProjectContext, primarySources, secondarySources, token);
}
public async Task<IEnumerable<ResolvedAction>> PreviewProjectsInstallPackageAsync(
IReadOnlyCollection<NuGetProject> nuGetProjects,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IReadOnlyCollection<SourceRepository> activeSources,
CancellationToken token)
{
return await PreviewProjectsInstallPackageAsync(nuGetProjects, packageIdentity, resolutionContext, nuGetProjectContext, activeSources, versionRange: null, token);
}
public async Task<IEnumerable<ResolvedAction>> PreviewProjectsInstallPackageAsync(
IReadOnlyCollection<NuGetProject> nuGetProjects,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IReadOnlyCollection<SourceRepository> activeSources,
VersionRange versionRange,
CancellationToken token)
{
return await PreviewProjectsInstallPackageAsync(
nuGetProjects,
packageIdentity,
resolutionContext,
nuGetProjectContext,
activeSources,
versionRange,
newMappingID: null,
newMappingSource: null,
token);
}
// Preview and return ResolvedActions for many NuGetProjects.
public async Task<IEnumerable<ResolvedAction>> PreviewProjectsInstallPackageAsync(
IReadOnlyCollection<NuGetProject> nuGetProjects,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IReadOnlyCollection<SourceRepository> activeSources,
VersionRange versionRange,
string newMappingID,
string newMappingSource,
CancellationToken token)
{
if (nuGetProjects == null)
{
throw new ArgumentNullException(nameof(nuGetProjects));
}
if (packageIdentity == null)
{
throw new ArgumentNullException(nameof(packageIdentity));
}
if (resolutionContext == null)
{
throw new ArgumentNullException(nameof(resolutionContext));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
if (activeSources == null)
{
throw new ArgumentNullException(nameof(activeSources));
}
if (activeSources.Count == 0)
{
throw new ArgumentException("At least 1 item expected for " + nameof(activeSources));
}
if (packageIdentity.Version == null)
{
throw new ArgumentNullException(nameof(packageIdentity));
}
var results = new List<ResolvedAction>();
// BuildIntegratedNuGetProject type projects are now supports parallel preview action for faster performance.
var buildIntegratedProjectsToUpdate = new List<BuildIntegratedNuGetProject>();
// Currently packages.config projects are not supported are supports parallel previews.
// Here is follow up issue to address it https://github.com/NuGet/Home/issues/9906
var otherTargetProjectsToUpdate = new List<NuGetProject>();
foreach (var proj in nuGetProjects)
{
if (proj is BuildIntegratedNuGetProject buildIntegratedNuGetProject)
{
buildIntegratedProjectsToUpdate.Add(buildIntegratedNuGetProject);
}
else
{
otherTargetProjectsToUpdate.Add(proj);
}
}
if (buildIntegratedProjectsToUpdate.Count != 0)
{
// Only automatically create Source Mappings when there's exclusively BuildIntegratedProjects.
if (otherTargetProjectsToUpdate.Count > 0)
{
newMappingID = null;
newMappingSource = null;
}
// Run build integrated project preview for all projects at the same time
var resolvedActions = await PreviewBuildIntegratedProjectsActionsAsync(
buildIntegratedProjectsToUpdate,
nugetProjectActionsLookup: null, // no nugetProjectActionsLookup so it'll be derived from packageIdentity and activeSources
packageIdentity,
activeSources,
nuGetProjectContext,
versionRange,
newMappingID,
newMappingSource,
token);
results.AddRange(resolvedActions);
}
foreach (var target in otherTargetProjectsToUpdate)
{
var actions = await PreviewInstallPackageAsync(
target,
packageIdentity,
resolutionContext,
nuGetProjectContext,
activeSources,
null,
token);
results.AddRange(actions.Select(a => new ResolvedAction(target, a)));
}
return results;
}
public async Task<IEnumerable<NuGetProjectAction>> PreviewInstallPackageAsync(
NuGetProject nuGetProject,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
CancellationToken token)
{
return await PreviewInstallPackageAsync(nuGetProject, packageIdentity, resolutionContext, nuGetProjectContext, primarySources, secondarySources, versionRange: null, token);
}
public async Task<IEnumerable<NuGetProjectAction>> PreviewInstallPackageAsync(
NuGetProject nuGetProject,
PackageIdentity packageIdentity,
ResolutionContext resolutionContext,
INuGetProjectContext nuGetProjectContext,
IEnumerable<SourceRepository> primarySources,
IEnumerable<SourceRepository> secondarySources,
VersionRange versionRange,
CancellationToken token)
{
if (nuGetProject == null)
{
throw new ArgumentNullException(nameof(nuGetProject));
}
if (packageIdentity == null)
{
throw new ArgumentNullException(nameof(packageIdentity));
}
if (resolutionContext == null)
{
throw new ArgumentNullException(nameof(resolutionContext));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
if (primarySources == null)
{
throw new ArgumentNullException(nameof(primarySources));
}
if (secondarySources == null)
{
secondarySources = SourceRepositoryProvider.GetRepositories().Where(e => e.PackageSource.IsEnabled);
}
if (!primarySources.Any())
{
throw new ArgumentException(
message: Strings.Argument_Cannot_Be_Null_Or_Empty,
paramName: nameof(primarySources));
}
if (packageIdentity.Version == null)
{
throw new ArgumentException(
message: string.Format(CultureInfo.CurrentCulture, Strings.PropertyCannotBeNull, nameof(packageIdentity.Version)),
paramName: nameof(packageIdentity));
}
if (nuGetProject is INuGetIntegratedProject)
{
var action = NuGetProjectAction.CreateInstallProjectAction(packageIdentity, primarySources.First(), nuGetProject, versionRange);
var actions = new[] { action };
var buildIntegratedProject = nuGetProject as BuildIntegratedNuGetProject;
if (buildIntegratedProject != null)
{
actions = new[] {
await PreviewBuildIntegratedProjectActionsAsync(buildIntegratedProject, actions, nuGetProjectContext, token)
};
}
return actions;
}
var projectName = NuGetProject.GetUniqueNameOrName(nuGetProject);
var stopWatch = Stopwatch.StartNew();
var projectInstalledPackageReferences = await nuGetProject.GetInstalledPackagesAsync(token);
var oldListOfInstalledPackages = projectInstalledPackageReferences.Select(p => p.PackageIdentity);
if (oldListOfInstalledPackages.Any(p => p.Equals(packageIdentity)))
{
var alreadyInstalledMessage = string.Format(CultureInfo.CurrentCulture, Strings.PackageAlreadyExistsInProject, packageIdentity, projectName ?? string.Empty);
throw new InvalidOperationException(alreadyInstalledMessage, new PackageAlreadyInstalledException(alreadyInstalledMessage));
}
var nuGetProjectActions = new List<NuGetProjectAction>();
var effectiveSources = GetEffectiveSources(primarySources, secondarySources);
foreach (SourceRepository enabledSource in effectiveSources)
{
PackageSource source = enabledSource.PackageSource;
if (source.IsHttp && !source.IsHttps && !source.AllowInsecureConnections)
{
nuGetProjectContext.Log(MessageLevel.Error, Strings.Error_HttpSource_Single, "install", source.Source);
}
}
if (resolutionContext.DependencyBehavior != DependencyBehavior.Ignore)
{
try
{
var downgradeAllowed = false;
var packageTargetsForResolver = new HashSet<PackageIdentity>(oldListOfInstalledPackages, PackageIdentity.Comparer);
// Note: resolver needs all the installed packages as targets too. And, metadata should be gathered for the installed packages as well
var installedPackageWithSameId = packageTargetsForResolver.FirstOrDefault(p => p.Id.Equals(packageIdentity.Id, StringComparison.OrdinalIgnoreCase));
if (installedPackageWithSameId != null)
{
packageTargetsForResolver.Remove(installedPackageWithSameId);
if (installedPackageWithSameId.Version > packageIdentity.Version)
{
// Looks like the installed package is of higher version than one being installed. So, we take it that downgrade is allowed
downgradeAllowed = true;
}
}
packageTargetsForResolver.Add(packageIdentity);
// Step-1 : Get metadata resources using gatherer
var targetFramework = nuGetProject.GetMetadata<NuGetFramework>(NuGetProjectMetadataKeys.TargetFramework);
nuGetProjectContext.Log(NuGet.ProjectManagement.MessageLevel.Info, Environment.NewLine);
nuGetProjectContext.Log(ProjectManagement.MessageLevel.Info, Strings.AttemptingToGatherDependencyInfo, packageIdentity, projectName, targetFramework);
var primaryPackages = new List<PackageIdentity> { packageIdentity };
HashSet<SourcePackageDependencyInfo> availablePackageDependencyInfoWithSourceSet = null;
var packageSourceMapping = PackageSourceMapping.GetPackageSourceMapping(Settings);
var gatherContext = new GatherContext(packageSourceMapping)
{
InstalledPackages = oldListOfInstalledPackages.ToList(),
PrimaryTargets = primaryPackages,
TargetFramework = targetFramework,
PrimarySources = primarySources.ToList(),
AllSources = effectiveSources.ToList(),
PackagesFolderSource = PackagesFolderSourceRepository,
ResolutionContext = resolutionContext,
AllowDowngrades = downgradeAllowed,
ProjectContext = nuGetProjectContext
};
availablePackageDependencyInfoWithSourceSet = await ResolverGather.GatherAsync(gatherContext, token);
// emit gather dependency telemetry event and restart timer
stopWatch.Stop();
var gatherTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.GatherDependencyStepName,
stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(gatherTelemetryEvent);
stopWatch.Restart();
if (!availablePackageDependencyInfoWithSourceSet.Any())
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.UnableToGatherDependencyInfo, packageIdentity));
}
// Prune the results down to only what we would allow to be installed
// Keep only the target package we are trying to install for that Id
var prunedAvailablePackages = PrunePackageTree.RemoveAllVersionsForIdExcept(availablePackageDependencyInfoWithSourceSet, packageIdentity);
if (!downgradeAllowed)
{
prunedAvailablePackages = PrunePackageTree.PruneDowngrades(prunedAvailablePackages, projectInstalledPackageReferences);
}
if (!resolutionContext.IncludePrerelease)
{
prunedAvailablePackages = PrunePackageTree.PrunePreleaseForStableTargets(
prunedAvailablePackages,
packageTargetsForResolver,
new[] { packageIdentity });
}
var log = new LoggerAdapter(nuGetProjectContext);
// Verify that the target is allowed by packages.config
GatherExceptionHelpers.ThrowIfVersionIsDisallowedByPackagesConfig(packageIdentity.Id, projectInstalledPackageReferences, prunedAvailablePackages, log);
// Remove versions that do not satisfy 'allowedVersions' attribute in packages.config, if any
prunedAvailablePackages = PrunePackageTree.PruneDisallowedVersions(prunedAvailablePackages, projectInstalledPackageReferences);
// Step-2 : Call PackageResolver.Resolve to get new list of installed packages
// Note: resolver prefers installed package versions if the satisfy the dependency version constraints
// So, since we want an exact version of a package, create a new list of installed packages where the packageIdentity being installed
// is present after removing the one with the same id
var preferredPackageReferences = new List<PackageReference>(projectInstalledPackageReferences.Where(pr =>
!pr.PackageIdentity.Id.Equals(packageIdentity.Id, StringComparison.OrdinalIgnoreCase)))
{
new PackageReference(packageIdentity, targetFramework)
};
var packageResolverContext = new PackageResolverContext(resolutionContext.DependencyBehavior,
new string[] { packageIdentity.Id },
oldListOfInstalledPackages.Select(package => package.Id),
projectInstalledPackageReferences,
preferredPackageReferences.Select(package => package.PackageIdentity),
prunedAvailablePackages,
SourceRepositoryProvider.GetRepositories().Select(s => s.PackageSource),
log);
nuGetProjectContext.Log(MessageLevel.Info, Strings.AttemptingToResolveDependencies, packageIdentity, resolutionContext.DependencyBehavior);
var packageResolver = new PackageResolver();
var newListOfInstalledPackages = packageResolver.Resolve(packageResolverContext, token);
// emit resolve dependency telemetry event and restart timer
stopWatch.Stop();
var resolveTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.ResolveDependencyStepName,
stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(resolveTelemetryEvent);
stopWatch.Restart();
if (newListOfInstalledPackages == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.UnableToResolveDependencyInfo, packageIdentity, resolutionContext.DependencyBehavior));
}
// Step-3 : Get the list of nuGetProjectActions to perform, install/uninstall on the nugetproject
// based on newPackages obtained in Step-2 and project.GetInstalledPackages
nuGetProjectContext.Log(MessageLevel.Info, Strings.ResolvingActionsToInstallPackage, packageIdentity);
var newPackagesToUninstall = new List<PackageIdentity>();
foreach (var oldInstalledPackage in oldListOfInstalledPackages)
{
var newPackageWithSameId = newListOfInstalledPackages
.FirstOrDefault(np =>
oldInstalledPackage.Id.Equals(np.Id, StringComparison.OrdinalIgnoreCase) &&
!oldInstalledPackage.Version.Equals(np.Version));
if (newPackageWithSameId != null)
{
newPackagesToUninstall.Add(oldInstalledPackage);
}
}
var newPackagesToInstall = newListOfInstalledPackages.Where(p => !oldListOfInstalledPackages.Contains(p));
foreach (var newPackageToUninstall in newPackagesToUninstall)
{
nuGetProjectActions.Add(NuGetProjectAction.CreateUninstallProjectAction(newPackageToUninstall, nuGetProject));
}
// created hashset of packageIds we are OK with touching
// the scenario here is that the user might have done an uninstall-package -Force on a particular package
// this will be the new set of target ids which includes current target ids as well as packages which are being updated
//It fixes the issue where we were only getting dependencies for target ids ignoring other packages which are also being updated. #2724
var newTargetIds = new HashSet<string>(newPackagesToUninstall.Select(p => p.Id), StringComparer.OrdinalIgnoreCase);
newTargetIds.Add(packageIdentity.Id);
// get all dependencies of new target ids so that we can have all the required install actions.
var allowed = GetDependencies(newTargetIds, newListOfInstalledPackages, prunedAvailablePackages);
foreach (var newPackageToInstall in newPackagesToInstall)
{
// we should limit actions to just packages that are in the dependency set of the target we are installing
if (allowed.Contains(newPackageToInstall.Id))
{
// find the package match based on identity
var sourceDepInfo = prunedAvailablePackages.SingleOrDefault(p => PackageIdentity.Comparer.Equals(p, newPackageToInstall));
if (sourceDepInfo == null)
{
// this really should never happen
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.PackageNotFound, packageIdentity));
}
nuGetProjectActions.Add(NuGetProjectAction.CreateInstallProjectAction(sourceDepInfo, sourceDepInfo.Source, nuGetProject));
}
}
}
catch (InvalidOperationException)
{
throw;
}
catch (AggregateException aggregateEx)
{
throw new InvalidOperationException(aggregateEx.Message, aggregateEx);
}
catch (Exception ex)
{
if (string.IsNullOrEmpty(ex.Message))
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.PackageCouldNotBeInstalled, packageIdentity), ex);
}
throw new InvalidOperationException(ex.Message, ex);
}
}
else
{
var logger = new ProjectContextLogger(nuGetProjectContext);
var sourceRepository = await GetSourceRepository(packageIdentity, effectiveSources, resolutionContext.SourceCacheContext, logger, token);
nuGetProjectActions.Add(NuGetProjectAction.CreateInstallProjectAction(packageIdentity, sourceRepository, nuGetProject));
}
// emit resolve actions telemetry event
stopWatch.Stop();
var actionTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.ResolvedActionsStepName,
stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(actionTelemetryEvent);
nuGetProjectContext.Log(MessageLevel.Info, Strings.ResolvedActionsToInstallPackage, packageIdentity);
return nuGetProjectActions;
}
/// <summary>
/// Check all sources in parallel to see if the package exists while respecting the order of the list.
/// This is only used by PreviewInstall with DependencyBehavior.Ignore.
/// Since, resolver gather is not used when dependencies are not used,
/// we simply get the source repository using MetadataResource.Exists
/// </summary>
private static async Task<SourceRepository> GetSourceRepository(PackageIdentity packageIdentity,
IEnumerable<SourceRepository> sourceRepositories,
SourceCacheContext sourceCacheContext,
ILogger logger,
CancellationToken cancellationToken)
{
SourceRepository source = null;
// TODO: move this timeout to a better place
// TODO: what should the timeout be?
// Give up after 5 minutes
using var tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var results = new Queue<KeyValuePair<SourceRepository, Task<bool>>>();
foreach (var sourceRepository in sourceRepositories)
{
// TODO: fetch the resource in parallel also
var metadataResource = await sourceRepository.GetResourceAsync<MetadataResource>(cancellationToken);
if (metadataResource != null)
{
var task = Task.Run(() => metadataResource.Exists(packageIdentity, sourceCacheContext, logger, tokenSource.Token), tokenSource.Token);
results.Enqueue(new KeyValuePair<SourceRepository, Task<bool>>(sourceRepository, task));
}
}
while (results.Count > 0)
{
var pair = results.Dequeue();
try
{
var exists = await pair.Value;
// take only the first true result, but continue waiting for the remaining cancelled
// tasks to keep things from getting out of control.
if (source == null && exists)
{
source = pair.Key;
// there is no need to finish trying the others
tokenSource.Cancel();
}
}
catch (OperationCanceledException)
{
// ignore these
}
catch (Exception ex)
{
logger.LogWarning(
string.Format(CultureInfo.CurrentCulture,
Strings.Warning_ErrorFindingRepository,
pair.Key.PackageSource.Source,
ExceptionUtilities.DisplayMessage(ex)));
}
}
if (source == null)
{
// no matches were found
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
Strings.UnknownPackageSpecificVersion, packageIdentity.Id, packageIdentity.Version));
}
return source;
}
/// <summary>
/// Gives the preview as a list of NuGetProjectActions that will be performed to uninstall for many NuGetProjects.
/// </summary>
/// <param name="nuGetProjects"></param>
/// <param name="packageId"></param>
/// <param name="uninstallationContext"></param>
/// <param name="nuGetProjectContext"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<IEnumerable<NuGetProjectAction>> PreviewProjectsUninstallPackageAsync(
IReadOnlyCollection<NuGetProject> nuGetProjects,
string packageId,
UninstallationContext uninstallationContext,
INuGetProjectContext nuGetProjectContext,
CancellationToken token)
{
if (nuGetProjects == null)
{
throw new ArgumentNullException(nameof(nuGetProjects));
}
if (packageId == null)
{
throw new ArgumentNullException(nameof(packageId));
}
if (uninstallationContext == null)
{
throw new ArgumentNullException(nameof(uninstallationContext));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
token.ThrowIfCancellationRequested();
var results = new List<NuGetProjectAction>();
var buildIntegratedProjectsToUpdate = new List<BuildIntegratedNuGetProject>();
var otherTargetProjectsToUpdate = new List<NuGetProject>();
foreach (var project in nuGetProjects)
{
if (project == null)
{
throw new ArgumentException(
message: string.Format(CultureInfo.CurrentCulture, Strings.PropertyCannotBeNull, nameof(project)),
paramName: nameof(nuGetProjects));
}
if (project is BuildIntegratedNuGetProject buildIntegratedNuGetProject)
{
buildIntegratedProjectsToUpdate.Add(buildIntegratedNuGetProject);
}
else
{
otherTargetProjectsToUpdate.Add(project);
}
}
if (buildIntegratedProjectsToUpdate.Count != 0)
{
// Run build integrated project preview for all projects at the same time
// There we can do evaluation DependencyGraphRestoreUtility.PreviewRestoreProjectsAsync in batch projects instead of one by one projects.
// So for BuildIntegratedNuGetProject type we share same method 'PreviewBuildIntegratedProjectsActionsAsync' for both install and uninstall actions.
var uninstallActions = await PreviewBuildIntegratedNuGetProjectsUninstallPackageInternalAsync(
buildIntegratedProjectsToUpdate,
packageId,
nuGetProjectContext,
token);
results.AddRange(uninstallActions);
}
foreach (var project in otherTargetProjectsToUpdate)
{
if (project == null)
{
throw new ArgumentException(
message: string.Format(CultureInfo.CurrentCulture, Strings.PropertyCannotBeNull, nameof(project)),
paramName: nameof(nuGetProjects));
}
// Step-1: Get the packageIdentity corresponding to packageId and check if it exists to be uninstalled
var installedPackages = await project.GetInstalledPackagesAsync(token);
var packageReference = installedPackages.FirstOrDefault(pr => pr.PackageIdentity.Id.Equals(packageId, StringComparison.OrdinalIgnoreCase));
if (packageReference?.PackageIdentity == null)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.PackageToBeUninstalledCouldNotBeFound,
packageId, project.GetMetadata<string>(NuGetProjectMetadataKeys.Name)));
}
IEnumerable<NuGetProjectAction> uninstallActions = await PreviewUninstallPackageInternalAsync(project, packageReference, uninstallationContext, nuGetProjectContext, token);
results.AddRange(uninstallActions);
}
return results;
}
private async Task<IEnumerable<NuGetProjectAction>> PreviewBuildIntegratedNuGetProjectsUninstallPackageInternalAsync(
IReadOnlyList<BuildIntegratedNuGetProject> buildIntegratedProjects,
string packageId,
INuGetProjectContext nuGetProjectContext,
CancellationToken token)
{
if (SolutionManager == null)
{
throw new InvalidOperationException(Strings.SolutionManagerNotAvailableForUninstall);
}
var nugetProjectActionsLookup = new Dictionary<string, NuGetProjectAction[]>(PathUtility.GetStringComparerBasedOnOS());
foreach (BuildIntegratedNuGetProject buildIntegratedProject in buildIntegratedProjects)
{
// Get the packageIdentity corresponding to packageId and check if it exists to be uninstalled
var installedPackages = await buildIntegratedProject.GetInstalledPackagesAsync(token);
var packageReference = installedPackages.FirstOrDefault(pr => pr.PackageIdentity.Id.Equals(packageId, StringComparison.OrdinalIgnoreCase));
if (packageReference?.PackageIdentity == null)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.PackageToBeUninstalledCouldNotBeFound,
packageId, buildIntegratedProject.GetMetadata<string>(NuGetProjectMetadataKeys.Name)));
}
NuGetProjectAction action = NuGetProjectAction.CreateUninstallProjectAction(packageReference.PackageIdentity, buildIntegratedProject);
NuGetProjectAction[] actions = new[] { action };
nugetProjectActionsLookup[buildIntegratedProject.MSBuildProjectPath] = actions;
}
IEnumerable<ResolvedAction> resolvedActions = await PreviewBuildIntegratedProjectsActionsAsync(
buildIntegratedProjects,
nugetProjectActionsLookup,
packageIdentity: null, // since we have nuGetProjectActions no need packageIdentity
primarySources: null, // since we have nuGetProjectActions no need primarySources
nuGetProjectContext,
versionRange: null,
newMappingID: null,
newMappingSource: null,
token);
return resolvedActions.Select(r => r.Action as BuildIntegratedProjectAction);
}
/// <summary>
/// Gives the preview as a list of NuGetProjectActions that will be performed to uninstall
/// <paramref name="packageId" /> into <paramref name="nuGetProject" />
/// <paramref name="uninstallationContext" /> and <paramref name="nuGetProjectContext" /> are used in the
/// process.
/// </summary>
public async Task<IEnumerable<NuGetProjectAction>> PreviewUninstallPackageAsync(NuGetProject nuGetProject, string packageId,
UninstallationContext uninstallationContext, INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
if (nuGetProject == null)
{
throw new ArgumentNullException(nameof(nuGetProject));
}
if (packageId == null)
{
throw new ArgumentNullException(nameof(packageId));
}
if (uninstallationContext == null)
{
throw new ArgumentNullException(nameof(uninstallationContext));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
// Step-1: Get the packageIdentity corresponding to packageId and check if it exists to be uninstalled
var installedPackages = await nuGetProject.GetInstalledPackagesAsync(token);
var packageReference = installedPackages.FirstOrDefault(pr => pr.PackageIdentity.Id.Equals(packageId, StringComparison.OrdinalIgnoreCase));
if (packageReference?.PackageIdentity == null)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.PackageToBeUninstalledCouldNotBeFound,
packageId, nuGetProject.GetMetadata<string>(NuGetProjectMetadataKeys.Name)));
}
return await PreviewUninstallPackageInternalAsync(nuGetProject, packageReference, uninstallationContext, nuGetProjectContext, token);
}
/// <summary>
/// Gives the preview as a list of <see cref="NuGetProjectAction" /> that will be performed to uninstall
/// <paramref name="packageIdentity" /> into <paramref name="nuGetProject" />
/// <paramref name="uninstallationContext" /> and <paramref name="nuGetProjectContext" /> are used in the
/// process.
/// </summary>
public async Task<IEnumerable<NuGetProjectAction>> PreviewUninstallPackageAsync(NuGetProject nuGetProject, PackageIdentity packageIdentity,
UninstallationContext uninstallationContext, INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
if (nuGetProject == null)
{
throw new ArgumentNullException(nameof(nuGetProject));
}
if (packageIdentity == null)
{
throw new ArgumentNullException(nameof(packageIdentity));
}
if (uninstallationContext == null)
{
throw new ArgumentNullException(nameof(uninstallationContext));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
// Step-1: Get the packageIdentity corresponding to packageId and check if it exists to be uninstalled
var installedPackages = await nuGetProject.GetInstalledPackagesAsync(token);
var packageReference = installedPackages.FirstOrDefault(pr => pr.PackageIdentity.Equals(packageIdentity));
if (packageReference?.PackageIdentity == null)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.PackageToBeUninstalledCouldNotBeFound,
packageIdentity.Id, nuGetProject.GetMetadata<string>(NuGetProjectMetadataKeys.Name)));
}
return await PreviewUninstallPackageInternalAsync(nuGetProject, packageReference, uninstallationContext, nuGetProjectContext, token);
}
private async Task<IEnumerable<NuGetProjectAction>> PreviewUninstallPackageInternalAsync(NuGetProject nuGetProject, Packaging.PackageReference packageReference,
UninstallationContext uninstallationContext, INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
if (SolutionManager == null)
{
throw new InvalidOperationException(Strings.SolutionManagerNotAvailableForUninstall);
}
if (nuGetProject is INuGetIntegratedProject)
{
var action = NuGetProjectAction.CreateUninstallProjectAction(packageReference.PackageIdentity, nuGetProject);
var actions = new[] { action };
var buildIntegratedProject = nuGetProject as BuildIntegratedNuGetProject;
if (buildIntegratedProject != null)
{
actions = new[] {
await PreviewBuildIntegratedProjectActionsAsync(buildIntegratedProject, actions, nuGetProjectContext, token)
};
}
return actions;
}
// Step-1 : Get the metadata resources from "packages" folder or custom repository path
var packageIdentity = packageReference.PackageIdentity;
var projectName = NuGetProject.GetUniqueNameOrName(nuGetProject);
var packageReferenceTargetFramework = packageReference.TargetFramework;
nuGetProjectContext.Log(NuGet.ProjectManagement.MessageLevel.Info, Environment.NewLine);
nuGetProjectContext.Log(ProjectManagement.MessageLevel.Info, Strings.AttemptingToGatherDependencyInfo, packageIdentity, projectName, packageReferenceTargetFramework);
var installedPackageIdentities = (await nuGetProject.GetInstalledPackagesAsync(token)).Select(pr => pr.PackageIdentity);
var dependencyInfoFromPackagesFolder = await GetDependencyInfoFromPackagesFolderAsync(installedPackageIdentities,
packageReferenceTargetFramework,
includeUnresolved: false,
token);
nuGetProjectContext.Log(ProjectManagement.MessageLevel.Info, Strings.ResolvingActionsToUninstallPackage, packageIdentity);
// Step-2 : Determine if the package can be uninstalled based on the metadata resources
var packagesToBeUninstalled = UninstallResolver.GetPackagesToBeUninstalled(packageIdentity, dependencyInfoFromPackagesFolder, installedPackageIdentities, uninstallationContext);
var nuGetProjectActions =
packagesToBeUninstalled.Select(
package => NuGetProjectAction.CreateUninstallProjectAction(package, nuGetProject));
nuGetProjectContext.Log(MessageLevel.Info, Strings.ResolvedActionsToUninstallPackage, packageIdentity);
return nuGetProjectActions;
}
private async Task<IEnumerable<PackageDependencyInfo>> GetDependencyInfoFromPackagesFolderAsync(IEnumerable<PackageIdentity> packageIdentities,
NuGetFramework nuGetFramework,
bool includeUnresolved,
CancellationToken cancellationToken)
{
var dependencyInfoResource = await PackagesFolderSourceRepository.GetResourceAsync<DependencyInfoResource>(cancellationToken);
return await PackageGraphAnalysisUtilities.GetDependencyInfoForPackageIdentitiesAsync(packageIdentities, nuGetFramework, dependencyInfoResource, NullSourceCacheContext.Instance, includeUnresolved, NullLogger.Instance, cancellationToken);
}
/// <summary>
/// Executes the list of <paramref name="nuGetProjectActions" /> on list of <paramref name="nuGetProjects" /> , which is
/// likely obtained by calling into
/// <see
/// cref="PreviewInstallPackageAsync(IEnumerable{NuGetProject},string,ResolutionContext,INuGetProjectContext,SourceRepository,IEnumerable{SourceRepository},CancellationToken)" />
/// <paramref name="nuGetProjectContext" /> is used in the process.
/// </summary>
public async Task ExecuteNuGetProjectActionsAsync(IEnumerable<NuGetProject> nuGetProjects,
IEnumerable<NuGetProjectAction> nuGetProjectActions,
INuGetProjectContext nuGetProjectContext,
SourceCacheContext sourceCacheContext,
CancellationToken token)
{
var projects = nuGetProjects.ToList();
// find out build integrated projects so that we can arrange them in reverse dependency order
var buildIntegratedProjectsToUpdate = projects.OfType<BuildIntegratedNuGetProject>().ToList();
// order won't matter for other type of projects so just add rest of the projects in result
var sortedProjectsToUpdate = projects.Except(buildIntegratedProjectsToUpdate).ToList();
if (buildIntegratedProjectsToUpdate.Count > 0)
{
var logger = new ProjectContextLogger(nuGetProjectContext);
var referenceContext = new DependencyGraphCacheContext(logger, Settings);
_buildIntegratedProjectsUpdateSet = new HashSet<string>(PathUtility.GetStringComparerBasedOnOS());
var projectUniqueNamesForBuildIntToUpdate
= buildIntegratedProjectsToUpdate.ToDictionary((project) => project.MSBuildProjectPath);
var dgFile = await DependencyGraphRestoreUtility.GetSolutionRestoreSpec(SolutionManager, referenceContext);
_buildIntegratedProjectsCache = dgFile;
var allSortedProjects = DependencyGraphSpec.SortPackagesByDependencyOrder(dgFile.Projects);
// cache these already evaluated(without commit) buildIntegratedProjects project ids which will be used to avoid duplicate restore as part of parent projects
_buildIntegratedProjectsUpdateSet.AddRange(
buildIntegratedProjectsToUpdate.Select(child => child.MSBuildProjectPath));
foreach (var projectUniqueName in allSortedProjects.Select(e => e.RestoreMetadata.ProjectUniqueName))
{
BuildIntegratedNuGetProject project;
if (projectUniqueNamesForBuildIntToUpdate.TryGetValue(projectUniqueName, out project))
{
sortedProjectsToUpdate.Add(project);
}
}
}
// execute all nuget project actions
foreach (var project in sortedProjectsToUpdate)
{
var nugetActions = nuGetProjectActions.Where(action => action.Project.Equals(project));
await ExecuteNuGetProjectActionsAsync(project, nugetActions, nuGetProjectContext, sourceCacheContext, token);
}
// clear cache which could temper with other updates
_buildIntegratedProjectsUpdateSet?.Clear();
_buildIntegratedProjectsCache = null;
_restoreProviderCache = null;
}
/// <summary>
/// Executes the list of <paramref name="nuGetProjectActions" /> on <paramref name="nuGetProject" /> , which is
/// likely obtained by calling into
/// <see
/// cref="PreviewInstallPackageAsync(NuGetProject,string,ResolutionContext,INuGetProjectContext,SourceRepository,IEnumerable{SourceRepository},CancellationToken)" />
/// <paramref name="nuGetProjectContext" /> is used in the process.
/// </summary>
public async Task ExecuteNuGetProjectActionsAsync(NuGetProject nuGetProject,
IEnumerable<NuGetProjectAction> nuGetProjectActions,
INuGetProjectContext nuGetProjectContext,
SourceCacheContext sourceCacheContext,
CancellationToken token)
{
var logger = new LoggerAdapter(nuGetProjectContext);
var downloadContext = new PackageDownloadContext(sourceCacheContext)
{
ParentId = nuGetProjectContext.OperationId,
ClientPolicyContext = ClientPolicyContext.GetClientPolicy(Settings, logger)
};
await ExecuteNuGetProjectActionsAsync(nuGetProject,
nuGetProjectActions,
nuGetProjectContext,
downloadContext,
token);
}
/// <summary>
/// Executes the list of <paramref name="nuGetProjectActions" /> on <paramref name="nuGetProject" /> , which is
/// likely obtained by calling into
/// <see
/// cref="PreviewInstallPackageAsync(NuGetProject,string,ResolutionContext,INuGetProjectContext,SourceRepository,IEnumerable{SourceRepository},CancellationToken)" />
/// <paramref name="nuGetProjectContext" /> is used in the process.
/// </summary>
public async Task ExecuteNuGetProjectActionsAsync(NuGetProject nuGetProject,
IEnumerable<NuGetProjectAction> nuGetProjectActions,
INuGetProjectContext nuGetProjectContext,
PackageDownloadContext downloadContext,
CancellationToken token)
{
if (nuGetProject == null)
{
throw new ArgumentNullException(nameof(nuGetProject));
}
if (nuGetProjectActions == null)
{
throw new ArgumentNullException(nameof(nuGetProjectActions));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
var stopWatch = Stopwatch.StartNew();
ExceptionDispatchInfo exceptionInfo = null;
// DNU: Find the closure before executing the actions
var buildIntegratedProject = nuGetProject as BuildIntegratedNuGetProject;
if (buildIntegratedProject != null)
{
await ExecuteBuildIntegratedProjectActionsAsync(buildIntegratedProject,
nuGetProjectActions,
nuGetProjectContext,
token);
}
else
{
// Set the original packages config if it exists
var msbuildProject = nuGetProject as MSBuildNuGetProject;
if (msbuildProject != null)
{
nuGetProjectContext.OriginalPackagesConfig =
msbuildProject.PackagesConfigNuGetProject?.GetPackagesConfig();
}
var executedNuGetProjectActions = new Stack<NuGetProjectAction>();
var packageWithDirectoriesToBeDeleted = new HashSet<PackageIdentity>(PackageIdentity.Comparer);
var ideExecutionContext = nuGetProjectContext.ExecutionContext as IDEExecutionContext;
if (ideExecutionContext != null)
{
await ideExecutionContext.SaveExpandedNodeStates(SolutionManager);
}
var logger = new ProjectContextLogger(nuGetProjectContext);
Dictionary<PackageIdentity, PackagePreFetcherResult> downloadTasks = null;
CancellationTokenSource downloadTokenSource = null;
// batch events argument object
PackageProjectEventArgs packageProjectEventArgs = null;
try
{
// PreProcess projects
await nuGetProject.PreProcessAsync(nuGetProjectContext, token);
var actionsList = nuGetProjectActions.ToList();
var hasInstalls = actionsList.Any(action =>
action.NuGetProjectActionType == NuGetProjectActionType.Install);
if (hasInstalls)
{
// Make this independently cancelable.
downloadTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
// Download all packages up front in parallel
downloadTasks = await PackagePreFetcher.GetPackagesAsync(
actionsList,
PackagesFolderNuGetProject,
downloadContext,
SettingsUtility.GetGlobalPackagesFolder(Settings),
logger,
downloadTokenSource.Token);
// Log download information
PackagePreFetcher.LogFetchMessages(
downloadTasks.Values,
PackagesFolderNuGetProject.Root,
logger);
}
// raise Nuget batch start event
var batchId = Guid.NewGuid().ToString();
string name;
nuGetProject.TryGetMetadata(NuGetProjectMetadataKeys.Name, out name);
var projectPath = msbuildProject?.MSBuildProjectPath;
packageProjectEventArgs = new PackageProjectEventArgs(batchId, name, projectPath);
BatchStart?.Invoke(this, packageProjectEventArgs);
PackageProjectEventsProvider.Instance.NotifyBatchStart(packageProjectEventArgs);
try
{
if (msbuildProject != null)
{
//start batch processing for msbuild
await msbuildProject.ProjectSystem.BeginProcessingAsync();
}
foreach (var nuGetProjectAction in actionsList)
{
if (nuGetProjectAction.NuGetProjectActionType == NuGetProjectActionType.Uninstall)
{
executedNuGetProjectActions.Push(nuGetProjectAction);
await ExecuteUninstallAsync(nuGetProject,
nuGetProjectAction.PackageIdentity,
packageWithDirectoriesToBeDeleted,
nuGetProjectContext, token);
nuGetProjectContext.Log(
ProjectManagement.MessageLevel.Info,
Strings.SuccessfullyUninstalled,
nuGetProjectAction.PackageIdentity,
nuGetProject.GetMetadata<string>(NuGetProjectMetadataKeys.Name));
}
}
}
finally
{
if (msbuildProject != null)
{
// end batch for msbuild and let it save everything.
// always calls it before PostProcessAsync or binding redirects
await msbuildProject.ProjectSystem.EndProcessingAsync();
}
}
try
{
if (msbuildProject != null)
{
//start batch processing for msbuild
await msbuildProject.ProjectSystem.BeginProcessingAsync();
}
foreach (var nuGetProjectAction in actionsList)
{
if (nuGetProjectAction.NuGetProjectActionType == NuGetProjectActionType.Install)
{
executedNuGetProjectActions.Push(nuGetProjectAction);
// Retrieve the downloaded package
// This will wait on the package if it is still downloading
var preFetchResult = downloadTasks[nuGetProjectAction.PackageIdentity];
using (var downloadPackageResult = await preFetchResult.GetResultAsync())
{
// use the version exactly as specified in the nuspec file
var packageIdentity = await downloadPackageResult.PackageReader.GetIdentityAsync(token);
await ExecuteInstallAsync(
nuGetProject,
packageIdentity,
downloadPackageResult,
packageWithDirectoriesToBeDeleted,
nuGetProjectContext,
token);
}
var identityString = string.Format(CultureInfo.InvariantCulture, "{0} {1}",
nuGetProjectAction.PackageIdentity.Id,
nuGetProjectAction.PackageIdentity.Version.ToNormalizedString());
preFetchResult.EmitTelemetryEvent(nuGetProjectContext.OperationId);
nuGetProjectContext.Log(
ProjectManagement.MessageLevel.Info,
Strings.SuccessfullyInstalled,
identityString,
nuGetProject.GetMetadata<string>(NuGetProjectMetadataKeys.Name));
}
}
}
finally
{
if (msbuildProject != null)
{
// end batch for msbuild and let it save everything.
// always calls it before PostProcessAsync or binding redirects
await msbuildProject.ProjectSystem.EndProcessingAsync();
}
}
PackagesConfigLockFileUtility.UpdateLockFile(msbuildProject,
actionsList,
token);
// Post process
await nuGetProject.PostProcessAsync(nuGetProjectContext, token);
// Open readme file
await OpenReadmeFile(nuGetProject, nuGetProjectContext, token);
}
catch (SignatureException ex)
{
var errors = ex.Results.SelectMany(r => r.GetErrorIssues());
var warnings = ex.Results.SelectMany(r => r.GetWarningIssues());
SignatureException unwrappedException = null;
if (errors.Count() == 1)
{
// In case of one error, throw it as the exception
var error = errors.First();
unwrappedException = new SignatureException(error.Code, error.Message, ex.PackageIdentity);
}
else
{
// In case of multiple errors, wrap them in a general NU3000 error
var errorMessage = string.Format(CultureInfo.CurrentCulture,
Strings.SignatureVerificationMultiple,
$"{Environment.NewLine}{string.Join(Environment.NewLine, errors.Select(e => e.FormatWithCode()))}");
unwrappedException = new SignatureException(NuGetLogCode.NU3000, errorMessage, ex.PackageIdentity);
}
foreach (var warning in warnings)
{
nuGetProjectContext.Log(warning);
}
exceptionInfo = ExceptionDispatchInfo.Capture(unwrappedException);
}
catch (Exception ex)
{
exceptionInfo = ExceptionDispatchInfo.Capture(ex);
}
finally
{
if (downloadTasks != null)
{
// Wait for all downloads to cancel and dispose
downloadTokenSource.Cancel();
foreach (var result in downloadTasks.Values)
{
await result.EnsureResultAsync();
result.Dispose();
}
}
downloadTokenSource?.Dispose();
if (msbuildProject != null)
{
// raise nuget batch end event
if (packageProjectEventArgs != null)
{
BatchEnd?.Invoke(this, packageProjectEventArgs);
PackageProjectEventsProvider.Instance.NotifyBatchEnd(packageProjectEventArgs);
}
}
}
if (exceptionInfo != null)
{
await RollbackAsync(nuGetProject, executedNuGetProjectActions, packageWithDirectoriesToBeDeleted, nuGetProjectContext, token);
}
if (ideExecutionContext != null)
{
await ideExecutionContext.CollapseAllNodes(SolutionManager);
}
// Delete the package directories as the last step, so that, if an uninstall had to be rolled back, we can just use the package file on the directory
// Also, always perform deletion of package directories, even in a rollback, so that there are no stale package directories
foreach (var packageWithDirectoryToBeDeleted in packageWithDirectoriesToBeDeleted)
{
var packageFolderPath = PackagesFolderNuGetProject.GetInstalledPath(packageWithDirectoryToBeDeleted);
try
{
await DeletePackageAsync(packageWithDirectoryToBeDeleted, nuGetProjectContext, token);
}
finally
{
if (DeleteOnRestartManager != null)
{
if (Directory.Exists(packageFolderPath))
{
DeleteOnRestartManager.MarkPackageDirectoryForDeletion(
packageWithDirectoryToBeDeleted,
packageFolderPath,
nuGetProjectContext);
// Raise the event to notify listners to update the UI etc.
DeleteOnRestartManager.CheckAndRaisePackageDirectoriesMarkedForDeletion();
}
}
}
}
// Save project
await nuGetProject.SaveAsync(token);
// Clear direct install
SetDirectInstall(null, nuGetProjectContext);
}
// calculate total time taken to execute all nuget actions
stopWatch.Stop();
nuGetProjectContext.Log(
MessageLevel.Info, Strings.NugetActionsTotalTime,
DatetimeUtility.ToReadableTimeFormat(stopWatch.Elapsed));
// emit resolve actions telemetry event
var actionTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.ExecuteActionStepName, stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(actionTelemetryEvent);
if (exceptionInfo != null)
{
exceptionInfo.Throw();
}
}
/// <summary>
/// Run project actions for a build integrated project.
/// </summary>
public async Task<BuildIntegratedProjectAction> PreviewBuildIntegratedProjectActionsAsync(
BuildIntegratedNuGetProject buildIntegratedProject,
IEnumerable<NuGetProjectAction> nuGetProjectActions,
INuGetProjectContext nuGetProjectContext,
CancellationToken token)
{
if (nuGetProjectActions == null)
{
throw new ArgumentNullException(nameof(nuGetProjectActions));
}
if (buildIntegratedProject == null)
{
throw new ArgumentNullException(nameof(buildIntegratedProject));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
if (!nuGetProjectActions.Any())
{
// Return null if there are no actions.
return null;
}
var resolvedAction = await PreviewBuildIntegratedProjectsActionsAsync(
new List<BuildIntegratedNuGetProject>() { buildIntegratedProject },
new Dictionary<string, NuGetProjectAction[]>(PathUtility.GetStringComparerBasedOnOS())
{
{ buildIntegratedProject.MSBuildProjectPath, nuGetProjectActions.ToArray()}
},
packageIdentity: null, // since we have nuGetProjectActions no need packageIdentity
primarySources: null, // since we have nuGetProjectActions no need primarySources
nuGetProjectContext,
versionRange: null,
newMappingID: null,
newMappingSource: null,
token);
return resolvedAction.FirstOrDefault(r => r.Project == buildIntegratedProject)?.Action as BuildIntegratedProjectAction;
}
/// <summary>
/// Run project actions for build integrated many projects.
/// </summary>
internal async Task<IEnumerable<ResolvedAction>> PreviewBuildIntegratedProjectsActionsAsync(
IReadOnlyCollection<BuildIntegratedNuGetProject> buildIntegratedProjects,
Dictionary<string, NuGetProjectAction[]> nugetProjectActionsLookup,
PackageIdentity packageIdentity,
IReadOnlyCollection<SourceRepository> primarySources,
INuGetProjectContext nuGetProjectContext,
VersionRange versionRange,
string newMappingID,
string newMappingSource,
CancellationToken token)
{
if (nugetProjectActionsLookup == null)
{
nugetProjectActionsLookup = new Dictionary<string, NuGetProjectAction[]>(PathUtility.GetStringComparerBasedOnOS());
}
if (buildIntegratedProjects == null)
{
throw new ArgumentNullException(nameof(buildIntegratedProjects));
}
if (buildIntegratedProjects.Count == 0)
{
// Return empty if there are no buildIntegratedProjects.
return Enumerable.Empty<ResolvedAction>();
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
if (nugetProjectActionsLookup.Count == 0 && packageIdentity == null)
{
// Return empty if there are neither actions nor packageIdentity.
return Enumerable.Empty<ResolvedAction>();
}
var stopWatch = Stopwatch.StartNew();
var logger = new ProjectContextLogger(nuGetProjectContext);
var result = new List<ResolvedAction>();
var lockFileLookup = new Dictionary<string, LockFile>(PathUtility.GetStringComparerBasedOnOS());
PackageSourceMappingProvider packageSourceMappingProvider = null;
IReadOnlyList<PackageSourceMappingSourceItem> originalPackageSourceMappings = null;
if (newMappingID != null && newMappingSource != null)
{
packageSourceMappingProvider = new PackageSourceMappingProvider(Settings, shouldSkipSave: true);
originalPackageSourceMappings = packageSourceMappingProvider.GetPackageSourceMappingItems();
AddNewPackageSourceMappingToSettings(newMappingID, newMappingSource, packageSourceMappingProvider);
}
var dependencyGraphContext = new DependencyGraphCacheContext(logger, Settings);
var pathContext = NuGetPathContext.Create(Settings);
var providerCache = new RestoreCommandProvidersCache();
var updatedNugetPackageSpecLookup = new Dictionary<string, PackageSpec>(PathUtility.GetStringComparerBasedOnOS());
var originalNugetPackageSpecLookup = new Dictionary<string, PackageSpec>(PathUtility.GetStringComparerBasedOnOS());
var nuGetProjectSourceLookup = new Dictionary<string, HashSet<SourceRepository>>(PathUtility.GetStringComparerBasedOnOS());
// For installs only use cache entries newer than the current time.
// This is needed for scenarios where a new package shows up in search
// but a previous cache entry does not yet have it.
// So we want to capture the time once here, then pass it down to the two
// restores happening in this flow.
var now = DateTimeOffset.UtcNow;
void cacheModifier(SourceCacheContext cache) => cache.MaxAge = now;
// Add all enabled sources for the existing projects
var enabledSources = SourceRepositoryProvider.GetRepositories();
var allSources = new HashSet<SourceRepository>(enabledSources, SourceRepositoryComparer.Instance);
foreach (var buildIntegratedProject in buildIntegratedProjects)
{
NuGetProjectAction[] nuGetProjectActions;
if (packageIdentity != null)
{
if (primarySources == null || primarySources.Count == 0)
{
throw new ArgumentNullException(nameof(primarySources), $"Should have value in {nameof(primarySources)} if there is value for {nameof(packageIdentity)}");
}
var nugetAction = NuGetProjectAction.CreateInstallProjectAction(packageIdentity, primarySources.First(), buildIntegratedProject, versionRange);
nuGetProjectActions = new[] { nugetAction };
nugetProjectActionsLookup[buildIntegratedProject.MSBuildProjectPath] = nuGetProjectActions;
}
else
{
if (!nugetProjectActionsLookup.TryGetValue(buildIntegratedProject.MSBuildProjectPath, out nuGetProjectActions))
{
throw new ArgumentException(
message: string.Format(CultureInfo.CurrentCulture, Strings.UnableToFindPathInLookupOrList, nameof(nugetProjectActionsLookup), buildIntegratedProject.MSBuildProjectPath, nameof(packageIdentity), nameof(primarySources)),
paramName: nameof(nugetProjectActionsLookup));
}
if (nuGetProjectActions.Length == 0)
{
// Continue to next project if there are no actions for current project.
continue;
}
}
// Find all sources used in the project actions
var sources = new HashSet<SourceRepository>(
nuGetProjectActions.Where(action => action.SourceRepository != null)
.Select(action => action.SourceRepository),
SourceRepositoryComparer.Instance);
allSources.UnionWith(sources);
sources.UnionWith(enabledSources);
nuGetProjectSourceLookup[buildIntegratedProject.MSBuildProjectPath] = sources;
// Read the current lock file if it exists
LockFile originalLockFile = null;
var lockFileFormat = new LockFileFormat();
var lockFilePath = await buildIntegratedProject.GetAssetsFilePathAsync();
if (File.Exists(lockFilePath))
{
originalLockFile = lockFileFormat.Read(lockFilePath);
}
// Get Package Spec as json object
var originalPackageSpec = await DependencyGraphRestoreUtility.GetProjectSpec(buildIntegratedProject, dependencyGraphContext);
originalNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath] = originalPackageSpec;
// Create a copy to avoid modifying the original spec which may be shared.
var updatedPackageSpec = originalPackageSpec.Clone();
// If the lock file does not exist, restore before starting the operations
if (originalLockFile == null)
{
var originalRestoreResult = await DependencyGraphRestoreUtility.PreviewRestoreAsync(
SolutionManager,
buildIntegratedProject,
originalPackageSpec,
dependencyGraphContext,
providerCache,
cacheModifier,
sources,
nuGetProjectContext.OperationId,
token);
originalLockFile = originalRestoreResult.Result.LockFile;
}
lockFileLookup[buildIntegratedProject.MSBuildProjectPath] = originalLockFile;
foreach (var action in nuGetProjectActions)
{
if (action.NuGetProjectActionType == NuGetProjectActionType.Uninstall)
{
// Remove the package from all frameworks and dependencies section.
PackageSpecOperations.RemoveDependency(updatedPackageSpec, action.PackageIdentity.Id);
}
else if (action.NuGetProjectActionType == NuGetProjectActionType.Install)
{
if (updatedPackageSpec.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference)
{
var packageDependency = new PackageDependency(action.PackageIdentity.Id, action.VersionRange ?? new VersionRange(action.PackageIdentity.Version));
PackageSpecOperations.AddOrUpdateDependency(updatedPackageSpec, packageDependency);
}
else
{
PackageSpecOperations.AddOrUpdateDependency(updatedPackageSpec, action.PackageIdentity);
}
}
updatedNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath] = updatedPackageSpec;
dependencyGraphContext.PackageSpecCache[buildIntegratedProject.MSBuildProjectPath] = updatedPackageSpec;
}
}
// Restore based on the modified package specs for many projects. This operation does not write the lock files to disk.
var restoreResults = await DependencyGraphRestoreUtility.PreviewRestoreProjectsAsync(
SolutionManager,
buildIntegratedProjects,
updatedNugetPackageSpecLookup.Values,
dependencyGraphContext,
providerCache,
cacheModifier,
allSources,
nuGetProjectContext.OperationId,
logger,
token);
foreach (var buildIntegratedProject in buildIntegratedProjects)
{
NuGetProjectAction[] nuGetProjectActions = nugetProjectActionsLookup[buildIntegratedProject.MSBuildProjectPath];
var updatedPackageSpec = updatedNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath];
var originalPackageSpec = originalNugetPackageSpecLookup[buildIntegratedProject.MSBuildProjectPath];
var originalLockFile = lockFileLookup[buildIntegratedProject.MSBuildProjectPath];
var sources = nuGetProjectSourceLookup[buildIntegratedProject.MSBuildProjectPath];
var restoreResult = restoreResults.Single(r =>
string.Equals(
r.SummaryRequest.Request.Project.RestoreMetadata.ProjectPath,
buildIntegratedProject.MSBuildProjectPath,
StringComparison.OrdinalIgnoreCase));
var unsuccessfulFrameworks = restoreResult
.Result
.CompatibilityCheckResults
.Where(t => !t.Success)
.Select(t => t.Graph.TargetAlias ?? string.Empty)
.Distinct()
.ToList();
List<(NuGetProjectAction, BuildIntegratedInstallationContext)> projectActionsAndInstallationContexts = new(nuGetProjectActions.Length);
foreach (var action in nuGetProjectActions)
{
PackageSpec referencePackageSpec = null;
referencePackageSpec = action.NuGetProjectActionType switch
{
NuGetProjectActionType.Install or NuGetProjectActionType.Update => restoreResult.Result.LockFile.PackageSpec,
NuGetProjectActionType.Uninstall => originalPackageSpec,
_ => throw new InvalidOperationException("Unknown NuGetProjectActionType"),
};
BuildIntegratedInstallationContext installationContext = CreateInstallationContextForPackageId(action.PackageIdentity.Id, referencePackageSpec, originalPackageSpec, unsuccessfulFrameworks);
projectActionsAndInstallationContexts.Add((action, installationContext));
}
var firstInstallationAndProjectContext = projectActionsAndInstallationContexts[0];
var firstAction = firstInstallationAndProjectContext.Item1;
var successfulFrameworks = firstInstallationAndProjectContext.Item2.SuccessfulFrameworks.AsList();
// If the restore failed and this was a single package install, try to install the package to a subset of
// the target frameworks.
if (nuGetProjectActions.Length == 1 &&
firstAction.NuGetProjectActionType == NuGetProjectActionType.Install &&
!restoreResult.Result.Success &&
successfulFrameworks.Count > 0 &&
unsuccessfulFrameworks.Count > 0 &&
!PackageSpecOperations.HasPackage(originalPackageSpec, firstAction.PackageIdentity.Id))
{
updatedPackageSpec = originalPackageSpec.Clone();
PackageSpecOperations.AddOrUpdateDependency(
updatedPackageSpec,
firstAction.PackageIdentity,
successfulFrameworks);
restoreResult = await DependencyGraphRestoreUtility.PreviewRestoreAsync(
SolutionManager,
buildIntegratedProject,
updatedPackageSpec,
dependencyGraphContext,
providerCache,
cacheModifier,
sources,
nuGetProjectContext.OperationId,
token);
}
// If HideWarningsAndErrors is true then restore will not display the warnings and errors.
// Further, replay errors and warnings only if restore failed because the assets file will not be committed.
// If there were only warnings then those are written to assets file and committed. The design time build will replay them.
if (updatedPackageSpec.RestoreSettings.HideWarningsAndErrors &&
!restoreResult.Result.Success)
{
await MSBuildRestoreUtility.ReplayWarningsAndErrorsAsync(restoreResult.Result.LockFile?.LogMessages, logger);
}
InstallationCompatibility.EnsurePackageCompatibility(
buildIntegratedProject,
pathContext,
nuGetProjectActions,
restoreResult.Result);
// If this build integrated project action represents only uninstalls, mark the entire operation
// as an uninstall. Otherwise, mark it as an install. This is important because install operations
// are a bit more sensitive to errors (thus resulting in rollbacks).
var actionType = NuGetProjectActionType.Install;
if (nuGetProjectActions.All(x => x.NuGetProjectActionType == NuGetProjectActionType.Uninstall))
{
actionType = NuGetProjectActionType.Uninstall;
}
var nugetProjectAction = new BuildIntegratedProjectAction(
buildIntegratedProject,
nuGetProjectActions.First().PackageIdentity,
actionType,
originalLockFile,
restoreResult,
sources.ToList(),
projectActionsAndInstallationContexts,
versionRange);
result.Add(new ResolvedAction(buildIntegratedProject, nugetProjectAction));
}
// Put back the Package Source Mappings that existed prior to this Preview.
if (originalPackageSourceMappings != null && packageSourceMappingProvider != null)
{
packageSourceMappingProvider.SavePackageSourceMappings(originalPackageSourceMappings);
}
stopWatch.Stop();
var actionTelemetryEvent = new ActionTelemetryStepEvent(
nuGetProjectContext.OperationId.ToString(),
TelemetryConstants.PreviewBuildIntegratedStepName, stopWatch.Elapsed.TotalSeconds);
NuGetTelemetryService?.EmitTelemetryEvent(actionTelemetryEvent);
return result;
}
/// <summary>
/// Build a context for package given package id.
/// The "successful" frameworks are the ones that contain the package and are not part of the failed list.
/// The "unsuccessful" frameworks are the ones that never had the package or are part of the unsuccessful list.
/// </summary>>
internal static BuildIntegratedInstallationContext CreateInstallationContextForPackageId(string packageIdentityId, PackageSpec packageSpec, PackageSpec originalPackageSpec, List<string> unsuccessfulFrameworks)
{
var frameworksWithResultingPackage = packageSpec
.TargetFrameworks
.Where(e => e.Dependencies.Any(a => string.Equals(a.Name, packageIdentityId, StringComparison.OrdinalIgnoreCase)))
.Select(e => e.TargetAlias ?? string.Empty)
.Distinct();
var frameworksWithoutResultingPackage = packageSpec
.TargetFrameworks
.Where(e => !e.Dependencies.Any(a => string.Equals(a.Name, packageIdentityId, StringComparison.OrdinalIgnoreCase)))
.Select(e => e.TargetAlias ?? string.Empty)
.Distinct();
var successfulFrameworksWithPackage = frameworksWithResultingPackage
.Except(unsuccessfulFrameworks)
.ToList();
var areAllPackagesConditional = DoesPackageAppearWithDifferentVersions(packageIdentityId, originalPackageSpec);
return new BuildIntegratedInstallationContext()
{
SuccessfulFrameworks = successfulFrameworksWithPackage,
UnsuccessfulFrameworks = unsuccessfulFrameworks.Union(frameworksWithoutResultingPackage).Distinct().ToList(),
AreAllPackagesConditional = areAllPackagesConditional
};
}
private static bool DoesPackageAppearWithDifferentVersions(string packageIdentityId, PackageSpec packageSpec)
{
HashSet<VersionRange> versions = default;
foreach (var framework in packageSpec.TargetFrameworks)
{
foreach (var dependency in framework.Dependencies)
{
if (string.Equals(dependency.Name, packageIdentityId, StringComparison.OrdinalIgnoreCase))
{
versions ??= new();
versions.Add(dependency.LibraryRange.VersionRange);
break;
}
}
}
return versions?.Count > 1;
}
/// <summary>
/// Reads existing Package Source Mappings from settings and appends a new mapping for the <paramref name="newMappingID"/> and a glob "*" pattern
/// for the <paramref name="newMappingSource"/>.
/// The intention is that Preview Restore can run and expect all newly installed packages to be source mapped to the new source.
/// </summary>
/// <returns>If a new mapping was provided, returns all persisted mappings appended with the new mapping. Otherwise, null.</returns>
private void AddNewPackageSourceMappingToSettings(string newMappingID, string newMappingSource, PackageSourceMappingProvider mappingProvider)
{
List<PackagePatternItem> newPatternItems = new()
{
new PackagePatternItem(newMappingID),
new PackagePatternItem("*")
};
List<PackageSourceMappingSourceItem> newAndExistingPackageSourceMappingItems = mappingProvider.GetPackageSourceMappingItems().ToList();
newAndExistingPackageSourceMappingItems.Add(new PackageSourceMappingSourceItem(newMappingSource, newPatternItems));
mappingProvider.SavePackageSourceMappings(newAndExistingPackageSourceMappingItems);
}
/// <summary>
/// Run project actions for build integrated projects.
/// </summary>
public async Task ExecuteBuildIntegratedProjectActionsAsync(
BuildIntegratedNuGetProject buildIntegratedProject,
IEnumerable<NuGetProjectAction> nuGetProjectActions,
INuGetProjectContext nuGetProjectContext,
CancellationToken token)
{
if (buildIntegratedProject == null)
{
throw new ArgumentNullException(nameof(buildIntegratedProject));
}
BuildIntegratedProjectAction projectAction = null;
if (nuGetProjectActions.Count() == 1
&& nuGetProjectActions.All(action => action is BuildIntegratedProjectAction))
{
projectAction = nuGetProjectActions.Single() as BuildIntegratedProjectAction;
}
else if (nuGetProjectActions.Any())
{
projectAction = await PreviewBuildIntegratedProjectActionsAsync(
buildIntegratedProject,
nuGetProjectActions,
nuGetProjectContext,
token);
}
else
{
// There are no actions, this is a no-op
return;
}
var actions = projectAction.GetProjectActions();
// Check if all actions are uninstalls
var uninstallOnly = projectAction.NuGetProjectActionType == NuGetProjectActionType.Uninstall
&& actions.All(action => action.NuGetProjectActionType == NuGetProjectActionType.Uninstall);
var restoreResult = projectAction.RestoreResult;
// Avoid committing the changes if the restore did not succeed
// For uninstalls continue even if the restore failed to avoid blocking the user
if (restoreResult.Success || uninstallOnly)
{
// Get all install actions
var ignoreActions = new HashSet<NuGetProjectAction>();
var installedIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach ((var action, _) in projectAction.ActionAndContextList.Reverse())
{
if (action.NuGetProjectActionType == NuGetProjectActionType.Install)
{
installedIds.Add(action.PackageIdentity.Id);
}
else if (installedIds.Contains(action.PackageIdentity.Id))
{
ignoreActions.Add(action);
}
}
var pathResolver = new FallbackPackagePathResolver(
projectAction.RestoreResult.LockFile.PackageSpec.RestoreMetadata.PackagesPath,
projectAction.RestoreResult.LockFile.PackageSpec.RestoreMetadata.FallbackFolders);
foreach ((var originalAction, var installationContext) in projectAction.ActionAndContextList.Where(e => !ignoreActions.Contains(e.Item1)))
{
// This is where we calculate what goes into CPSPackageReferenceProject
if (originalAction.NuGetProjectActionType == NuGetProjectActionType.Install)
{
if (buildIntegratedProject.ProjectStyle == ProjectStyle.PackageReference)
{
BuildIntegratedRestoreUtility.UpdatePackageReferenceMetadata(
projectAction.RestoreResult.LockFile,
pathResolver,
originalAction.PackageIdentity);
var framework = installationContext.SuccessfulFrameworks.First();
TargetFrameworkInformation matchingTfi = projectAction.RestoreResult.LockFile.PackageSpec.TargetFrameworks.Count == 1 ?
projectAction.RestoreResult.LockFile.PackageSpec.TargetFrameworks[0] :
projectAction.RestoreResult.LockFile.PackageSpec.TargetFrameworks.FirstOrDefault(fm => fm.TargetAlias.Equals(framework));
var resolvedAction = matchingTfi
.Dependencies.First(dependency => dependency.Name.Equals(originalAction.PackageIdentity.Id, StringComparison.OrdinalIgnoreCase));
installationContext.SuppressParent = resolvedAction.SuppressParent;
installationContext.IncludeType = resolvedAction.IncludeType;
}
// Install the package to the project
await buildIntegratedProject.InstallPackageAsync(
originalAction.PackageIdentity.Id,
originalAction.VersionRange ?? new VersionRange(originalAction.PackageIdentity.Version),
nuGetProjectContext,
installationContext,
token: token);
}
else if (originalAction.NuGetProjectActionType == NuGetProjectActionType.Uninstall)
{
await buildIntegratedProject.UninstallPackageAsync(
originalAction.PackageIdentity.Id,
installationContext,
token: token);
}
}
var logger = new ProjectContextLogger(nuGetProjectContext);
var referenceContext = new DependencyGraphCacheContext(logger, Settings);
var now = DateTime.UtcNow;
void cacheContextModifier(SourceCacheContext c) => c.MaxAge = now;
// Write out the lock file, now no need bubbling re-evaluating of parent projects when you restore from PM UI.
// We already taken account of that concern in PreviewBuildIntegratedProjectsActionsAsync method.
bool isNoOp = projectAction.RestoreResultPair.Result is NoOpRestoreResult;
IReadOnlyList<string> filesToBeUpdated = isNoOp ? null : GetFilesToBeUpdated(projectAction.RestoreResultPair);
if (!isNoOp)
{
RestoreProgressReporter?.StartProjectUpdate(projectAction.RestoreResultPair.SummaryRequest.Request.Project.FilePath, filesToBeUpdated);
}
try
{
await RestoreRunner.CommitAsync(projectAction.RestoreResultPair, token);
}
finally
{
if (!isNoOp)
{
RestoreProgressReporter?.EndProjectUpdate(projectAction.RestoreResultPair.SummaryRequest.Request.Project.FilePath, filesToBeUpdated);
}
}
// add packages lock file into project
if (PackagesLockFileUtilities.IsNuGetLockFileEnabled(projectAction.RestoreResult.LockFile.PackageSpec))
{
var lockFilePath = PackagesLockFileUtilities.GetNuGetLockFilePath(projectAction.RestoreResult.LockFile.PackageSpec);
await buildIntegratedProject.AddFileToProjectAsync(lockFilePath);
}
// Write out a message for each action
foreach (var action in actions)
{
var identityString = string.Format(CultureInfo.InvariantCulture, "{0} {1}",
action.PackageIdentity.Id,
action.PackageIdentity.Version.ToNormalizedString());
if (action.NuGetProjectActionType == NuGetProjectActionType.Install)
{
nuGetProjectContext.Log(
MessageLevel.Info,
Strings.SuccessfullyInstalled,
identityString,
buildIntegratedProject.GetMetadata<string>(NuGetProjectMetadataKeys.Name));
}
else
{
// uninstall
nuGetProjectContext.Log(
MessageLevel.Info,
Strings.SuccessfullyUninstalled,
identityString,
buildIntegratedProject.GetMetadata<string>(NuGetProjectMetadataKeys.Name));
}
}
// Run init.ps1 scripts
var addedPackages = BuildIntegratedRestoreUtility.GetAddedPackages(
projectAction.OriginalLockFile,
restoreResult.LockFile);
await BuildIntegratedRestoreUtility.ExecuteInitPs1ScriptsAsync(
buildIntegratedProject,
addedPackages,
pathResolver,
nuGetProjectContext,
token);
// find list of buildintegrated projects
var projects = (await SolutionManager.GetNuGetProjectsAsync()).OfType<BuildIntegratedNuGetProject>().ToList();
// build reference cache if not done already
if (_buildIntegratedProjectsCache == null)
{
_buildIntegratedProjectsCache = await
DependencyGraphRestoreUtility.GetSolutionRestoreSpec(SolutionManager, referenceContext);
}
// Restore parent projects. These will be updated to include the transitive changes.
var parents = BuildIntegratedRestoreUtility.GetParentProjectsInClosure(
projects,
buildIntegratedProject,
_buildIntegratedProjectsCache);
// The settings contained in the context are applied to the dg spec.
var dgSpecForParents = await DependencyGraphRestoreUtility.GetSolutionRestoreSpec(SolutionManager, referenceContext);
dgSpecForParents = dgSpecForParents.WithoutRestores();
foreach (var parent in parents)
{
// We only evaluate unseen parents.
if (_buildIntegratedProjectsUpdateSet == null ||
!_buildIntegratedProjectsUpdateSet.Contains(parent.MSBuildProjectPath))
{
// Mark project for restore
dgSpecForParents.AddRestore(parent.MSBuildProjectPath);
_buildIntegratedProjectsUpdateSet?.Add(parent.MSBuildProjectPath);
}
}
if (dgSpecForParents.Restore.Count > 0)
{
// Restore and commit the lock file to disk regardless of the result
// This will restore all parents in a single restore
await DependencyGraphRestoreUtility.RestoreAsync(
dgSpecForParents,
referenceContext,
GetRestoreProviderCache(),
cacheContextModifier,
projectAction.Sources,
nuGetProjectContext.OperationId,
forceRestore: false, // No need to force restore as the inputs would've changed here anyways
isRestoreOriginalAction: false, // not an explicit restore request instead being done as part of install or update
additionalMessages: null,
progressReporter: RestoreProgressReporter,
log: logger,
token: token);
}
}
else
{
// Fail and display a rollback message to let the user know they have returned to the original state
var message = string.Format(
CultureInfo.InvariantCulture,
Strings.RestoreFailedRollingBack,
buildIntegratedProject.ProjectName);
// Read additional errors from the lock file if one exists
var logMessages = restoreResult.LockFile?
.LogMessages
.Where(e => e.Level == LogLevel.Error || e.Level == LogLevel.Warning)
.Select(e => e.AsRestoreLogMessage())
?? Enumerable.Empty<ILogMessage>();
// Throw an exception containing all errors, these will be displayed in the error list
throw new PackageReferenceRollbackException(message, logMessages);
}
await OpenReadmeFile(buildIntegratedProject, nuGetProjectContext, token);
}
private static IReadOnlyList<string> GetFilesToBeUpdated(RestoreResultPair result)
{
List<string> filesToBeUpdated = new(3); // We know that we have 3 files.
filesToBeUpdated.Add(result.Result.LockFilePath);
foreach (MSBuildOutputFile msbuildOutputFile in result.Result.MSBuildOutputFiles)
{
filesToBeUpdated.Add(msbuildOutputFile.Path);
}
return filesToBeUpdated.AsReadOnly();
}
private async Task RollbackAsync(
NuGetProject nuGetProject,
Stack<NuGetProjectAction> executedNuGetProjectActions,
HashSet<PackageIdentity> packageWithDirectoriesToBeDeleted,
INuGetProjectContext nuGetProjectContext,
CancellationToken token)
{
if (executedNuGetProjectActions.Count > 0)
{
// Only print the rollback warning if we have something to rollback
nuGetProjectContext.Log(MessageLevel.Warning, Strings.Warning_RollingBack);
}
while (executedNuGetProjectActions.Count > 0)
{
var nuGetProjectAction = executedNuGetProjectActions.Pop();
try
{
if (nuGetProjectAction.NuGetProjectActionType == NuGetProjectActionType.Install)
{
// Rolling back an install would be to uninstall the package
await ExecuteUninstallAsync(nuGetProject, nuGetProjectAction.PackageIdentity, packageWithDirectoriesToBeDeleted, nuGetProjectContext, token);
}
else
{
packageWithDirectoriesToBeDeleted.Remove(nuGetProjectAction.PackageIdentity);
var packagePath = PackagesFolderNuGetProject.GetInstalledPackageFilePath(nuGetProjectAction.PackageIdentity);
if (File.Exists(packagePath))
{
using (var downloadResourceResult = new DownloadResourceResult(File.OpenRead(packagePath), nuGetProjectAction.SourceRepository?.PackageSource?.Source))
{
await ExecuteInstallAsync(nuGetProject, nuGetProjectAction.PackageIdentity, downloadResourceResult, packageWithDirectoriesToBeDeleted, nuGetProjectContext, token);
}
}
}
}
catch (Exception)
{
// TODO: We are ignoring exceptions on rollback. Is this OK?
}
}
}
private Task OpenReadmeFile(NuGetProject nuGetProject, INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
var executionContext = nuGetProjectContext.ExecutionContext;
if (executionContext != null
&& executionContext.DirectInstall != null)
{
//packagesPath is different for project.json vs Packages.config scenarios. So check if the project is a build-integrated project
var buildIntegratedProject = nuGetProject as BuildIntegratedNuGetProject;
var readmeFilePath = string.Empty;
if (buildIntegratedProject != null)
{
var pathContext = NuGetPathContext.Create(Settings);
var pathResolver = new FallbackPackagePathResolver(pathContext);
var identity = nuGetProjectContext.ExecutionContext.DirectInstall;
var packageFolderPath = pathResolver.GetPackageDirectory(identity.Id, identity.Version);
if (!string.IsNullOrEmpty(packageFolderPath))
{
readmeFilePath = Path.Combine(packageFolderPath, Constants.ReadmeFileName);
}
}
else
{
var packagePath = PackagesFolderNuGetProject.GetInstalledPackageFilePath(executionContext.DirectInstall);
if (!string.IsNullOrEmpty(packagePath))
{
readmeFilePath = Path.Combine(Path.GetDirectoryName(packagePath), Constants.ReadmeFileName);
}
}
if (!token.IsCancellationRequested &&
!string.IsNullOrEmpty(readmeFilePath) &&
File.Exists(readmeFilePath))
{
return executionContext.OpenFile(readmeFilePath);
}
}
return TaskResult.False;
}
/// <summary>
/// RestorePackage is only allowed on a folderNuGetProject. In most cases, one will simply use the
/// packagesFolderPath from NuGetPackageManager
/// to create a folderNuGetProject before calling into this method
/// </summary>
public async Task<bool> RestorePackageAsync(
PackageIdentity packageIdentity,
INuGetProjectContext nuGetProjectContext,
PackageDownloadContext downloadContext,
IEnumerable<SourceRepository> sourceRepositories,
CancellationToken token)
{
token.ThrowIfCancellationRequested();
if (PackageExistsInPackagesFolder(packageIdentity, nuGetProjectContext.PackageExtractionContext.PackageSaveMode))
{
return false;
}
token.ThrowIfCancellationRequested();
nuGetProjectContext.Log(MessageLevel.Info, string.Format(CultureInfo.CurrentCulture, Strings.RestoringPackage, packageIdentity));
var enabledSources = (sourceRepositories != null && sourceRepositories.Any()) ? sourceRepositories :
SourceRepositoryProvider.GetRepositories().Where(e => e.PackageSource.IsEnabled);
token.ThrowIfCancellationRequested();
using (var downloadResult = await PackageDownloader.GetDownloadResourceResultAsync(
enabledSources,
packageIdentity,
downloadContext,
SettingsUtility.GetGlobalPackagesFolder(Settings),
new ProjectContextLogger(nuGetProjectContext),
token))
{
// Install package whether returned from the cache or a direct download
await PackagesFolderNuGetProject.InstallPackageAsync(packageIdentity, downloadResult, nuGetProjectContext, token);
}
return true;
}
public Task<bool> CopySatelliteFilesAsync(PackageIdentity packageIdentity, INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
return PackagesFolderNuGetProject.CopySatelliteFilesAsync(packageIdentity, nuGetProjectContext, token);
}
/// <summary>
/// Checks whether package exists in packages folder and verifies that nupkg and nuspec are present as specified by packageSaveMode
/// </summary>
public bool PackageExistsInPackagesFolder(PackageIdentity packageIdentity, PackageSaveMode packageSaveMode)
{
return PackagesFolderNuGetProject.PackageExists(packageIdentity, packageSaveMode);
}
public bool PackageExistsInPackagesFolder(PackageIdentity packageIdentity)
{
return PackagesFolderNuGetProject.PackageExists(packageIdentity);
}
private async Task ExecuteInstallAsync(
NuGetProject nuGetProject,
PackageIdentity packageIdentity,
DownloadResourceResult resourceResult,
HashSet<PackageIdentity> packageWithDirectoriesToBeDeleted,
INuGetProjectContext nuGetProjectContext,
CancellationToken token)
{
// TODO: EnsurePackageCompatibility check should be performed in preview. Can easily avoid a lot of rollback
await InstallationCompatibility.EnsurePackageCompatibilityAsync(nuGetProject, packageIdentity, resourceResult, token);
packageWithDirectoriesToBeDeleted.Remove(packageIdentity);
await nuGetProject.InstallPackageAsync(packageIdentity, resourceResult, nuGetProjectContext, token);
}
private async Task ExecuteUninstallAsync(NuGetProject nuGetProject, PackageIdentity packageIdentity, HashSet<PackageIdentity> packageWithDirectoriesToBeDeleted,
INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
// Step-1: Call nuGetProject.UninstallPackage
await nuGetProject.UninstallPackageAsync(packageIdentity, nuGetProjectContext, token);
// Step-2: Check if the package directory could be deleted
if (!(nuGetProject is INuGetIntegratedProject)
&& !await PackageExistsInAnotherNuGetProject(nuGetProject, packageIdentity, SolutionManager, token, excludeIntegrated: true))
{
packageWithDirectoriesToBeDeleted.Add(packageIdentity);
}
}
/// <summary>
/// Checks if package <paramref name="packageIdentity" /> that is installed in
/// project <paramref name="nuGetProject" /> is also installed in any
/// other projects in the solution.
/// </summary>
public static async Task<bool> PackageExistsInAnotherNuGetProject(NuGetProject nuGetProject, PackageIdentity packageIdentity, ISolutionManager solutionManager, CancellationToken token, bool excludeIntegrated = false)
{
if (nuGetProject == null)
{
throw new ArgumentNullException(nameof(nuGetProject));
}
if (packageIdentity == null)
{
throw new ArgumentNullException(nameof(packageIdentity));
}
if (solutionManager == null)
{
// If the solution manager is null, simply assume that the
// package exists on another nuget project to not delete it
return true;
}
var nuGetProjectName = NuGetProject.GetUniqueNameOrName(nuGetProject);
foreach (var otherNuGetProject in (await solutionManager.GetNuGetProjectsAsync()))
{
if (excludeIntegrated && otherNuGetProject is INuGetIntegratedProject)
{
continue;
}
var otherNuGetProjectName = NuGetProject.GetUniqueNameOrName(otherNuGetProject);
if (otherNuGetProjectName.Equals(nuGetProjectName, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var packageExistsInAnotherNuGetProject = (await otherNuGetProject.GetInstalledPackagesAsync(token)).Any(pr => pr.PackageIdentity.Equals(packageIdentity));
if (packageExistsInAnotherNuGetProject)
{
return true;
}
}
return false;
}
private async Task<bool> DeletePackageAsync(PackageIdentity packageIdentity, INuGetProjectContext nuGetProjectContext, CancellationToken token)
{
if (packageIdentity == null)
{
throw new ArgumentNullException(nameof(packageIdentity));
}
if (nuGetProjectContext == null)
{
throw new ArgumentNullException(nameof(nuGetProjectContext));
}
// 1. Check if the Package exists at root, if not, return false
if (!PackagesFolderNuGetProject.PackageExists(packageIdentity))
{
nuGetProjectContext.Log(ProjectManagement.MessageLevel.Warning, Strings.PackageDoesNotExistInFolder, packageIdentity, PackagesFolderNuGetProject.Root);
return false;
}
nuGetProjectContext.Log(ProjectManagement.MessageLevel.Info, Strings.RemovingPackageFromFolder, packageIdentity, PackagesFolderNuGetProject.Root);
// 2. Delete the package folder and files from the root directory of this FileSystemNuGetProject
// Remember that the following code may throw System.UnauthorizedAccessException
await PackagesFolderNuGetProject.DeletePackage(packageIdentity, nuGetProjectContext, token);
nuGetProjectContext.Log(ProjectManagement.MessageLevel.Info, Strings.RemovedPackageFromFolder, packageIdentity, PackagesFolderNuGetProject.Root);
return true;
}
public static Task<ResolvedPackage> GetLatestVersionAsync(
string packageId,
NuGetFramework framework,
ResolutionContext resolutionContext,
SourceRepository primarySourceRepository,
Common.ILogger log,
CancellationToken token)
{
return GetLatestVersionAsync(
packageId,
framework,
resolutionContext,
new List<SourceRepository> { primarySourceRepository },
log,
token);
}
public static Task<ResolvedPackage> GetLatestVersionAsync(
string packageId,
NuGetProject project,
ResolutionContext resolutionContext,
SourceRepository primarySourceRepository,
Common.ILogger log,
CancellationToken token)
{
NuGetFramework framework;
if (!project.TryGetMetadata<NuGetFramework>(NuGetProjectMetadataKeys.TargetFramework, out framework))
{
// Default to the any framework if the project does not specify a framework.
framework = NuGetFramework.AnyFramework;
}
return GetLatestVersionAsync(
packageId,
framework,
resolutionContext,
new List<SourceRepository> { primarySourceRepository },
log,
token);
}
public static async Task<ResolvedPackage> GetLatestVersionAsync(
string packageId,
NuGetProject project,
ResolutionContext resolutionContext,
IEnumerable<SourceRepository> sources,
Common.ILogger log,
CancellationToken token)
{
NuGetFramework framework;
if (!project.TryGetMetadata<NuGetFramework>(NuGetProjectMetadataKeys.TargetFramework, out framework))
{
// Default to the any framework if the project does not specify a framework.
framework = NuGetFramework.AnyFramework;
}
return await GetLatestVersionAsync(packageId, framework, resolutionContext, sources, log, token);
}
public static async Task<ResolvedPackage> GetLatestVersionAsync(
string packageId,
NuGetFramework framework,
ResolutionContext resolutionContext,
IEnumerable<SourceRepository> sources,
Common.ILogger log,
CancellationToken token)
{
var tasks = new List<Task<ResolvedPackage>>();
NuGetVersion version = null;
foreach (var source in sources)
{
tasks.Add(Task.Run(async ()
=> await GetLatestVersionCoreAsync(packageId, version, framework, resolutionContext, source, log, token)));
}
var resolvedPackages = await Task.WhenAll(tasks);
var latestVersion = resolvedPackages.Select(v => v.LatestVersion).Where(v => v != null).Max();
return new ResolvedPackage(latestVersion, resolvedPackages.Any(p => p.Exists));
}
public static async Task<ResolvedPackage> GetLatestVersionAsync(
PackageReference package,
NuGetFramework framework,
ResolutionContext resolutionContext,
IEnumerable<SourceRepository> sources,
Common.ILogger log,
CancellationToken token)
{
var tasks = new List<Task<ResolvedPackage>>();
foreach (var source in sources)
{
tasks.Add(Task.Run(async ()
=> await GetLatestVersionCoreAsync(package.PackageIdentity.Id, package.PackageIdentity.Version, framework, resolutionContext, source, log, token)));
}
var resolvedPackages = await Task.WhenAll(tasks);
var latestVersion = resolvedPackages
.Select(v => v.LatestVersion)
.Where(v => v != null && (package.AllowedVersions == null || package.AllowedVersions.Satisfies(v))).Max();
return new ResolvedPackage(latestVersion, resolvedPackages.Any(p => p.Exists));
}
private static async Task<ResolvedPackage> GetLatestVersionCoreAsync(
string packageId,
NuGetVersion version,
NuGetFramework framework,
ResolutionContext resolutionContext,
SourceRepository source,
Common.ILogger log,
CancellationToken token)
{
var dependencyInfoResource = await source.GetResourceAsync<DependencyInfoResource>(token);
// Resolve the package for the project framework and cache the results in the
// resolution context for the gather to use during the next step.
// Using the metadata resource will result in multiple calls to the same url during an install.
var packages = (await dependencyInfoResource.ResolvePackages(packageId, framework, resolutionContext.SourceCacheContext, log, token)).ToList();
Debug.Assert(resolutionContext.GatherCache != null);
// Cache the results, even if the package was not found.
resolutionContext.GatherCache.AddAllPackagesForId(
source.PackageSource,
packageId,
framework,
packages);
if (version != null)
{
packages = PrunePackageTree.PruneByUpdateConstraints(packages, version, resolutionContext.VersionConstraints).ToList();
}
// Find the latest version
var latestVersion = packages.Where(package => (package.Listed || resolutionContext.IncludeUnlisted)
&& (resolutionContext.IncludePrerelease || !package.Version.IsPrerelease))
.OrderByDescending(package => package.Version, VersionComparer.Default)
.Select(package => package.Version)
.FirstOrDefault();
return new ResolvedPackage(latestVersion, packages.Count > 0);
}
private IEnumerable<SourceRepository> GetEffectiveSources(IEnumerable<SourceRepository> primarySources, IEnumerable<SourceRepository> secondarySources)
{
// Always have to add the packages folder as the primary repository so that
// dependency info for an installed package that is unlisted from the server is still available :(
// count = primarySources.Count + secondarySources.Count + 1 for PackagesFolderSourceRepository
var count = (primarySources?.Count() ?? 0) +
(secondarySources?.Count() ?? 0)
+ 1;
var effectiveSources = new List<SourceRepository>(count)
{
PackagesFolderSourceRepository
};
if (primarySources != null)
{
effectiveSources.AddRange(primarySources);
}
if (secondarySources != null)
{
effectiveSources.AddRange(secondarySources);
}
return new HashSet<SourceRepository>(effectiveSources, SourceRepositoryComparer.Instance);
}
public static void SetDirectInstall(PackageIdentity directInstall,
INuGetProjectContext nuGetProjectContext)
{
if (directInstall != null
&& nuGetProjectContext != null
&& nuGetProjectContext.ExecutionContext != null)
{
var ideExecutionContext = nuGetProjectContext.ExecutionContext as IDEExecutionContext;
if (ideExecutionContext != null)
{
ideExecutionContext.IDEDirectInstall = directInstall;
}
}
}
public static void ClearDirectInstall(INuGetProjectContext nuGetProjectContext)
{
if (nuGetProjectContext != null
&& nuGetProjectContext.ExecutionContext != null)
{
var ideExecutionContext = nuGetProjectContext.ExecutionContext as IDEExecutionContext;
if (ideExecutionContext != null)
{
ideExecutionContext.IDEDirectInstall = null;
}
}
}
private RestoreCommandProvidersCache GetRestoreProviderCache()
{
if (_restoreProviderCache == null)
{
_restoreProviderCache = new RestoreCommandProvidersCache();
}
return _restoreProviderCache;
}
}
}
|