org.gradle.api.internaltransform.DefaultTransformer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2019 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.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.reflect.TypeToken;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.artifacts.transform.InputArtifact;
import org.gradle.api.artifacts.transform.InputArtifactDependencies;
import org.gradle.api.artifacts.transform.TransformAction;
import org.gradle.api.artifacts.transform.TransformParameters;
import org.gradle.api.artifacts.transform.VariantTransformConfigurationException;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.internal.DomainObjectContext;
import org.gradle.api.internal.attributes.ImmutableAttributes;
import org.gradle.api.internal.file.FileCollectionFactory;
import org.gradle.api.internal.file.FileLookup;
import org.gradle.api.internal.plugins.DslObject;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.internal.tasks.NodeExecutionContext;
import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
import org.gradle.api.internal.tasks.properties.FileParameterUtils;
import org.gradle.api.internal.tasks.properties.InputFilePropertyType;
import org.gradle.api.internal.tasks.properties.InputParameterUtils;
import org.gradle.api.internal.tasks.properties.OutputFilePropertyType;
import org.gradle.api.internal.tasks.properties.PropertyValue;
import org.gradle.api.internal.tasks.properties.PropertyVisitor;
import org.gradle.api.internal.tasks.properties.PropertyWalker;
import org.gradle.api.provider.Provider;
import org.gradle.api.reflect.InjectionPointQualifier;
import org.gradle.api.tasks.FileNormalizer;
import org.gradle.internal.Describables;
import org.gradle.internal.deprecation.DeprecationLogger;
import org.gradle.internal.exceptions.DefaultMultiCauseException;
import org.gradle.internal.fingerprint.AbsolutePathInputNormalizer;
import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint;
import org.gradle.internal.fingerprint.FileCollectionFingerprinter;
import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry;
import org.gradle.internal.fingerprint.FileNormalizationSpec;
import org.gradle.internal.fingerprint.impl.DefaultFileNormalizationSpec;
import org.gradle.internal.fingerprint.DirectorySensitivity;
import org.gradle.internal.hash.ClassLoaderHierarchyHasher;
import org.gradle.internal.hash.HashCode;
import org.gradle.internal.hash.Hasher;
import org.gradle.internal.hash.Hashing;
import org.gradle.internal.instantiation.InstanceFactory;
import org.gradle.internal.instantiation.InstantiationScheme;
import org.gradle.internal.isolated.IsolationScheme;
import org.gradle.internal.isolation.Isolatable;
import org.gradle.internal.isolation.IsolatableFactory;
import org.gradle.internal.logging.text.TreeFormatter;
import org.gradle.internal.model.CalculatedValueContainer;
import org.gradle.internal.model.CalculatedValueContainerFactory;
import org.gradle.internal.model.ModelContainer;
import org.gradle.internal.model.ValueCalculator;
import org.gradle.internal.operations.BuildOperationContext;
import org.gradle.internal.operations.BuildOperationDescriptor;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.operations.BuildOperationType;
import org.gradle.internal.operations.RunnableBuildOperation;
import org.gradle.internal.reflect.DefaultTypeValidationContext;
import org.gradle.internal.reflect.TypeValidationContext;
import org.gradle.internal.service.ServiceLookup;
import org.gradle.internal.service.ServiceLookupException;
import org.gradle.internal.service.UnknownServiceException;
import org.gradle.internal.snapshot.ValueSnapshot;
import org.gradle.internal.snapshot.ValueSnapshotter;
import org.gradle.model.internal.type.ModelType;
import org.gradle.work.InputChanges;
import javax.annotation.Nullable;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.gradle.internal.reflect.TypeValidationContext.Severity.WARNING;
public class DefaultTransformer extends AbstractTransformer> {
private final Class fileNormalizer;
private final Class dependenciesNormalizer;
private final FileLookup fileLookup;
private final ServiceLookup internalServices;
private final boolean requiresDependencies;
private final boolean requiresInputChanges;
private final InstanceFactory> instanceFactory;
private final boolean cacheable;
private final CalculatedValueContainer isolatedParameters;
private final DirectorySensitivity artifactDirectorySensitivity;
private final DirectorySensitivity dependenciesDirectorySensitivity;
public DefaultTransformer(
Class> implementationClass,
@Nullable TransformParameters parameterObject,
ImmutableAttributes fromAttributes,
Class inputArtifactNormalizer,
Class dependenciesNormalizer,
boolean cacheable,
DirectorySensitivity artifactDirectorySensitivity,
DirectorySensitivity dependenciesDirectorySensitivity,
BuildOperationExecutor buildOperationExecutor,
ClassLoaderHierarchyHasher classLoaderHierarchyHasher,
IsolatableFactory isolatableFactory,
ValueSnapshotter valueSnapshotter,
FileCollectionFactory fileCollectionFactory,
FileLookup fileLookup,
PropertyWalker parameterPropertyWalker,
InstantiationScheme actionInstantiationScheme,
DomainObjectContext owner,
CalculatedValueContainerFactory calculatedValueContainerFactory,
ServiceLookup internalServices
) {
super(implementationClass, fromAttributes);
this.fileNormalizer = inputArtifactNormalizer;
this.dependenciesNormalizer = dependenciesNormalizer;
this.fileLookup = fileLookup;
this.internalServices = internalServices;
this.instanceFactory = actionInstantiationScheme.forType(implementationClass);
this.requiresDependencies = instanceFactory.serviceInjectionTriggeredByAnnotation(InputArtifactDependencies.class);
this.requiresInputChanges = instanceFactory.requiresService(InputChanges.class);
this.cacheable = cacheable;
this.artifactDirectorySensitivity = artifactDirectorySensitivity;
this.dependenciesDirectorySensitivity = dependenciesDirectorySensitivity;
this.isolatedParameters = calculatedValueContainerFactory.create(Describables.of("parameters of", this),
new IsolateTransformerParameters(parameterObject, implementationClass, cacheable, owner, parameterPropertyWalker, isolatableFactory, buildOperationExecutor, classLoaderHierarchyHasher,
valueSnapshotter, fileCollectionFactory));
}
/**
* Used to recreate a transformer from the configuration cache.
*/
public DefaultTransformer(
Class> implementationClass,
CalculatedValueContainer isolatedParameters,
ImmutableAttributes fromAttributes,
Class inputArtifactNormalizer,
Class dependenciesNormalizer,
boolean cacheable,
FileLookup fileLookup,
InstantiationScheme actionInstantiationScheme,
ServiceLookup internalServices,
DirectorySensitivity artifactDirectorySensitivity,
DirectorySensitivity dependenciesDirectorySensitivity) {
super(implementationClass, fromAttributes);
this.fileNormalizer = inputArtifactNormalizer;
this.dependenciesNormalizer = dependenciesNormalizer;
this.fileLookup = fileLookup;
this.internalServices = internalServices;
this.instanceFactory = actionInstantiationScheme.forType(implementationClass);
this.requiresDependencies = instanceFactory.serviceInjectionTriggeredByAnnotation(InputArtifactDependencies.class);
this.requiresInputChanges = instanceFactory.requiresService(InputChanges.class);
this.cacheable = cacheable;
this.isolatedParameters = isolatedParameters;
this.artifactDirectorySensitivity = artifactDirectorySensitivity;
this.dependenciesDirectorySensitivity = dependenciesDirectorySensitivity;
}
public static void validateInputFileNormalizer(String propertyName, @Nullable Class normalizer, boolean cacheable, TypeValidationContext validationContext) {
if (cacheable) {
if (normalizer == AbsolutePathInputNormalizer.class) {
validationContext.visitPropertyProblem(WARNING,
propertyName,
"is declared to be sensitive to absolute paths. This is not allowed for cacheable transforms"
);
}
}
}
@Override
public Class getInputArtifactNormalizer() {
return fileNormalizer;
}
@Override
public Class getInputArtifactDependenciesNormalizer() {
return dependenciesNormalizer;
}
@Override
public boolean isIsolated() {
return isolatedParameters.getOrNull() != null;
}
@Override
public boolean requiresDependencies() {
return requiresDependencies;
}
@Override
public boolean requiresInputChanges() {
return requiresInputChanges;
}
@Override
public boolean isCacheable() {
return cacheable;
}
@Override
public DirectorySensitivity getInputArtifactDirectorySensitivity() {
return artifactDirectorySensitivity;
}
@Override
public DirectorySensitivity getInputArtifactDependenciesDirectorySensitivity() {
return dependenciesDirectorySensitivity;
}
@Override
public HashCode getSecondaryInputHash() {
return isolatedParameters.get().getSecondaryInputsHash();
}
@Override
public ImmutableList transform(Provider inputArtifactProvider, File outputDir, ArtifactTransformDependencies dependencies, @Nullable InputChanges inputChanges) {
TransformAction transformAction = newTransformAction(inputArtifactProvider, dependencies, inputChanges);
DefaultTransformOutputs transformOutputs = new DefaultTransformOutputs(inputArtifactProvider.get().getAsFile(), outputDir, fileLookup);
transformAction.transform(transformOutputs);
return transformOutputs.getRegisteredOutputs();
}
@Override
public void visitDependencies(TaskDependencyResolveContext context) {
context.add(isolatedParameters);
}
@Override
public void isolateParametersIfNotAlready() {
isolatedParameters.finalizeIfNotAlready();
}
private static void fingerprintParameters(
ValueSnapshotter valueSnapshotter,
FileCollectionFingerprinterRegistry fingerprinterRegistry,
FileCollectionFactory fileCollectionFactory,
PropertyWalker propertyWalker,
Hasher hasher,
Object parameterObject,
boolean cacheable
) {
ImmutableSortedMap.Builder inputParameterFingerprintsBuilder = ImmutableSortedMap.naturalOrder();
ImmutableSortedMap.Builder inputFileParameterFingerprintsBuilder = ImmutableSortedMap.naturalOrder();
DefaultTypeValidationContext validationContext = DefaultTypeValidationContext.withoutRootType(cacheable);
propertyWalker.visitProperties(parameterObject, validationContext, new PropertyVisitor.Adapter() {
@Override
public void visitInputProperty(String propertyName, PropertyValue value, boolean optional) {
try {
Object preparedValue = InputParameterUtils.prepareInputParameterValue(value);
if (preparedValue == null && !optional) {
validationContext.visitPropertyProblem(WARNING,
propertyName,
"does not have a value specified"
);
}
inputParameterFingerprintsBuilder.put(propertyName, valueSnapshotter.snapshot(preparedValue));
} catch (Throwable e) {
throw new InvalidUserDataException(String.format(
"Error while evaluating property '%s' of %s",
propertyName,
getParameterObjectDisplayName(parameterObject)
), e);
}
}
@Override
public void visitOutputFileProperty(String propertyName, boolean optional, PropertyValue value, OutputFilePropertyType filePropertyType) {
validationContext.visitPropertyProblem(WARNING,
propertyName,
"is annotated with an output annotation"
);
}
@Override
public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, DirectorySensitivity directorySensitivity, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) {
validateInputFileNormalizer(propertyName, fileNormalizer, cacheable, validationContext);
FileNormalizationSpec fileNormalizationSpec = DefaultFileNormalizationSpec.from(FileParameterUtils.normalizerOrDefault(fileNormalizer), directorySensitivity);
FileCollectionFingerprinter fingerprinter = fingerprinterRegistry.getFingerprinter(fileNormalizationSpec);
FileCollection inputFileValue = FileParameterUtils.resolveInputFileValue(fileCollectionFactory, filePropertyType, value);
CurrentFileCollectionFingerprint fingerprint = fingerprinter.fingerprint(inputFileValue);
inputFileParameterFingerprintsBuilder.put(propertyName, fingerprint);
}
});
ImmutableMap validationMessages = validationContext.getProblems();
if (!validationMessages.isEmpty()) {
throw new DefaultMultiCauseException(
String.format(validationMessages.size() == 1 ? "A problem was found with the configuration of the artifact transform parameter %s." : "Some problems were found with the configuration of the artifact transform parameter %s.", getParameterObjectDisplayName(parameterObject)),
validationMessages.keySet().stream().sorted().map(InvalidUserDataException::new).collect(Collectors.toList())
);
}
for (Map.Entry entry : inputParameterFingerprintsBuilder.build().entrySet()) {
hasher.putString(entry.getKey());
entry.getValue().appendToHasher(hasher);
}
for (Map.Entry entry : inputFileParameterFingerprintsBuilder.build().entrySet()) {
hasher.putString(entry.getKey());
hasher.putHash(entry.getValue().getHash());
}
}
private static String getParameterObjectDisplayName(Object parameterObject) {
return ModelType.of(new DslObject(parameterObject).getDeclaredType()).getDisplayName();
}
private TransformAction newTransformAction(Provider inputArtifactProvider, ArtifactTransformDependencies artifactTransformDependencies, @Nullable InputChanges inputChanges) {
TransformParameters parameters = isolatedParameters.get().getIsolatedParameterObject().isolate();
ServiceLookup services = new IsolationScheme<>(TransformAction.class, TransformParameters.class, TransformParameters.None.class).servicesForImplementation(parameters, internalServices);
services = new TransformServiceLookup(inputArtifactProvider, requiresDependencies ? artifactTransformDependencies : null, inputChanges, services);
return instanceFactory.newInstance(services);
}
public CalculatedValueContainer getIsolatedParameters() {
return isolatedParameters;
}
private static class TransformServiceLookup implements ServiceLookup {
private static final Type FILE_SYSTEM_LOCATION_PROVIDER = new TypeToken>() {
}.getType();
private final ImmutableList injectionPoints;
private final ServiceLookup delegate;
public TransformServiceLookup(Provider inputFileProvider, @Nullable ArtifactTransformDependencies artifactTransformDependencies, @Nullable InputChanges inputChanges, ServiceLookup delegate) {
this.delegate = delegate;
ImmutableList.Builder builder = ImmutableList.builder();
builder.add(InjectionPoint.injectedByAnnotation(InputArtifact.class, File.class, () -> {
DeprecationLogger
.deprecate("Injecting the input artifact of a transform as a File")
.withAdvice("Declare the input artifact as Provider instead.")
.willBeRemovedInGradle7()
.withUserManual("artifact_transforms", "sec:implementing-artifact-transforms")
.nagUser();
return inputFileProvider.get().getAsFile();
}));
builder.add(InjectionPoint.injectedByAnnotation(InputArtifact.class, FILE_SYSTEM_LOCATION_PROVIDER, () -> inputFileProvider));
if (artifactTransformDependencies != null) {
builder.add(InjectionPoint.injectedByAnnotation(InputArtifactDependencies.class, artifactTransformDependencies::getFiles));
}
if (inputChanges != null) {
builder.add(InjectionPoint.injectedByType(InputChanges.class, () -> inputChanges));
}
this.injectionPoints = builder.build();
}
@Nullable
private Object find(Type serviceType, @Nullable Class annotatedWith) {
TypeToken serviceTypeToken = TypeToken.of(serviceType);
for (InjectionPoint injectionPoint : injectionPoints) {
if (annotatedWith == injectionPoint.getAnnotation() && serviceTypeToken.isSupertypeOf(injectionPoint.getInjectedType())) {
return injectionPoint.getValueToInject();
}
}
return null;
}
@Nullable
@Override
public Object find(Type serviceType) throws ServiceLookupException {
Object result = find(serviceType, null);
if (result != null) {
return result;
}
return delegate.find(serviceType);
}
@Override
public Object get(Type serviceType) throws UnknownServiceException, ServiceLookupException {
Object result = find(serviceType);
if (result == null) {
throw new UnknownServiceException(serviceType, "No service of type " + serviceType + " available.");
}
return result;
}
@Override
public Object get(Type serviceType, Class annotatedWith) throws UnknownServiceException, ServiceLookupException {
Object result = find(serviceType, annotatedWith);
if (result != null) {
return result;
}
return delegate.get(serviceType, annotatedWith);
}
private static class InjectionPoint {
private final Class annotation;
private final Type injectedType;
private final Supplier