diff --git a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildRunner.java b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildRunner.java index fc59a611eacc..1b893a45a302 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildRunner.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildRunner.java @@ -210,7 +210,7 @@ public class BuildRunner { } fileSet.add(file); if (targetTypesToForceBuild.contains(descriptor.getTarget().getTargetType())) { - pd.fsState.markDirty(null, file, descriptor, timestamps); + pd.fsState.markDirty(null, file, descriptor, timestamps, false); } } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java index 1b6bf999b702..b0470155ff36 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java @@ -253,7 +253,7 @@ final class BuildSession implements Runnable, CanceledStatus { else { // apply events to already loaded state try { - applyFSEvent(pd, myInitialFSDelta); + applyFSEvent(pd, myInitialFSDelta, false); } catch (Throwable e) { LOG.error(e); @@ -269,7 +269,7 @@ final class BuildSession implements Runnable, CanceledStatus { try { try { fsState.load(fsStateStream, pd.getModel(), pd.getBuildRootIndex()); - applyFSEvent(pd, myInitialFSDelta); + applyFSEvent(pd, myInitialFSDelta, false); TimingLog.LOG.debug("FS Delta loaded"); } finally { @@ -349,7 +349,7 @@ final class BuildSession implements Runnable, CanceledStatus { @Override public void run() { try { - applyFSEvent(myProjectDescriptor, event); + applyFSEvent(myProjectDescriptor, event, true); myLastEventOrdinal += 1; } catch (IOException e) { @@ -378,7 +378,7 @@ final class BuildSession implements Runnable, CanceledStatus { } } - private static void applyFSEvent(ProjectDescriptor pd, @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent event) throws IOException { + private static void applyFSEvent(ProjectDescriptor pd, @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent event, final boolean saveEventStamp) throws IOException { if (event == null) { return; } @@ -425,7 +425,7 @@ final class BuildSession implements Runnable, CanceledStatus { pd.getFSCache().clear(); cacheCleared = true; } - pd.fsState.markDirty(null, file, descriptor, timestamps); + pd.fsState.markDirty(null, file, descriptor, timestamps, saveEventStamp); } else { if (LOG.isDebugEnabled()) { diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ChainedTargetsBuildListener.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ChainedTargetsBuildListener.java index 304aa5250b6a..489dc218297a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ChainedTargetsBuildListener.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ChainedTargetsBuildListener.java @@ -47,7 +47,7 @@ class ChainedTargetsBuildListener implements BuildListener { Collection descriptors = rootsIndex.findAllParentDescriptors(file, null, myContext); for (BuildRootDescriptor descriptor : descriptors) { try { - fsState.markDirty(myContext, file, descriptor, myContext.getProjectDescriptor().timestamps.getStorage()); + fsState.markDirty(myContext, file, descriptor, myContext.getProjectDescriptor().timestamps.getStorage(), false); } catch (IOException ignored) { } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/FSOperations.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/FSOperations.java index 29d2bdedc237..f20963439f49 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/FSOperations.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/FSOperations.java @@ -82,7 +82,7 @@ public class FSOperations { final JavaSourceRootDescriptor rd = context.getProjectDescriptor().getBuildRootIndex().findJavaRootDescriptor(context, file); if (rd != null) { final ProjectDescriptor pd = context.getProjectDescriptor(); - pd.fsState.markDirty(context, round, file, rd, pd.timestamps.getStorage()); + pd.fsState.markDirty(context, round, file, rd, pd.timestamps.getStorage(), false); } } @@ -245,7 +245,7 @@ public class FSOperations { // if it is full project rebuild, all storages are already completely cleared; // so passing null because there is no need to access the storage to clear non-existing data final Timestamps marker = context.isProjectRebuild() ? null : tsStorage; - context.getProjectDescriptor().fsState.markDirty(context, round, file, rd, marker); + context.getProjectDescriptor().fsState.markDirty(context, round, file, rd, marker, false); } if (currentFiles != null) { currentFiles.add(file); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/BuildFSState.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/BuildFSState.java index 8b59df6dcb61..50454d0f89ac 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/BuildFSState.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/BuildFSState.java @@ -18,9 +18,11 @@ 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 com.intellij.openapi.util.io.FileUtil; import com.intellij.util.SmartList; import com.intellij.util.containers.MultiMap; import com.intellij.util.io.IOUtil; +import gnu.trove.TObjectLongHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.ModuleChunk; @@ -51,6 +53,7 @@ public class BuildFSState { // alternatively, when false, after first scan will rely on external notifications about changes private final boolean myAlwaysScanFS; private final Set> myInitialScanPerformed = Collections.synchronizedSet(new HashSet>()); + private final TObjectLongHashMap myRegistrationStamps = new TObjectLongHashMap(FileUtil.FILE_HASHING_STRATEGY); private final Map, FilesDelta> myDeltas = Collections.synchronizedMap(new HashMap, FilesDelta>()); public BuildFSState(boolean alwaysScanFS) { @@ -105,6 +108,10 @@ public class BuildFSState { getDelta(rd.getTarget()).clearRecompile(rd); } + public long getEventRegistrationStamp(File file) { + return myRegistrationStamps.get(file); + } + public boolean hasWorkToDo(BuildTarget target) { if (!myInitialScanPerformed.contains(target)) { return true; @@ -185,11 +192,11 @@ public class BuildFSState { * Note: marked file will well be visible as "dirty" only on the next compilation round! * @throws IOException */ - public final boolean markDirty(@Nullable CompileContext context, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage) throws IOException { - return markDirty(context, CompilationRound.NEXT, file, rd, tsStorage); + public final boolean markDirty(@Nullable CompileContext context, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage, boolean saveEventStamp) throws IOException { + return markDirty(context, CompilationRound.NEXT, file, rd, tsStorage, saveEventStamp); } - public boolean markDirty(@Nullable CompileContext context, CompilationRound round, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage) throws IOException { + public boolean markDirty(@Nullable CompileContext context, CompilationRound round, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage, boolean saveEventStamp) throws IOException { final FilesDelta roundDelta = getRoundDelta(round == CompilationRound.NEXT? NEXT_ROUND_DELTA_KEY : CURRENT_ROUND_DELTA_KEY, context); if (roundDelta != null && isInCurrentContextTargets(context, rd)) { roundDelta.markRecompile(rd, file); @@ -203,6 +210,9 @@ public class BuildFSState { if (LOG.isDebugEnabled()) { LOG.debug(rd.getTarget() + ": MARKED DIRTY: " + file.getPath()); } + if (saveEventStamp) { + myRegistrationStamps.put(file, System.currentTimeMillis()); + } if (tsStorage != null) { tsStorage.removeStamp(file, rd.getTarget()); } @@ -248,6 +258,7 @@ public class BuildFSState { clearContextChunk(null); myInitialScanPerformed.clear(); myDeltas.clear(); + myRegistrationStamps.clear(); } public void clearContextRoundData(@Nullable CompileContext context) { @@ -323,9 +334,12 @@ public class BuildFSState { for (File file : files) { if (scope.isAffected(target, file)) { final long currentFileStamp = FileSystemUtil.lastModified(file); - if (!rd.isGenerated() && currentFileStamp > targetBuildStartStamp) { + if (!rd.isGenerated() && (currentFileStamp > targetBuildStartStamp || getEventRegistrationStamp(file) > targetBuildStartStamp)) { // if the file was modified after the compilation had started, // do not save the stamp considering file dirty + // Important! + // Event registration stamp check is essential for the files that were actually changed _before_ targetBuildStart, + // but corresponding change event was received and processed _after_ targetBuildStart if (Utils.IS_TEST_MODE) { LOG.info("Timestamp after compilation started; marking dirty again: " + file.getPath()); }