From 6e472a9459a9477ddff86f0f5b531e95fffe3c6f Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Mon, 28 Sep 2015 14:21:21 +0200 Subject: [PATCH] notify IDE about unprocessed sources that were changed during build --- .../intellij/compiler/impl/CompileDriver.java | 15 +++++++ .../server/AutoMakeMessageHandler.java | 15 +++++++ .../compiler/server/BuildManager.java | 3 ++ .../org/jetbrains/jps/api/GlobalOptions.java | 5 +++ .../jps/incremental/CompileContextImpl.java | 2 +- .../jps/incremental/IncProjectBuilder.java | 13 ++++++ .../jps/incremental/fs/BuildFSState.java | 45 +++++++++++++++++-- .../UnprocessedFSChangesNotification.java | 32 +++++++++++++ 8 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 jps/jps-builders/src/org/jetbrains/jps/incremental/messages/UnprocessedFSChangesNotification.java diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 677f77a1c0d1..0b351b150220 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -73,6 +73,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import org.jetbrains.jps.api.CmdlineProtoUtil; import org.jetbrains.jps.api.CmdlineRemoteProto; +import org.jetbrains.jps.api.GlobalOptions; import org.jetbrains.jps.api.TaskFuture; import org.jetbrains.jps.model.java.JavaSourceRootType; @@ -318,6 +319,7 @@ public class CompileDriver { ArtifactsCompiler.addWrittenPaths(compileContext, writtenArtifactOutputPaths); } break; + case BUILD_COMPLETED: ExitStatus status = ExitStatus.SUCCESS; if (event.hasCompletionStatus()) { @@ -339,6 +341,19 @@ public class CompileDriver { } compileContext.putUserDataIfAbsent(COMPILE_SERVER_BUILD_STATUS, status); break; + + case CUSTOM_BUILDER_MESSAGE: + if (event.hasCustomBuilderMessage()) { + final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.CustomBuilderMessage message = event.getCustomBuilderMessage(); + if (GlobalOptions.JPS_SYSTEM_BUILDER_ID.equals(message.getBuilderId()) && GlobalOptions.JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID.equals(message.getMessageType())) { + final String text = message.getMessageText(); + if (!StringUtil.isEmpty(text)) { + compileContext.addMessage(CompilerMessageCategory.INFORMATION, text, null, -1, -1); + } + } + } + break; + } } }); diff --git a/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java index 4a792bb121e0..cbdcc3a43475 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java @@ -31,6 +31,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.problems.Problem; import com.intellij.problems.WolfTheProblemSolver; import org.jetbrains.jps.api.CmdlineRemoteProto; +import org.jetbrains.jps.api.GlobalOptions; import java.util.Collections; import java.util.UUID; @@ -44,6 +45,7 @@ class AutoMakeMessageHandler extends DefaultMessageHandler { private CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status myBuildStatus; private final Project myProject; private final WolfTheProblemSolver myWolf; + private volatile boolean myUnprocessedFSChangesDetected = false; public AutoMakeMessageHandler(Project project) { super(project); @@ -52,6 +54,10 @@ class AutoMakeMessageHandler extends DefaultMessageHandler { myWolf = WolfTheProblemSolver.getInstance(project); } + public boolean unprocessedFSChangesDetected() { + return myUnprocessedFSChangesDetected; + } + @Override public void buildStarted(UUID sessionId) { } @@ -77,6 +83,15 @@ class AutoMakeMessageHandler extends DefaultMessageHandler { } return; + case CUSTOM_BUILDER_MESSAGE: + if (event.hasCustomBuilderMessage()) { + final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.CustomBuilderMessage message = event.getCustomBuilderMessage(); + if (GlobalOptions.JPS_SYSTEM_BUILDER_ID.equals(message.getBuilderId()) && GlobalOptions.JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID.equals(message.getMessageType())) { + myUnprocessedFSChangesDetected = true; + } + } + return; + default: return; } diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java index a38b85ca186c..ea51c61db41c 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java @@ -516,6 +516,9 @@ public class BuildManager implements Disposable { } finally { myAutomakeFutures.remove(future); + if (handler.unprocessedFSChangesDetected()) { + scheduleAutoMake(); + } } } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java index 223d57a532f9..ee357c7dbcfe 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java @@ -26,4 +26,9 @@ public interface GlobalOptions { String COMPILE_PARALLEL_MAX_THREADS_OPTION = "compile.parallel.max.threads"; String REBUILD_ON_DEPENDENCY_CHANGE_OPTION = "rebuild.on.dependency.change"; String LOG_DIR_OPTION = "jps.log.dir"; + + // builder ID for all global build messages sent to the controlling IDE + String JPS_SYSTEM_BUILDER_ID = "JPS"; + // notification about the files changed during compilation, but not compiled in current compilation session + String JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID = "!unprocessed_fs_changes_detected!"; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContextImpl.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContextImpl.java index 9886590b5d30..a8a2c99ecf22 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContextImpl.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/CompileContextImpl.java @@ -62,7 +62,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon myScope = scope; myDelegateMessageHandler = delegateMessageHandler; } - // todo: add timestamp-setting code + @Override public long getCompilationStartStamp(BuildTarget target) { synchronized (myCompilationStartStamp) { diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java index fcff504552d6..a7377d628118 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java @@ -193,9 +193,11 @@ public class IncProjectBuilder { runBuild(context, forceCleanCaches); myProjectDescriptor.dataManager.saveVersion(); reportRebuiltModules(context); + reportUnprocessedChanges(context); } catch (StopBuildException e) { reportRebuiltModules(context); + reportUnprocessedChanges(context); // some builder decided to stop the build // report optional progress message if any final String msg = e.getMessage(); @@ -309,6 +311,17 @@ public class IncProjectBuilder { context.processMessage(new CompilerMessage("", BuildMessage.Kind.INFO, message.toString())); } + private static void reportUnprocessedChanges(CompileContextImpl context) { + final ProjectDescriptor pd = context.getProjectDescriptor(); + final BuildFSState fsState = pd.fsState; + for (BuildTarget target : pd.getBuildTargetIndex().getAllTargets()) { + if (fsState.hasUnprocessedChanges(context, target)) { + context.processMessage(new UnprocessedFSChangesNotification()); + break; + } + } + } + private static void flushContext(CompileContext context) { if (context != null) { final ProjectDescriptor pd = context.getProjectDescriptor(); 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 50454d0f89ac..b7caa0b575f7 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 @@ -109,7 +109,9 @@ public class BuildFSState { } public long getEventRegistrationStamp(File file) { - return myRegistrationStamps.get(file); + synchronized (myRegistrationStamps) { + return myRegistrationStamps.get(file); + } } public boolean hasWorkToDo(BuildTarget target) { @@ -120,6 +122,38 @@ public class BuildFSState { return delta != null && delta.hasChanges(); } + /** + * @return true if there were changed files reported for the specified target, _after_ the target compilation had been started + */ + public boolean hasUnprocessedChanges(@NotNull CompileContext context, @NotNull BuildTarget target) { + if (!myInitialScanPerformed.contains(target)) { + return false; + } + final FilesDelta delta = myDeltas.get(target); + if (delta == null) { + return false; + } + final long targetBuildStart = context.getCompilationStartStamp(target); + if (targetBuildStart <= 0L) { + return false; + } + final CompileScope scope = context.getScope(); + try { + delta.lockData(); + for (Set files : delta.getSourcesToRecompile().values()) { + for (File file : files) { + if ((getEventRegistrationStamp(file) > targetBuildStart || FileSystemUtil.lastModified(file) > targetBuildStart) && scope.isAffected(target, file)) { + return true; + } + } + } + } + finally { + delta.unlockData(); + } + return false; + } + public void markInitialScanPerformed(BuildTarget target) { myInitialScanPerformed.add(target); } @@ -211,7 +245,10 @@ public class BuildFSState { LOG.debug(rd.getTarget() + ": MARKED DIRTY: " + file.getPath()); } if (saveEventStamp) { - myRegistrationStamps.put(file, System.currentTimeMillis()); + final long eventStamp = System.currentTimeMillis(); + synchronized (myRegistrationStamps) { + myRegistrationStamps.put(file, eventStamp); + } } if (tsStorage != null) { tsStorage.removeStamp(file, rd.getTarget()); @@ -258,7 +295,9 @@ public class BuildFSState { clearContextChunk(null); myInitialScanPerformed.clear(); myDeltas.clear(); - myRegistrationStamps.clear(); + synchronized (myRegistrationStamps) { + myRegistrationStamps.clear(); + } } public void clearContextRoundData(@Nullable CompileContext context) { diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/messages/UnprocessedFSChangesNotification.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/messages/UnprocessedFSChangesNotification.java new file mode 100644 index 000000000000..3fdb7b0b6b55 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/messages/UnprocessedFSChangesNotification.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2015 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.messages; + +import org.jetbrains.jps.api.GlobalOptions; + +/** + * @author Eugene Zhuravlev + * Date: 26-Sep-15 + */ +public class UnprocessedFSChangesNotification extends CustomBuilderMessage{ + public UnprocessedFSChangesNotification() { + super( + GlobalOptions.JPS_SYSTEM_BUILDER_ID, + GlobalOptions.JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID, + "Some files were changed during the build. Additional compilation may be required." + ); + } +}