/// <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);
        }
Пример #3
0
        /// <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);
        }