/* * 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; import com.google.common.collect.Sets; import org.gradle.api.Nullable; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.FileCollectionDependency; import org.gradle.api.artifacts.LenientConfiguration; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.ResolveException; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.artifacts.UnresolvedDependency; import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.internal.artifacts.DependencyGraphNodeResult; import org.gradle.api.internal.artifacts.ResolveArtifactsBuildOperationType; import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ArtifactVisitor; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.BuildDependenciesVisitor; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.CompositeArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ParallelResolveArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvedArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.SelectedArtifactResults; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.SelectedArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.SelectedFileDependencyResults; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.VisitedArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.VisitedArtifactsResults; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.VisitedFileDependencyResults; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResults; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.oldresult.TransientConfigurationResultsLoader; import org.gradle.api.internal.artifacts.transform.ArtifactTransforms; import org.gradle.api.internal.artifacts.transform.VariantSelector; import org.gradle.api.internal.attributes.AttributeContainerInternal; import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; import org.gradle.internal.UncheckedException; import org.gradle.internal.graph.CachingDirectedGraphWalker; import org.gradle.internal.graph.DirectedGraphWithEdgeValues; import org.gradle.internal.operations.BuildOperationContext; import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.RunnableBuildOperation; import org.gradle.internal.progress.BuildOperationDescriptor; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; public class DefaultLenientConfiguration implements LenientConfiguration, VisitedArtifactSet { private final ConfigurationInternal configuration; private final Set<UnresolvedDependency> unresolvedDependencies; private final VisitedArtifactsResults artifactResults; private final VisitedFileDependencyResults fileDependencyResults; private final TransientConfigurationResultsLoader transientConfigurationResultsFactory; private final ArtifactTransforms artifactTransforms; private final AttributeContainerInternal implicitAttributes; private final BuildOperationExecutor buildOperationExecutor; // Selected for the configuration private SelectedArtifactResults artifactsForThisConfiguration; private SelectedFileDependencyResults filesForThisConfiguration; public DefaultLenientConfiguration(ConfigurationInternal configuration, Set<UnresolvedDependency> unresolvedDependencies, VisitedArtifactsResults artifactResults, VisitedFileDependencyResults fileDependencyResults, TransientConfigurationResultsLoader transientConfigurationResultsLoader, ArtifactTransforms artifactTransforms, BuildOperationExecutor buildOperationExecutor) { this.configuration = configuration; this.implicitAttributes = configuration.getAttributes().asImmutable(); this.unresolvedDependencies = unresolvedDependencies; this.artifactResults = artifactResults; this.fileDependencyResults = fileDependencyResults; this.transientConfigurationResultsFactory = transientConfigurationResultsLoader; this.artifactTransforms = artifactTransforms; this.buildOperationExecutor = buildOperationExecutor; } private BuildOperationDescriptor.Builder computeResolveAllBuildOperationDetails(AttributeContainer requestedAttributes) { String displayName = "Resolve artifacts " + (requestedAttributes == null || requestedAttributes.isEmpty() ? "of " : "view of ") + configuration.getPath(); return BuildOperationDescriptor.displayName(displayName) .details(new ResolveArtifactsBuildOperationType.DetailsImpl(configuration.getPath())); } private SelectedArtifactResults getSelectedArtifacts() { if (artifactsForThisConfiguration == null) { artifactsForThisConfiguration = artifactResults.select(Specs.<ComponentIdentifier>satisfyAll(), artifactTransforms.variantSelector(implicitAttributes, false)); } return artifactsForThisConfiguration; } private SelectedFileDependencyResults getSelectedFiles() { if (filesForThisConfiguration == null) { filesForThisConfiguration = fileDependencyResults.select(Specs.<ComponentIdentifier>satisfyAll(), artifactTransforms.variantSelector(implicitAttributes, false)); } return filesForThisConfiguration; } public SelectedArtifactSet select() { return select(Specs.<Dependency>satisfyAll(), implicitAttributes, Specs.<ComponentIdentifier>satisfyAll(), false); } public SelectedArtifactSet select(final Spec<? super Dependency> dependencySpec) { return select(dependencySpec, implicitAttributes, Specs.<ComponentIdentifier>satisfyAll(), false); } @Override public SelectedArtifactSet select(final Spec<? super Dependency> dependencySpec, final AttributeContainerInternal requestedAttributes, final Spec<? super ComponentIdentifier> componentSpec, boolean allowNoMatchingVariants) { final SelectedArtifactResults artifactResults; final SelectedFileDependencyResults fileDependencyResults; VariantSelector selector = artifactTransforms.variantSelector(requestedAttributes, allowNoMatchingVariants); artifactResults = this.artifactResults.select(componentSpec, selector); fileDependencyResults = this.fileDependencyResults.select(componentSpec, selector); return new SelectedArtifactSet() { @Override public void collectBuildDependencies(BuildDependenciesVisitor visitor) { if (hasError()) { visitor.visitFailure(getFailure()); } artifactResults.getArtifacts().collectBuildDependencies(visitor); } @Override public void visitArtifacts(ArtifactVisitor visitor) { if (hasError()) { visitor.visitFailure(getFailure()); } DefaultLenientConfiguration.this.visitArtifactsWithBuildOperation(dependencySpec, artifactResults, fileDependencyResults, visitor, requestedAttributes); } }; } private ResolveException getFailure() { List<Throwable> failures = new ArrayList<Throwable>(); for (UnresolvedDependency unresolvedDependency : unresolvedDependencies) { failures.add(unresolvedDependency.getProblem()); } return new ResolveException(configuration.toString(), failures); } public boolean hasError() { return unresolvedDependencies.size() > 0; } public Set<UnresolvedDependency> getUnresolvedModuleDependencies() { return unresolvedDependencies; } public void rethrowFailure() throws ResolveException { if (hasError()) { throw getFailure(); } } private TransientConfigurationResults loadTransientGraphResults(SelectedArtifactResults artifactResults) { return transientConfigurationResultsFactory.create(artifactResults); } public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) { Set<ResolvedDependency> matches = new LinkedHashSet<ResolvedDependency>(); for (DependencyGraphNodeResult node : getFirstLevelNodes(dependencySpec)) { matches.add(node.getPublicView()); } return matches; } private Set<DependencyGraphNodeResult> getFirstLevelNodes(Spec<? super Dependency> dependencySpec) { Set<DependencyGraphNodeResult> matches = new LinkedHashSet<DependencyGraphNodeResult>(); TransientConfigurationResults graphResults = loadTransientGraphResults(getSelectedArtifacts()); for (Map.Entry<ModuleDependency, DependencyGraphNodeResult> entry : graphResults.getFirstLevelDependencies().entrySet()) { if (dependencySpec.isSatisfiedBy(entry.getKey())) { matches.add(entry.getValue()); } } return matches; } public Set<ResolvedDependency> getAllModuleDependencies() { Set<ResolvedDependency> resolvedElements = new LinkedHashSet<ResolvedDependency>(); Deque<ResolvedDependency> workQueue = new LinkedList<ResolvedDependency>(); workQueue.addAll(loadTransientGraphResults(getSelectedArtifacts()).getRootNode().getPublicView().getChildren()); while (!workQueue.isEmpty()) { ResolvedDependency item = workQueue.removeFirst(); if (resolvedElements.add(item)) { final Set<ResolvedDependency> children = item.getChildren(); if (children != null) { workQueue.addAll(children); } } } return resolvedElements; } @Override public Set<File> getFiles() { return getFiles(Specs.<Dependency>satisfyAll()); } /** * Recursive but excludes unsuccessfully resolved artifacts. */ public Set<File> getFiles(Spec<? super Dependency> dependencySpec) { LenientFilesAndArtifactResolveVisitor visitor = new LenientFilesAndArtifactResolveVisitor(); visitArtifactsWithBuildOperation(dependencySpec, getSelectedArtifacts(), getSelectedFiles(), visitor, null); return visitor.files; } @Override public Set<ResolvedArtifact> getArtifacts() { return getArtifacts(Specs.<Dependency>satisfyAll()); } /** * Recursive but excludes unsuccessfully resolved artifacts. */ public Set<ResolvedArtifact> getArtifacts(Spec<? super Dependency> dependencySpec) { LenientArtifactCollectingVisitor visitor = new LenientArtifactCollectingVisitor(); visitArtifactsWithBuildOperation(dependencySpec, getSelectedArtifacts(), getSelectedFiles(), visitor, null); return visitor.artifacts; } private void visitArtifactsWithBuildOperation(final Spec<? super Dependency> dependencySpec, final SelectedArtifactResults artifactResults, final SelectedFileDependencyResults fileDependencyResults, final ArtifactVisitor visitor, @Nullable final AttributeContainer requestedAttributes) { buildOperationExecutor.run(new RunnableBuildOperation() { @Override public void run(BuildOperationContext context) { visitArtifacts(dependencySpec, artifactResults, fileDependencyResults, visitor); } @Override public BuildOperationDescriptor.Builder description() { return computeResolveAllBuildOperationDetails(requestedAttributes); } }); } /** * Recursive, includes unsuccessfully resolved artifacts * * @param dependencySpec dependency spec */ private void visitArtifacts(Spec<? super Dependency> dependencySpec, SelectedArtifactResults artifactResults, SelectedFileDependencyResults fileDependencyResults, ArtifactVisitor visitor) { //this is not very nice might be good enough until we get rid of ResolvedConfiguration and friends //avoid traversing the graph causing the full ResolvedDependency graph to be loaded for the most typical scenario if (dependencySpec == Specs.SATISFIES_ALL) { ParallelResolveArtifactSet.wrap(artifactResults.getArtifacts(), buildOperationExecutor).visit(visitor); return; } List<ResolvedArtifactSet> artifactSets = new ArrayList<ResolvedArtifactSet>(); if (visitor.includeFiles()) { for (Map.Entry<FileCollectionDependency, ResolvedArtifactSet> entry : fileDependencyResults.getFirstLevelFiles().entrySet()) { if (dependencySpec.isSatisfiedBy(entry.getKey())) { artifactSets.add(entry.getValue()); } } } CachingDirectedGraphWalker<DependencyGraphNodeResult, ResolvedArtifact> walker = new CachingDirectedGraphWalker<DependencyGraphNodeResult, ResolvedArtifact>(new ResolvedDependencyArtifactsGraph(artifactResults, artifactSets)); DependencyGraphNodeResult rootNode = loadTransientGraphResults(artifactResults).getRootNode(); for (DependencyGraphNodeResult node : getFirstLevelNodes(dependencySpec)) { artifactSets.add(node.getArtifactsForIncomingEdge(rootNode)); walker.add(node); } walker.findValues(); ParallelResolveArtifactSet.wrap(CompositeArtifactSet.of(artifactSets), buildOperationExecutor).visit(visitor); } public ConfigurationInternal getConfiguration() { return configuration; } public Set<ResolvedDependency> getFirstLevelModuleDependencies() { return loadTransientGraphResults(getSelectedArtifacts()).getRootNode().getPublicView().getChildren(); } private static class LenientArtifactCollectingVisitor implements ArtifactVisitor { final Set<ResolvedArtifact> artifacts = Sets.newLinkedHashSet(); @Override public void visitArtifact(AttributeContainer variant, ResolvedArtifact artifact) { try { if (artifact.getId().getComponentIdentifier() instanceof ModuleComponentIdentifier) { // Trigger download // TODO - get rid of the special case, it's used as a side effect by the IDE plugins to avoid building the JAR for included builds artifact.getFile(); } artifacts.add(artifact); } catch (org.gradle.internal.resolve.ArtifactResolveException e) { //ignore } } @Override public boolean includeFiles() { return false; } @Override public boolean requireArtifactFiles() { return false; } @Override public void visitFailure(Throwable failure) { throw UncheckedException.throwAsUncheckedException(failure); } @Override public void visitFile(ComponentArtifactIdentifier artifactIdentifier, AttributeContainer variant, File file) { throw new UnsupportedOperationException(); } } private static class LenientFilesAndArtifactResolveVisitor implements ArtifactVisitor { final Set<File> files = Sets.newLinkedHashSet(); @Override public void visitArtifact(AttributeContainer variant, ResolvedArtifact artifact) { try { files.add(artifact.getFile()); } catch (org.gradle.internal.resolve.ArtifactResolveException e) { //ignore } } @Override public boolean requireArtifactFiles() { return false; } @Override public boolean includeFiles() { return true; } @Override public void visitFailure(Throwable failure) { throw UncheckedException.throwAsUncheckedException(failure); } @Override public void visitFile(ComponentArtifactIdentifier artifactIdentifier, AttributeContainer variant, File file) { files.add(file); } } public static class ArtifactResolveException extends ResolveException { private final String type; private final String displayName; public ArtifactResolveException(String type, String path, String displayName, Iterable<Throwable> failures) { super(path, failures); this.type = type; this.displayName = displayName; } // Need to override as error message is hardcoded in constructor of public type ResolveException @Override public String getMessage() { return String.format("Could not resolve all %s for %s.", type, displayName); } } private static class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<DependencyGraphNodeResult, ResolvedArtifact> { private final SelectedArtifactResults selectedArtifactResults; private final List<ResolvedArtifactSet> dest; ResolvedDependencyArtifactsGraph(SelectedArtifactResults selectedArtifactResults, List<ResolvedArtifactSet> dest) { this.selectedArtifactResults = selectedArtifactResults; this.dest = dest; } @Override public void getNodeValues(DependencyGraphNodeResult node, Collection<? super ResolvedArtifact> values, Collection<? super DependencyGraphNodeResult> connectedNodes) { connectedNodes.addAll(node.getOutgoingEdges()); dest.add(selectedArtifactResults.getArtifactsForNode(node.getNodeId())); } @Override public void getEdgeValues(DependencyGraphNodeResult from, DependencyGraphNodeResult to, Collection<ResolvedArtifact> values) { dest.add(to.getArtifactsForIncomingEdge(from)); } } }