예제 #1
0
        /// <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, "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(package.Equals);

                if (existingPackage != null)
                {
                    // check if each dependency can be satisfied with existing packages
                    var brokenDependencies = GetBrokenDependencies(existingPackage, installedPackages);

                    if (brokenDependencies != 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 == 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
            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, "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.ToArray());
                }
            }

            var absentPackages = bestSolution.Where(x => x?.Absent == true).ToList();

            if (absentPackages.Any())
            {
                throw new NuGetResolverConstraintException($"There were {absentPackages.Count} absent packages found:\r\n{string.Join(", ", absentPackages.Select(x => x.Id))}");
            }


            // 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);
        }