All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gradle.api.internal.tasks.SnapshotTaskInputsBuildOperationResult Maven / Gradle / Ivy

There is a newer version: 8.11.1
Show newest version
/*
 * 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.tasks;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import org.gradle.api.NonNullApi;
import org.gradle.api.internal.tasks.properties.InputFilePropertySpec;
import org.gradle.api.internal.tasks.properties.PropertySpec;
import org.gradle.api.tasks.ClasspathNormalizer;
import org.gradle.api.tasks.CompileClasspathNormalizer;
import org.gradle.api.tasks.FileNormalizer;
import org.gradle.caching.BuildCacheKey;
import org.gradle.internal.execution.caching.CachingState;
import org.gradle.internal.execution.history.BeforeExecutionState;
import org.gradle.internal.execution.history.InputExecutionState;
import org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep;
import org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep;
import org.gradle.internal.file.FileType;
import org.gradle.internal.fingerprint.AbsolutePathInputNormalizer;
import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint;
import org.gradle.internal.fingerprint.DirectorySensitivity;
import org.gradle.internal.fingerprint.FileSystemLocationFingerprint;
import org.gradle.internal.fingerprint.FingerprintingStrategy;
import org.gradle.internal.fingerprint.IgnoredPathInputNormalizer;
import org.gradle.internal.fingerprint.LineEndingSensitivity;
import org.gradle.internal.fingerprint.NameOnlyInputNormalizer;
import org.gradle.internal.fingerprint.RelativePathInputNormalizer;
import org.gradle.internal.fingerprint.impl.AbsolutePathFingerprintingStrategy;
import org.gradle.internal.fingerprint.impl.IgnoredPathFingerprintingStrategy;
import org.gradle.internal.fingerprint.impl.NameOnlyFingerprintingStrategy;
import org.gradle.internal.fingerprint.impl.RelativePathFingerprintingStrategy;
import org.gradle.internal.hash.HashCode;
import org.gradle.internal.hash.Hasher;
import org.gradle.internal.hash.Hashing;
import org.gradle.internal.operations.trace.CustomOperationTraceSerialization;
import org.gradle.internal.scan.UsedByScanPlugin;
import org.gradle.internal.snapshot.DirectorySnapshot;
import org.gradle.internal.snapshot.FileSystemLocationSnapshot;
import org.gradle.internal.snapshot.FileSystemSnapshotHierarchyVisitor;
import org.gradle.internal.snapshot.SnapshotVisitResult;
import org.gradle.internal.snapshot.impl.ImplementationSnapshot;

import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * This operation represents the work of analyzing the task's inputs plus the calculating the cache key.
 *
 * 

* These two operations should be captured separately, but for historical reasons we don't yet do that. * To reproduce this composite operation we capture across executors by starting an operation * in {@link MarkSnapshottingInputsStartedStep} and finished in {@link MarkSnapshottingInputsFinishedStep}. *

*/ public class SnapshotTaskInputsBuildOperationResult implements SnapshotTaskInputsBuildOperationType.Result, CustomOperationTraceSerialization { private static final Map, String> FINGERPRINTING_STRATEGIES_BY_NORMALIZER = ImmutableMap., String>builder() .put(ClasspathNormalizer.class, FingerprintingStrategy.CLASSPATH_IDENTIFIER) .put(CompileClasspathNormalizer.class, FingerprintingStrategy.COMPILE_CLASSPATH_IDENTIFIER) .put(AbsolutePathInputNormalizer.class, AbsolutePathFingerprintingStrategy.IDENTIFIER) .put(RelativePathInputNormalizer.class, RelativePathFingerprintingStrategy.IDENTIFIER) .put(NameOnlyInputNormalizer.class, NameOnlyFingerprintingStrategy.IDENTIFIER) .put(IgnoredPathInputNormalizer.class, IgnoredPathFingerprintingStrategy.IDENTIFIER) .build(); @VisibleForTesting final CachingState cachingState; private final Map propertySpecsByName; public SnapshotTaskInputsBuildOperationResult(CachingState cachingState, Set inputFileProperties) { this.cachingState = cachingState; this.propertySpecsByName = Maps.uniqueIndex(inputFileProperties, PropertySpec::getPropertyName); } @Override public Map getInputValueHashesBytes() { return getBeforeExecutionState() .map(InputExecutionState::getInputProperties) .filter(inputValueFingerprints -> !inputValueFingerprints.isEmpty()) .map(inputValueFingerprints -> inputValueFingerprints.entrySet().stream() .collect(toLinkedHashMap(valueSnapshot -> { Hasher hasher = Hashing.newHasher(); valueSnapshot.appendToHasher(hasher); return hasher.hash().toByteArray(); }))) .orElse(null); } @NonNullApi private final class State implements VisitState, FileSystemSnapshotHierarchyVisitor { final InputFilePropertyVisitor visitor; final Deque unvisitedDirectories = new ArrayDeque<>(); Map fingerprints; String propertyName; HashCode propertyHash; String name; String path; HashCode hash; int depth; public State(InputFilePropertyVisitor visitor) { this.visitor = visitor; } @Override public String getPropertyName() { return propertyName; } @Override public byte[] getPropertyHashBytes() { return propertyHash.toByteArray(); } @Override public Set getPropertyAttributes() { InputFilePropertySpec propertySpec = propertySpec(propertyName); return ImmutableSortedSet.of( FilePropertyAttribute.fromNormalizerClass(propertySpec.getNormalizer()).name(), FilePropertyAttribute.from(propertySpec.getDirectorySensitivity()).name(), FilePropertyAttribute.from(propertySpec.getLineEndingNormalization()).name() ); } @Override @Deprecated public String getPropertyNormalizationStrategyName() { InputFilePropertySpec propertySpec = propertySpec(propertyName); Class normalizer = propertySpec.getNormalizer(); String normalizationStrategy = FINGERPRINTING_STRATEGIES_BY_NORMALIZER.get(normalizer); if (normalizationStrategy == null) { throw new IllegalStateException("No strategy name for " + normalizer); } return normalizationStrategy; } @Override public String getName() { return name; } @Override public String getPath() { return path; } @Override public byte[] getHashBytes() { return hash.toByteArray(); } @Override public void enterDirectory(DirectorySnapshot physicalSnapshot) { this.path = physicalSnapshot.getAbsolutePath(); this.name = physicalSnapshot.getName(); this.hash = null; if (depth++ == 0) { visitor.preRoot(this); } FileSystemLocationFingerprint fingerprint = fingerprints.get(path); if (fingerprint == null) { // This directory is not part of the fingerprint. // Store it to visit later if it contains anything that was fingerprinted unvisitedDirectories.add(physicalSnapshot); } else { visitUnvisitedDirectories(); visitor.preDirectory(this); } } @Override public SnapshotVisitResult visitEntry(FileSystemLocationSnapshot snapshot) { if (snapshot.getType() == FileType.Directory) { return SnapshotVisitResult.CONTINUE; } FileSystemLocationFingerprint fingerprint = fingerprints.get(snapshot.getAbsolutePath()); if (fingerprint == null) { return SnapshotVisitResult.CONTINUE; } visitUnvisitedDirectories(); this.path = snapshot.getAbsolutePath(); this.name = snapshot.getName(); this.hash = fingerprint.getNormalizedContentHash(); boolean isRoot = depth == 0; if (isRoot) { visitor.preRoot(this); } visitor.file(this); if (isRoot) { visitor.postRoot(); } return SnapshotVisitResult.CONTINUE; } @Override public void leaveDirectory(DirectorySnapshot directorySnapshot) { DirectorySnapshot lastUnvisitedDirectory = unvisitedDirectories.pollLast(); if (lastUnvisitedDirectory == null) { visitor.postDirectory(); } if (--depth == 0) { visitor.postRoot(); } } private void visitUnvisitedDirectories() { DirectorySnapshot unvisited; while ((unvisited = unvisitedDirectories.poll()) != null) { visitor.preDirectory(new DirectoryVisitState(unvisited, this)); } } } private static class DirectoryVisitState implements VisitState { private final VisitState delegate; private final DirectorySnapshot directorySnapshot; public DirectoryVisitState(DirectorySnapshot unvisited, VisitState delegate) { this.directorySnapshot = unvisited; this.delegate = delegate; } @Override public String getPath() { return directorySnapshot.getAbsolutePath(); } @Override public String getName() { return directorySnapshot.getName(); } @Override public byte[] getHashBytes() { throw new UnsupportedOperationException("Cannot query hash for directories"); } @Override public String getPropertyName() { return delegate.getPropertyName(); } @Override public byte[] getPropertyHashBytes() { return delegate.getPropertyHashBytes(); } @SuppressWarnings("deprecation") @Override public String getPropertyNormalizationStrategyName() { return delegate.getPropertyNormalizationStrategyName(); } @Override public Set getPropertyAttributes() { return delegate.getPropertyAttributes(); } } private InputFilePropertySpec propertySpec(String propertyName) { InputFilePropertySpec propertySpec = propertySpecsByName.get(propertyName); if (propertySpec == null) { throw new IllegalStateException("Unknown input property '" + propertyName + "' (known: " + propertySpecsByName.keySet() + ")"); } return propertySpec; } @Override public void visitInputFileProperties(final InputFilePropertyVisitor visitor) { getBeforeExecutionState() .map(BeforeExecutionState::getInputFileProperties) .ifPresent(inputFileProperties -> { State state = new State(visitor); for (Map.Entry entry : inputFileProperties.entrySet()) { CurrentFileCollectionFingerprint fingerprint = entry.getValue(); state.propertyName = entry.getKey(); state.propertyHash = fingerprint.getHash(); state.fingerprints = fingerprint.getFingerprints(); visitor.preProperty(state); fingerprint.getSnapshot().accept(state); visitor.postProperty(); } }); } @Nullable @SuppressWarnings("deprecation") @Override public Set getInputPropertiesLoadedByUnknownClassLoader() { return null; } @Override public byte[] getClassLoaderHashBytes() { return getBeforeExecutionState() .map(InputExecutionState::getImplementation) .map(ImplementationSnapshot::getClassLoaderHash) .map(HashCode::toByteArray) .orElse(null); } @Override public List getActionClassLoaderHashesBytes() { return getBeforeExecutionState() .map(BeforeExecutionState::getAdditionalImplementations) .filter(additionalImplementation -> !additionalImplementation.isEmpty()) .map(additionalImplementations -> additionalImplementations.stream() .map(input -> input.getClassLoaderHash() == null ? null : input.getClassLoaderHash().toByteArray()) .collect(Collectors.toList())) .orElse(null); } @Nullable @Override public List getActionClassNames() { return getBeforeExecutionState() .map(BeforeExecutionState::getAdditionalImplementations) .filter(additionalImplementations -> !additionalImplementations.isEmpty()) .map(additionalImplementations -> additionalImplementations.stream() .map(ImplementationSnapshot::getTypeName) .collect(Collectors.toList()) ) .orElse(null); } @Nullable @Override public List getOutputPropertyNames() { return getBeforeExecutionState() .map(BeforeExecutionState::getOutputFileLocationSnapshots) .map(ImmutableSortedMap::keySet) .filter(outputPropertyNames -> !outputPropertyNames.isEmpty()) .map(ImmutableSet::asList) .orElse(null); } @Override public byte[] getHashBytes() { return getKey() .map(BuildCacheKey::toByteArray) .orElse(null); } @Override public Object getCustomOperationTraceSerializableModel() { Map model = new TreeMap<>(); List actionClassLoaderHashesBytes = getActionClassLoaderHashesBytes(); if (actionClassLoaderHashesBytes != null) { List actionClassloaderHashes = getActionClassLoaderHashesBytes().stream() .map(hash -> hash == null ? null : HashCode.fromBytes(hash).toString()) .collect(Collectors.toList()); model.put("actionClassLoaderHashes", actionClassloaderHashes); } else { model.put("actionClassLoaderHashes", null); } model.put("actionClassNames", getActionClassNames()); byte[] hashBytes = getHashBytes(); if (hashBytes != null) { model.put("hash", HashCode.fromBytes(hashBytes).toString()); } else { model.put("hash", null); } byte[] classLoaderHashBytes = getClassLoaderHashBytes(); if (classLoaderHashBytes != null) { model.put("classLoaderHash", HashCode.fromBytes(classLoaderHashBytes).toString()); } else { model.put("classLoaderHash", null); } model.put("inputFileProperties", fileProperties()); Map inputValueHashesBytes = getInputValueHashesBytes(); if (inputValueHashesBytes != null) { Map inputValueHashes = inputValueHashesBytes.entrySet().stream() .collect(toLinkedHashMap(value -> value == null ? null : HashCode.fromBytes(value).toString())); model.put("inputValueHashes", inputValueHashes); } else { model.put("inputValueHashes", null); } model.put("outputPropertyNames", getOutputPropertyNames()); return model; } private static Collector, ?, LinkedHashMap> toLinkedHashMap(Function valueMapper) { return Collectors.toMap( Map.Entry::getKey, entry -> valueMapper.apply(entry.getValue()), (a, b) -> b, LinkedHashMap::new ); } protected Map fileProperties() { final Map fileProperties = new TreeMap<>(); visitInputFileProperties(new InputFilePropertyVisitor() { Property property; final Deque dirStack = new ArrayDeque<>(); class Property { private final String hash; private final String normalization; private final Set attributes; private final List roots = new ArrayList<>(); public Property(String hash, String normalization, Set attributes) { this.hash = hash; this.normalization = normalization; this.attributes = attributes; } public String getHash() { return hash; } public String getNormalization() { return normalization; } public Set getAttributes() { return attributes; } public Collection getRoots() { return roots; } } abstract class Entry { private final String path; public Entry(String path) { this.path = path; } public String getPath() { return path; } } class FileEntry extends Entry { private final String hash; FileEntry(String path, String hash) { super(path); this.hash = hash; } public String getHash() { return hash; } } class DirEntry extends Entry { private final List children = new ArrayList<>(); DirEntry(String path) { super(path); } public Collection getChildren() { return children; } } @SuppressWarnings("deprecation") @Override public void preProperty(VisitState state) { property = new Property(HashCode.fromBytes(state.getPropertyHashBytes()).toString(), state.getPropertyNormalizationStrategyName(), state.getPropertyAttributes()); fileProperties.put(state.getPropertyName(), property); } @Override public void preRoot(VisitState state) { } @Override public void preDirectory(VisitState state) { boolean isRoot = dirStack.isEmpty(); DirEntry dir = new DirEntry(isRoot ? state.getPath() : state.getName()); if (isRoot) { property.roots.add(dir); } else { //noinspection ConstantConditions dirStack.peek().children.add(dir); } dirStack.push(dir); } @Override public void file(VisitState state) { boolean isRoot = dirStack.isEmpty(); FileEntry file = new FileEntry(isRoot ? state.getPath() : state.getName(), HashCode.fromBytes(state.getHashBytes()).toString()); if (isRoot) { property.roots.add(file); } else { //noinspection ConstantConditions dirStack.peek().children.add(file); } } @Override public void postDirectory() { dirStack.pop(); } @Override public void postRoot() { } @Override public void postProperty() { } }); return fileProperties; } private Optional getBeforeExecutionState() { return cachingState.fold( enabled -> Optional.of(enabled.getBeforeExecutionState()), CachingState.Disabled::getBeforeExecutionState ); } private Optional getKey() { return cachingState.fold( enabled -> Optional.of(enabled.getKey()), CachingState.Disabled::getKey ); } @UsedByScanPlugin("The value names are used as part of build scan data and cannot be changed - new values can be added") enum FilePropertyAttribute { // When adding new values, be sure to comment which Gradle version started emitting the attribute. // Additionally, indicate any other relevant constraints with regard to other attributes or changes. // Every property has exactly one of the following FINGERPRINTING_STRATEGY_ABSOLUTE_PATH, FINGERPRINTING_STRATEGY_NAME_ONLY, FINGERPRINTING_STRATEGY_RELATIVE_PATH, FINGERPRINTING_STRATEGY_IGNORED_PATH, FINGERPRINTING_STRATEGY_CLASSPATH, FINGERPRINTING_STRATEGY_COMPILE_CLASSPATH, // Every property has exactly one of the following DIRECTORY_SENSITIVITY_DEFAULT, DIRECTORY_SENSITIVITY_IGNORE_DIRECTORIES, // Every property has exactly one of the following LINE_ENDING_SENSITIVITY_DEFAULT, LINE_ENDING_SENSITIVITY_NORMALIZE_LINE_ENDINGS; private static final Map, FilePropertyAttribute> BY_NORMALIZER_CLASS = ImmutableMap., FilePropertyAttribute>builder() .put(ClasspathNormalizer.class, FINGERPRINTING_STRATEGY_CLASSPATH) .put(CompileClasspathNormalizer.class, FINGERPRINTING_STRATEGY_COMPILE_CLASSPATH) .put(AbsolutePathInputNormalizer.class, FINGERPRINTING_STRATEGY_ABSOLUTE_PATH) .put(RelativePathInputNormalizer.class, FINGERPRINTING_STRATEGY_RELATIVE_PATH) .put(NameOnlyInputNormalizer.class, FINGERPRINTING_STRATEGY_NAME_ONLY) .put(IgnoredPathInputNormalizer.class, FINGERPRINTING_STRATEGY_IGNORED_PATH) .build(); @SuppressWarnings("deprecation") private static final Map BY_DIRECTORY_SENSITIVITY = Maps.immutableEnumMap(ImmutableMap.builder() .put(DirectorySensitivity.DEFAULT, DIRECTORY_SENSITIVITY_DEFAULT) .put(DirectorySensitivity.UNSPECIFIED, DIRECTORY_SENSITIVITY_DEFAULT) .put(DirectorySensitivity.IGNORE_DIRECTORIES, DIRECTORY_SENSITIVITY_IGNORE_DIRECTORIES) .build()); private static final Map BY_LINE_ENDING_SENSITIVITY = Maps.immutableEnumMap(ImmutableMap.builder() .put(LineEndingSensitivity.DEFAULT, LINE_ENDING_SENSITIVITY_DEFAULT) .put(LineEndingSensitivity.NORMALIZE_LINE_ENDINGS, LINE_ENDING_SENSITIVITY_NORMALIZE_LINE_ENDINGS) .build()); private static FilePropertyAttribute findFor(T value, Map mapping) { FilePropertyAttribute attribute = mapping.get(value); if (attribute == null) { throw new IllegalStateException("Did not find property attribute mapping for '" + value + "' (from: " + mapping.keySet() + ")"); } return attribute; } static FilePropertyAttribute fromNormalizerClass(Class normalizerClass) { return findFor(normalizerClass, BY_NORMALIZER_CLASS); } static FilePropertyAttribute from(DirectorySensitivity directorySensitivity) { return findFor(directorySensitivity, BY_DIRECTORY_SENSITIVITY); } static FilePropertyAttribute from(LineEndingSensitivity lineEndingSensitivity) { return findFor(lineEndingSensitivity, BY_LINE_ENDING_SENSITIVITY); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy