blob: bb28c422044ba8d37e6f46ce2e5cfff2c4efca76 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* 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.jetbrains.jps.incremental.fs;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileSystemUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.builders.BuildRootDescriptor;
import org.jetbrains.jps.builders.BuildTarget;
import org.jetbrains.jps.builders.FileProcessor;
import org.jetbrains.jps.builders.impl.BuildTargetChunk;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.CompileScope;
import org.jetbrains.jps.incremental.ModuleBuildTarget;
import org.jetbrains.jps.incremental.Utils;
import org.jetbrains.jps.incremental.storage.Timestamps;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* @author Eugene Zhuravlev
* Date: 12/16/11
*/
public class BuildFSState extends FSState {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.fs.BuildFSState");
private static final Key<Set<? extends BuildTarget<?>>> CONTEXT_TARGETS_KEY = Key.create("_fssfate_context_targets_");
private static final Key<FilesDelta> CURRENT_ROUND_DELTA_KEY = Key.create("_current_round_delta_");
private static final Key<FilesDelta> LAST_ROUND_DELTA_KEY = Key.create("_last_round_delta_");
// when true, will always determine dirty files by scanning FS and comparing timestamps
// alternatively, when false, after first scan will rely on external notifications about changes
private final boolean myAlwaysScanFS;
public BuildFSState(boolean alwaysScanFS) {
myAlwaysScanFS = alwaysScanFS;
}
@Override
public boolean isInitialScanPerformed(BuildTarget<?> target) {
return !myAlwaysScanFS && super.isInitialScanPerformed(target);
}
@Override
public Map<BuildRootDescriptor, Set<File>> getSourcesToRecompile(@NotNull CompileContext context, BuildTarget<?> target) {
if (target instanceof ModuleBuildTarget) {
// multiple compilation rounds are applicable to ModuleBuildTarget only
final FilesDelta lastRoundDelta = getRoundDelta(LAST_ROUND_DELTA_KEY, context);
if (lastRoundDelta != null) {
return lastRoundDelta.getSourcesToRecompile();
}
}
return super.getSourcesToRecompile(context, target);
}
public boolean isMarkedForRecompilation(@Nullable CompileContext context, BuildRootDescriptor rd, File file) {
FilesDelta delta = getRoundDelta(LAST_ROUND_DELTA_KEY, context);
if (delta == null) {
delta = getDelta(rd.getTarget());
}
final Map<BuildRootDescriptor, Set<File>> recompile = delta.getSourcesToRecompile();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (recompile) {
final Set<File> files = recompile.get(rd);
return files != null && files.contains(file);
}
}
/**
* Note: marked file will well be visible as "dirty" only on the next compilation round!
* @throws IOException
*/
@Override
public boolean markDirty(@Nullable CompileContext context, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage, boolean saveEventStamp) throws IOException {
final FilesDelta roundDelta = getRoundDelta(CURRENT_ROUND_DELTA_KEY, context);
if (roundDelta != null && isInCurrentContextTargets(context, rd)) {
roundDelta.markRecompile(rd, file);
}
return super.markDirty(context, file, rd, tsStorage, saveEventStamp);
}
private static boolean isInCurrentContextTargets(CompileContext context, BuildRootDescriptor rd) {
if (context == null) {
return false;
}
Set<? extends BuildTarget<?>> targets = CONTEXT_TARGETS_KEY.get(context, Collections.<BuildTarget<?>>emptySet());
return targets.contains(rd.getTarget());
}
@Override
public boolean markDirtyIfNotDeleted(@Nullable CompileContext context, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage) throws IOException {
final boolean marked = super.markDirtyIfNotDeleted(context, file, rd, tsStorage);
if (marked) {
final FilesDelta roundDelta = getRoundDelta(CURRENT_ROUND_DELTA_KEY, context);
if (roundDelta != null) {
if (isInCurrentContextTargets(context, rd)) {
roundDelta.markRecompile(rd, file);
}
}
}
return marked;
}
public void clearAll() {
clearContextRoundData(null);
clearContextChunk(null);
super.clearAll();
}
public void clearContextRoundData(@Nullable CompileContext context) {
setRoundDelta(CURRENT_ROUND_DELTA_KEY, context, null);
setRoundDelta(LAST_ROUND_DELTA_KEY, context, null);
}
public void clearContextChunk(@Nullable CompileContext context) {
setContextTargets(context, null);
}
public void beforeChunkBuildStart(@NotNull CompileContext context, BuildTargetChunk chunk) {
setContextTargets(context, chunk.getTargets());
}
public void beforeNextRoundStart(@NotNull CompileContext context, ModuleChunk chunk) {
setRoundDelta(LAST_ROUND_DELTA_KEY, context, getRoundDelta(CURRENT_ROUND_DELTA_KEY, context));
setRoundDelta(CURRENT_ROUND_DELTA_KEY, context, new FilesDelta());
}
public <R extends BuildRootDescriptor, T extends BuildTarget<R>> boolean processFilesToRecompile(CompileContext context, final @NotNull T target, final FileProcessor<R, T> processor) throws IOException {
final Map<BuildRootDescriptor, Set<File>> data = getSourcesToRecompile(context, target);
final CompileScope scope = context.getScope();
synchronized (data) {
for (Map.Entry<BuildRootDescriptor, Set<File>> entry : data.entrySet()) {
//noinspection unchecked
R root = (R)entry.getKey();
if (!target.equals(root.getTarget())) {
// the data can contain roots from other targets (e.g. when compiling module cycles)
continue;
}
for (File file : entry.getValue()) {
if (!scope.isAffected(target, file)) {
continue;
}
if (!processor.apply(target, file, root)) {
return false;
}
}
}
}
return true;
}
/**
* @return true if marked something, false otherwise
*/
public boolean markAllUpToDate(CompileContext context, final BuildRootDescriptor rd, final Timestamps stamps) throws IOException {
boolean marked = false;
final FilesDelta delta = getDelta(rd.getTarget());
final Set<File> files = delta.clearRecompile(rd);
if (files != null) {
CompileScope scope = context.getScope();
final long compilationStartStamp = context.getCompilationStartStamp();
for (File file : files) {
if (scope.isAffected(rd.getTarget(), file)) {
final long currentFileStamp = FileSystemUtil.lastModified(file);
if (!rd.isGenerated() && (currentFileStamp > compilationStartStamp || getEventRegistrationStamp(file) > compilationStartStamp)) {
// if the file was modified after the compilation had started,
// do not save the stamp considering file dirty
if (Utils.IS_TEST_MODE) {
LOG.info("Timestamp after compilation started; marking dirty again: " + file.getPath());
}
delta.markRecompile(rd, file);
}
else {
marked = true;
stamps.saveStamp(file, rd.getTarget(), currentFileStamp);
}
}
else {
if (Utils.IS_TEST_MODE) {
LOG.info("Not affected by compile scope; marking dirty again: " + file.getPath());
}
delta.markRecompile(rd, file);
}
}
}
return marked;
}
private static void setContextTargets(@Nullable CompileContext context, @Nullable Set<? extends BuildTarget<?>> targets) {
if (context != null) {
CONTEXT_TARGETS_KEY.set(context, targets);
}
}
@Nullable
private static FilesDelta getRoundDelta(@NotNull Key<FilesDelta> key, @Nullable CompileContext context) {
return context != null? key.get(context) : null;
}
private static void setRoundDelta(@NotNull Key<FilesDelta> key, @Nullable CompileContext context, @Nullable FilesDelta delta) {
if (context != null) {
key.set(context, delta);
}
}
}