platform: added async version of execute() in RunProfileStarter

It allows to create asynchronous run profiles which can be started after BeforeRun tasks (e.g. Build)
This commit is contained in:
Artem Bukhonov
2017-01-24 17:46:50 +03:00
committed by nik
parent d3d675ab33
commit ca52397881
3 changed files with 71 additions and 48 deletions
@@ -20,6 +20,9 @@ import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.RunContentDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import static org.jetbrains.concurrency.Promises.rejectedPromise;
/**
* The callback used to execute a process from the {@link ExecutionManager#startRunProfile(RunProfileStarter, com.intellij.execution.configurations.RunProfileState, com.intellij.execution.runners.ExecutionEnvironment)}
@@ -29,4 +32,19 @@ import org.jetbrains.annotations.Nullable;
public abstract class RunProfileStarter {
@Nullable
public abstract RunContentDescriptor execute(@NotNull RunProfileState state, @NotNull ExecutionEnvironment environment) throws ExecutionException;
/**
* Async version of {@link #execute(RunProfileState, ExecutionEnvironment)}.
* You must NOT throw exceptions in this method.
* Instead return {@link org.jetbrains.concurrency.Promises#rejectedPromise(Throwable)} or call {@link org.jetbrains.concurrency.AsyncPromise#setError(Throwable)}
*/
public Promise<RunContentDescriptor> executeAsync(@NotNull RunProfileState state, @NotNull ExecutionEnvironment environment) {
try {
RunContentDescriptor result = execute(state, environment);
return Promise.resolve(result);
}
catch (ExecutionException e) {
return rejectedPromise(e);
}
}
}
@@ -60,6 +60,14 @@ public abstract class AsyncGenericProgramRunner<Settings extends RunnerSettings>
@Nullable final Callback callback,
@Nullable final RunProfileStarter starter) {
ExecutionManager.getInstance(environment.getProject()).startRunProfile(new RunProfileStarter() {
@Override
public Promise<RunContentDescriptor> executeAsync(@NotNull RunProfileState state, @NotNull ExecutionEnvironment environment) {
if (starter == null) {
return Promise.resolve(postProcess(environment, null, callback));
}
return starter.executeAsync(state, environment).then(descriptor -> postProcess(environment, descriptor, callback));
}
@Override
public RunContentDescriptor execute(@NotNull RunProfileState state, @NotNull ExecutionEnvironment environment) throws ExecutionException {
return postProcess(environment, starter == null ? null : starter.execute(state, environment), callback);
@@ -53,6 +53,7 @@ import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.AppUIUtil;
import com.intellij.ui.docking.DockManager;
import com.intellij.util.Alarm;
import com.intellij.util.SmartList;
@@ -377,63 +378,59 @@ public class ExecutionManagerImpl extends ExecutionManager implements Disposable
}
RunProfile profile = environment.getRunProfile();
boolean started = false;
try {
project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarting(executor.getId(), environment);
project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarting(executor.getId(), environment);
final RunContentDescriptor descriptor = starter.execute(state, environment);
if (descriptor != null) {
final Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity =
Trinity.create(descriptor, environment.getRunnerAndConfigurationSettings(), executor);
myRunningConfigurations.add(trinity);
Disposer.register(descriptor, () -> myRunningConfigurations.remove(trinity));
getContentManager().showRunContent(executor, descriptor, environment.getContentToReuse());
final ProcessHandler processHandler = descriptor.getProcessHandler();
if (processHandler != null) {
if (!processHandler.isStartNotified()) {
processHandler.startNotify();
}
project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarted(executor.getId(), environment, processHandler);
started = true;
starter.executeAsync(state, environment).done(descriptor -> {
AppUIUtil.invokeOnEdt(() -> {
if (descriptor != null) {
final Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity =
Trinity.create(descriptor, environment.getRunnerAndConfigurationSettings(), executor);
myRunningConfigurations.add(trinity);
Disposer.register(descriptor, () -> myRunningConfigurations.remove(trinity));
getContentManager().showRunContent(executor, descriptor, environment.getContentToReuse());
final ProcessHandler processHandler = descriptor.getProcessHandler();
if (processHandler != null) {
if (!processHandler.isStartNotified()) {
processHandler.startNotify();
}
project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarted(executor.getId(), environment, processHandler);
ProcessExecutionListener listener = new ProcessExecutionListener(project, executor.getId(), environment, processHandler, descriptor);
processHandler.addProcessListener(listener);
// Since we cannot guarantee that the listener is added before process handled is start notified,
// we have to make sure the process termination events are delivered to the clients.
// Here we check the current process state and manually deliver events, while
// the ProcessExecutionListener guarantees each such event is only delivered once
// either by this code, or by the ProcessHandler.
ProcessExecutionListener listener = new ProcessExecutionListener(project, executor.getId(), environment, processHandler, descriptor);
processHandler.addProcessListener(listener);
boolean terminating = processHandler.isProcessTerminating();
boolean terminated = processHandler.isProcessTerminated();
if (terminating || terminated) {
listener.processWillTerminate(new ProcessEvent(processHandler), false /*doesn't matter*/);
// Since we cannot guarantee that the listener is added before process handled is start notified,
// we have to make sure the process termination events are delivered to the clients.
// Here we check the current process state and manually deliver events, while
// the ProcessExecutionListener guarantees each such event is only delivered once
// either by this code, or by the ProcessHandler.
if (terminated) {
//noinspection ConstantConditions
int exitCode = processHandler.getExitCode();
listener.processTerminated(new ProcessEvent(processHandler, exitCode));
boolean terminating = processHandler.isProcessTerminating();
boolean terminated = processHandler.isProcessTerminated();
if (terminating || terminated) {
listener.processWillTerminate(new ProcessEvent(processHandler), false /*doesn't matter*/);
if (terminated) {
//noinspection ConstantConditions
int exitCode = processHandler.getExitCode();
listener.processTerminated(new ProcessEvent(processHandler, exitCode));
}
}
}
environment.setContentToReuse(descriptor);
}
environment.setContentToReuse(descriptor);
else {
project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment);
}
}, o -> project.isDisposed());
}).rejected(e -> {
if (!(e instanceof ProcessCanceledException)) {
ExecutionException error = e instanceof ExecutionException ? (ExecutionException)e : new ExecutionException(e);
ExecutionUtil.handleExecutionError(project, ExecutionManager.getInstance(project).getContentManager().getToolWindowIdByEnvironment(environment),
profile, error);
}
}
catch (ProcessCanceledException e) {
LOG.info(e);
}
catch (ExecutionException e) {
ExecutionUtil.handleExecutionError(project,
ExecutionManager.getInstance(project).getContentManager().getToolWindowIdByEnvironment(environment),
profile, e);
LOG.info(e);
}
finally {
if (!started) {
project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment);
}
}
project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment);
});
};
if (ApplicationManager.getApplication().isUnitTestMode() && !myForceCompilationInTests) {