/* * Copyright 2010 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.changedetection.state; import com.google.common.collect.ImmutableMap; import org.gradle.api.internal.TaskExecutionHistory; import org.gradle.internal.nativeintegration.filesystem.FileType; import java.util.HashMap; import java.util.Map; /** * Takes a snapshot of the output files of a task. */ public class OutputFilesSnapshotter { public TaskExecutionHistory.OverlappingOutputs detectOverlappingOutputs(final String propertyName, final FileCollectionSnapshot previousExecution, FileCollectionSnapshot beforeExecution) { Map<String, NormalizedFileSnapshot> previousSnapshots = previousExecution.getSnapshots(); Map<String, NormalizedFileSnapshot> beforeSnapshots = beforeExecution.getSnapshots(); for (Map.Entry<String, NormalizedFileSnapshot> beforeSnapshot : beforeSnapshots.entrySet()) { final String path = beforeSnapshot.getKey(); NormalizedFileSnapshot fileSnapshot = beforeSnapshot.getValue(); NormalizedFileSnapshot previousSnapshot = previousSnapshots.get(path); // Missing files or just directories can be ignored // It would be nice to consider directories too, but we can't distinguish between an existing _root_ directory of an output property // and a directory inside the root directory. if (fileSnapshot.getSnapshot().getType() == FileType.RegularFile) { if (createdSincePreviousExecution(previousSnapshot) || changedSincePreviousExecution(fileSnapshot, previousSnapshot)) { return new TaskExecutionHistory.OverlappingOutputs(propertyName, fileSnapshot.getNormalizedPath()); } } } return null; } private boolean changedSincePreviousExecution(NormalizedFileSnapshot fileSnapshot, NormalizedFileSnapshot previousSnapshot) { // _changed_ since last execution, possibly by another task return !previousSnapshot.getSnapshot().isContentUpToDate(fileSnapshot.getSnapshot()); } private boolean createdSincePreviousExecution(NormalizedFileSnapshot previousSnapshot) { // created since last execution, possibly by another task return previousSnapshot == null; } /** * Returns a new snapshot that filters out entries that should not be considered outputs of the task. */ public FileCollectionSnapshot createOutputSnapshot( FileCollectionSnapshot afterPreviousExecution, FileCollectionSnapshot beforeExecution, FileCollectionSnapshot afterExecution ) { FileCollectionSnapshot filesSnapshot; Map<String, NormalizedFileSnapshot> afterSnapshots = afterExecution.getSnapshots(); if (!beforeExecution.getSnapshots().isEmpty() && !afterSnapshots.isEmpty()) { Map<String, NormalizedFileSnapshot> beforeSnapshots = beforeExecution.getSnapshots(); Map<String, NormalizedFileSnapshot> afterPreviousSnapshots = afterPreviousExecution != null ? afterPreviousExecution.getSnapshots() : new HashMap<String, NormalizedFileSnapshot>(); int newEntryCount = 0; ImmutableMap.Builder<String, NormalizedFileSnapshot> outputEntries = ImmutableMap.builder(); for (Map.Entry<String, NormalizedFileSnapshot> entry : afterSnapshots.entrySet()) { final String path = entry.getKey(); NormalizedFileSnapshot fileSnapshot = entry.getValue(); if (isOutputEntry(path, fileSnapshot, beforeSnapshots, afterPreviousSnapshots)) { outputEntries.put(entry.getKey(), fileSnapshot); newEntryCount++; } } // Are all files snapshot after execution accounted for as new entries? if (newEntryCount == afterSnapshots.size()) { filesSnapshot = afterExecution; } else { filesSnapshot = new DefaultFileCollectionSnapshot(outputEntries.build(), TaskFilePropertyCompareStrategy.OUTPUT, true); } } else { filesSnapshot = afterExecution; } return filesSnapshot; } /** * Decide whether an entry should be considered to be part of the output. Entries that are considered outputs are: * <ul> * <li>an entry that did not exist before the execution, but exists after the execution</li> * <li>an entry that did exist before the execution, and has been changed during the execution</li> * <li>an entry that did wasn't changed during the execution, but was already considered an output during the previous execution</li> * </ul> */ private static boolean isOutputEntry(String path, NormalizedFileSnapshot fileSnapshot, Map<String, NormalizedFileSnapshot> beforeSnapshots, Map<String, NormalizedFileSnapshot> afterPreviousSnapshots) { NormalizedFileSnapshot beforeSnapshot = beforeSnapshots.get(path); // Was it created during execution? if (beforeSnapshot == null) { return true; } // Was it updated during execution? if (!fileSnapshot.getSnapshot().isContentAndMetadataUpToDate(beforeSnapshot.getSnapshot())) { return true; } // Did we already consider it as an output after the previous execution? if (afterPreviousSnapshots.containsKey(path)) { return true; } return false; } }