/*
* 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.configurations;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.Describable;
import org.gradle.api.DomainObjectSet;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Nullable;
import org.gradle.api.artifacts.ArtifactCollection;
import org.gradle.api.artifacts.ArtifactView;
import org.gradle.api.artifacts.ConfigurablePublishArtifact;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationPublications;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencyResolutionListener;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.ExcludeRule;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.PublishArtifact;
import org.gradle.api.artifacts.PublishArtifactSet;
import org.gradle.api.artifacts.ResolutionStrategy;
import org.gradle.api.artifacts.ResolvableDependencies;
import org.gradle.api.artifacts.ResolveException;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.artifacts.result.ResolutionResult;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.AttributeContainer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.CompositeDomainObjectSet;
import org.gradle.api.internal.DefaultDomainObjectSet;
import org.gradle.api.internal.artifacts.ConfigurationResolver;
import org.gradle.api.internal.artifacts.DefaultDependencySet;
import org.gradle.api.internal.artifacts.DefaultExcludeRule;
import org.gradle.api.internal.artifacts.DefaultPublishArtifactSet;
import org.gradle.api.internal.artifacts.DefaultResolverResults;
import org.gradle.api.internal.artifacts.ExcludeRuleNotationConverter;
import org.gradle.api.internal.artifacts.Module;
import org.gradle.api.internal.artifacts.ResolverResults;
import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
import org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration;
import org.gradle.api.internal.artifacts.ivyservice.ResolvedArtifactCollectingVisitor;
import org.gradle.api.internal.artifacts.ivyservice.ResolvedFilesCollectingVisitor;
import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.RootComponentMetadataBuilder;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.BuildDependenciesVisitor;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.SelectedArtifactSet;
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedProjectConfiguration;
import org.gradle.api.internal.attributes.AttributeContainerInternal;
import org.gradle.api.internal.attributes.DefaultMutableAttributeContainer;
import org.gradle.api.internal.attributes.ImmutableAttributes;
import org.gradle.api.internal.attributes.ImmutableAttributesFactory;
import org.gradle.api.internal.file.AbstractFileCollection;
import org.gradle.api.internal.file.FileCollectionFactory;
import org.gradle.api.internal.file.FileCollectionInternal;
import org.gradle.api.internal.file.FileSystemSubset;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.internal.tasks.AbstractTaskDependency;
import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.api.tasks.TaskDependency;
import org.gradle.initialization.ProjectAccessListener;
import org.gradle.internal.Cast;
import org.gradle.internal.DisplayName;
import org.gradle.internal.Describables;
import org.gradle.internal.Factories;
import org.gradle.internal.Factory;
import org.gradle.internal.ImmutableActionSet;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.component.model.ComponentResolveMetadata;
import org.gradle.internal.event.ListenerBroadcast;
import org.gradle.internal.event.ListenerManager;
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 org.gradle.internal.reflect.Instantiator;
import org.gradle.internal.typeconversion.NotationParser;
import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
import org.gradle.util.CollectionUtils;
import org.gradle.util.Path;
import org.gradle.util.WrapUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.gradle.api.internal.artifacts.configurations.ConfigurationInternal.InternalState.*;
import static org.gradle.util.ConfigureUtil.configure;
public class DefaultConfiguration extends AbstractFileCollection implements ConfigurationInternal, MutationValidator {
private final ConfigurationResolver resolver;
private final ListenerManager listenerManager;
private final DependencyMetaDataProvider metaDataProvider;
private final DefaultDependencySet dependencies;
private final CompositeDomainObjectSet<Dependency> inheritedDependencies;
private final DefaultDependencySet allDependencies;
private ImmutableActionSet<DependencySet> defaultDependencyActions = ImmutableActionSet.empty();
private final DefaultPublishArtifactSet artifacts;
private final CompositeDomainObjectSet<PublishArtifact> inheritedArtifacts;
private final DefaultPublishArtifactSet allArtifacts;
private final ConfigurationResolvableDependencies resolvableDependencies;
private ListenerBroadcast<DependencyResolutionListener> dependencyResolutionListeners;
private final BuildOperationExecutor buildOperationExecutor;
private final Instantiator instantiator;
private final NotationParser<Object, ConfigurablePublishArtifact> artifactNotationParser;
private final ProjectAccessListener projectAccessListener;
private final ProjectFinder projectFinder;
private Factory<ResolutionStrategyInternal> resolutionStrategyFactory;
private ResolutionStrategyInternal resolutionStrategy;
private final FileCollectionFactory fileCollectionFactory;
private final Set<MutationValidator> childMutationValidators = Sets.newHashSet();
private final MutationValidator parentMutationValidator = new MutationValidator() {
@Override
public void validateMutation(MutationType type) {
DefaultConfiguration.this.validateParentMutation(type);
}
};
private final RootComponentMetadataBuilder rootComponentMetadataBuilder;
private final ConfigurationsProvider configurationsProvider;
private final Path identityPath;
// These fields are not covered by mutation lock
private final Path path;
private final String name;
private final DefaultConfigurationPublications outgoing;
private boolean visible = true;
private boolean transitive = true;
private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
private String description;
private Set<ExcludeRule> excludeRules = new LinkedHashSet<ExcludeRule>();
private final Object observationLock = new Object();
private InternalState observedState = UNRESOLVED;
private final Object resolutionLock = new Object();
private InternalState resolvedState = UNRESOLVED;
private boolean insideBeforeResolve;
private ResolverResults cachedResolverResults;
private boolean dependenciesModified;
private boolean canBeConsumed = true;
private boolean canBeResolved = true;
private AttributeContainerInternal configurationAttributes;
private final ImmutableAttributesFactory attributesFactory;
private final FileCollection intrinsicFiles;
private final DisplayName displayName;
public DefaultConfiguration(final Path identityPath, Path path, String name,
ConfigurationsProvider configurationsProvider,
ConfigurationResolver resolver,
ListenerManager listenerManager,
DependencyMetaDataProvider metaDataProvider,
Factory<ResolutionStrategyInternal> resolutionStrategyFactory,
ProjectAccessListener projectAccessListener,
ProjectFinder projectFinder,
FileCollectionFactory fileCollectionFactory,
BuildOperationExecutor buildOperationExecutor,
Instantiator instantiator,
NotationParser<Object, ConfigurablePublishArtifact> artifactNotationParser,
ImmutableAttributesFactory attributesFactory,
RootComponentMetadataBuilder rootComponentMetadataBuilder) {
this.identityPath = identityPath;
this.path = path;
this.name = name;
this.configurationsProvider = configurationsProvider;
this.resolver = resolver;
this.listenerManager = listenerManager;
this.metaDataProvider = metaDataProvider;
this.resolutionStrategyFactory = resolutionStrategyFactory;
this.projectAccessListener = projectAccessListener;
this.projectFinder = projectFinder;
this.fileCollectionFactory = fileCollectionFactory;
this.dependencyResolutionListeners = listenerManager.createAnonymousBroadcaster(DependencyResolutionListener.class);
this.buildOperationExecutor = buildOperationExecutor;
this.instantiator = instantiator;
this.artifactNotationParser = artifactNotationParser;
this.attributesFactory = attributesFactory;
this.configurationAttributes = new DefaultMutableAttributeContainer(attributesFactory);
this.intrinsicFiles = new ConfigurationFileCollection(Specs.<Dependency>satisfyAll());
this.resolvableDependencies = instantiator.newInstance(ConfigurationResolvableDependencies.class, this);
displayName = Describables.memoize(new ConfigurationDescription(identityPath));
DefaultDomainObjectSet<Dependency> ownDependencies = new DefaultDomainObjectSet<Dependency>(Dependency.class);
ownDependencies.beforeChange(validateMutationType(this, MutationType.DEPENDENCIES));
this.dependencies = new DefaultDependencySet(Describables.of(displayName, "dependencies"), this, ownDependencies);
this.inheritedDependencies = CompositeDomainObjectSet.create(Dependency.class, ownDependencies);
this.allDependencies = new DefaultDependencySet(Describables.of(displayName, "all dependencies"), this, inheritedDependencies);
DefaultDomainObjectSet<PublishArtifact> ownArtifacts = new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact.class);
ownArtifacts.beforeChange(validateMutationType(this, MutationType.ARTIFACTS));
this.artifacts = new DefaultPublishArtifactSet(Describables.of(displayName, "artifacts"), ownArtifacts, fileCollectionFactory);
this.inheritedArtifacts = CompositeDomainObjectSet.create(PublishArtifact.class, ownArtifacts);
this.allArtifacts = new DefaultPublishArtifactSet(Describables.of(displayName, "all artifacts"), inheritedArtifacts, fileCollectionFactory);
this.outgoing = instantiator.newInstance(DefaultConfigurationPublications.class, displayName, artifacts, allArtifacts, configurationAttributes, instantiator, artifactNotationParser, fileCollectionFactory, attributesFactory);
this.rootComponentMetadataBuilder = rootComponentMetadataBuilder;
}
private static Action<Void> validateMutationType(final MutationValidator mutationValidator, final MutationType type) {
return new Action<Void>() {
@Override
public void execute(Void arg) {
mutationValidator.validateMutation(type);
}
};
}
public String getName() {
return name;
}
public State getState() {
synchronized (resolutionLock) {
if (resolvedState == ARTIFACTS_RESOLVED || resolvedState == GRAPH_RESOLVED) {
if (cachedResolverResults.hasError()) {
return State.RESOLVED_WITH_FAILURES;
} else {
return State.RESOLVED;
}
} else {
return State.UNRESOLVED;
}
}
}
public InternalState getResolvedState() {
return resolvedState;
}
public Module getModule() {
return metaDataProvider.getModule();
}
public boolean isVisible() {
return visible;
}
public Configuration setVisible(boolean visible) {
validateMutation(MutationType.DEPENDENCIES);
this.visible = visible;
return this;
}
public Set<Configuration> getExtendsFrom() {
return Collections.unmodifiableSet(extendsFrom);
}
public Configuration setExtendsFrom(Iterable<Configuration> extendsFrom) {
validateMutation(MutationType.DEPENDENCIES);
for (Configuration configuration : this.extendsFrom) {
inheritedArtifacts.removeCollection(configuration.getAllArtifacts());
inheritedDependencies.removeCollection(configuration.getAllDependencies());
((ConfigurationInternal) configuration).removeMutationValidator(parentMutationValidator);
}
this.extendsFrom = new LinkedHashSet<Configuration>();
for (Configuration configuration : extendsFrom) {
extendsFrom(configuration);
}
return this;
}
public Configuration extendsFrom(Configuration... extendsFrom) {
validateMutation(MutationType.DEPENDENCIES);
for (Configuration configuration : extendsFrom) {
if (configuration.getHierarchy().contains(this)) {
throw new InvalidUserDataException(String.format(
"Cyclic extendsFrom from %s and %s is not allowed. See existing hierarchy: %s", this,
configuration, configuration.getHierarchy()));
}
if (this.extendsFrom.add(configuration)) {
inheritedArtifacts.addCollection(configuration.getAllArtifacts());
inheritedDependencies.addCollection(configuration.getAllDependencies());
((ConfigurationInternal) configuration).addMutationValidator(parentMutationValidator);
}
}
return this;
}
public boolean isTransitive() {
return transitive;
}
public Configuration setTransitive(boolean transitive) {
validateMutation(MutationType.DEPENDENCIES);
this.transitive = transitive;
return this;
}
public String getDescription() {
return description;
}
public Configuration setDescription(String description) {
this.description = description;
return this;
}
public Set<Configuration> getHierarchy() {
if (extendsFrom.isEmpty()) {
return Collections.<Configuration>singleton(this);
}
Set<Configuration> result = WrapUtil.<Configuration>toLinkedSet(this);
collectSuperConfigs(this, result);
return result;
}
private void collectSuperConfigs(Configuration configuration, Set<Configuration> result) {
for (Configuration superConfig : configuration.getExtendsFrom()) {
if (result.contains(superConfig)) {
result.remove(superConfig);
}
result.add(superConfig);
collectSuperConfigs(superConfig, result);
}
}
@Override
public Configuration defaultDependencies(final Action<? super DependencySet> action) {
validateMutation(MutationType.DEPENDENCIES);
defaultDependencyActions = defaultDependencyActions.add(new Action<DependencySet>() {
@Override
public void execute(DependencySet dependencies) {
if (dependencies.isEmpty()) {
action.execute(dependencies);
}
}
});
return this;
}
@Override
public void triggerWhenEmptyActionsIfNecessary() {
if (dependencies.isEmpty()) {
defaultDependencyActions.execute(dependencies);
}
// Discard actions
defaultDependencyActions = ImmutableActionSet.empty();
for (Configuration superConfig : extendsFrom) {
((ConfigurationInternal) superConfig).triggerWhenEmptyActionsIfNecessary();
}
}
public Set<Configuration> getAll() {
return ImmutableSet.<Configuration>copyOf(configurationsProvider.getAll());
}
public Set<File> resolve() {
return getFiles();
}
public Set<File> getFiles() {
return intrinsicFiles.getFiles();
}
public Set<File> files(Dependency... dependencies) {
return fileCollection(dependencies).getFiles();
}
public Set<File> files(Closure dependencySpecClosure) {
return fileCollection(dependencySpecClosure).getFiles();
}
public Set<File> files(Spec<? super Dependency> dependencySpec) {
return fileCollection(dependencySpec).getFiles();
}
public FileCollection fileCollection(Spec<? super Dependency> dependencySpec) {
return new ConfigurationFileCollection(dependencySpec);
}
public FileCollection fileCollection(Closure dependencySpecClosure) {
return new ConfigurationFileCollection(dependencySpecClosure);
}
public FileCollection fileCollection(Dependency... dependencies) {
return new ConfigurationFileCollection(WrapUtil.toLinkedSet(dependencies));
}
public void markAsObserved(InternalState requestedState) {
markThisObserved(requestedState);
markParentsObserved(requestedState);
}
private void markThisObserved(InternalState requestedState) {
synchronized (observationLock) {
if (observedState.compareTo(requestedState) < 0) {
observedState = requestedState;
}
}
}
private void markParentsObserved(InternalState requestedState) {
for (Configuration configuration : extendsFrom) {
((ConfigurationInternal) configuration).markAsObserved(requestedState);
}
}
public ResolvedConfiguration getResolvedConfiguration() {
resolveToStateOrLater(ARTIFACTS_RESOLVED);
return cachedResolverResults.getResolvedConfiguration();
}
private void resolveToStateOrLater(InternalState requestedState) {
assertResolvingAllowed();
synchronized (resolutionLock) {
if (requestedState == GRAPH_RESOLVED || requestedState == ARTIFACTS_RESOLVED) {
resolveGraphIfRequired(requestedState);
}
if (requestedState == ARTIFACTS_RESOLVED) {
resolveArtifactsIfRequired();
}
}
}
private void resolveGraphIfRequired(final InternalState requestedState) {
if (resolvedState == ARTIFACTS_RESOLVED) {
if (dependenciesModified) {
throw new InvalidUserDataException(String.format("Attempted to resolve %s that has been resolved previously.", getDisplayName()));
}
return;
}
if (resolvedState == GRAPH_RESOLVED) {
if (!dependenciesModified) {
return;
}
throw new InvalidUserDataException(String.format("Resolved %s again after modification", getDisplayName()));
}
if (resolvedState != UNRESOLVED) {
throw new IllegalStateException("Graph resolution already performed");
}
buildOperationExecutor.run(new RunnableBuildOperation() {
@Override
public void run(BuildOperationContext context) {
lockAttributes();
ResolvableDependencies incoming = getIncoming();
performPreResolveActions(incoming);
cachedResolverResults = new DefaultResolverResults();
resolver.resolveGraph(DefaultConfiguration.this, cachedResolverResults);
dependenciesModified = false;
resolvedState = GRAPH_RESOLVED;
// Mark all affected configurations as observed
markParentsObserved(requestedState);
markReferencedProjectConfigurationsObserved(requestedState);
dependencyResolutionListeners.getSource().afterResolve(incoming);
// Discard listeners
dependencyResolutionListeners.removeAll();
}
@Override
public BuildOperationDescriptor.Builder description() {
return BuildOperationDescriptor.displayName("Resolve dependencies of " + identityPath);
}
});
}
private void performPreResolveActions(ResolvableDependencies incoming) {
DependencyResolutionListener dependencyResolutionListener = dependencyResolutionListeners.getSource();
insideBeforeResolve = true;
try {
dependencyResolutionListener.beforeResolve(incoming);
} finally {
insideBeforeResolve = false;
}
triggerWhenEmptyActionsIfNecessary();
}
private void markReferencedProjectConfigurationsObserved(final InternalState requestedState) {
for (ResolvedProjectConfiguration projectResult : cachedResolverResults.getResolvedLocalComponents().getResolvedProjectConfigurations()) {
if (projectResult.getId().getBuild().isCurrentBuild()) {
ProjectInternal project = projectFinder.getProject(projectResult.getId().getProjectPath());
ConfigurationInternal targetConfig = (ConfigurationInternal) project.getConfigurations().getByName(projectResult.getTargetConfiguration());
targetConfig.markAsObserved(requestedState);
}
}
}
private void resolveArtifactsIfRequired() {
if (resolvedState == ARTIFACTS_RESOLVED) {
return;
}
if (resolvedState != GRAPH_RESOLVED) {
throw new IllegalStateException("Cannot resolve artifacts before graph has been resolved.");
}
resolver.resolveArtifacts(DefaultConfiguration.this, cachedResolverResults);
resolvedState = ARTIFACTS_RESOLVED;
}
public TaskDependency getBuildDependencies() {
assertResolvingAllowed();
return intrinsicFiles.getBuildDependencies();
}
/**
* {@inheritDoc}
*/
public TaskDependency getTaskDependencyFromProjectDependency(final boolean useDependedOn, final String taskName) {
if (useDependedOn) {
return new TasksFromProjectDependencies(taskName, getAllDependencies(), projectAccessListener);
} else {
return new TasksFromDependentProjects(taskName, getName());
}
}
public DependencySet getDependencies() {
return dependencies;
}
public DependencySet getAllDependencies() {
return allDependencies;
}
public PublishArtifactSet getArtifacts() {
return artifacts;
}
public PublishArtifactSet getAllArtifacts() {
return allArtifacts;
}
public Set<ExcludeRule> getExcludeRules() {
return Collections.unmodifiableSet(excludeRules);
}
public void setExcludeRules(Set<ExcludeRule> excludeRules) {
validateMutation(MutationType.DEPENDENCIES);
this.excludeRules = excludeRules;
}
public DefaultConfiguration exclude(Map<String, String> excludeRuleArgs) {
validateMutation(MutationType.DEPENDENCIES);
excludeRules.add(ExcludeRuleNotationConverter.parser().parseNotation(excludeRuleArgs)); //TODO SF try using ExcludeRuleContainer
return this;
}
public String getUploadTaskName() {
return Configurations.uploadTaskName(getName());
}
public String getDisplayName() {
return displayName.getDisplayName();
}
public ResolvableDependencies getIncoming() {
return resolvableDependencies;
}
@Override
public ConfigurationPublications getOutgoing() {
return outgoing;
}
@Override
public OutgoingVariant convertToOutgoingVariant() {
return outgoing.convertToOutgoingVariant();
}
@Override
public void lockAttributes() {
AttributeContainerInternal delegatee = configurationAttributes.asImmutable();
configurationAttributes = new AttributeContainerWithErrorMessage(delegatee);
}
@Override
public void outgoing(Action<? super ConfigurationPublications> action) {
action.execute(outgoing);
}
public ConfigurationInternal copy() {
return createCopy(getDependencies(), false);
}
public Configuration copyRecursive() {
return createCopy(getAllDependencies(), true);
}
public Configuration copy(Spec<? super Dependency> dependencySpec) {
return createCopy(CollectionUtils.filter(getDependencies(), dependencySpec), false);
}
public Configuration copyRecursive(Spec<? super Dependency> dependencySpec) {
return createCopy(CollectionUtils.filter(getAllDependencies(), dependencySpec), true);
}
private DefaultConfiguration createCopy(Set<Dependency> dependencies, boolean recursive) {
DetachedConfigurationsProvider configurationsProvider = new DetachedConfigurationsProvider();
RootComponentMetadataBuilder rootComponentMetadataBuilder = this.rootComponentMetadataBuilder.withConfigurationsProvider(configurationsProvider);
String newName = name + "Copy";
Path newIdentityPath = identityPath.getParent().child(newName);
Path newPath = path.getParent().child(newName);
Factory<ResolutionStrategyInternal> childResolutionStrategy = resolutionStrategy != null ? Factories.constant(resolutionStrategy.copy()) : resolutionStrategyFactory;
DefaultConfiguration copiedConfiguration = instantiator.newInstance(DefaultConfiguration.class, newIdentityPath, newPath, newName,
configurationsProvider, resolver, listenerManager, metaDataProvider, childResolutionStrategy, projectAccessListener, projectFinder, fileCollectionFactory, buildOperationExecutor, instantiator, artifactNotationParser, attributesFactory,
rootComponentMetadataBuilder);
configurationsProvider.setTheOnlyConfiguration(copiedConfiguration);
// state, cachedResolvedConfiguration, and extendsFrom intentionally not copied - must re-resolve copy
// copying extendsFrom could mess up dependencies when copy was re-resolved
copiedConfiguration.visible = visible;
copiedConfiguration.transitive = transitive;
copiedConfiguration.description = description;
copiedConfiguration.defaultDependencyActions = defaultDependencyActions;
copiedConfiguration.dependencyResolutionListeners = dependencyResolutionListeners;
copiedConfiguration.canBeConsumed = canBeConsumed;
copiedConfiguration.canBeResolved = canBeResolved;
copiedConfiguration.getArtifacts().addAll(getAllArtifacts());
if (!configurationAttributes.isEmpty()) {
for (Attribute<?> attribute : configurationAttributes.keySet()) {
Object value = configurationAttributes.getAttribute(attribute);
copiedConfiguration.getAttributes().attribute(Cast.<Attribute<Object>>uncheckedCast(attribute), value);
}
}
// todo An ExcludeRule is a value object but we don't enforce immutability for DefaultExcludeRule as strong as we
// should (we expose the Map). We should provide a better API for ExcludeRule (I don't want to use unmodifiable Map).
// As soon as DefaultExcludeRule is truly immutable, we don't need to create a new instance of DefaultExcludeRule.
Set<Configuration> excludeRuleSources = new LinkedHashSet<Configuration>();
excludeRuleSources.add(this);
if (recursive) {
excludeRuleSources.addAll(getHierarchy());
}
for (Configuration excludeRuleSource : excludeRuleSources) {
for (ExcludeRule excludeRule : excludeRuleSource.getExcludeRules()) {
copiedConfiguration.excludeRules.add(new DefaultExcludeRule(excludeRule.getGroup(), excludeRule.getModule()));
}
}
DomainObjectSet<Dependency> copiedDependencies = copiedConfiguration.getDependencies();
for (Dependency dependency : dependencies) {
copiedDependencies.add(dependency.copy());
}
return copiedConfiguration;
}
public Configuration copy(Closure dependencySpec) {
return copy(Specs.<Dependency>convertClosureToSpec(dependencySpec));
}
public Configuration copyRecursive(Closure dependencySpec) {
return copyRecursive(Specs.<Dependency>convertClosureToSpec(dependencySpec));
}
public ResolutionStrategyInternal getResolutionStrategy() {
if (resolutionStrategy == null) {
resolutionStrategy = resolutionStrategyFactory.create();
resolutionStrategy.setMutationValidator(this);
resolutionStrategyFactory = null;
}
return resolutionStrategy;
}
public ComponentResolveMetadata toRootComponentMetaData() {
return rootComponentMetadataBuilder.toRootComponentMetaData();
}
public String getPath() {
return path.getPath();
}
@Override
public Configuration resolutionStrategy(Closure closure) {
configure(closure, getResolutionStrategy());
return this;
}
@Override
public Configuration resolutionStrategy(Action<? super ResolutionStrategy> action) {
action.execute(getResolutionStrategy());
return this;
}
@Override
public void addMutationValidator(MutationValidator validator) {
childMutationValidators.add(validator);
}
@Override
public void removeMutationValidator(MutationValidator validator) {
childMutationValidators.remove(validator);
}
private void validateParentMutation(MutationType type) {
// Strategy changes in a parent configuration do not affect this configuration, or any of its children, in any way
if (type == MutationType.STRATEGY) {
return;
}
if (resolvedState == ARTIFACTS_RESOLVED) {
throw new InvalidUserDataException(String.format("Cannot change %s of parent of %s after it has been resolved", type, getDisplayName()));
} else if (resolvedState == GRAPH_RESOLVED) {
if (type == MutationType.DEPENDENCIES) {
throw new InvalidUserDataException(String.format("Cannot change %s of parent of %s after task dependencies have been resolved", type, getDisplayName()));
}
}
markAsModifiedAndNotifyChildren(type);
}
public void validateMutation(MutationType type) {
if (resolvedState == ARTIFACTS_RESOLVED) {
// The public result for the configuration has been calculated.
// It is an error to change anything that would change the dependencies or artifacts
throw new InvalidUserDataException(String.format("Cannot change %s of %s after it has been resolved.", type, getDisplayName()));
} else if (resolvedState == GRAPH_RESOLVED) {
// The task dependencies for the configuration have been calculated using Configuration.getBuildDependencies().
throw new InvalidUserDataException(String.format("Cannot change %s of %s after task dependencies have been resolved", type, getDisplayName()));
} else if (observedState == GRAPH_RESOLVED || observedState == ARTIFACTS_RESOLVED) {
// The configuration has been used in a resolution, and it is an error for build logic to change any dependencies,
// exclude rules or parent configurations (values that will affect the resolved graph).
if (type != MutationType.STRATEGY) {
String extraMessage = insideBeforeResolve ? " Use 'defaultDependencies' instead of 'beforeResolve' to specify default dependencies for a configuration." : "";
throw new InvalidUserDataException(String.format("Cannot change %s of %s after it has been included in dependency resolution.%s", type, getDisplayName(), extraMessage));
}
}
markAsModifiedAndNotifyChildren(type);
}
private void markAsModifiedAndNotifyChildren(MutationType type) {
// Notify child configurations
for (MutationValidator validator : childMutationValidators) {
validator.validateMutation(type);
}
if (type != MutationType.STRATEGY) {
dependenciesModified = true;
}
}
private static class ConfigurationDescription implements Describable {
private final Path identityPath;
ConfigurationDescription(Path identityPath) {
this.identityPath = identityPath;
}
@Override
public String getDisplayName() {
return "configuration '" + identityPath + "'";
}
}
private class ConfigurationFileCollection extends AbstractFileCollection {
private final Spec<? super Dependency> dependencySpec;
private final AttributeContainerInternal viewAttributes;
private final Spec<? super ComponentIdentifier> componentSpec;
private final boolean lenient;
private final boolean allowNoMatchingVariants;
private SelectedArtifactSet selectedArtifacts;
private ConfigurationFileCollection(Spec<? super Dependency> dependencySpec) {
assertResolvingAllowed();
this.dependencySpec = dependencySpec;
this.viewAttributes = configurationAttributes;
this.componentSpec = Specs.satisfyAll();
lenient = false;
allowNoMatchingVariants = false;
}
private ConfigurationFileCollection(Spec<? super Dependency> dependencySpec, AttributeContainerInternal viewAttributes,
Spec<? super ComponentIdentifier> componentSpec, boolean lenient, boolean allowNoMatchingVariants) {
this.dependencySpec = dependencySpec;
this.viewAttributes = viewAttributes.asImmutable();
this.componentSpec = componentSpec;
this.lenient = lenient;
this.allowNoMatchingVariants = allowNoMatchingVariants;
}
private ConfigurationFileCollection(Closure dependencySpecClosure) {
this(Specs.convertClosureToSpec(dependencySpecClosure));
}
private ConfigurationFileCollection(final Set<Dependency> dependencies) {
this(new Spec<Dependency>() {
public boolean isSatisfiedBy(Dependency element) {
return dependencies.contains(element);
}
});
}
@Override
public TaskDependency getBuildDependencies() {
assertResolvingAllowed();
return new ConfigurationTaskDependency(dependencySpec, viewAttributes, componentSpec, allowNoMatchingVariants);
}
public Spec<? super Dependency> getDependencySpec() {
return dependencySpec;
}
public String getDisplayName() {
return DefaultConfiguration.this.getDisplayName();
}
public Set<File> getFiles() {
ResolvedFilesCollectingVisitor visitor = new ResolvedFilesCollectingVisitor();
getSelectedArtifacts().visitArtifacts(visitor);
if (!lenient) {
rethrowFailure("files", visitor.getFailures());
}
return visitor.getFiles();
}
private SelectedArtifactSet getSelectedArtifacts() {
assertResolvingAllowed();
if (selectedArtifacts == null) {
resolveToStateOrLater(ARTIFACTS_RESOLVED);
selectedArtifacts = cachedResolverResults.getVisitedArtifacts().select(dependencySpec, viewAttributes, componentSpec, allowNoMatchingVariants);
}
return selectedArtifacts;
}
}
private void rethrowFailure(String type, Collection<Throwable> failures) {
if (failures.isEmpty()) {
return;
}
if (failures.size() == 1) {
Throwable failure = failures.iterator().next();
if (failure instanceof ResolveException) {
throw UncheckedException.throwAsUncheckedException(failure);
}
}
throw new DefaultLenientConfiguration.ArtifactResolveException(type, getPath(), getDisplayName(), failures);
}
private void assertResolvingAllowed() {
if (!canBeResolved) {
throw new IllegalStateException("Resolving configuration '" + name + "' directly is not allowed");
}
}
@Override
public void registerWatchPoints(FileSystemSubset.Builder builder) {
for (Dependency dependency : allDependencies) {
if (dependency instanceof FileCollectionDependency) {
FileCollection files = ((FileCollectionDependency) dependency).getFiles();
((FileCollectionInternal) files).registerWatchPoints(builder);
}
}
super.registerWatchPoints(builder);
}
@Override
public AttributeContainerInternal getAttributes() {
return configurationAttributes;
}
@Override
public Configuration attributes(Action<? super AttributeContainer> action) {
action.execute(configurationAttributes);
return this;
}
@Override
public boolean isCanBeConsumed() {
return canBeConsumed;
}
@Override
public void setCanBeConsumed(boolean allowed) {
validateMutation(MutationType.ROLE);
canBeConsumed = allowed;
}
@Override
public boolean isCanBeResolved() {
return canBeResolved;
}
@Override
public void setCanBeResolved(boolean allowed) {
validateMutation(MutationType.ROLE);
canBeResolved = allowed;
}
/**
* Print a formatted representation of a Configuration
*/
public String dump() {
StringBuilder reply = new StringBuilder();
reply.append("\nConfiguration:");
reply.append(" class='" + this.getClass() + "'");
reply.append(" name='" + this.getName() + "'");
reply.append(" hashcode='" + this.hashCode() + "'");
reply.append("\nLocal Dependencies:");
if (getDependencies().size() > 0) {
for (Dependency d : getDependencies()) {
reply.append("\n " + d);
}
} else {
reply.append("\n none");
}
reply.append("\nLocal Artifacts:");
if (getArtifacts().size() > 0) {
for (PublishArtifact a : getArtifacts()) {
reply.append("\n " + a);
}
} else {
reply.append("\n none");
}
reply.append("\nAll Dependencies:");
if (getAllDependencies().size() > 0) {
for (Dependency d : getAllDependencies()) {
reply.append("\n " + d);
}
} else {
reply.append("\n none");
}
reply.append("\nAll Artifacts:");
if (getAllArtifacts().size() > 0) {
for (PublishArtifact a : getAllArtifacts()) {
reply.append("\n " + a);
}
} else {
reply.append("\n none");
}
return reply.toString();
}
public class ConfigurationResolvableDependencies implements ResolvableDependencies {
public String getName() {
return name;
}
public String getPath() {
return path.getPath();
}
@Override
public String toString() {
return "dependencies '" + path + "'";
}
public FileCollection getFiles() {
return new ConfigurationFileCollection(Specs.<Dependency>satisfyAll());
}
public DependencySet getDependencies() {
return getAllDependencies();
}
public void beforeResolve(Action<? super ResolvableDependencies> action) {
dependencyResolutionListeners.add("beforeResolve", action);
}
public void beforeResolve(Closure action) {
dependencyResolutionListeners.add(new ClosureBackedMethodInvocationDispatch("beforeResolve", action));
}
public void afterResolve(Action<? super ResolvableDependencies> action) {
dependencyResolutionListeners.add("afterResolve", action);
}
public void afterResolve(Closure action) {
dependencyResolutionListeners.add(new ClosureBackedMethodInvocationDispatch("afterResolve", action));
}
public ResolutionResult getResolutionResult() {
DefaultConfiguration.this.resolveToStateOrLater(ARTIFACTS_RESOLVED);
return DefaultConfiguration.this.cachedResolverResults.getResolutionResult();
}
@Override
public ArtifactCollection getArtifacts() {
return new ConfigurationArtifactCollection();
}
@Override
public ArtifactView artifactView(Action<? super ArtifactView.ViewConfiguration> configAction) {
ArtifactViewConfiguration config = createArtifactViewConfiguration();
configAction.execute(config);
return createArtifactView(config);
}
private ArtifactView createArtifactView(ArtifactViewConfiguration config) {
ImmutableAttributes viewAttributes = config.lockViewAttributes();
// This is a little coincidental: if view attributes have not been accessed, don't allow no matching variants
boolean allowNoMatchingVariants = config.attributesUsed;
return new ConfigurationArtifactView(viewAttributes, config.lockComponentFilter(), config.lenient, allowNoMatchingVariants);
}
private DefaultConfiguration.ArtifactViewConfiguration createArtifactViewConfiguration() {
return instantiator.newInstance(ArtifactViewConfiguration.class, attributesFactory, configurationAttributes);
}
@Override
public AttributeContainer getAttributes() {
return configurationAttributes;
}
private class ConfigurationArtifactView implements ArtifactView {
private final ImmutableAttributes viewAttributes;
private final Spec<? super ComponentIdentifier> componentFilter;
private final boolean lenient;
private final boolean allowNoMatchingVariants;
ConfigurationArtifactView(ImmutableAttributes viewAttributes, Spec<? super ComponentIdentifier> componentFilter, boolean lenient, boolean allowNoMatchingVariants) {
this.viewAttributes = viewAttributes;
this.componentFilter = componentFilter;
this.lenient = lenient;
this.allowNoMatchingVariants = allowNoMatchingVariants;
}
@Override
public AttributeContainer getAttributes() {
return viewAttributes;
}
@Override
public ArtifactCollection getArtifacts() {
return new ConfigurationArtifactCollection(viewAttributes, componentFilter, lenient, allowNoMatchingVariants);
}
@Override
public FileCollection getFiles() {
return new ConfigurationFileCollection(Specs.<Dependency>satisfyAll(), viewAttributes, componentFilter, lenient, allowNoMatchingVariants);
}
}
}
public static class ArtifactViewConfiguration implements ArtifactView.ViewConfiguration {
private final ImmutableAttributesFactory attributesFactory;
private final AttributeContainerInternal configurationAttributes;
private AttributeContainerInternal viewAttributes;
private Spec<? super ComponentIdentifier> componentFilter;
private boolean lenient;
private boolean attributesUsed;
public ArtifactViewConfiguration(ImmutableAttributesFactory attributesFactory, AttributeContainerInternal configurationAttributes) {
this.attributesFactory = attributesFactory;
this.configurationAttributes = configurationAttributes;
}
@Override
public AttributeContainer getAttributes() {
if (viewAttributes == null) {
viewAttributes = new DefaultMutableAttributeContainer(attributesFactory, configurationAttributes);
attributesUsed = true;
}
return viewAttributes;
}
@Override
public ArtifactViewConfiguration attributes(Action<? super AttributeContainer> action) {
action.execute(getAttributes());
return this;
}
@Override
public ArtifactViewConfiguration componentFilter(Spec<? super ComponentIdentifier> componentFilter) {
assertComponentFilterUnset();
this.componentFilter = componentFilter;
return this;
}
@Override
public ArtifactViewConfiguration lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
private void assertComponentFilterUnset() {
if (componentFilter != null) {
throw new IllegalStateException("The component filter can only be set once before the view was computed");
}
}
private Spec<? super ComponentIdentifier> lockComponentFilter() {
if (componentFilter == null) {
componentFilter = Specs.satisfyAll();
}
return componentFilter;
}
private ImmutableAttributes lockViewAttributes() {
if (viewAttributes == null) {
viewAttributes = configurationAttributes.asImmutable();
} else {
viewAttributes = viewAttributes.asImmutable();
}
return viewAttributes.asImmutable();
}
}
private class ConfigurationArtifactCollection implements ArtifactCollection {
private final ConfigurationFileCollection fileCollection;
private final AttributeContainerInternal viewAttributes;
private final Spec<? super ComponentIdentifier> componentFilter;
private final boolean lenient;
private Set<ResolvedArtifactResult> artifactResults;
private Set<Throwable> failures;
ConfigurationArtifactCollection() {
this(configurationAttributes, Specs.<ComponentIdentifier>satisfyAll(), false, false);
}
ConfigurationArtifactCollection(AttributeContainerInternal attributes, Spec<? super ComponentIdentifier> componentFilter, boolean lenient, boolean allowNoMatchingVariants) {
assertResolvingAllowed();
this.viewAttributes = attributes.asImmutable();
this.componentFilter = componentFilter;
this.fileCollection = new ConfigurationFileCollection(Specs.<Dependency>satisfyAll(), viewAttributes, this.componentFilter, lenient, allowNoMatchingVariants);
this.lenient = lenient;
}
@Override
public FileCollection getArtifactFiles() {
return fileCollection;
}
@Override
public Set<ResolvedArtifactResult> getArtifacts() {
ensureResolved();
return artifactResults;
}
@Override
public Iterator<ResolvedArtifactResult> iterator() {
ensureResolved();
return artifactResults.iterator();
}
@Override
public Collection<Throwable> getFailures() {
ensureResolved();
return failures;
}
private synchronized void ensureResolved() {
if (artifactResults != null) {
return;
}
ResolvedArtifactCollectingVisitor visitor = new ResolvedArtifactCollectingVisitor();
fileCollection.getSelectedArtifacts().visitArtifacts(visitor);
artifactResults = visitor.getArtifacts();
failures = visitor.getFailures();
if (!lenient) {
rethrowFailure("artifacts", failures);
}
}
}
private class ConfigurationTaskDependency extends AbstractTaskDependency {
private final Spec<? super Dependency> dependencySpec;
private final AttributeContainerInternal requestedAttributes;
private final Spec<? super ComponentIdentifier> componentIdentifierSpec;
private final boolean allowNoMatchingVariants;
ConfigurationTaskDependency(Spec<? super Dependency> dependencySpec, AttributeContainerInternal requestedAttributes, Spec<? super ComponentIdentifier> componentIdentifierSpec, boolean allowNoMatchingVariants) {
this.dependencySpec = dependencySpec;
this.requestedAttributes = requestedAttributes;
this.componentIdentifierSpec = componentIdentifierSpec;
this.allowNoMatchingVariants = allowNoMatchingVariants;
}
@Override
public void visitDependencies(final TaskDependencyResolveContext context) {
synchronized (resolutionLock) {
if (getResolutionStrategy().resolveGraphToDetermineTaskDependencies()) {
// Force graph resolution as this is required to calculate build dependencies
resolveToStateOrLater(GRAPH_RESOLVED);
}
ResolverResults results;
if (getState() == State.UNRESOLVED) {
// Traverse graph
results = new DefaultResolverResults();
resolver.resolveBuildDependencies(DefaultConfiguration.this, results);
} else {
// Otherwise, already have a result, so reuse it
results = cachedResolverResults;
}
final List<Throwable> failures = new ArrayList<Throwable>();
results.getVisitedArtifacts().select(dependencySpec, requestedAttributes, componentIdentifierSpec, allowNoMatchingVariants).collectBuildDependencies(new BuildDependenciesVisitor() {
@Override
public void visitDependency(Object dep) {
context.add(dep);
}
@Override
public void visitFailure(Throwable failure) {
failures.add(failure);
}
});
rethrowFailure("task dependencies", failures);
}
}
}
private class AttributeContainerWithErrorMessage implements AttributeContainerInternal {
private final AttributeContainerInternal delegate;
AttributeContainerWithErrorMessage(AttributeContainerInternal delegate) {
this.delegate = delegate;
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public ImmutableAttributes asImmutable() {
return delegate.asImmutable();
}
@Override
public AttributeContainerInternal copy() {
return delegate.copy();
}
@Override
public Set<Attribute<?>> keySet() {
return delegate.keySet();
}
@Override
public <T> AttributeContainer attribute(Attribute<T> key, T value) {
throw new IllegalArgumentException(String.format("Cannot change attributes of %s after it has been resolved", getDisplayName()));
}
@Nullable
@Override
public <T> T getAttribute(Attribute<T> key) {
return delegate.getAttribute(key);
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean contains(Attribute<?> key) {
return delegate.contains(key);
}
@Override
public AttributeContainer getAttributes() {
return delegate.getAttributes();
}
}
}