private Func <LibraryRange, DependencyResult> ChainPredicate(Func <LibraryRange, DependencyResult> predicate, GraphNode <RemoteResolveResult> node, LibraryDependency dependency) { var item = node.Item; return(library => { if (StringComparer.OrdinalIgnoreCase.Equals(item.Data.Match.Library.Name, library.Name)) { return DependencyResult.Cycle; } foreach (var d in item.Data.Dependencies) { if (d != dependency && library.IsEclipsedBy(d.LibraryRange)) { if (d.LibraryRange.VersionRange != null && library.VersionRange != null && !IsGreaterThanOrEqualTo(d.LibraryRange.VersionRange, library.VersionRange)) { return DependencyResult.PotentiallyDowngraded; } return DependencyResult.Eclipsed; } } return predicate(library); }); }
private async Task <GraphNode <RemoteResolveResult> > CreateGraphNode( LibraryRange libraryRange, NuGetFramework framework, string runtimeName, RuntimeGraph runtimeGraph, Func <LibraryRange, DependencyResult> predicate, GraphEdge <RemoteResolveResult> outerEdge) { var dependencies = new List <LibraryDependency>(); var runtimeDependencies = new HashSet <string>(); if (!string.IsNullOrEmpty(runtimeName) && runtimeGraph != null) { // HACK(davidfowl): This is making runtime.json support package redirects // Look up any additional dependencies for this package foreach (var runtimeDependency in runtimeGraph.FindRuntimeDependencies(runtimeName, libraryRange.Name)) { var libraryDependency = new LibraryDependency { LibraryRange = new LibraryRange() { Name = runtimeDependency.Id, VersionRange = runtimeDependency.VersionRange, TypeConstraint = LibraryDependencyTarget.PackageProjectExternal } }; if (StringComparer.OrdinalIgnoreCase.Equals(runtimeDependency.Id, libraryRange.Name)) { if (libraryRange.VersionRange != null && runtimeDependency.VersionRange != null && libraryRange.VersionRange.MinVersion < runtimeDependency.VersionRange.MinVersion) { libraryRange = libraryDependency.LibraryRange; } } else { // Otherwise it's a dependency of this node dependencies.Add(libraryDependency); runtimeDependencies.Add(libraryDependency.Name); } } } var node = new GraphNode <RemoteResolveResult>(libraryRange) { // Resolve the dependency from the cache or sources Item = await FindLibraryCached( _context.FindLibraryEntryCache, libraryRange, framework, outerEdge, CancellationToken.None) }; Debug.Assert(node.Item != null, "FindLibraryCached should return an unresolved item instead of null"); if (node.Key.VersionRange != null && node.Key.VersionRange.IsFloating) { var cacheKey = new LibraryRangeCacheKey(node.Key, framework); _context.FindLibraryEntryCache.TryAdd(cacheKey, Task.FromResult(node.Item)); } var tasks = new List <Task <GraphNode <RemoteResolveResult> > >(); if (dependencies.Count > 0) { // Create a new item on this node so that we can update it with the new dependencies from // runtime.json files // We need to clone the item since they can be shared across multiple nodes node.Item = new GraphItem <RemoteResolveResult>(node.Item.Key) { Data = new RemoteResolveResult() { Dependencies = dependencies.Concat(node.Item.Data.Dependencies.Where(d => !runtimeDependencies.Contains(d.Name))).ToList(), Match = node.Item.Data.Match } }; } foreach (var dependency in node.Item.Data.Dependencies) { // Skip dependencies if the dependency edge has 'all' excluded and // the node is not a direct dependency. if (outerEdge == null || dependency.SuppressParent != LibraryIncludeFlags.All) { var result = predicate(dependency.LibraryRange); if (result == DependencyResult.Acceptable) { // Dependency edge from the current node to the dependency var innerEdge = new GraphEdge <RemoteResolveResult>(outerEdge, node.Item, dependency); tasks.Add(CreateGraphNode( dependency.LibraryRange, framework, runtimeName, runtimeGraph, ChainPredicate(predicate, node, dependency), innerEdge)); } else { // Keep the node in the tree if we need to look at it later if (result == DependencyResult.PotentiallyDowngraded || result == DependencyResult.Cycle) { var dependencyNode = new GraphNode <RemoteResolveResult>(dependency.LibraryRange) { Disposition = result == DependencyResult.Cycle ? Disposition.Cycle : Disposition.PotentiallyDowngraded }; dependencyNode.OuterNode = node; node.InnerNodes.Add(dependencyNode); } } } } while (tasks.Any()) { // Wait for any node to finish resolving var task = await Task.WhenAny(tasks); // Extract the resolved node tasks.Remove(task); var dependencyNode = await task; dependencyNode.OuterNode = node; node.InnerNodes.Add(dependencyNode); } return(node); }
public static bool TryResolveConflicts <TItem>(this GraphNode <TItem> root) { // now we walk the tree as often as it takes to determine // which paths are accepted or rejected, based on conflicts occuring // between cousin packages var patience = 1000; var incomplete = true; while (incomplete && --patience != 0) { // Create a picture of what has not been rejected yet var tracker = new Tracker <TItem>(); root.ForEach(true, (node, state) => { if (!state || node.Disposition == Disposition.Rejected) { // Mark all nodes as rejected if they aren't already marked node.Disposition = Disposition.Rejected; return(false); } tracker.Track(node.Item); return(true); }); // Inform tracker of ambiguity beneath nodes that are not resolved yet // between: // a1->b1->d1->x1 // a1->c1->d2->z1 // first attempt // d1/d2 are considered disputed // x1 and z1 are considered ambiguous // d1 is rejected // second attempt // d1 is rejected, d2 is accepted // x1 is no longer seen, and z1 is not ambiguous // z1 is accepted root.ForEach("Walking", (node, state) => { if (node.Disposition == Disposition.Rejected) { return("Rejected"); } if (state == "Walking" && tracker.IsDisputed(node.Item)) { return("Ambiguous"); } if (state == "Ambiguous") { tracker.MarkAmbiguous(node.Item); } return(state); }); // Now mark unambiguous nodes as accepted or rejected root.ForEach(true, (node, state) => { if (!state || node.Disposition == Disposition.Rejected) { return(false); } if (tracker.IsAmbiguous(node.Item)) { return(false); } if (node.Disposition == Disposition.Acceptable) { node.Disposition = tracker.IsBestVersion(node.Item) ? Disposition.Accepted : Disposition.Rejected; } return(node.Disposition == Disposition.Accepted); }); incomplete = false; root.ForEach(node => incomplete |= node.Disposition == Disposition.Acceptable); } return(!incomplete); }