org.gradle.api.internalivyservice.resolveengine.graph.conflicts.DefaultCapabilitiesConflictHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.5 API redistribution.
/*
* Copyright 2018 the original author or authors.
*
* Licensed 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.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.gradle.api.Action;
import org.gradle.api.Describable;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.capabilities.Capability;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.ComponentState;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.NodeState;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ComponentSelectionDescriptorInternal;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ComponentSelectionReasons;
import org.gradle.internal.component.external.model.CapabilityInternal;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DefaultCapabilitiesConflictHandler implements CapabilitiesConflictHandler {
private final List resolvers = Lists.newArrayListWithExpectedSize(3);
private final Map> capabilityWithoutVersionToNodes = Maps.newHashMap();
private final Deque conflicts = new ArrayDeque<>();
@Override
public PotentialConflict registerCandidate(CapabilitiesConflictHandler.Candidate candidate) {
CapabilityInternal capability = (CapabilityInternal) candidate.getCapability();
String group = capability.getGroup();
String name = capability.getName();
final Set nodes = findNodesFor(capability);
Collection implicitCapabilityProviders = candidate.getImplicitCapabilityProviders();
nodes.addAll(implicitCapabilityProviders);
NodeState node = candidate.getNode();
if ((nodes.add(node) || implicitCapabilityProviders.contains(node)) && nodes.size() > 1) {
// The registered nodes may contain nodes which are no longer selected.
// We don't remove them from the list in the first place because it proved to be
// slower than filtering as needed.
ModuleIdentifier rootId = null;
final List candidatesForConflict = Lists.newArrayListWithCapacity(nodes.size());
for (NodeState ns : nodes) {
if (ns.isSelected()) {
candidatesForConflict.add(ns);
if (ns.isRoot()) {
rootId = ns.getComponent().getId().getModule();
}
}
}
if (rootId != null && candidatesForConflict.size() > 1) {
// This is a special case for backwards compatibility: it is possible to have
// a cycle where the root component depends on a library which transitively
// depends on a different version of the root module. In this case, we effectively
// allow 2 modules to have the same capability, so we filter the nodes coming
// from transitive dependencies
ModuleIdentifier rootModuleId = rootId;
candidatesForConflict.removeIf(n -> !n.isRoot() && n.getComponent().getId().getModule().equals(rootModuleId));
}
if (candidatesForConflict.size() > 1) {
PotentialConflict conflict = new PotentialConflict() {
@Override
public void withParticipatingModules(Action action) {
for (NodeState node : candidatesForConflict) {
action.execute(node.getComponent().getId().getModule());
}
}
@Override
public boolean conflictExists() {
return true;
}
};
conflicts.add(new CapabilityConflict(group, name, candidatesForConflict));
return conflict;
}
}
return PotentialConflictFactory.noConflict();
}
private Set findNodesFor(CapabilityInternal capability) {
String capabilityId = capability.getCapabilityId();
return capabilityWithoutVersionToNodes.computeIfAbsent(capabilityId, k -> Sets.newLinkedHashSet());
}
@Override
public boolean hasConflicts() {
return !conflicts.isEmpty();
}
@Override
public void resolveNextConflict(Action resolutionAction) {
CapabilityConflict conflict = conflicts.poll();
Details details = new Details(conflict);
for (Resolver resolver : resolvers) {
resolver.resolve(details);
if (details.hasResult()) {
resolutionAction.execute(details);
ComponentSelectionDescriptorInternal conflictResolution = ComponentSelectionReasons.CONFLICT_RESOLUTION;
if (details.reason != null) {
conflictResolution = conflictResolution.withDescription(details.reason);
}
details.getSelected().addCause(conflictResolution);
return;
}
}
}
@Override
public void registerResolver(Resolver conflictResolver) {
resolvers.add(conflictResolver);
}
@Override
public boolean hasSeenCapability(Capability capability) {
return capabilityWithoutVersionToNodes.containsKey(((CapabilityInternal) capability).getCapabilityId());
}
public static CapabilitiesConflictHandler.Candidate candidate(NodeState node, Capability capability, Collection implicitCapabilityProviders) {
return new Candidate(node, capability, implicitCapabilityProviders);
}
private static class Candidate implements CapabilitiesConflictHandler.Candidate {
private final NodeState node;
private final Capability capability;
private final Collection implicitCapabilityProviders;
public Candidate(NodeState node, Capability capability, Collection implicitCapabilityProviders) {
this.node = node;
this.capability = capability;
this.implicitCapabilityProviders = implicitCapabilityProviders;
}
@Override
public NodeState getNode() {
return node;
}
@Override
public Capability getCapability() {
return capability;
}
@Override
public Collection getImplicitCapabilityProviders() {
return implicitCapabilityProviders;
}
}
private static class Details implements ResolutionDetails {
private final CapabilityConflict conflict;
private final Set evicted = Sets.newHashSet();
private NodeState selected;
private Describable reason;
private Details(CapabilityConflict conflict) {
this.conflict = conflict;
}
@Override
public Collection getCapabilityVersions() {
return conflict.descriptors;
}
@Override
public Collection getCandidates(Capability capability) {
ImmutableList.Builder candidates = new ImmutableList.Builder<>();
String group = capability.getGroup();
String name = capability.getName();
String version = capability.getVersion();
for (final NodeState node : conflict.nodes) {
if (!evicted.contains(node)) {
Capability componentCapability = node.findCapability(group, name);
if (componentCapability != null && componentCapability.getVersion().equals(version)) {
candidates.add(new CandidateDetails() {
@Override
public ComponentIdentifier getId() {
return node.getComponent().getComponentId();
}
@Override
public String getVariantName() {
return node.getResolvedConfigurationId().getConfiguration();
}
@Override
public void evict() {
node.evict();
evicted.add(node);
}
@Override
public void select() {
selected = node;
}
@Override
public void reject() {
ComponentState component = node.getComponent();
component.rejectForCapabilityConflict(capability, conflictedNodes(node, conflict.nodes));
component.selectAndRestartModule();
}
@Override
public void byReason(Describable description) {
reason = description;
}
});
}
}
}
return candidates.build();
}
private Collection conflictedNodes(NodeState node, Collection nodes) {
List conflictedNodes = Lists.newArrayList(nodes);
conflictedNodes.remove(node);
return conflictedNodes;
}
@Override
public void withParticipatingModules(Action action) {
Set seen = Sets.newHashSet();
for (NodeState node : conflict.nodes) {
ModuleIdentifier module = node.getComponent().getId().getModule();
if (seen.add(module)) {
action.execute(module);
}
}
}
@Override
public boolean hasResult() {
return selected != null;
}
@Override
public ComponentState getSelected() {
return selected.getComponent();
}
}
private static class CapabilityConflict {
private final Collection nodes;
private final Set descriptors;
private CapabilityConflict(String group, String name, Collection nodes) {
this.nodes = nodes;
final ImmutableSet.Builder builder = new ImmutableSet.Builder<>();
for (final NodeState node : nodes) {
Capability capability = node.findCapability(group, name);
if (capability != null) {
builder.add(capability);
}
}
this.descriptors = builder.build();
}
}
private static boolean sameComponentAppearsMultipleTimes(CapabilityConflict conflict) {
Set components = Sets.newHashSet();
for (NodeState node : conflict.nodes) {
if (!components.add(node.getComponent())) {
return true;
}
}
return false;
}
}