/*
* Copyright 2013 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.language.nativeplatform.internal.incremental;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
import org.gradle.api.internal.hash.FileHasher;
import org.gradle.cache.PersistentStateCache;
import org.gradle.language.nativeplatform.internal.IncludeDirectives;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class IncrementalCompileProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(IncrementalCompileProcessor.class);
private final PersistentStateCache<CompilationState> previousCompileStateCache;
private final SourceIncludesParser sourceIncludesParser;
private final SourceIncludesResolver sourceIncludesResolver;
private final FileHasher hasher;
public IncrementalCompileProcessor(PersistentStateCache<CompilationState> previousCompileStateCache, SourceIncludesResolver sourceIncludesResolver, SourceIncludesParser sourceIncludesParser, FileHasher hasher) {
this.previousCompileStateCache = previousCompileStateCache;
this.sourceIncludesResolver = sourceIncludesResolver;
this.sourceIncludesParser = sourceIncludesParser;
this.hasher = hasher;
}
public IncrementalCompilation processSourceFiles(Collection<File> sourceFiles) {
CompilationState previousCompileState = previousCompileStateCache.get();
final IncrementalCompileFiles result = new IncrementalCompileFiles(previousCompileState);
for (File sourceFile : sourceFiles) {
result.processSource(sourceFile);
}
return new DefaultIncrementalCompilation(result.current.snapshot(), result.getModifiedSources(), result.getRemovedSources(), result.getDiscoveredInputs());
}
private class IncrementalCompileFiles {
private final CompilationState previous;
private final BuildableCompilationState current = new BuildableCompilationState();
private final Map<File, Boolean> processed = new HashMap<File, Boolean>();
private final List<File> toRecompile = new ArrayList<File>();
private final Set<File> discoveredInputs = Sets.newHashSet();
public IncrementalCompileFiles(CompilationState previousCompileState) {
this.previous = previousCompileState == null ? new CompilationState() : previousCompileState;
}
public void processSource(File sourceFile) {
current.addSourceInput(sourceFile);
if (checkChangedAndUpdateState(sourceFile) || !previous.getSourceInputs().contains(sourceFile)) {
toRecompile.add(sourceFile);
}
}
public boolean checkChangedAndUpdateState(File file) {
boolean changed = false;
if (processed.containsKey(file)) {
return processed.get(file);
}
if (!file.exists()) {
return true;
}
// Assume unchanged if we recurse to the same file due to dependency cycle
processed.put(file, false);
CompilationFileState previousState = previous.getState(file);
HashCode newHash = hasher.hash(file);
IncludeDirectives includeDirectives;
if (!sameHash(previousState, newHash)) {
changed = true;
includeDirectives = sourceIncludesParser.parseIncludes(file);
} else {
includeDirectives = previousState.getIncludeDirectives();
}
SourceIncludesResolver.ResolvedSourceIncludes resolutionResult = resolveIncludes(file, includeDirectives);
CompilationFileState newState = new CompilationFileState(newHash, includeDirectives, ImmutableSet.copyOf(resolutionResult.getResolvedIncludes()));
discoveredInputs.addAll(resolutionResult.getCheckedLocations());
// Compare the previous resolved includes with resolving now.
if (!sameResolved(previousState, newState)) {
changed = true;
}
current.setState(file, newState);
for (ResolvedInclude dep : newState.getResolvedIncludes()) {
if (dep.isUnknown()) {
LOGGER.info("Cannot determine changed state of included '{}' in source file '{}'. Assuming changed.", dep.getInclude(), file.getName());
changed = true;
} else {
boolean depChanged = checkChangedAndUpdateState(dep.getFile());
changed = changed || depChanged;
}
}
processed.put(file, changed);
return changed;
}
private boolean sameHash(CompilationFileState previousState, HashCode newHash) {
return previousState != null && newHash.equals(previousState.getHash());
}
private boolean sameResolved(CompilationFileState previousState, CompilationFileState newState) {
return previousState != null && newState.getResolvedIncludes().equals(previousState.getResolvedIncludes());
}
private SourceIncludesResolver.ResolvedSourceIncludes resolveIncludes(File file, IncludeDirectives includeDirectives) {
return sourceIncludesResolver.resolveIncludes(file, includeDirectives);
}
public List<File> getModifiedSources() {
return toRecompile;
}
public List<File> getRemovedSources() {
List<File> removed = new ArrayList<File>();
for (File previousSource : previous.getSourceInputs()) {
if (!current.getSourceInputs().contains(previousSource)) {
removed.add(previousSource);
}
}
return removed;
}
public Set<File> getDiscoveredInputs() {
return discoveredInputs;
}
}
}