private static bool TryResolveConflicts <TItem>(this GraphNode <TItem> root, List <VersionConflictResult <TItem> > versionConflicts) { // 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 acceptedLibraries = Cache <TItem> .RentDictionary(); var patience = 1000; var incomplete = true; var tracker = Cache <TItem> .RentTracker(); Func <GraphNode <TItem>, bool> skipNode = null; var centralTransitiveNodes = root.InnerNodes.Where(n => n.Item.IsCentralTransitive).ToList(); var hasCentralTransitiveDependencies = centralTransitiveNodes.Count > 0; if (hasCentralTransitiveDependencies) { skipNode = (node) => { return(node.Item.IsCentralTransitive); }; } while (incomplete && --patience != 0) { // Create a picture of what has not been rejected yet root.ForEach(true, (node, state, context) => WalkTreeRejectNodesOfRejectedNodes(state, node, context), tracker, skipNode); if (hasCentralTransitiveDependencies) { // Some of the central transitive nodes may be rejected now because their parents were rejected // Reject them accordingly root.RejectCentralTransitiveBecauseOfRejectedParents(tracker, centralTransitiveNodes); } // Inform tracker of ambiguity beneath nodes that are not resolved yet root.ForEach(WalkState.Walking, (node, state, context) => WalkTreeMarkAmbiguousNodes(node, state, context), tracker); if (hasCentralTransitiveDependencies) { DetectAndMarkAmbiguousCentralTransitiveDependencies(tracker, centralTransitiveNodes); } root.ForEach(true, (node, state, context) => WalkTreeAcceptOrRejectNodes(context, state, node), CreateState(tracker, acceptedLibraries)); incomplete = root.ForEachGlobalState(false, (node, state) => state || node.Disposition == Disposition.Acceptable); tracker.Clear(); } Cache <TItem> .ReleaseTracker(tracker); root.ForEach((node, context) => WalkTreeDectectConflicts(node, context), CreateState(versionConflicts, acceptedLibraries)); Cache <TItem> .ReleaseDictionary(acceptedLibraries); return(!incomplete); }
private static void CheckCycleAndNearestWins( this GraphNode <RemoteResolveResult> root, List <DowngradeResult <RemoteResolveResult> > downgrades, List <GraphNode <RemoteResolveResult> > cycles) { var workingDowngrades = RentDowngradesDictionary(); root.ForEach((node, context) => WalkTreeCheckCycleAndNearestWins(context, node), CreateState(cycles, workingDowngrades)); #if IS_DESKTOP || NETSTANDARD2_0 // Increase List size for items to be added, if too small var requiredCapacity = downgrades.Count + workingDowngrades.Count; if (downgrades.Capacity < requiredCapacity) { downgrades.Capacity = requiredCapacity; } #endif foreach (var p in workingDowngrades) { downgrades.Add(new DowngradeResult <RemoteResolveResult> { DowngradedFrom = p.Key, DowngradedTo = p.Value }); } ReleaseDowngradesDictionary(workingDowngrades); }
private static bool TryResolveConflicts <TItem>(this GraphNode <TItem> root, List <VersionConflictResult <TItem> > versionConflicts) { // 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 acceptedLibraries = Cache <TItem> .RentDictionary(); var patience = 1000; var incomplete = true; var tracker = Cache <TItem> .RentTracker(); while (incomplete && --patience != 0) { // Create a picture of what has not been rejected yet root.ForEach(true, (node, state, context) => WalkTreeRejectNodesOfRejectedNodes(state, node, context), tracker); // Inform tracker of ambiguity beneath nodes that are not resolved yet root.ForEach(WalkState.Walking, (node, state, context) => WalkTreeMarkAmbiguousNodes(node, state, context), tracker); // Now mark unambiguous nodes as accepted or rejected root.ForEach(true, (node, state, context) => WalkTreeAcceptOrRejectNodes(context, state, node), CreateState(tracker, acceptedLibraries)); incomplete = root.ForEachGlobalState(false, (node, state) => state || node.Disposition == Disposition.Acceptable); tracker.Clear(); } Cache <TItem> .ReleaseTracker(tracker); root.ForEach((node, context) => WalkTreeDectectConflicts(node, context), CreateState(versionConflicts, acceptedLibraries)); Cache <TItem> .ReleaseDictionary(acceptedLibraries); return(!incomplete); }
public GraphNode <Library> Walk(string name, NuGetVersion version, NuGetFramework framework) { var key = new LibraryRange { Name = name, VersionRange = new VersionRange(version) }; var root = new GraphNode <Library>(key); var resolvedItems = new Dictionary <LibraryRange, GraphItem <Library> >(); // Recurse through dependencies optimistically, asking resolvers for dependencies // based on best match of each encountered dependency root.ForEach(node => { node.Item = Resolve(resolvedItems, node.Key, framework); if (node.Item == null) { node.Disposition = Disposition.Rejected; return; } foreach (var dependency in node.Item.Data.Dependencies) { // determine if a child dependency is eclipsed by // a reference on the line leading to this point. this // prevents cyclical dependencies, and also implements the // "nearest wins" rule. var eclipsed = false; for (var scanNode = node; scanNode != null && !eclipsed; scanNode = scanNode.OuterNode) { eclipsed |= scanNode.Key.IsEclipsedBy(dependency.LibraryRange); if (eclipsed) { throw new InvalidOperationException(string.Format("Circular dependency detected {0}.", GetChain(node, dependency))); } foreach (var sideNode in scanNode.InnerNodes) { eclipsed |= sideNode.Key.IsEclipsedBy(dependency.LibraryRange); if (eclipsed) { break; } } } if (!eclipsed) { var innerNode = new GraphNode <Library>(dependency.LibraryRange) { OuterNode = node }; node.InnerNodes.Add(innerNode); } } }); return(root); }
private static void CheckCycleAndNearestWins <TItem>(this GraphNode <TItem> root, List <DowngradeResult <TItem> > downgrades, List <GraphNode <TItem> > cycles) { // Cycle // A -> B -> A (cycle) // Downgrade // A -> B -> C -> D 2.0 (downgrage) // -> D 1.0 // Potential downgrade that turns out to not downgrade // This should never happen in practice since B would have never been valid to begin with. // A -> B -> C -> D 2.0 // -> D 1.0 // -> D 2.0 var workingDowngrades = new Dictionary <GraphNode <TItem>, GraphNode <TItem> >(); root.ForEach(node => { if (node.Disposition == Disposition.Cycle) { cycles.Add(node); // Remove this node from the tree so the nothing else evaluates this. // This is ok since we have a parent pointer and we can still print the path node.OuterNode.InnerNodes.Remove(node); return; } if (node.Disposition != Disposition.PotentiallyDowngraded) { return; } // REVIEW: This could probably be done in a single pass where we keep track // of what is nearer as we walk down the graph (BFS) for (var n = node.OuterNode; n != null; n = n.OuterNode) { foreach (var sideNode in n.InnerNodes) { if (sideNode != node && StringComparer.OrdinalIgnoreCase.Equals(sideNode.Key.Name, node.Key.Name)) { // Nodes that have no version range should be ignored as potential downgrades e.g. framework reference if (sideNode.Key.VersionRange != null && node.Key.VersionRange != null && !RemoteDependencyWalker.IsGreaterThanOrEqualTo(sideNode.Key.VersionRange, node.Key.VersionRange)) { workingDowngrades[node] = sideNode; } else { workingDowngrades.Remove(node); } } } } // Remove this node from the tree so the nothing else evaluates this. // This is ok since we have a parent pointer and we can still print the path node.OuterNode.InnerNodes.Remove(node); }); downgrades.AddRange(workingDowngrades.Select(p => new DowngradeResult <TItem> { DowngradedFrom = p.Key, DowngradedTo = p.Value })); }
private static bool TryResolveConflicts <TItem>(this GraphNode <TItem> root, List <VersionConflictResult <TItem> > versionConflicts) { // 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 acceptedLibraries = new Dictionary <string, GraphNode <TItem> >(StringComparer.OrdinalIgnoreCase); 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(WalkState.Walking, (node, state) => { if (node.Disposition == Disposition.Rejected) { return(WalkState.Rejected); } if (state == WalkState.Walking && tracker.IsDisputed(node.Item)) { return(WalkState.Ambiguous); } if (state == WalkState.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) { if (tracker.IsBestVersion(node.Item)) { node.Disposition = Disposition.Accepted; acceptedLibraries[node.Key.Name] = node; } else { node.Disposition = Disposition.Rejected; } } return(node.Disposition == Disposition.Accepted); }); incomplete = false; root.ForEach(node => incomplete |= node.Disposition == Disposition.Acceptable); } root.ForEach(node => { if (node.Disposition != Disposition.Accepted) { return; } // For all accepted nodes, find dependencies that aren't satisfied by the version // of the package that we have selected foreach (var childNode in node.InnerNodes) { GraphNode <TItem> acceptedNode; if (acceptedLibraries.TryGetValue(childNode.Key.Name, out acceptedNode) && childNode != acceptedNode && childNode.Key.VersionRange != null && acceptedNode.Item.Key.Version != null) { var acceptedType = LibraryDependencyTargetUtils.Parse(acceptedNode.Item.Key.Type); var childType = childNode.Key.TypeConstraint; // Check the type constraints, if there is any overlap check for conflict if ((childType & acceptedType) != LibraryDependencyTarget.None) { var versionRange = childNode.Key.VersionRange; var checkVersion = acceptedNode.Item.Key.Version; if (!versionRange.Satisfies(checkVersion)) { versionConflicts.Add(new VersionConflictResult <TItem> { Selected = acceptedNode, Conflicting = childNode }); } } } } }); return(!incomplete); }
private static void CheckCycleAndNearestWins( this GraphNode <RemoteResolveResult> root, List <DowngradeResult <RemoteResolveResult> > downgrades, List <GraphNode <RemoteResolveResult> > cycles) { // Cycle: // // A -> B -> A (cycle) // // Downgrade: // // A -> B -> C -> D 2.0 (downgrade) // -> D 1.0 // // Potential downgrades that turns out to not be downgrades: // // 1. This should never happen in practice since B would have never been valid to begin with. // // A -> B -> C -> D 2.0 // -> D 1.0 // -> D 2.0 // // 2. This occurs if none of the sources have version C 1.0 so C 1.0 is bumped up to C 2.0. // // A -> B -> C 2.0 // -> C 1.0 var workingDowngrades = new Dictionary <GraphNode <RemoteResolveResult>, GraphNode <RemoteResolveResult> >(); root.ForEach(node => { if (node.Disposition == Disposition.Cycle) { cycles.Add(node); // Remove this node from the tree so the nothing else evaluates this. // This is ok since we have a parent pointer and we can still print the path node.OuterNode.InnerNodes.Remove(node); return; } if (node.Disposition != Disposition.PotentiallyDowngraded) { return; } // REVIEW: This could probably be done in a single pass where we keep track // of what is nearer as we walk down the graph (BFS) for (var n = node.OuterNode; n != null; n = n.OuterNode) { foreach (var sideNode in n.InnerNodes) { if (sideNode != node && StringComparer.OrdinalIgnoreCase.Equals(sideNode.Key.Name, node.Key.Name)) { // Nodes that have no version range should be ignored as potential downgrades e.g. framework reference if (sideNode.Key.VersionRange != null && node.Key.VersionRange != null && !RemoteDependencyWalker.IsGreaterThanOrEqualTo(sideNode.Key.VersionRange, node.Key.VersionRange)) { // Is the resolved version actually within node's version range? This happen if there // was a different request for a lower version of the library than this version range // allows but no matching library was found, so the library is bumped up into this // version range. var resolvedVersion = sideNode?.Item?.Data?.Match?.Library?.Version; if (resolvedVersion != null && node.Key.VersionRange.Satisfies(resolvedVersion)) { continue; } workingDowngrades[node] = sideNode; } else { workingDowngrades.Remove(node); } } } } // Remove this node from the tree so the nothing else evaluates this. // This is ok since we have a parent pointer and we can still print the path node.OuterNode.InnerNodes.Remove(node); }); downgrades.AddRange(workingDowngrades.Select(p => new DowngradeResult <RemoteResolveResult> { DowngradedFrom = p.Key, DowngradedTo = p.Value })); }
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(WalkState.Walking, (node, state) => { if (node.Disposition == Disposition.Rejected) { return(WalkState.Rejected); } if (state == WalkState.Walking && tracker.IsDisputed(node.Item)) { return(WalkState.Ambiguous); } if (state == WalkState.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); }
public GraphNode <ResolveResult> Walk(string name, NuGetVersion version, NuGetFramework framework) { var key = new LibraryRange { Name = name, VersionRange = new NuGetVersionRange(version) }; var root = new GraphNode <ResolveResult>(key); var resolvedItems = new Dictionary <LibraryRange, GraphItem <ResolveResult> >(); // Recurse through dependencies optimistically, asking resolvers for dependencies // based on best match of each encountered dependency root.ForEach(node => { node.Item = Resolve(resolvedItems, node.Key, framework); if (node.Item == null) { node.Disposition = Disposition.Rejected; return; } foreach (var dependency in node.Item.Data.LibraryDescription.Dependencies) { // determine if a child dependency is eclipsed by // a reference on the line leading to this point. this // prevents cyclical dependencies, and also implements the // "nearest wins" rule. var eclipsed = false; for (var scanNode = node; scanNode != null && !eclipsed; scanNode = scanNode.OuterNode) { eclipsed |= string.Equals( scanNode.Key.Name, dependency.Name, StringComparison.OrdinalIgnoreCase); if (eclipsed) { break; } foreach (var sideNode in scanNode.InnerNodes) { eclipsed |= string.Equals( sideNode.Key.Name, dependency.Name, StringComparison.OrdinalIgnoreCase); if (eclipsed) { break; } } } if (!eclipsed) { var innerNode = new GraphNode <ResolveResult>(dependency.LibraryRange) { OuterNode = node }; node.InnerNodes.Add(innerNode); } } }); return(root); }