private Resolver.PackageResolverContext GetResolverContext(PackageIdentity package, DependencyBehavior dependencyBehavior, IEnumerable <SourcePackageDependencyInfo> availablePackages, IEnumerable <PackageReference> packagesConfig, IEnumerable <PackageIdentity> ignoredDependenciesList) { var idArray = new[] { package.Id }; var requiredPackageIds = availablePackages.Select(x => x.Id).Distinct(); var resolverContext = new Resolver.PackageResolverContext( dependencyBehavior, idArray, requiredPackageIds: requiredPackageIds, packagesConfig: packagesConfig, preferredVersions: Enumerable.Empty <PackageIdentity>(), availablePackages, _sourceRepositoryProvider.GetRepositories().Select(x => x.PackageSource), ignoredDependenciesList.Select(x => x.Id), _nugetLogger ); return(resolverContext); }
public static IEnumerable <PackageIdentity> Resolve(this PackageResolver resolver, PackageResolverContext context, CancellationToken token) { return(resolver.Resolve(context, token)); }
/// <summary> /// Resolve a package closure /// </summary> public IEnumerable <SourcePackageDependencyInfo> Resolve(PackageResolverContext context, CancellationToken token) { var stopWatch = new Stopwatch(); token.ThrowIfCancellationRequested(); if (context is null) { throw new ArgumentNullException(nameof(context)); } // validation foreach (var requiredId in context.RequiredPackageIds) { if (!context.AvailablePackages.Any(p => StringComparer.OrdinalIgnoreCase.Equals(p.Id, requiredId))) { throw new NuGetResolverInputException(string.Format(CultureInfo.CurrentCulture, "Unable to find package '{0}'. Existing packages must be restored before performing an install or update.", requiredId)); } } var invalidExistingPackages = new List <string>(); var installedPackages = context.PackagesConfig.Select(p => p.PackageIdentity).ToArray(); // validate existing package.config for any invalid dependency foreach (var package in installedPackages) { var existingPackage = context.AvailablePackages.FirstOrDefault( p => StringComparer.OrdinalIgnoreCase.Equals(p.Id, package.Id) && p.Version.Equals(package.Version)); if (existingPackage is not null) { // check if each dependency can be satisfied with existing packages var brokenDependencies = GetBrokenDependencies(existingPackage, installedPackages); if (brokenDependencies is not null && brokenDependencies.Any()) { invalidExistingPackages.AddRange(brokenDependencies.Select(dependency => FormatDependencyConstraint(existingPackage, dependency))); } } else { // check same package is being updated and we've a higher version then // ignore logging warning for that. existingPackage = context.AvailablePackages.FirstOrDefault( p => StringComparer.OrdinalIgnoreCase.Equals(p.Id, package.Id) && VersionComparer.Default.Compare(p.Version, package.Version) > 0); if (existingPackage is null) { var packageString = $"'{package.Id} {package.Version.ToNormalizedString()}'"; invalidExistingPackages.Add(packageString); } } } // log warning message for all the invalid package dependencies if (invalidExistingPackages.Count > 0) { context.Log.LogWarning( string.Format( CultureInfo.CurrentCulture, "One or more unresolved package dependency constraints detected in the existing packages.config file. All dependency constraints must be resolved to add or update packages. If these packages are being updated this message may be ignored, if not the following error(s) may be blocking the current package operation: {0}", string.Join(", ", invalidExistingPackages))); } // convert the available packages into ResolverPackages var resolverPackages = new List <ResolverPackage>(); // pre-process the available packages to remove any packages that can't possibly form part of a solution var availablePackages = RemoveImpossiblePackages(context.AvailablePackages, context.RequiredPackageIds); foreach (var package in availablePackages) { IEnumerable <PackageDependency> dependencies = null; // clear out the dependencies if the behavior is set to ignore if (context.DependencyBehavior == DependencyBehavior.Ignore) { dependencies = Enumerable.Empty <PackageDependency>(); } else { dependencies = package.Dependencies ?? Enumerable.Empty <PackageDependency>(); } resolverPackages.Add(new ResolverPackage(package.Id, package.Version, dependencies, package.Listed, false)); } // Sort the packages to make this process as deterministic as possible resolverPackages.Sort(PackageIdentityComparer.Default); // Keep track of the ids we have added var groupsAdded = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var grouped = new List <List <ResolverPackage> >(); // group the packages by id foreach (var group in resolverPackages.GroupBy(e => e.Id, StringComparer.OrdinalIgnoreCase)) { groupsAdded.Add(group.Key); var curSet = group.ToList(); // add an absent package for non-targets // being absent allows the resolver to throw it out if it is not needed if (!context.RequiredPackageIds.Contains(group.Key, StringComparer.OrdinalIgnoreCase)) { curSet.Add(new ResolverPackage(id: group.Key, version: null, dependencies: null, listed: true, absent: true)); } grouped.Add(curSet); } // find all needed dependencies, excluding manually ignored dependencies var dependencyIds = resolverPackages.Where(e => e.Dependencies is not null) .SelectMany(e => e.Dependencies.Select(d => d.Id)).Distinct(StringComparer.OrdinalIgnoreCase); //var ignoredDependencyIds = dependencyIds. foreach (string depId in dependencyIds) { // packages which are unavailable need to be added as absent packages // ex: if A -> B and B is not found anywhere in the source repositories we add B as absent if (!groupsAdded.Contains(depId)) { groupsAdded.Add(depId); grouped.Add(new List <ResolverPackage>() { new ResolverPackage(id: depId, version: null, dependencies: null, listed: true, absent: true) }); } } token.ThrowIfCancellationRequested(); // keep track of the best partial solution var bestSolution = Enumerable.Empty <ResolverPackage>(); Action <IEnumerable <ResolverPackage> > diagnosticOutput = (partialSolution) => { // store each solution as they pass through. // the combination solver verifies that the last one returned is the best bestSolution = partialSolution; }; // Run solver var comparer = new ResolverComparer(context.DependencyBehavior, context.PreferredVersions, context.TargetIds); var sortedGroups = ResolverInputSort.TreeFlatten(grouped, context); var solution = CombinationSolver <ResolverPackage> .FindSolution( groupedItems : sortedGroups, itemSorter : comparer, shouldRejectPairFunc : ShouldRejectPackagePair, diagnosticOutput : diagnosticOutput); // check if a solution was found if (solution is not null) { var nonAbsentCandidates = solution.Where(c => !c.Absent); if (nonAbsentCandidates.Any()) { // topologically sort non absent packages var sortedSolution = ResolverUtility.TopologicalSort(nonAbsentCandidates); // Find circular dependency for topologically sorted non absent packages since it will help maintain cache of // already processed packages var circularReferences = ResolverUtility.FindFirstCircularDependency(sortedSolution); if (circularReferences.Any()) { // the resolver is able to handle circular dependencies, however we should throw here to keep these from happening throw new NuGetResolverConstraintException( string.Format(CultureInfo.CurrentCulture, "Circular dependency detected '{0}'.", string.Join(" => ", circularReferences.Select(package => $"{package.Id} {package.Version.ToNormalizedString()}")))); } // solution found! stopWatch.Stop(); context.Log.LogMinimal( string.Format("Resolving dependency information took {0}", DatetimeUtility.ToReadableTimeFormat(stopWatch.Elapsed))); return(sortedSolution.Where(x => !context.IgnoredIds.Contains(x.Id)).ToArray()); } } // no solution was found, throw an error with a diagnostic message var message = ResolverUtility.GetDiagnosticMessage(bestSolution, context.AvailablePackages, context.PackagesConfig, context.TargetIds, context.PackageSources); throw new NuGetResolverConstraintException(message); }
public async Task <InstallerResult> InstallAsync( PackageIdentity package, IExtensibleProject project, IReadOnlyList <SourceRepository> repositories, bool ignoreMissingPackages = false, CancellationToken cancellationToken = default) { try { // Step 1. Decide what framework version used on package resolving // Enforce platform-specific framework for .NET 5.0 var targetFramework = FrameworkParser.TryParseFrameworkName(project.Framework, _frameworkNameProvider); var reducer = new FrameworkReducer(); #if NET5_0_OR_GREATER var mostSpecific = reducer.ReduceUpwards(project.SupportedPlatforms).FirstOrDefault(); targetFramework = mostSpecific; #endif _nugetLogger.LogInformation($"Installing package {package}, Target framework: {targetFramework}"); // Prepare to step 2. Add globals if cache enabled as available repository with highest priority. // Note: This part falls under responsibility of RepositoryContextService but the same logic used to determine what packages are found by IPackageLoaderService // To not break behavior for now add here if (!project.NoCache) { var repositoryList = repositories.ToList(); repositoryList.Insert(0, new SourceRepository(new PackageSource(DefaultNuGetFolders.GetGlobalPackagesFolder(), ".nuget"), Repository.Provider.GetCoreV3())); repositories = repositoryList; } // Step 2. Build list of dependencies and determine DependencyBehavior if some packages are misssed in current feed Resolver.PackageResolverContext resolverContext = null; using (var cacheContext = new SourceCacheContext()) { #pragma warning disable IDISP013 // Await in using. var getDependencyResourcesTasks = repositories.Select(repo => repo.GetResourceAsync <DependencyInfoResource>()); #pragma warning restore IDISP013 // Await in using. var dependencyResources = (await getDependencyResourcesTasks.WhenAllOrExceptionAsync()).Where(x => x.IsSuccess && x.Result is not null) .Select(x => x.Result).ToArray(); var dependencyInfoResources = new DependencyInfoResourceCollection(dependencyResources); resolverContext = await ResolveDependenciesAsync(package, targetFramework, PackageIdentityComparer.Default, dependencyInfoResources, cacheContext, project, ignoreMissingPackages, cancellationToken); if (resolverContext is null || !(resolverContext?.AvailablePackages?.Any() ?? false)) { var errorMessage = $"Package {package} cannot be resolved with current settings (TFM: {targetFramework}) for chosen destination"; _nugetLogger.LogWarning(errorMessage); return(new InstallerResult(errorMessage)); } // Step 3. Try to check is main package can be downloaded from resource var mainPackageInfo = resolverContext.AvailablePackages.FirstOrDefault(p => p.Id == package.Id); _nugetLogger.LogInformation($"Downloading {package}..."); var mainDownloadedFiles = await DownloadPackageResourceAsync(mainPackageInfo, cacheContext, cancellationToken); _nugetLogger.LogInformation($"{package} download completed"); if (!mainDownloadedFiles.IsAvailable()) { // Downlod failed by some reasons (probably connection issue or package goes deleted before feed updated) var errorMessage = $"Current source lists package {package} but attempts to download it have failed. The source in invalid or required packages were removed while the current operation was in progress"; _nugetLogger.LogError(errorMessage); return(new InstallerResult(errorMessage)); } // Step 4. Check is main package compatible with target Framework var canBeInstalled = await CheckCanBeInstalledAsync(project, mainDownloadedFiles.PackageReader, targetFramework, cancellationToken); if (!canBeInstalled) { throw new IncompatiblePackageException($"Package {package} incompatible with project target platform {targetFramework}"); } // Step 5. Build install list using NuGet Resolver and select available resources. // Track packages which already installed and make sure only one version of package exists var resolver = new Resolver.PackageResolver(); var availablePackagesToInstall = await resolver.ResolveWithVersionOverrideAsync(resolverContext, project, DependencyBehavior.Highest, (project, conflict) => _fileSystemService.CreateDeleteme(conflict.PackageIdentity.Id, project.GetInstallPath(conflict.PackageIdentity)), cancellationToken); // Step 6. Download everything except main package and extract all availablePackagesToInstall.Remove(mainPackageInfo); _nugetLogger.LogInformation($"Downloading package dependencies..."); var downloadResults = await DownloadPackagesResourcesAsync(availablePackagesToInstall, cacheContext, cancellationToken); downloadResults[mainPackageInfo] = mainDownloadedFiles; _nugetLogger.LogInformation($"{downloadResults.Count - 1} dependencies downloaded"); var extractionContext = GetExtractionContext(); await ExtractPackagesResourcesAsync(downloadResults, project, extractionContext, cancellationToken); await CheckLibAndFrameworkItemsAsync(downloadResults, targetFramework, cancellationToken); return(new InstallerResult(downloadResults)); } } catch (NuGetResolverInputException ex) { throw new IncompatiblePackageException($"Package {package} or some of it dependencies are missed for current target framework", ex); } catch (Exception ex) { Log.Error(ex); throw; } }
public async Task <List <SourcePackageDependencyInfo> > ResolveWithVersionOverrideAsync(PackageResolverContext context, IExtensibleProject project, DependencyBehavior dependencyBehavior, Action <IExtensibleProject, PackageReference> conflictResolveAction, CancellationToken cancellationToken) { var availablePackages = Resolve(context, cancellationToken); // note: probably this is not required var installablePackages = availablePackages .Select( x => context.AvailablePackages .Single(p => PackageIdentityComparer.Default.Equals(p, x))).ToList(); var incomingPackages = installablePackages.ToList(); foreach (var package in incomingPackages) { var packageConflicts = context.PackagesConfig .Where(reference => string.Equals(reference.PackageIdentity.Id, package.Id) && reference.PackageIdentity.Version.CompareTo(package.Version, VersionComparison.VersionReleaseMetadata) != 0).ToList(); // Note: workaround to make only one version of package appears in the same time. The correct package version set in packages.config // while local files handled by extensibility foreach (var conflict in packageConflicts) { bool needToFix = false; var conflictedVersion = conflict.PackageIdentity.Version; switch (dependencyBehavior) { case DependencyBehavior.HighestMinor: case DependencyBehavior.HighestPatch: case DependencyBehavior.Highest: needToFix = conflictedVersion.CompareTo(package.Version, VersionComparison.VersionReleaseMetadata) < 0; break; case DependencyBehavior.Lowest: needToFix = conflictedVersion.CompareTo(package.Version, VersionComparison.VersionReleaseMetadata) > 0; break; case DependencyBehavior.Ignore: continue; } if (needToFix) { conflictResolveAction(project, conflict); } else { // cancel package installation installablePackages.Remove(package); } } } return(installablePackages); }