/* * Copyright 2016 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.transform; import com.google.common.io.Files; import org.gradle.api.Buildable; import org.gradle.api.Transformer; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.internal.artifacts.DefaultResolvedArtifact; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ArtifactVisitor; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.BrokenResolvedArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.BuildDependenciesVisitor; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvedArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvedVariant; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvedVariantSet; import org.gradle.api.internal.attributes.AttributeContainerInternal; import org.gradle.api.internal.attributes.AttributesSchemaInternal; import org.gradle.api.tasks.TaskDependency; import org.gradle.internal.Pair; import org.gradle.internal.component.AmbiguousVariantSelectionException; import org.gradle.internal.component.NoMatchingVariantSelectionException; import org.gradle.internal.component.local.model.ComponentFileArtifactIdentifier; import org.gradle.internal.component.model.AttributeMatcher; import org.gradle.internal.component.model.DefaultIvyArtifactName; import org.gradle.internal.component.model.IvyArtifactName; import org.gradle.internal.operations.BuildOperationContext; import org.gradle.internal.operations.BuildOperationQueue; import org.gradle.internal.operations.RunnableBuildOperation; import org.gradle.internal.progress.BuildOperationDescriptor; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class DefaultArtifactTransforms implements ArtifactTransforms { private final VariantAttributeMatchingCache matchingCache; private final AttributesSchemaInternal schema; public DefaultArtifactTransforms(VariantAttributeMatchingCache matchingCache, AttributesSchemaInternal schema) { this.matchingCache = matchingCache; this.schema = schema; } public VariantSelector variantSelector(AttributeContainerInternal consumerAttributes, boolean allowNoMatchingVariants) { return new AttributeMatchingVariantSelector(matchingCache, schema, consumerAttributes.asImmutable(), allowNoMatchingVariants); } private static class AttributeMatchingVariantSelector implements VariantSelector { private final VariantAttributeMatchingCache matchingCache; private final AttributesSchemaInternal schema; private final AttributeContainerInternal requested; private final boolean ignoreWhenNoMatches; private AttributeMatchingVariantSelector(VariantAttributeMatchingCache matchingCache, AttributesSchemaInternal schema, AttributeContainerInternal requested, boolean ignoreWhenNoMatches) { this.matchingCache = matchingCache; this.schema = schema; this.requested = requested; this.ignoreWhenNoMatches = ignoreWhenNoMatches; } @Override public String toString() { return "Variant selector for " + requested; } @Override public ResolvedArtifactSet select(ResolvedVariantSet producer) { try { return doSelect(producer); } catch (Throwable t) { return new BrokenResolvedArtifactSet(t); } } private ResolvedArtifactSet doSelect(ResolvedVariantSet producer) { AttributeMatcher matcher = schema.withProducer(producer.getSchema()); List<? extends ResolvedVariant> matches = matcher.matches(producer.getVariants(), requested); if (matches.size() == 1) { return matches.get(0).getArtifacts(); } if (matches.size() > 1) { throw new AmbiguousVariantSelectionException(producer.asDescribable().getDisplayName(), requested, matches, matcher); } List<Pair<ResolvedVariant, ConsumerVariantMatchResult.ConsumerVariant>> candidates = new ArrayList<Pair<ResolvedVariant, ConsumerVariantMatchResult.ConsumerVariant>>(); for (ResolvedVariant variant : producer.getVariants()) { AttributeContainerInternal variantAttributes = variant.getAttributes().asImmutable(); ConsumerVariantMatchResult matchResult = new ConsumerVariantMatchResult(); matchingCache.collectConsumerVariants(variantAttributes, requested, matchResult); for (ConsumerVariantMatchResult.ConsumerVariant consumerVariant : matchResult.getMatches()) { candidates.add(Pair.of(variant, consumerVariant)); } } if (candidates.size() == 1) { Pair<ResolvedVariant, ConsumerVariantMatchResult.ConsumerVariant> result = candidates.get(0); return new ConsumerProvidedResolvedVariant(result.getLeft().getArtifacts(), result.getRight().attributes, result.getRight().transformer); } if (!candidates.isEmpty()) { throw new AmbiguousTransformException(producer.asDescribable().getDisplayName(), requested, candidates); } if (ignoreWhenNoMatches) { return ResolvedArtifactSet.EMPTY; } throw new NoMatchingVariantSelectionException(producer.asDescribable().getDisplayName(), requested, producer.getVariants(), matcher); } } private static class ConsumerProvidedResolvedVariant implements ResolvedArtifactSet { private final ResolvedArtifactSet delegate; private final AttributeContainerInternal attributes; private final Transformer<List<File>, File> transform; ConsumerProvidedResolvedVariant(ResolvedArtifactSet delegate, AttributeContainerInternal target, Transformer<List<File>, File> transform) { this.delegate = delegate; this.attributes = target; this.transform = transform; } @Override public Completion startVisit(BuildOperationQueue<RunnableBuildOperation> actions, AsyncArtifactListener listener) { Map<ResolvedArtifact, TransformArtifactOperation> artifactResults = new ConcurrentHashMap<ResolvedArtifact, TransformArtifactOperation>(); Map<File, TransformFileOperation> fileResults = new ConcurrentHashMap<File, TransformFileOperation>(); Completion result = delegate.startVisit(actions, new TransformingAsyncArtifactListener(artifactResults, actions, transform, listener, fileResults)); return new TransformingResult(result, artifactResults, fileResults); } @Override public void collectBuildDependencies(BuildDependenciesVisitor visitor) { delegate.collectBuildDependencies(visitor); } private static class TransformingAsyncArtifactListener implements AsyncArtifactListener { private final Map<ResolvedArtifact, TransformArtifactOperation> artifactResults; private final BuildOperationQueue<RunnableBuildOperation> actions; private final AsyncArtifactListener listener; private final Map<File, TransformFileOperation> fileResults; private final Transformer<List<File>, File> transform; TransformingAsyncArtifactListener(Map<ResolvedArtifact, TransformArtifactOperation> artifactResults, BuildOperationQueue<RunnableBuildOperation> actions, Transformer<List<File>, File> transform, AsyncArtifactListener listener, Map<File, TransformFileOperation> fileResults) { this.artifactResults = artifactResults; this.actions = actions; this.transform = transform; this.listener = listener; this.fileResults = fileResults; } @Override public void artifactAvailable(ResolvedArtifact artifact) { TransformArtifactOperation operation = new TransformArtifactOperation(artifact, transform); artifactResults.put(artifact, operation); actions.add(operation); } @Override public boolean requireArtifactFiles() { // Always need the files, as we need to run the transform in order to calculate the output artifacts. return true; } @Override public boolean includeFileDependencies() { return listener.includeFileDependencies(); } @Override public void fileAvailable(File file) { TransformFileOperation operation = new TransformFileOperation(file, transform); fileResults.put(file, operation); actions.add(operation); } } private class TransformingResult implements Completion { private final Completion result; private final Map<ResolvedArtifact, TransformArtifactOperation> artifactResults; private final Map<File, TransformFileOperation> fileResults; public TransformingResult(Completion result, Map<ResolvedArtifact, TransformArtifactOperation> artifactResults, Map<File, TransformFileOperation> fileResults) { this.result = result; this.artifactResults = artifactResults; this.fileResults = fileResults; } @Override public void visit(ArtifactVisitor visitor) { result.visit(new ArtifactTransformingVisitor(visitor, attributes, artifactResults, fileResults)); } } } private static class TransformArtifactOperation implements RunnableBuildOperation { private final ResolvedArtifact artifact; private final Transformer<List<File>, File> transform; private Throwable failure; private List<File> result; TransformArtifactOperation(ResolvedArtifact artifact, Transformer<List<File>, File> transform) { this.artifact = artifact; this.transform = transform; } @Override public void run(BuildOperationContext context) { try { result = transform.transform(artifact.getFile()); } catch (Throwable t) { failure = t; } } @Override public BuildOperationDescriptor.Builder description() { return BuildOperationDescriptor.displayName("Apply " + transform + " to " + artifact); } } private static class TransformFileOperation implements RunnableBuildOperation { private final File file; private final Transformer<List<File>, File> transform; private Throwable failure; private List<File> result; TransformFileOperation(File file, Transformer<List<File>, File> transform) { this.file = file; this.transform = transform; } @Override public void run(BuildOperationContext context) { try { result = transform.transform(file); } catch (Throwable t) { failure = t; } } @Override public BuildOperationDescriptor.Builder description() { return BuildOperationDescriptor.displayName("Apply " + transform + " to " + file); } } private static class ArtifactTransformingVisitor implements ArtifactVisitor { private final ArtifactVisitor visitor; private final AttributeContainerInternal target; private final Map<ResolvedArtifact, TransformArtifactOperation> artifactResults; private final Map<File, TransformFileOperation> fileResults; private ArtifactTransformingVisitor(ArtifactVisitor visitor, AttributeContainerInternal target, Map<ResolvedArtifact, TransformArtifactOperation> artifactResults, Map<File, TransformFileOperation> fileResults) { this.visitor = visitor; this.target = target; this.artifactResults = artifactResults; this.fileResults = fileResults; } @Override public void visitArtifact(AttributeContainer variant, ResolvedArtifact artifact) { TransformArtifactOperation operation = artifactResults.get(artifact); if (operation.failure != null) { visitor.visitFailure(operation.failure); return; } List<File> transformedFiles = operation.result; TaskDependency buildDependencies = ((Buildable) artifact).getBuildDependencies(); for (File output : transformedFiles) { ComponentArtifactIdentifier newId = new ComponentFileArtifactIdentifier(artifact.getId().getComponentIdentifier(), output.getName()); String extension = Files.getFileExtension(output.getName()); IvyArtifactName artifactName = new DefaultIvyArtifactName(output.getName(), extension, extension); ResolvedArtifact resolvedArtifact = new DefaultResolvedArtifact(artifact.getModuleVersion().getId(), artifactName, newId, buildDependencies, output); visitor.visitArtifact(target, resolvedArtifact); } } @Override public void visitFailure(Throwable failure) { visitor.visitFailure(failure); } @Override public boolean includeFiles() { return visitor.includeFiles(); } @Override public boolean requireArtifactFiles() { return visitor.requireArtifactFiles(); } @Override public void visitFile(ComponentArtifactIdentifier artifactIdentifier, AttributeContainer variant, File file) { TransformFileOperation operation = fileResults.get(file); if (operation.failure != null) { visitor.visitFailure(operation.failure); return; } List<File> result = operation.result; for (File outputFile : result) { visitor.visitFile(new ComponentFileArtifactIdentifier(artifactIdentifier.getComponentIdentifier(), outputFile.getName()), target, outputFile); } } } }