org.apache.ivy.plugins.conflict.LatestCompatibleConflictManager Maven / Gradle / Ivy
/*
* 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
*
* http://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.
*
*/
package org.apache.ivy.plugins.conflict;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Stack;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.resolve.IvyNode;
import org.apache.ivy.core.resolve.IvyNodeBlacklist;
import org.apache.ivy.core.resolve.ResolveData;
import org.apache.ivy.core.resolve.RestartResolveProcess;
import org.apache.ivy.core.resolve.IvyNodeCallers.Caller;
import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.latest.LatestStrategy;
import org.apache.ivy.plugins.version.VersionMatcher;
import org.apache.ivy.util.Message;
/**
* This conflict manager can be used to allow only compatible dependencies to be used together (like
* the strict conflict manager), but it has the advantage of using a best effort algorithm to find a
* set of compatible dependencies, even if it requires stepping back to older revisions (as long as
* they are in the set of compatibility).
*
* Here is an example of what this conflict manager is able to do:
* Available Modules:
*
* #A;2->{ #B;[1.0,1.5] #C;[2.0,2.5] }
* #B;1.4->#D;1.5
* #B;1.5->#D;2.0
* #C;2.5->#D;[1.0,1.6]
*
*
* Result: #B;1.4, #C;2.5, #D;1.5
* DetailsThe conflict manager finds that the latest matching version
* of #B (1.5) depends on a version of #D incompatible with what is expected by the latest matching
* version of #C. Hence the conflict manager blacklists #B;1.5, and the version range [1.0,1.5] is
* resolved again to end up with #B;1.4 which depends on #D;1.5, which is fine to work with #C;2.5.
*
*/
public class LatestCompatibleConflictManager extends LatestConflictManager {
public LatestCompatibleConflictManager() {
}
public LatestCompatibleConflictManager(String name, LatestStrategy strategy) {
super(name, strategy);
}
public Collection resolveConflicts(IvyNode parent, Collection conflicts) {
if (conflicts.size() < 2) {
return conflicts;
}
VersionMatcher versionMatcher = getSettings().getVersionMatcher();
Iterator iter = conflicts.iterator();
IvyNode node = (IvyNode) iter.next();
ModuleRevisionId mrid = node.getResolvedId();
if (versionMatcher.isDynamic(mrid)) {
while (iter.hasNext()) {
IvyNode other = (IvyNode) iter.next();
if (versionMatcher.isDynamic(other.getResolvedId())) {
// two dynamic versions in conflict, not enough information yet
return null;
} else if (!versionMatcher.accept(mrid, other.getResolvedId())) {
// incompatibility found
if (!handleIncompatibleConflict(parent, conflicts, node, other)) {
return null;
}
}
}
// no incompatibility nor dynamic version found, let's return the latest static version
if (conflicts.size() == 2) {
// very common special case of only two modules in conflict,
// let's return the second one (static)
Iterator it = conflicts.iterator();
it.next();
return Collections.singleton(it.next());
}
Collection newConflicts = new LinkedHashSet(conflicts);
newConflicts.remove(node);
return super.resolveConflicts(parent, newConflicts);
} else {
// the first node is a static revision, let's see if all other versions match
while (iter.hasNext()) {
IvyNode other = (IvyNode) iter.next();
if (!versionMatcher.accept(other.getResolvedId(), mrid)) {
// incompatibility found
if (!handleIncompatibleConflict(parent, conflicts, node, other)) {
return null;
}
}
}
// no incompatibility found, let's return this static version
return Collections.singleton(node);
}
}
/**
* Handles an incompatible conflict
*
* An incompatible conflicts is handled with this pseudo algorithm:
*
*
* take latest among two nodes in conflict
* for all callers
* if dependency is a version constraint (dynamic)
* blacklist the mapped version
* else
* recurse for all callers
* if a version constraint has been found
* restart resolve
* else
* throw strict conflict exception
*
*
*
*
* @param parent
* the parent node of nodes in conflict
* @param conflicts
* all the nodes in conflict
* @param node
* one of the two incompatible nodes
* @param other
* the other incompatible node
* @return true if the incompatible conflict has been handled, false otherwise (in which case
* resolveConflicts should return null)
*/
private boolean handleIncompatibleConflict(
IvyNode parent, Collection conflicts, IvyNode node, IvyNode other) {
// we never actually return anything else than false or throw an exception,
// but returning a boolean make the calling code cleaner
try {
IvyNodeArtifactInfo latest = (IvyNodeArtifactInfo)
getStrategy().findLatest(
toArtifactInfo(Arrays.asList(new IvyNode[] {node, other})), null);
if (latest != null) {
IvyNode latestNode = latest.getNode();
IvyNode oldestNode = latestNode == node ? other : node;
blackListIncompatibleCallerAndRestartResolveIfPossible(
getSettings(), parent, oldestNode, latestNode);
// if we arrive here, we haven' managed to blacklist all paths to the latest
// node, we try with the oldest
blackListIncompatibleCallerAndRestartResolveIfPossible(
getSettings(), parent, latestNode, oldestNode);
// still not possible, we aren't able to find a solution to the incompatibility
handleUnsolvableConflict(parent, conflicts, node, other);
return true; // never actually reached
} else {
return false;
}
} catch (NoConflictResolvedYetException ex) {
// we have not enough informations in the nodes to resolve conflict
// according to the resolveConflicts contract, resolveConflicts must return null
return false;
}
}
private void blackListIncompatibleCallerAndRestartResolveIfPossible(IvySettings settings,
IvyNode parent, IvyNode selected, IvyNode evicted) {
Stack callerStack = new Stack();
callerStack.push(evicted);
final Collection toBlacklist = blackListIncompatibleCaller(
settings.getVersionMatcher(), parent, selected, evicted, callerStack);
if (toBlacklist != null) {
final StringBuffer blacklisted = new StringBuffer();
for (Iterator iterator = toBlacklist.iterator(); iterator.hasNext();) {
IvyNodeBlacklist blacklist = (IvyNodeBlacklist) iterator.next();
blacklist.getBlacklistedNode().blacklist(blacklist);
blacklisted.append(blacklist.getBlacklistedNode());
if (iterator.hasNext()) {
blacklisted.append(" ");
}
}
String rootModuleConf =
parent.getData().getReport().getConfiguration();
evicted.markEvicted(
new EvictionData(rootModuleConf, parent, this, Collections.singleton(selected),
"with blacklisting of " + blacklisted));
if (settings.debugConflictResolution()) {
Message.debug("evicting " + evicted + " by "
+ evicted.getEvictedData(rootModuleConf));
}
throw new RestartResolveProcess("trying to handle incompatibilities between "
+ selected + " and " + evicted);
}
}
/**
* Tries to blacklist exactly one version for all callers paths.
*
* @param versionMatcher
* the version matcher to use to interpret versions
* @param conflictParent
* the node in which the conflict is occurring
* @param selectedNode
* the node in favor of which the conflict is resolved
* @param evictedNode
* the node which will be evicted if we are able to blacklist all paths
* @param node
* the node for which callers should be considered
* @return the collection of blacklisting to do, null if a blacklist is not possible in at least
* one caller path
*/
private Collection/**/ blackListIncompatibleCaller(
VersionMatcher versionMatcher,
IvyNode conflictParent, IvyNode selectedNode, IvyNode evictedNode,
Stack/**/ callerStack) {
Collection/**/ blacklisted = new ArrayList/**/();
IvyNode node = (IvyNode) callerStack.peek();
String rootModuleConf = conflictParent.getData().getReport().getConfiguration();
Caller[] callers = node.getCallers(rootModuleConf);
for (int i = 0; i < callers.length; i++) {
IvyNode callerNode = node.findNode(callers[i].getModuleRevisionId());
if (callerNode.isBlacklisted(rootModuleConf)) {
continue;
}
if (versionMatcher.isDynamic(callers[i].getAskedDependencyId(node.getData()))) {
blacklisted.add(new IvyNodeBlacklist(
conflictParent, selectedNode, evictedNode, node, rootModuleConf));
} else {
if (callerStack.subList(0, callerStack.size() - 1).contains(node)) {
// circular dependency found and handled: the current top of the stack (node)
// was already contained in the rest of the stack, the circle is closed, nothing
// else to do
} else {
if (callerNode == null) {
// we have reached the root without finding a way to change the blacklist a
// caller in a particular path, this is a strict conflict
return null;
}
callerStack.push(callerNode);
Collection sub = blackListIncompatibleCaller(
versionMatcher, conflictParent, selectedNode, evictedNode, callerStack);
callerStack.pop();
if (sub == null) {
// propagate the fact that a path with unblacklistable caller has been found
return null;
} else {
blacklisted.addAll(sub);
}
}
}
}
if (blacklisted.isEmpty()
&& !callerStack.subList(0, callerStack.size() - 1).contains(node)) {
return null;
}
return blacklisted;
}
protected void handleUnsolvableConflict(
IvyNode parent, Collection conflicts, IvyNode node1, IvyNode node2) {
throw new StrictConflictException(node1, node2);
}
public void handleAllBlacklistedRevisions(
DependencyDescriptor dd, Collection/**/ foundBlacklisted) {
ResolveData resolveData = IvyContext.getContext().getResolveData();
Collection/**/ blacklisted = new HashSet();
for (Iterator iterator = foundBlacklisted.iterator(); iterator.hasNext();) {
ModuleRevisionId mrid = (ModuleRevisionId) iterator.next();
blacklisted.add(resolveData.getNode(mrid));
}
for (Iterator iterator = blacklisted.iterator(); iterator.hasNext();) {
IvyNode node = (IvyNode) iterator.next();
IvyNodeBlacklist bdata = node.getBlacklistData(
resolveData.getReport().getConfiguration());
handleUnsolvableConflict(
bdata.getConflictParent(),
Arrays.asList(new Object[] {
bdata.getEvictedNode(), bdata.getSelectedNode()}),
bdata.getEvictedNode(),
bdata.getSelectedNode());
}
}
public String toString() {
return getName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy