blob: f8509507a29f798b5ec2aec3d149aef00c83dbc8 [file] [log] [blame]
////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
////
Here are some explanations about the conflict management algorithm in Ivy.
First, one should have a good understanding on how Ivy resolves dependencies, and especially
transitive dependencies.
During the resolve process, Ivy visits each module of the dependency graph. +
Let's call each module a *node*, including the module we are trying to resolve dependencies for.
Each node should be able to give a conflict manager for a particular ModuleId. +
Let's name it *node.cm(mid)*.
Each node should be able to maintain a map from ModuleId to a resolved Collection of nodes.
This resolved collection will never contain any evicted node FOR the concerned node as far
as Ivy knows, depending on where it is in graph visit. +
Let's call this map resolved, and the corresponding resolved collection *node.resolved(mid)*.
During the visit, Ivy should always know from which node it came to visit another node. Let's call
the first node from which Ivy came *node.parent*. Note that this concept is slightly different from
node parent, since a node can have several parents in the graph, but there is also one *node.parent*
during the visit.
Let's say that a conflict manager is able to filter a collection of nodes to return only those
that are not evicted. Let's call that *cm.resolveConflicts(collection)*.
Let's call *node.dependencies* the collection of direct dependencies of a node.
Let's call *node.revision* the module revision id of a node.
And now for the algo. This algo attempts to evict nodes on the fly, i.e. during the Ivy visit,
to minimize the number of resolved modules, and thus the number of Ivy files to download.
It is presented in a very simplified description language, far away from the whole real complexity,
but giving a good understanding of how it works. In particular, it completely hides some complexity due
to configurations management.
[source,java,subs="verbatim,quotes"]
----
resolve(node) {
node.resolved(node.mid) = _collection_(node);
resolveConflict(node, node.parent, empty);
if (!node.evicted && !node.alreadyResolved) {
node.loadData();
resolveConflict(node, node.parent, empty);
if (!node.evicted) {
// actually do resolve
foreach (dep in node.dependencies) {
resolve(dep);
}
}
}
}
resolveConflict(node, parent, toevict) {
if (node.revision.exact && parent.resolved(node.mid).revision.contains(node.revision)) {
// exact revision already in resolved
// => job already done
return;
}
if (parent.resolved(node.mid).containsAny(toevict)) {
// parent.resolved(node.mid) is not up to date:
// recompute resolved from all sub nodes
resolved = parent.cm(node.mid).resolveConflicts(parent.dependencies.resolved(node.mid));
} else {
resolved = parent.cm(node.mid).resolveConflicts(_collection_(node, parent.resolved(node.mid)));
}
if (resolved.contains(node)) {
// node has been selected for the current parent
// we update its eviction... but it can still be evicted by parent !
node.evicted = false;
// handle previously selected nodes that are now evicted by this new node
toevict = parent.resolved(node.mid) - resolved;
foreach (te in toevict) {
te.evicted = true;
}
// it's very important to update resolved BEFORE recompute parent call
// to allow it to recompute its resolved collection with correct data
// if necessary
parent.resolved(node.mid) = resolved;
if (parent.parent != null) {
resolveConflict(node, parent.parent, toevict);
}
} else {
// node has been evicted for the current parent
// it's time to update parent resolved with found resolved...
// if they have not been recomputed, it does not change anything
parent.resolved(node.mid) = resolved;
node.evicted = true;
}
}
----