/// <summary> /// Packages occuring first are more likely to get their preferred version, for this /// reason installed packages should go first, then targets. /// </summary> private static int GetTreeFlattenPriority(string id, PackageResolverContext context) { // Targets go in the middle // this needs to be checked first since the target may also exist in the installed packages (upgrade) if (context.TargetIds.Contains(id, StringComparer.OrdinalIgnoreCase)) { return(1); } // Installed packages go first if (context.PackagesConfig.Select(package => package.PackageIdentity.Id).Contains(id, StringComparer.OrdinalIgnoreCase)) { return(0); } // New dependencies go last return(2); }
/// <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); }
/// <summary> /// Resolve a package closure /// </summary> public IEnumerable<PackageIdentity> Resolve(PackageResolverContext context, CancellationToken token) { 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 solution = CombinationSolver<ResolverPackage>.FindSolution( groupedItems: grouped, 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()) { var circularReferences = ResolverUtility.FindCircularDependency(solution); 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.ToString()}")))); } // solution found! var sortedSolution = ResolverUtility.TopologicalSort(nonAbsentCandidates); 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); throw new NuGetResolverConstraintException(message); }
/// <summary> /// Order package trees into a flattened list /// /// Package Id (Parent count) /// Iteration 1: A(0) -> B(1) -> D(2) /// C(0) -> D(2) /// [Select A] /// /// Iteration 2: B(0) -> D(2) /// C(0) -> D(2) /// [Select B] /// /// Iteration 2: C(0) -> D(1) /// [Select C] /// /// Result: A, B, C, D /// </summary> public static List <List <ResolverPackage> > TreeFlatten(List <List <ResolverPackage> > grouped, PackageResolverContext context) { var sorted = new List <List <ResolverPackage> >(); // find all package ids var groupIds = grouped.Select(group => group.First().Id).ToList(); // find all dependencies for each id var dependencies = grouped.Select(group => new SortedSet <string>( group.SelectMany(g => g.Dependencies) .Select(d => d.Id), StringComparer.OrdinalIgnoreCase)) .ToList(); // track all parents of an id var parents = new Dictionary <string, SortedSet <string> >(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < grouped.Count; i++) { var parentsForId = new SortedSet <string>(StringComparer.OrdinalIgnoreCase); for (int j = 0; j < grouped.Count; j++) { if (i != j && dependencies[j].Contains(groupIds[i])) { parentsForId.Add(groupIds[j]); } } parents.Add(groupIds[i], parentsForId); } var idsToSort = new List <string>(groupIds); var childrenOfLastId = new SortedSet <string>(StringComparer.OrdinalIgnoreCase); // Loop through the package ids taking the best one each time // and removing it from the parent list. while (idsToSort.Count > 0) { // 1. Lowest number of parents remaining goes // 2. Prefer children of the last id sorted next // 3. Installed, target, then new package // 4. Highest number of dependencies goes first // 5. Fallback to string sort var nextId = idsToSort.OrderBy(id => parents[id].Count) .ThenBy(id => childrenOfLastId.Contains(id) ? 0 : 1) .ThenBy(id => GetTreeFlattenPriority(id, context)) .ThenByDescending(id => parents.Values.Where(parentIds => parentIds.Contains(id)).Count()) .ThenBy(id => id, StringComparer.OrdinalIgnoreCase) .First(); // Find the group for the best id var nextGroup = grouped.Where(group => StringComparer.OrdinalIgnoreCase.Equals(group.First().Id, nextId)).Single(); sorted.Add(nextGroup); childrenOfLastId.Clear(); // Remove the id from the parent list now that we have found a place for it foreach (var childId in parents.Keys) { var parentIds = parents[childId]; if (parentIds.Remove(nextId)) { childrenOfLastId.Add(childId); } } // Complete the id grouped.Remove(nextGroup); idsToSort.Remove(nextId); } return(sorted); }