06c716221c7cb48edf80dd16309bd44e559d4fbc
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / impl / ExecutionManagerImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution.impl;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.execution.*;
20 import com.intellij.execution.configuration.CompatibilityAwareRunProfile;
21 import com.intellij.execution.configurations.RunConfiguration;
22 import com.intellij.execution.configurations.RunProfile;
23 import com.intellij.execution.configurations.RunProfileState;
24 import com.intellij.execution.process.ProcessAdapter;
25 import com.intellij.execution.process.ProcessEvent;
26 import com.intellij.execution.process.ProcessHandler;
27 import com.intellij.execution.runners.ExecutionEnvironment;
28 import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
29 import com.intellij.execution.runners.ExecutionUtil;
30 import com.intellij.execution.runners.ProgramRunner;
31 import com.intellij.execution.ui.RunContentDescriptor;
32 import com.intellij.execution.ui.RunContentManager;
33 import com.intellij.execution.ui.RunContentManagerImpl;
34 import com.intellij.ide.SaveAndSyncHandler;
35 import com.intellij.openapi.Disposable;
36 import com.intellij.openapi.actionSystem.DataContext;
37 import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.openapi.components.ServiceManager;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.progress.ProcessCanceledException;
42 import com.intellij.openapi.project.DumbService;
43 import com.intellij.openapi.project.IndexNotReadyException;
44 import com.intellij.openapi.project.Project;
45 import com.intellij.openapi.ui.DialogWrapper;
46 import com.intellij.openapi.ui.Messages;
47 import com.intellij.openapi.util.Condition;
48 import com.intellij.openapi.util.Disposer;
49 import com.intellij.openapi.util.Key;
50 import com.intellij.openapi.util.Trinity;
51 import com.intellij.openapi.util.registry.Registry;
52 import com.intellij.openapi.util.text.StringUtil;
53 import com.intellij.ui.docking.DockManager;
54 import com.intellij.util.Alarm;
55 import com.intellij.util.SmartList;
56 import com.intellij.util.containers.ContainerUtil;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59 import org.jetbrains.annotations.TestOnly;
60
61 import javax.swing.*;
62 import java.util.Collections;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.Set;
66
67 public class ExecutionManagerImpl extends ExecutionManager implements Disposable {
68   public static final Key<Object> EXECUTION_SESSION_ID_KEY = Key.create("EXECUTION_SESSION_ID_KEY");
69   public static final Key<Boolean> EXECUTION_SKIP_RUN = Key.create("EXECUTION_SKIP_RUN");
70
71   private static final Logger LOG = Logger.getInstance(ExecutionManagerImpl.class);
72   private static final ProcessHandler[] EMPTY_PROCESS_HANDLERS = new ProcessHandler[0];
73
74   private final Project myProject;
75   private final Alarm awaitingTerminationAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
76   private final List<Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor>> myRunningConfigurations =
77     ContainerUtil.createLockFreeCopyOnWriteList();
78   private RunContentManagerImpl myContentManager;
79   private volatile boolean myForceCompilationInTests;
80
81   @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
82   @NotNull
83   public static ExecutionManagerImpl getInstance(@NotNull Project project) {
84     return (ExecutionManagerImpl)ServiceManager.getService(project, ExecutionManager.class);
85   }
86
87   protected ExecutionManagerImpl(@NotNull Project project) {
88     myProject = project;
89   }
90
91   @NotNull
92   private static ExecutionEnvironmentBuilder createEnvironmentBuilder(@NotNull Project project,
93                                                                       @NotNull Executor executor,
94                                                                       @Nullable RunnerAndConfigurationSettings configuration) {
95     ExecutionEnvironmentBuilder builder = new ExecutionEnvironmentBuilder(project, executor);
96
97     ProgramRunner runner =
98       RunnerRegistry.getInstance().getRunner(executor.getId(), configuration != null ? configuration.getConfiguration() : null);
99     if (runner == null && configuration != null) {
100       LOG.error("Cannot find runner for " + configuration.getName());
101     }
102     else if (runner != null) {
103       assert configuration != null;
104       builder.runnerAndSettings(runner, configuration);
105     }
106     return builder;
107   }
108
109   public static boolean isProcessRunning(@Nullable RunContentDescriptor descriptor) {
110     ProcessHandler processHandler = descriptor == null ? null : descriptor.getProcessHandler();
111     return processHandler != null && !processHandler.isProcessTerminated();
112   }
113
114   private static void start(@NotNull ExecutionEnvironment environment) {
115     RunnerAndConfigurationSettings settings = environment.getRunnerAndConfigurationSettings();
116     ProgramRunnerUtil.executeConfiguration(environment, settings != null && settings.isEditBeforeRun(), true);
117   }
118
119   private static boolean userApprovesStopForSameTypeConfigurations(Project project, String configName, int instancesCount) {
120     RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project);
121     final RunManagerConfig config = runManager.getConfig();
122     if (!config.isRestartRequiresConfirmation()) return true;
123
124     DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() {
125       @Override
126       public boolean isToBeShown() {
127         return config.isRestartRequiresConfirmation();
128       }
129
130       @Override
131       public void setToBeShown(boolean value, int exitCode) {
132         config.setRestartRequiresConfirmation(value);
133       }
134
135       @Override
136       public boolean canBeHidden() {
137         return true;
138       }
139
140       @Override
141       public boolean shouldSaveOptionsOnCancel() {
142         return false;
143       }
144
145       @NotNull
146       @Override
147       public String getDoNotShowMessage() {
148         return CommonBundle.message("dialog.options.do.not.show");
149       }
150     };
151     return Messages.showOkCancelDialog(
152       project,
153       ExecutionBundle.message("rerun.singleton.confirmation.message", configName, instancesCount),
154       ExecutionBundle.message("process.is.running.dialog.title", configName),
155       ExecutionBundle.message("rerun.confirmation.button.text"),
156       CommonBundle.message("button.cancel"),
157       Messages.getQuestionIcon(), option) == Messages.OK;
158   }
159
160   private static boolean userApprovesStopForIncompatibleConfigurations(Project project,
161                                                                        String configName,
162                                                                        List<RunContentDescriptor> runningIncompatibleDescriptors) {
163     RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project);
164     final RunManagerConfig config = runManager.getConfig();
165     if (!config.isStopIncompatibleRequiresConfirmation()) return true;
166
167     DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() {
168       @Override
169       public boolean isToBeShown() {
170         return config.isStopIncompatibleRequiresConfirmation();
171       }
172
173       @Override
174       public void setToBeShown(boolean value, int exitCode) {
175         config.setStopIncompatibleRequiresConfirmation(value);
176       }
177
178       @Override
179       public boolean canBeHidden() {
180         return true;
181       }
182
183       @Override
184       public boolean shouldSaveOptionsOnCancel() {
185         return false;
186       }
187
188       @NotNull
189       @Override
190       public String getDoNotShowMessage() {
191         return CommonBundle.message("dialog.options.do.not.show");
192       }
193     };
194
195     final StringBuilder names = new StringBuilder();
196     for (final RunContentDescriptor descriptor : runningIncompatibleDescriptors) {
197       String name = descriptor.getDisplayName();
198       if (names.length() > 0) {
199         names.append(", ");
200       }
201       names.append(StringUtil.isEmpty(name) ? ExecutionBundle.message("run.configuration.no.name")
202                                             : String.format("'%s'", name));
203     }
204
205     //noinspection DialogTitleCapitalization
206     return Messages.showOkCancelDialog(
207       project,
208       ExecutionBundle.message("stop.incompatible.confirmation.message",
209                               configName, names.toString(), runningIncompatibleDescriptors.size()),
210       ExecutionBundle.message("incompatible.configuration.is.running.dialog.title", runningIncompatibleDescriptors.size()),
211       ExecutionBundle.message("stop.incompatible.confirmation.button.text"),
212       CommonBundle.message("button.cancel"),
213       Messages.getQuestionIcon(), option) == Messages.OK;
214   }
215
216   private static void stop(@Nullable RunContentDescriptor descriptor) {
217     ProcessHandler processHandler = descriptor != null ? descriptor.getProcessHandler() : null;
218     if (processHandler == null) {
219       return;
220     }
221
222     if (processHandler instanceof KillableProcess && processHandler.isProcessTerminating()) {
223       ((KillableProcess)processHandler).killProcess();
224       return;
225     }
226
227     if (!processHandler.isProcessTerminated()) {
228       if (processHandler.detachIsDefault()) {
229         processHandler.detachProcess();
230       }
231       else {
232         processHandler.destroyProcess();
233       }
234     }
235   }
236
237   @Override
238   public void dispose() {
239     for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) {
240       Disposer.dispose(trinity.first);
241     }
242     myRunningConfigurations.clear();
243   }
244
245   @NotNull
246   @Override
247   public RunContentManager getContentManager() {
248     if (myContentManager == null) {
249       myContentManager = new RunContentManagerImpl(myProject, DockManager.getInstance(myProject));
250       Disposer.register(myProject, myContentManager);
251     }
252     return myContentManager;
253   }
254
255   @NotNull
256   @Override
257   public ProcessHandler[] getRunningProcesses() {
258     if (myContentManager == null) return EMPTY_PROCESS_HANDLERS;
259     List<ProcessHandler> handlers = null;
260     for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) {
261       ProcessHandler processHandler = descriptor.getProcessHandler();
262       if (processHandler != null) {
263         if (handlers == null) {
264           handlers = new SmartList<ProcessHandler>();
265         }
266         handlers.add(processHandler);
267       }
268     }
269     return handlers == null ? EMPTY_PROCESS_HANDLERS : handlers.toArray(new ProcessHandler[handlers.size()]);
270   }
271
272   @Override
273   public void compileAndRun(@NotNull final Runnable startRunnable,
274                             @NotNull final ExecutionEnvironment environment,
275                             @Nullable final RunProfileState state,
276                             @Nullable final Runnable onCancelRunnable) {
277     long id = environment.getExecutionId();
278     if (id == 0) {
279       id = environment.assignNewExecutionId();
280     }
281
282     RunProfile profile = environment.getRunProfile();
283     if (!(profile instanceof RunConfiguration)) {
284       startRunnable.run();
285       return;
286     }
287
288     final RunConfiguration runConfiguration = (RunConfiguration)profile;
289     final List<BeforeRunTask> beforeRunTasks = RunManagerEx.getInstanceEx(myProject).getBeforeRunTasks(runConfiguration);
290     if (beforeRunTasks.isEmpty()) {
291       startRunnable.run();
292     }
293     else {
294       DataContext context = environment.getDataContext();
295       final DataContext projectContext = context != null ? context : SimpleDataContext.getProjectContext(myProject);
296       final long finalId = id;
297       final Long executionSessionId = new Long(id);
298       ApplicationManager.getApplication().executeOnPooledThread((Runnable)() -> {
299         for (BeforeRunTask task : beforeRunTasks) {
300           if (myProject.isDisposed()) {
301             return;
302           }
303           @SuppressWarnings("unchecked")
304           BeforeRunTaskProvider<BeforeRunTask> provider = BeforeRunTaskProvider.getProvider(myProject, task.getProviderId());
305           if (provider == null) {
306             LOG.warn("Cannot find BeforeRunTaskProvider for id='" + task.getProviderId() + "'");
307             continue;
308           }
309           ExecutionEnvironment taskEnvironment = new ExecutionEnvironmentBuilder(environment).contentToReuse(null).build();
310           taskEnvironment.setExecutionId(finalId);
311           EXECUTION_SESSION_ID_KEY.set(taskEnvironment, executionSessionId);
312           if (!provider.executeTask(projectContext, runConfiguration, taskEnvironment, task)) {
313             if (onCancelRunnable != null) {
314               SwingUtilities.invokeLater(onCancelRunnable);
315             }
316             return;
317           }
318         }
319
320         doRun(environment, startRunnable);
321       });
322     }
323   }
324
325   protected void doRun(@NotNull final ExecutionEnvironment environment, @NotNull final Runnable startRunnable) {
326     Boolean allowSkipRun = environment.getUserData(EXECUTION_SKIP_RUN);
327     if (allowSkipRun != null && allowSkipRun) {
328       environment.getProject().getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(environment.getExecutor().getId(),
329                                                                                                 environment);
330     }
331     else {
332       // important! Do not use DumbService.smartInvokeLater here because it depends on modality state
333       // and execution of startRunnable could be skipped if modality state check fails
334       //noinspection SSBasedInspection
335       SwingUtilities.invokeLater(() -> {
336         if (!myProject.isDisposed()) {
337           if (!Registry.is("dumb.aware.run.configurations")) {
338             DumbService.getInstance(myProject).runWhenSmart(startRunnable);
339           } else {
340             try {
341               DumbService.getInstance(myProject).setAlternativeResolveEnabled(true);
342               startRunnable.run();
343             } catch (IndexNotReadyException ignored) {
344               ExecutionUtil.handleExecutionError(environment, new ExecutionException("cannot start while indexing is in progress."));
345             } finally {
346               DumbService.getInstance(myProject).setAlternativeResolveEnabled(false);
347             }
348           }
349         }
350       });
351     }
352   }
353
354   @Override
355   public void startRunProfile(@NotNull final RunProfileStarter starter,
356                               @NotNull final RunProfileState state,
357                               @NotNull final ExecutionEnvironment environment) {
358     final Project project = environment.getProject();
359     RunContentDescriptor reuseContent = getContentManager().getReuseContent(environment);
360     if (reuseContent != null) {
361       reuseContent.setExecutionId(environment.getExecutionId());
362       environment.setContentToReuse(reuseContent);
363     }
364
365     final Executor executor = environment.getExecutor();
366     project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStartScheduled(executor.getId(), environment);
367
368     Runnable startRunnable;
369     startRunnable = () -> {
370       if (project.isDisposed()) {
371         return;
372       }
373
374       RunProfile profile = environment.getRunProfile();
375       boolean started = false;
376       try {
377         project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarting(executor.getId(), environment);
378
379         final RunContentDescriptor descriptor = starter.execute(state, environment);
380         if (descriptor != null) {
381           final Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity =
382             Trinity.create(descriptor, environment.getRunnerAndConfigurationSettings(), executor);
383           myRunningConfigurations.add(trinity);
384           Disposer.register(descriptor, () -> myRunningConfigurations.remove(trinity));
385           getContentManager().showRunContent(executor, descriptor, environment.getContentToReuse());
386           final ProcessHandler processHandler = descriptor.getProcessHandler();
387           if (processHandler != null) {
388             if (!processHandler.isStartNotified()) {
389               processHandler.startNotify();
390             }
391             project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarted(executor.getId(), environment, processHandler);
392             started = true;
393             processHandler.addProcessListener(new ProcessExecutionListener(project, profile, processHandler));
394           }
395           environment.setContentToReuse(descriptor);
396         }
397       }
398       catch (ProcessCanceledException e) {
399         LOG.info(e);
400       }
401       catch (ExecutionException e) {
402         ExecutionUtil.handleExecutionError(project, executor.getToolWindowId(), profile, e);
403         LOG.info(e);
404       }
405       finally {
406         if (!started) {
407           project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment);
408         }
409       }
410     };
411
412     if (ApplicationManager.getApplication().isUnitTestMode() && !myForceCompilationInTests) {
413       startRunnable.run();
414     }
415     else {
416       compileAndRun(startRunnable, environment, state, () -> {
417         if (!project.isDisposed()) {
418           project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment);
419         }
420       });
421     }
422   }
423
424   @Override
425   public void restartRunProfile(@NotNull Project project,
426                                 @NotNull Executor executor,
427                                 @NotNull ExecutionTarget target,
428                                 @Nullable RunnerAndConfigurationSettings configuration,
429                                 @Nullable ProcessHandler processHandler) {
430     ExecutionEnvironmentBuilder builder = createEnvironmentBuilder(project, executor, configuration);
431     if (processHandler != null) {
432       for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) {
433         if (descriptor.getProcessHandler() == processHandler) {
434           builder.contentToReuse(descriptor);
435           break;
436         }
437       }
438     }
439     restartRunProfile(builder.target(target).build());
440   }
441
442   @Override
443   public void restartRunProfile(@NotNull final ExecutionEnvironment environment) {
444     RunnerAndConfigurationSettings configuration = environment.getRunnerAndConfigurationSettings();
445
446     List<RunContentDescriptor> runningIncompatible;
447     if (configuration == null) {
448       runningIncompatible = Collections.emptyList();
449     }
450     else {
451       runningIncompatible = getIncompatibleRunningDescriptors(configuration);
452     }
453
454     RunContentDescriptor contentToReuse = environment.getContentToReuse();
455     final List<RunContentDescriptor> runningOfTheSameType = new SmartList<RunContentDescriptor>();
456     if (configuration != null && configuration.isSingleton()) {
457       runningOfTheSameType.addAll(getRunningDescriptorsOfTheSameConfigType(configuration));
458     }
459     else if (isProcessRunning(contentToReuse)) {
460       runningOfTheSameType.add(contentToReuse);
461     }
462
463     List<RunContentDescriptor> runningToStop = ContainerUtil.concat(runningOfTheSameType, runningIncompatible);
464     if (!runningToStop.isEmpty()) {
465       if (configuration != null) {
466         if (!runningOfTheSameType.isEmpty()
467             && (runningOfTheSameType.size() > 1 || contentToReuse == null || runningOfTheSameType.get(0) != contentToReuse) &&
468             !userApprovesStopForSameTypeConfigurations(environment.getProject(), configuration.getName(), runningOfTheSameType.size())) {
469           return;
470         }
471         if (!runningIncompatible.isEmpty()
472             && !userApprovesStopForIncompatibleConfigurations(myProject, configuration.getName(), runningIncompatible)) {
473           return;
474         }
475       }
476
477       for (RunContentDescriptor descriptor : runningToStop) {
478         stop(descriptor);
479       }
480     }
481
482     awaitingTerminationAlarm.addRequest(new Runnable() {
483       @Override
484       public void run() {
485         if ((DumbService.getInstance(myProject).isDumb() && !Registry.is("dumb.aware.run.configurations")) || ExecutorRegistry.getInstance().isStarting(environment)) {
486           awaitingTerminationAlarm.addRequest(this, 100);
487           return;
488         }
489
490         for (RunContentDescriptor descriptor : runningOfTheSameType) {
491           ProcessHandler processHandler = descriptor.getProcessHandler();
492           if (processHandler != null && !processHandler.isProcessTerminated()) {
493             awaitingTerminationAlarm.addRequest(this, 100);
494             return;
495           }
496         }
497         start(environment);
498       }
499     }, 50);
500   }
501
502   @TestOnly
503   public void setForceCompilationInTests(boolean forceCompilationInTests) {
504     myForceCompilationInTests = forceCompilationInTests;
505   }
506
507   @NotNull
508   private List<RunContentDescriptor> getRunningDescriptorsOfTheSameConfigType(@NotNull final RunnerAndConfigurationSettings configurationAndSettings) {
509     return getRunningDescriptors(runningConfigurationAndSettings -> configurationAndSettings == runningConfigurationAndSettings);
510   }
511
512   @NotNull
513   private List<RunContentDescriptor> getIncompatibleRunningDescriptors(@NotNull RunnerAndConfigurationSettings configurationAndSettings) {
514     final RunConfiguration configurationToCheckCompatibility = configurationAndSettings.getConfiguration();
515     return getRunningDescriptors(runningConfigurationAndSettings -> {
516       RunConfiguration runningConfiguration = runningConfigurationAndSettings == null ? null : runningConfigurationAndSettings.getConfiguration();
517       if (runningConfiguration == null || !(runningConfiguration instanceof CompatibilityAwareRunProfile)) {
518         return false;
519       }
520       return ((CompatibilityAwareRunProfile)runningConfiguration).mustBeStoppedToRun(configurationToCheckCompatibility);
521     });
522   }
523
524   @NotNull
525   public List<RunContentDescriptor> getRunningDescriptors(@NotNull Condition<RunnerAndConfigurationSettings> condition) {
526     List<RunContentDescriptor> result = new SmartList<RunContentDescriptor>();
527     for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) {
528       if (condition.value(trinity.getSecond())) {
529         ProcessHandler processHandler = trinity.getFirst().getProcessHandler();
530         if (processHandler != null /*&& !processHandler.isProcessTerminating()*/ && !processHandler.isProcessTerminated()) {
531           result.add(trinity.getFirst());
532         }
533       }
534     }
535     return result;
536   }
537
538   @NotNull
539   public Set<Executor> getExecutors(RunContentDescriptor descriptor) {
540     Set<Executor> result = new HashSet<Executor>();
541     for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) {
542       if (descriptor == trinity.first) result.add(trinity.third);
543     }
544     return result;
545   }
546
547   private static class ProcessExecutionListener extends ProcessAdapter {
548     private final Project myProject;
549     private final RunProfile myProfile;
550     private final ProcessHandler myProcessHandler;
551
552     public ProcessExecutionListener(Project project, RunProfile profile, ProcessHandler processHandler) {
553       myProject = project;
554       myProfile = profile;
555       myProcessHandler = processHandler;
556     }
557
558     @Override
559     public void processTerminated(ProcessEvent event) {
560       if (myProject.isDisposed()) return;
561
562       myProject.getMessageBus().syncPublisher(EXECUTION_TOPIC).processTerminated(myProfile, myProcessHandler);
563
564       SaveAndSyncHandler saveAndSyncHandler = SaveAndSyncHandler.getInstance();
565       if (saveAndSyncHandler != null) {
566         saveAndSyncHandler.scheduleRefresh();
567       }
568     }
569
570     @Override
571     public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
572       if (myProject.isDisposed()) return;
573
574       myProject.getMessageBus().syncPublisher(EXECUTION_TOPIC).processTerminating(myProfile, myProcessHandler);
575     }
576   }
577 }