/// <summary> /// Entry point for the combination evalutation phase of the algorithm. The algorithm /// combines forward checking [FC] (i.e. trying to eliminate future possible combinations to evaluate) /// with Conflict-directed Back Jumping. /// Based off the FC-CBJ algorithm described in Prosser's Hybrid /// Algorithms for the Constraint Satisfaction Problem: /// http://archive.nyu.edu/bitstream/2451/14410/1/IS-90-10.pdf /// </summary> /// <param name="groupedItems">The candidate enlistment items grouped by product.</param> /// <param name="itemSorter"> /// Function supplied by the caller to sort items in preferred/priority order. 'Higher /// priority' items should come *first* in the sort. /// </param> /// <param name="shouldRejectPairFunc"> /// Function supplied by the caller to determine whether two items are /// compatible or not. /// </param> /// <param name="diagnosticOutput"> /// Used to provide partial solutions to be used for diagnostic messages. /// </param> /// <returns>The 'best' solution (if one exists). Null otherwise.</returns> public static IEnumerable <T> FindSolution(IEnumerable <IEnumerable <T> > groupedItems, IComparer <T> itemSorter, Func <T, T, bool> shouldRejectPairFunc, Action <IEnumerable <T> > diagnosticOutput) { var solver = new CombinationSolver <T>(groupedItems, itemSorter, shouldRejectPairFunc); return(solver.FindSolution(diagnosticOutput)); }
/// <summary> /// Resolve a package closure /// </summary> public IEnumerable <PackageIdentity> Resolve(PackageResolverContext context, CancellationToken token) { var stopWatch = new Stopwatch(); token.ThrowIfCancellationRequested(); if (context == 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, Strings.MissingDependencyInfo, requiredId)); } } // 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 var dependencyIds = resolverPackages.Where(e => e.Dependencies != null) .SelectMany(e => e.Dependencies.Select(d => d.Id).Distinct(StringComparer.OrdinalIgnoreCase)); 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 : ResolverUtility.ShouldRejectPackagePair, diagnosticOutput : diagnosticOutput); // check if a solution was found if (solution != 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, Strings.CircularDependencyDetected, String.Join(" => ", circularReferences.Select(package => $"{package.Id} {package.Version.ToNormalizedString()}")))); } // solution found! stopWatch.Stop(); context.Log.LogMinimal( string.Format(Strings.ResolverTotalTime, DatetimeUtility.ToReadableTimeFormat(stopWatch.Elapsed))); return(sortedSolution.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 IEnumerable <PackageIdentity> Resolve(IEnumerable <PackageIdentity> targets, IEnumerable <PackageDependencyInfo> availablePackages, IEnumerable <PackageReference> installedPackages, CancellationToken token) { if (installedPackages != null) { _installedPackages = new HashSet <PackageIdentity>(installedPackages.Select(e => e.PackageIdentity), PackageIdentity.Comparer); } // find the list of new packages to add _newPackageIds = new HashSet <string>(targets.Select(e => e.Id).Except(_installedPackages.Select(e => e.Id), StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase); // validation foreach (var target in targets) { if (!availablePackages.Any(p => StringComparer.OrdinalIgnoreCase.Equals(p.Id, target.Id))) { throw new NuGetResolverInputException(String.Format(CultureInfo.CurrentUICulture, Strings.MissingDependencyInfo, target.Id)); } } // validation foreach (var installed in _installedPackages) { if (!availablePackages.Any(p => StringComparer.OrdinalIgnoreCase.Equals(p.Id, installed.Id))) { throw new NuGetResolverInputException(String.Format(CultureInfo.CurrentUICulture, Strings.MissingDependencyInfo, installed.Id)); } } // TODO: this will be removed later when the interface changes foreach (var installed in _installedPackages) { if (!targets.Any(p => StringComparer.OrdinalIgnoreCase.Equals(p.Id, installed.Id))) { throw new NuGetResolverInputException("Installed packages should be passed as targets"); } } // Solve var solver = new CombinationSolver <ResolverPackage>(); var comparer = new ResolverComparer(_dependencyBehavior, _installedPackages, _newPackageIds); List <List <ResolverPackage> > grouped = new List <List <ResolverPackage> >(); var packageComparer = PackageIdentity.Comparer; List <ResolverPackage> resolverPackages = new List <ResolverPackage>(); // convert the available packages into ResolverPackages foreach (var package in availablePackages) { IEnumerable <PackageDependency> dependencies = null; // clear out the dependencies if the behavior is set to ignore if (_dependencyBehavior == DependencyBehavior.Ignore) { dependencies = Enumerable.Empty <PackageDependency>(); } else { dependencies = package.Dependencies; } resolverPackages.Add(new ResolverPackage(package.Id, package.Version, dependencies)); } // Sort the packages to make this process as deterministic as possible resolverPackages.Sort(PackageIdentityComparer.Default); // Keep track of the ids we have added HashSet <string> groupsAdded = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // group the packages by id foreach (var group in resolverPackages.GroupBy(e => e.Id, StringComparer.OrdinalIgnoreCase)) { groupsAdded.Add(group.Key); List <ResolverPackage> 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 (!targets.Any(e => StringComparer.OrdinalIgnoreCase.Equals(e.Id, group.Key))) { curSet.Add(new ResolverPackage(group.Key, null, null, true)); } grouped.Add(curSet); } // find all needed dependencies var dependencyIds = resolverPackages.Where(e => e.Dependencies != null).SelectMany(e => e.Dependencies.Select(d => d.Id).Distinct(StringComparer.OrdinalIgnoreCase)); 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)) { grouped.Add(new List <ResolverPackage>() { new ResolverPackage(depId, null, null, true) }); } } var solution = solver.FindSolution(grouped, comparer, ShouldRejectPackagePair); if (solution != null) { var nonAbsentCandidates = solution.Where(c => !c.Absent); if (nonAbsentCandidates.Any()) { var sortedSolution = TopologicalSort(nonAbsentCandidates); return(sortedSolution.ToArray()); } } // no solution found throw new NuGetResolverConstraintException(Strings.NoSolution); }