/* * Copyright 2011 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; 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.artifacts.ModuleDependency; import org.gradle.api.artifacts.ModuleIdentifier; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ModuleVersionSelector; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.component.ComponentSelector; import org.gradle.api.artifacts.result.ComponentSelectionReason; import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory; import org.gradle.api.internal.artifacts.ResolveContext; import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.ComponentResolutionState; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.ModuleConflictResolver; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.excludes.ModuleExclusion; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.excludes.ModuleExclusions; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.CandidateModule; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.ConflictHandler; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.ConflictResolutionResult; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.conflicts.PotentialConflict; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons; import org.gradle.api.internal.attributes.AttributesSchemaInternal; import org.gradle.api.specs.Spec; import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier; import org.gradle.internal.component.local.model.DslOriginDependencyMetadata; import org.gradle.internal.component.local.model.LocalConfigurationMetadata; import org.gradle.internal.component.local.model.LocalFileDependencyMetadata; import org.gradle.internal.component.model.ComponentArtifactMetadata; import org.gradle.internal.component.model.ComponentResolveMetadata; import org.gradle.internal.component.model.ConfigurationMetadata; import org.gradle.internal.component.model.DefaultComponentOverrideMetadata; import org.gradle.internal.component.model.DependencyMetadata; import org.gradle.internal.component.model.Exclude; import org.gradle.internal.id.IdGenerator; import org.gradle.internal.id.LongIdGenerator; import org.gradle.internal.operations.BuildOperationContext; import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.BuildOperationQueue; import org.gradle.internal.operations.RunnableBuildOperation; import org.gradle.internal.progress.BuildOperationDescriptor; import org.gradle.internal.resolve.ModuleVersionResolveException; import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver; import org.gradle.internal.resolve.resolver.DependencyToComponentIdResolver; import org.gradle.internal.resolve.resolver.ResolveContextToComponentResolver; import org.gradle.internal.resolve.result.BuildableComponentIdResolveResult; import org.gradle.internal.resolve.result.ComponentIdResolveResult; import org.gradle.internal.resolve.result.ComponentResolveResult; import org.gradle.internal.resolve.result.DefaultBuildableComponentIdResolveResult; import org.gradle.internal.resolve.result.DefaultBuildableComponentResolveResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; public class DependencyGraphBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(DependencyGraphBuilder.class); private final ConflictHandler conflictHandler; private final Spec<? super DependencyMetadata> edgeFilter; private final ResolveContextToComponentResolver moduleResolver; private final DependencyToComponentIdResolver idResolver; private final ComponentMetaDataResolver metaDataResolver; private final AttributesSchemaInternal attributesSchema; private final ImmutableModuleIdentifierFactory moduleIdentifierFactory; private final ModuleExclusions moduleExclusions; private final BuildOperationExecutor buildOperationExecutor; public DependencyGraphBuilder(DependencyToComponentIdResolver componentIdResolver, ComponentMetaDataResolver componentMetaDataResolver, ResolveContextToComponentResolver resolveContextToComponentResolver, ConflictHandler conflictHandler, Spec<? super DependencyMetadata> edgeFilter, AttributesSchemaInternal attributesSchema, ImmutableModuleIdentifierFactory moduleIdentifierFactory, ModuleExclusions moduleExclusions, BuildOperationExecutor buildOperationExecutor) { this.idResolver = componentIdResolver; this.metaDataResolver = componentMetaDataResolver; this.moduleResolver = resolveContextToComponentResolver; this.conflictHandler = conflictHandler; this.edgeFilter = edgeFilter; this.attributesSchema = attributesSchema; this.moduleIdentifierFactory = moduleIdentifierFactory; this.moduleExclusions = moduleExclusions; this.buildOperationExecutor = buildOperationExecutor; } public void resolve(final ResolveContext resolveContext, final DependencyGraphVisitor modelVisitor) { IdGenerator<Long> idGenerator = new LongIdGenerator(); DefaultBuildableComponentResolveResult rootModule = new DefaultBuildableComponentResolveResult(); moduleResolver.resolve(resolveContext, rootModule); final ResolveState resolveState = new ResolveState(idGenerator, rootModule, resolveContext.getName(), idResolver, metaDataResolver, edgeFilter, attributesSchema, moduleIdentifierFactory, moduleExclusions); conflictHandler.registerResolver(new DirectDependencyForcingResolver(resolveState.root.component)); traverseGraph(resolveState); resolveState.root.component.setSelectionReason(VersionSelectionReasons.ROOT); assembleResult(resolveState, modelVisitor); } /** * Traverses the dependency graph, resolving conflicts and building the paths from the root configuration. */ private void traverseGraph(final ResolveState resolveState) { resolveState.onMoreSelected(resolveState.root); final List<EdgeState> dependencies = Lists.newArrayList(); final List<EdgeState> dependenciesMissingLocalMetadata = Lists.newArrayList(); final Map<ModuleVersionIdentifier, ComponentIdentifier> componentIdentifierCache = Maps.newHashMap(); while (resolveState.peek() != null || conflictHandler.hasConflicts()) { if (resolveState.peek() != null) { final NodeState node = resolveState.pop(); LOGGER.debug("Visiting configuration {}.", node); // Calculate the outgoing edges of this configuration dependencies.clear(); dependenciesMissingLocalMetadata.clear(); node.visitOutgoingDependencies(dependencies); resolveEdges(node, dependencies, dependenciesMissingLocalMetadata, resolveState, componentIdentifierCache); } else { // We have some batched up conflicts. Resolve the first, and continue traversing the graph conflictHandler.resolveNextConflict(new Action<ConflictResolutionResult>() { public void execute(final ConflictResolutionResult result) { result.getConflict().withParticipatingModules(new Action<ModuleIdentifier>() { public void execute(ModuleIdentifier moduleIdentifier) { ComponentState selected = result.getSelected(); // Restart each configuration. For the evicted configuration, this means moving incoming dependencies across to the // matching selected configuration. For the select configuration, this mean traversing its dependencies. resolveState.getModule(moduleIdentifier).restart(selected); } }); } }); } } } private void performSelection(final ResolveState resolveState, ComponentState moduleRevision) { ModuleIdentifier moduleId = moduleRevision.id.getModule(); // Check for a new conflict if (moduleRevision.state == ModuleState.New) { ModuleResolveState module = resolveState.getModule(moduleId); // A new module revision. Check for conflict PotentialConflict c = conflictHandler.registerModule(module); if (!c.conflictExists()) { // No conflict. Select it for now LOGGER.debug("Selecting new module version {}", moduleRevision); module.select(moduleRevision); } else { // We have a conflict LOGGER.debug("Found new conflicting module version {}", moduleRevision); // Deselect the currently selected version, and remove all outgoing edges from the version // This will propagate through the graph and prune configurations that are no longer required // For each module participating in the conflict (many times there is only one participating module that has multiple versions) c.withParticipatingModules(new Action<ModuleIdentifier>() { public void execute(ModuleIdentifier module) { ComponentState previouslySelected = resolveState.getModule(module).clearSelection(); if (previouslySelected != null) { for (NodeState configuration : previouslySelected.nodes) { configuration.deselect(); } } } }); } } } private void resolveEdges(final NodeState node, final List<EdgeState> dependencies, final List<EdgeState> dependenciesMissingMetadataLocally, final ResolveState resolveState, final Map<ModuleVersionIdentifier, ComponentIdentifier> componentIdentifierCache) { if (dependencies.isEmpty()) { return; } performSelectionSerially(dependencies, resolveState); computePreemptiveDownloadList(dependencies, dependenciesMissingMetadataLocally, componentIdentifierCache); downloadMetadataConcurrently(node, dependenciesMissingMetadataLocally); attachToTargetRevisionsSerially(dependencies); } private void attachToTargetRevisionsSerially(List<EdgeState> dependencies) { // the following only needs to be done serially to preserve ordering of dependencies in the graph: we have visited the edges // but we still didn't add the result to the queue. Doing it from resolve threads would result in non-reproducible graphs, where // edges could be added in different order. To avoid this, the addition of new edges is done serially. for (EdgeState dependency : dependencies) { if (dependency.targetModuleRevision != null && dependency.targetModuleRevision.state == ModuleState.Selected) { dependency.attachToTargetConfigurations(); } } } private void downloadMetadataConcurrently(NodeState node, final List<EdgeState> dependencies) { if (dependencies.isEmpty()) { return; } LOGGER.debug("Submitting {} metadata files to resolve in parallel for {}", dependencies.size(), node); buildOperationExecutor.runAll(new Action<BuildOperationQueue<RunnableBuildOperation>>() { @Override public void execute(BuildOperationQueue<RunnableBuildOperation> buildOperationQueue) { for (final EdgeState dependency : dependencies) { buildOperationQueue.add(new DownloadMetadataOperation(dependency.targetModuleRevision)); } } }); } private void performSelectionSerially(List<EdgeState> dependencies, ResolveState resolveState) { for (EdgeState dependency : dependencies) { ComponentState moduleRevision = dependency.resolveModuleRevisionId(); if (moduleRevision != null) { performSelection(resolveState, moduleRevision); } } } /** * Prepares the resolution of edges, either serially or concurrently. It uses a simple heuristic to determine * if we should perform concurrent resolution, based on the the number of edges, and whether they have unresolved * metadata. Determining this requires calls to `resolveModuleRevisionId`, which will *not* trigger metadata download. * * @param dependencies the dependencies to be resolved * @param dependenciesToBeResolvedInParallel output, edges which will need parallel metadata download */ private void computePreemptiveDownloadList(List<EdgeState> dependencies, List<EdgeState> dependenciesToBeResolvedInParallel, Map<ModuleVersionIdentifier, ComponentIdentifier> componentIdentifierCache) { for (EdgeState dependency : dependencies) { ComponentState state = dependency.targetModuleRevision; if (state != null && !state.fastResolve() && performPreemptiveDownload(state.state)) { if (!metaDataResolver.isFetchingMetadataCheap(toComponentId(state.getId(), componentIdentifierCache))) { dependenciesToBeResolvedInParallel.add(dependency); } } } if (dependenciesToBeResolvedInParallel.size() == 1) { // don't bother doing anything in parallel if there's a single edge dependenciesToBeResolvedInParallel.clear(); } } private static ComponentIdentifier toComponentId(ModuleVersionIdentifier id, Map<ModuleVersionIdentifier, ComponentIdentifier> componentIdentifierCache) { ComponentIdentifier identifier = componentIdentifierCache.get(id); if (identifier == null) { identifier = DefaultModuleComponentIdentifier.newId(id); componentIdentifierCache.put(id, identifier); } return identifier; } private static boolean performPreemptiveDownload(ModuleState state) { return state == ModuleState.Selected; } /** * Populates the result from the graph traversal state. */ private void assembleResult(ResolveState resolveState, DependencyGraphVisitor visitor) { visitor.start(resolveState.root); // Visit the selectors for (DependencyGraphSelector selector : resolveState.getSelectors()) { visitor.visitSelector(selector); } // Visit the components in consumer-first order List<ComponentState> queue = new ArrayList<ComponentState>(); for (ModuleResolveState module : resolveState.getModules()) { if (module.getSelected() != null) { queue.add(module.getSelected()); } } while (!queue.isEmpty()) { ComponentState component = queue.get(0); if (component.getVisitState() == VisitState.NotSeen) { component.setVisitState(VisitState.Visting); int pos = 0; for (NodeState node : component.getNodes()) { if (!node.isSelected()) { continue; } for (EdgeState edge : node.getIncomingEdges()) { ComponentState owner = edge.getFrom().getOwner(); if (owner.getVisitState() == VisitState.NotSeen) { queue.add(pos, owner); pos++; } // else, already visited or currently visiting (which means a cycle), skip } } if (pos == 0) { // have visited all consumers, so visit this node component.setVisitState(VisitState.Visited); queue.remove(0); for (NodeState node : component.getNodes()) { if (node.isSelected()) { visitor.visitNode(node); } } } } else if (component.getVisitState() == VisitState.Visting) { // have visited all consumers, so visit this node component.setVisitState(VisitState.Visited); queue.remove(0); for (NodeState node : component.getNodes()) { if (node.isSelected()) { visitor.visitNode(node); } } } else { // else, already visited previously, skip queue.remove(0); } } // Visit the edges for (NodeState node : resolveState.getNode()) { if (node.isSelected()) { visitor.visitEdges(node); } } visitor.finish(resolveState.root); } /** * Represents the edges in the dependency graph. */ private static class EdgeState implements DependencyGraphEdge { public final NodeState from; public final SelectorState selector; private final DependencyMetadata dependencyMetadata; private final ResolveState resolveState; private final ModuleExclusion moduleExclusion; private final Set<NodeState> targetNodes = new LinkedHashSet<NodeState>(); private ComponentState targetModuleRevision; EdgeState(NodeState from, DependencyMetadata dependencyMetadata, ModuleExclusion moduleExclusion, ResolveState resolveState) { this.from = from; this.dependencyMetadata = dependencyMetadata; this.moduleExclusion = moduleExclusion; this.resolveState = resolveState; this.selector = resolveState.getSelector(dependencyMetadata); } @Override public String toString() { return String.format("%s -> %s", from.toString(), dependencyMetadata); } @Override public NodeState getFrom() { return from; } @Override public DependencyGraphSelector getSelector() { return selector; } /** * @return The resolved module version */ public ComponentState resolveModuleRevisionId() { if (targetModuleRevision == null) { targetModuleRevision = selector.resolveModuleRevisionId(); selector.getSelectedModule().addUnattachedDependency(this); } return targetModuleRevision; } public boolean isTransitive() { return from.isTransitive() && dependencyMetadata.isTransitive(); } public void attachToTargetConfigurations() { if (targetModuleRevision.state != ModuleState.Selected) { return; } calculateTargetConfigurations(); for (NodeState targetConfiguration : targetNodes) { targetConfiguration.addIncomingEdge(this); } if (!targetNodes.isEmpty()) { selector.getSelectedModule().removeUnattachedDependency(this); } } public void removeFromTargetConfigurations() { for (NodeState targetConfiguration : targetNodes) { targetConfiguration.removeIncomingEdge(this); } targetNodes.clear(); if (targetModuleRevision != null) { selector.getSelectedModule().removeUnattachedDependency(this); } } public void restart(ComponentState selected) { removeFromTargetConfigurations(); targetModuleRevision = selected; attachToTargetConfigurations(); } private void calculateTargetConfigurations() { targetNodes.clear(); ComponentResolveMetadata targetModuleVersion = targetModuleRevision.getMetaData(); if (targetModuleVersion == null) { // Broken version return; } Set<ConfigurationMetadata> targetConfigurations = dependencyMetadata.selectConfigurations(from.component.metaData, from.metaData, targetModuleVersion, resolveState.getAttributesSchema()); for (ConfigurationMetadata targetConfiguration : targetConfigurations) { NodeState targetNodeState = resolveState.getNode(targetModuleRevision, targetConfiguration); this.targetNodes.add(targetNodeState); } } public ModuleExclusion toExclusions(DependencyMetadata md, ConfigurationMetadata from) { List<Exclude> excludes = md.getExcludes(from.getHierarchy()); if (excludes.isEmpty()) { return ModuleExclusions.excludeNone(); } return resolveState.moduleExclusions.excludeAny(excludes); } @Override public ModuleExclusion getExclusions(ModuleExclusions moduleExclusions) { ModuleExclusion edgeExclusions = toExclusions(dependencyMetadata, from.metaData); return resolveState.moduleExclusions.intersect(edgeExclusions, moduleExclusion); } @Override public ComponentSelector getRequested() { return dependencyMetadata.getSelector(); } @Override public ModuleVersionSelector getRequestedModuleVersion() { return dependencyMetadata.getRequested(); } @Override public ModuleVersionResolveException getFailure() { return selector.getFailure(); } @Override public Long getSelected() { return selector.getSelected().getResultId(); } @Override public ComponentSelectionReason getReason() { return selector.getSelectionReason(); } @Override public ModuleDependency getModuleDependency() { if (dependencyMetadata instanceof DslOriginDependencyMetadata) { return ((DslOriginDependencyMetadata) dependencyMetadata).getSource(); } return null; } @Override public Iterable<? extends DependencyGraphNode> getTargets() { return targetNodes; } @Override public Set<ComponentArtifactMetadata> getArtifacts(ConfigurationMetadata metaData1) { return dependencyMetadata.getArtifacts(from.metaData, metaData1); } } /** * Global resolution state. */ private static class ResolveState { private final Spec<? super DependencyMetadata> edgeFilter; private final Map<ModuleIdentifier, ModuleResolveState> modules = new LinkedHashMap<ModuleIdentifier, ModuleResolveState>(); private final Map<ResolvedConfigurationIdentifier, NodeState> nodes = new LinkedHashMap<ResolvedConfigurationIdentifier, NodeState>(); private final Map<ModuleVersionSelector, SelectorState> selectors = new LinkedHashMap<ModuleVersionSelector, SelectorState>(); private final RootNode root; private final IdGenerator<Long> idGenerator; private final DependencyToComponentIdResolver idResolver; private final ComponentMetaDataResolver metaDataResolver; private final Set<NodeState> queued = Sets.newHashSet(); private final LinkedList<NodeState> queue = new LinkedList<NodeState>(); private final AttributesSchemaInternal attributesSchema; private final ImmutableModuleIdentifierFactory moduleIdentifierFactory; private final ModuleExclusions moduleExclusions; public ResolveState(IdGenerator<Long> idGenerator, ComponentResolveResult rootResult, String rootConfigurationName, DependencyToComponentIdResolver idResolver, ComponentMetaDataResolver metaDataResolver, Spec<? super DependencyMetadata> edgeFilter, AttributesSchemaInternal attributesSchema, ImmutableModuleIdentifierFactory moduleIdentifierFactory, ModuleExclusions moduleExclusions) { this.idGenerator = idGenerator; this.idResolver = idResolver; this.metaDataResolver = metaDataResolver; this.edgeFilter = edgeFilter; this.attributesSchema = attributesSchema; this.moduleIdentifierFactory = moduleIdentifierFactory; this.moduleExclusions = moduleExclusions; ComponentState rootVersion = getRevision(rootResult.getId()); rootVersion.setMetaData(rootResult.getMetaData()); root = new RootNode(idGenerator.generateId(), rootVersion, new ResolvedConfigurationIdentifier(rootVersion.id, rootConfigurationName), this); nodes.put(root.id, root); root.component.module.select(root.component); } public Collection<ModuleResolveState> getModules() { return modules.values(); } public ModuleResolveState getModule(ModuleIdentifier id) { ModuleResolveState module = modules.get(id); if (module == null) { module = new ModuleResolveState(idGenerator, id, this, metaDataResolver); modules.put(id, module); } return module; } public ComponentState getRevision(ModuleVersionIdentifier id) { return getModule(id.getModule()).getVersion(id); } public Collection<NodeState> getNode() { return nodes.values(); } public NodeState getNode(ComponentState module, ConfigurationMetadata configurationMetadata) { ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(module.id, configurationMetadata.getName()); NodeState configuration = nodes.get(id); if (configuration == null) { configuration = new NodeState(idGenerator.generateId(), id, module, this, configurationMetadata); nodes.put(id, configuration); } return configuration; } public Collection<SelectorState> getSelectors() { return selectors.values(); } public SelectorState getSelector(DependencyMetadata dependencyMetadata) { ModuleVersionSelector requested = dependencyMetadata.getRequested(); SelectorState resolveState = selectors.get(requested); if (resolveState == null) { resolveState = new SelectorState(idGenerator.generateId(), dependencyMetadata, idResolver, this); selectors.put(requested, resolveState); } return resolveState; } public NodeState peek() { return queue.isEmpty() ? null : queue.getFirst(); } public NodeState pop() { NodeState next = queue.removeFirst(); queued.remove(next); return next; } /** * Called when a change is made to a configuration node, such that its dependency graph <em>may</em> now be larger than it previously was, and the node should be visited. */ public void onMoreSelected(NodeState node) { // Add to the end of the queue, so that we traverse the graph in breadth-wise order to pick up as many conflicts as // possible before attempting to resolve them if (queued.add(node)) { queue.addLast(node); } } /** * Called when a change is made to a configuration node, such that its dependency graph <em>may</em> now be smaller than it previously was, and the node should be visited. */ public void onFewerSelected(NodeState node) { // Add to the front of the queue, to flush out configurations that are no longer required. if (queued.add(node)) { queue.addFirst(node); } } public AttributesSchemaInternal getAttributesSchema() { return attributesSchema; } } enum ModuleState { New, Selected, Conflict, Evicted } /** * Resolution state for a given module. */ private static class ModuleResolveState implements CandidateModule { final ComponentMetaDataResolver metaDataResolver; final IdGenerator<Long> idGenerator; final ModuleIdentifier id; final Set<EdgeState> unattachedDependencies = new LinkedHashSet<EdgeState>(); final Map<ModuleVersionIdentifier, ComponentState> versions = new LinkedHashMap<ModuleVersionIdentifier, ComponentState>(); final Set<SelectorState> selectors = new HashSet<SelectorState>(); final ResolveState resolveState; ComponentState selected; private ModuleResolveState(IdGenerator<Long> idGenerator, ModuleIdentifier id, ResolveState resolveState, ComponentMetaDataResolver metaDataResolver) { this.idGenerator = idGenerator; this.id = id; this.resolveState = resolveState; this.metaDataResolver = metaDataResolver; } @Override public String toString() { return id.toString(); } @Override public ModuleIdentifier getId() { return id; } @Override public Collection<ComponentState> getVersions() { return versions.values(); } public ComponentState getSelected() { return selected; } public void select(ComponentState selected) { assert this.selected == null; this.selected = selected; for (ComponentState version : versions.values()) { version.state = ModuleState.Evicted; } selected.state = ModuleState.Selected; } public ComponentState clearSelection() { ComponentState previousSelection = selected; selected = null; for (ComponentState version : versions.values()) { version.state = ModuleState.Conflict; } return previousSelection; } public void restart(ComponentState selected) { select(selected); for (ComponentState version : versions.values()) { version.restart(selected); } for (SelectorState selector : selectors) { selector.restart(selected); } for (EdgeState dependency : new ArrayList<EdgeState>(unattachedDependencies)) { dependency.restart(selected); } unattachedDependencies.clear(); } public void addUnattachedDependency(EdgeState edge) { unattachedDependencies.add(edge); } public void removeUnattachedDependency(EdgeState edge) { unattachedDependencies.remove(edge); } public ComponentState getVersion(ModuleVersionIdentifier id) { ComponentState moduleRevision = versions.get(id); if (moduleRevision == null) { moduleRevision = new ComponentState(idGenerator.generateId(), this, id, metaDataResolver); versions.put(id, moduleRevision); } return moduleRevision; } public void addSelector(SelectorState selector) { selectors.add(selector); } } /** * Resolution state for a given component */ public static class ComponentState implements ComponentResolutionState, ComponentResult, DependencyGraphComponent { public final ModuleVersionIdentifier id; private final ComponentMetaDataResolver resolver; private final Set<NodeState> nodes = new LinkedHashSet<NodeState>(); private final Long resultId; private final ModuleResolveState module; private volatile ComponentResolveMetadata metaData; private ModuleState state = ModuleState.New; private ComponentSelectionReason selectionReason = VersionSelectionReasons.REQUESTED; private ModuleVersionResolveException failure; private SelectorState firstReference; private VisitState visitState = VisitState.NotSeen; private ComponentState(Long resultId, ModuleResolveState module, ModuleVersionIdentifier id, ComponentMetaDataResolver resolver) { this.resultId = resultId; this.module = module; this.id = id; this.resolver = resolver; } @Override public String toString() { return id.toString(); } @Override public String getVersion() { return id.getVersion(); } @Override public Long getResultId() { return resultId; } @Override public ModuleVersionIdentifier getId() { return id; } @Override public ModuleVersionIdentifier getModuleVersion() { return id; } public ModuleVersionResolveException getFailure() { return failure; } public VisitState getVisitState() { return visitState; } public void setVisitState(VisitState visitState) { this.visitState = visitState; } public Set<NodeState> getNodes() { return nodes; } @Override public ComponentResolveMetadata getMetadata() { return metaData; } public void restart(ComponentState selected) { for (NodeState configuration : nodes) { configuration.restart(selected); } } public void addResolver(SelectorState resolver) { if (firstReference == null) { firstReference = resolver; } } /** * Returns true if this module version can be resolved quickly (already resolved or local) * * @return true if it has been resolved in a cheap way */ public boolean fastResolve() { if (metaData != null || failure != null) { return true; } ComponentIdResolveResult idResolveResult = firstReference.idResolveResult; if (idResolveResult.getFailure() != null) { failure = idResolveResult.getFailure(); return true; } if (idResolveResult.getMetaData() != null) { metaData = idResolveResult.getMetaData(); return true; } return false; } public void resolve() { if (fastResolve()) { return; } ComponentIdResolveResult idResolveResult = firstReference.idResolveResult; DefaultBuildableComponentResolveResult result = new DefaultBuildableComponentResolveResult(); resolver.resolve(idResolveResult.getId(), DefaultComponentOverrideMetadata.forDependency(firstReference.dependencyMetadata), result); if (result.getFailure() != null) { failure = result.getFailure(); return; } metaData = result.getMetaData(); } @Override public ComponentResolveMetadata getMetaData() { if (metaData == null) { resolve(); } return metaData; } public void setMetaData(ComponentResolveMetadata metaData) { this.metaData = metaData; this.failure = null; } public void addConfiguration(NodeState node) { nodes.add(node); } @Override public ComponentSelectionReason getSelectionReason() { return selectionReason; } @Override public void setSelectionReason(ComponentSelectionReason reason) { this.selectionReason = reason; } @Override public ComponentIdentifier getComponentId() { return getMetaData().getComponentId(); } @Override public Set<ComponentState> getDependents() { Set<ComponentState> incoming = new LinkedHashSet<ComponentState>(); for (NodeState configuration : nodes) { for (EdgeState dependencyEdge : configuration.incomingEdges) { incoming.add(dependencyEdge.from.component); } } return incoming; } } enum VisitState { NotSeen, Visting, Visited } /** * Represents a node in the dependency graph. */ static class NodeState implements DependencyGraphNode { private final Long resultId; public final ComponentState component; public final Set<EdgeState> incomingEdges = new LinkedHashSet<EdgeState>(); public final Set<EdgeState> outgoingEdges = new LinkedHashSet<EdgeState>(); public final ResolvedConfigurationIdentifier id; private final ConfigurationMetadata metaData; private final ResolveState resolveState; private ModuleExclusion previousTraversalExclusions; private NodeState(Long resultId, ResolvedConfigurationIdentifier id, ComponentState component, ResolveState resolveState) { this(resultId, id, component, resolveState, component.metaData.getConfiguration(id.getConfiguration())); } private NodeState(Long resultId, ResolvedConfigurationIdentifier id, ComponentState component, ResolveState resolveState, ConfigurationMetadata md) { this.resultId = resultId; this.id = id; this.component = component; this.resolveState = resolveState; this.metaData = md; component.addConfiguration(this); } @Override public Long getNodeId() { return resultId; } @Override public boolean isRoot() { return false; } @Override public ResolvedConfigurationIdentifier getResolvedConfigurationId() { return id; } @Override public ComponentState getOwner() { return component; } @Override public Set<EdgeState> getIncomingEdges() { return incomingEdges; } @Override public Set<EdgeState> getOutgoingEdges() { return outgoingEdges; } @Override public ConfigurationMetadata getMetadata() { return metaData; } @Override public Set<? extends LocalFileDependencyMetadata> getOutgoingFileEdges() { if (metaData instanceof LocalConfigurationMetadata) { // Only when this node has a transitive incoming edge for (EdgeState incomingEdge : incomingEdges) { if (incomingEdge.isTransitive()) { return ((LocalConfigurationMetadata) metaData).getFiles(); } } } return Collections.emptySet(); } @Override public String toString() { return String.format("%s(%s)", component, id.getConfiguration()); } public boolean isTransitive() { return metaData.isTransitive(); } public void visitOutgoingDependencies(Collection<EdgeState> target) { // If this configuration's version is in conflict, don't do anything // If not traversed before, add all selected outgoing edges // If traversed before, and the selected modules have changed, remove previous outgoing edges and add outgoing edges again with // the new selections. // If traversed before, and the selected modules have not changed, ignore // If none of the incoming edges are transitive, then the node has no outgoing edges if (component.state != ModuleState.Selected) { LOGGER.debug("version for {} is not selected. ignoring.", this); return; } boolean hasIncomingEdges = !incomingEdges.isEmpty(); List<EdgeState> transitiveIncoming = hasIncomingEdges ? new ArrayList<EdgeState>() : Collections.<EdgeState>emptyList(); for (EdgeState edge : incomingEdges) { if (edge.isTransitive()) { transitiveIncoming.add(edge); } } if (transitiveIncoming.isEmpty() && this != resolveState.root) { if (previousTraversalExclusions != null) { removeOutgoingEdges(); } if (hasIncomingEdges) { LOGGER.debug("{} has no transitive incoming edges. ignoring outgoing edges.", this); } else { LOGGER.debug("{} has no incoming edges. ignoring.", this); } return; } ModuleExclusion resolutionFilter = getModuleResolutionFilter(transitiveIncoming); if (previousTraversalExclusions != null) { if (previousTraversalExclusions.excludesSameModulesAs(resolutionFilter)) { LOGGER.debug("Changed edges for {} selects same versions as previous traversal. ignoring", this); // Don't need to traverse again, but hang on to the new filter as the set of artifacts may have changed previousTraversalExclusions = resolutionFilter; return; } removeOutgoingEdges(); } for (DependencyMetadata dependency : metaData.getDependencies()) { if (isExcluded(resolutionFilter, dependency)) { continue; } EdgeState dependencyEdge = new EdgeState(this, dependency, resolutionFilter, resolveState); outgoingEdges.add(dependencyEdge); target.add(dependencyEdge); } previousTraversalExclusions = resolutionFilter; } private boolean isExcluded(ModuleExclusion selector, DependencyMetadata dependency) { if (!resolveState.edgeFilter.isSatisfiedBy(dependency)) { LOGGER.debug("{} is filtered.", dependency); return true; } ModuleIdentifier targetModuleId = resolveState.moduleIdentifierFactory.module(dependency.getRequested().getGroup(), dependency.getRequested().getName()); if (selector.excludeModule(targetModuleId)) { LOGGER.debug("{} is excluded from {}.", targetModuleId, this); return true; } return false; } public void addIncomingEdge(EdgeState dependencyEdge) { incomingEdges.add(dependencyEdge); resolveState.onMoreSelected(this); } public void removeIncomingEdge(EdgeState dependencyEdge) { incomingEdges.remove(dependencyEdge); resolveState.onFewerSelected(this); } public boolean isSelected() { return !incomingEdges.isEmpty(); } private ModuleExclusion getModuleResolutionFilter(List<EdgeState> transitiveEdges) { ModuleExclusion resolutionFilter; ModuleExclusions moduleExclusions = resolveState.moduleExclusions; if (transitiveEdges.isEmpty()) { resolutionFilter = ModuleExclusions.excludeNone(); } else { resolutionFilter = transitiveEdges.get(0).getExclusions(moduleExclusions); for (int i = 1; i < transitiveEdges.size(); i++) { EdgeState dependencyEdge = transitiveEdges.get(i); resolutionFilter = moduleExclusions.union(resolutionFilter, dependencyEdge.getExclusions(moduleExclusions)); } } resolutionFilter = moduleExclusions.intersect(resolutionFilter, metaData.getExclusions(moduleExclusions)); return resolutionFilter; } public void removeOutgoingEdges() { for (EdgeState outgoingDependency : outgoingEdges) { outgoingDependency.removeFromTargetConfigurations(); } outgoingEdges.clear(); previousTraversalExclusions = null; } public void restart(ComponentState selected) { // Restarting this configuration after conflict resolution. // If this configuration belongs to the select version, queue ourselves up for traversal. // If not, then remove our incoming edges, which triggers them to be moved across to the selected configuration if (component == selected) { resolveState.onMoreSelected(this); } else { for (EdgeState dependency : new ArrayList<EdgeState>(incomingEdges)) { dependency.restart(selected); } incomingEdges.clear(); } } public void deselect() { removeOutgoingEdges(); } } private static class RootNode extends NodeState { private RootNode(Long resultId, ComponentState moduleRevision, ResolvedConfigurationIdentifier id, ResolveState resolveState) { super(resultId, id, moduleRevision, resolveState); } @Override public boolean isRoot() { return true; } @Override public Set<? extends LocalFileDependencyMetadata> getOutgoingFileEdges() { return ((LocalConfigurationMetadata) getMetadata()).getFiles(); } @Override public boolean isSelected() { return true; } @Override public void deselect() { } } /** * Resolution state for a given module version selector. */ private static class SelectorState implements DependencyGraphSelector { final Long id; final DependencyMetadata dependencyMetadata; final DependencyToComponentIdResolver resolver; final ResolveState resolveState; ModuleVersionResolveException failure; ModuleResolveState targetModule; ComponentState selected; BuildableComponentIdResolveResult idResolveResult; private SelectorState(Long id, DependencyMetadata dependencyMetadata, DependencyToComponentIdResolver resolver, ResolveState resolveState) { this.id = id; this.dependencyMetadata = dependencyMetadata; this.resolver = resolver; this.resolveState = resolveState; targetModule = resolveState.getModule(resolveState.moduleIdentifierFactory.module(dependencyMetadata.getRequested().getGroup(), dependencyMetadata.getRequested().getName())); } @Override public Long getResultId() { return id; } @Override public String toString() { return dependencyMetadata.toString(); } @Override public ComponentSelector getRequested() { return dependencyMetadata.getSelector(); } private ModuleVersionResolveException getFailure() { return failure != null ? failure : selected.getFailure(); } public ComponentSelectionReason getSelectionReason() { return selected == null ? idResolveResult.getSelectionReason() : selected.getSelectionReason(); } public ComponentState getSelected() { return targetModule.selected; } public ModuleResolveState getSelectedModule() { return targetModule; } /** * @return The module version, or null if there is a failure to resolve this selector. */ public ComponentState resolveModuleRevisionId() { if (selected != null) { return selected; } if (failure != null) { return null; } idResolveResult = new DefaultBuildableComponentIdResolveResult(); resolver.resolve(dependencyMetadata, idResolveResult); if (idResolveResult.getFailure() != null) { failure = idResolveResult.getFailure(); return null; } selected = resolveState.getRevision(idResolveResult.getModuleVersionId()); selected.addResolver(this); selected.selectionReason = idResolveResult.getSelectionReason(); targetModule = selected.module; targetModule.addSelector(this); return selected; } public void restart(ComponentState moduleRevision) { this.selected = moduleRevision; this.targetModule = moduleRevision.module; } } private static class DirectDependencyForcingResolver implements ModuleConflictResolver { private final ComponentState root; private DirectDependencyForcingResolver(ComponentState root) { this.root = root; } public <T extends ComponentResolutionState> T select(Collection<? extends T> candidates) { for (NodeState configuration : root.nodes) { for (EdgeState outgoingEdge : configuration.outgoingEdges) { if (outgoingEdge.dependencyMetadata.isForce() && candidates.contains(outgoingEdge.targetModuleRevision)) { outgoingEdge.targetModuleRevision.selectionReason = VersionSelectionReasons.FORCED; return (T) outgoingEdge.targetModuleRevision; } } } return null; } } private static class DownloadMetadataOperation implements RunnableBuildOperation { private final ComponentState state; DownloadMetadataOperation(ComponentState state) { this.state = state; } @Override public void run(BuildOperationContext context) { state.getMetaData(); } @Override public BuildOperationDescriptor.Builder description() { return BuildOperationDescriptor.displayName("Resolving " + state); } } }