eca62e509a4773ce4d298a2ca06c229355cf7715
[idea/community.git] / platform / external-system-impl / src / com / intellij / openapi / externalSystem / service / execution / ExternalSystemRunConfiguration.java
1 package com.intellij.openapi.externalSystem.service.execution;
2
3 import com.intellij.execution.DefaultExecutionResult;
4 import com.intellij.execution.ExecutionException;
5 import com.intellij.execution.ExecutionResult;
6 import com.intellij.execution.Executor;
7 import com.intellij.execution.configurations.ConfigurationFactory;
8 import com.intellij.execution.configurations.LocatableConfigurationBase;
9 import com.intellij.execution.configurations.RunConfiguration;
10 import com.intellij.execution.configurations.RunProfileState;
11 import com.intellij.execution.executors.DefaultDebugExecutor;
12 import com.intellij.execution.process.ProcessHandler;
13 import com.intellij.execution.process.ProcessOutputTypes;
14 import com.intellij.execution.runners.ExecutionEnvironment;
15 import com.intellij.execution.runners.ProgramRunner;
16 import com.intellij.execution.ui.ExecutionConsole;
17 import com.intellij.openapi.application.ApplicationManager;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.externalSystem.execution.ExternalSystemExecutionConsoleManager;
20 import com.intellij.openapi.externalSystem.model.ProjectSystemId;
21 import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
22 import com.intellij.openapi.externalSystem.model.execution.ExternalTaskExecutionInfo;
23 import com.intellij.openapi.externalSystem.model.execution.ExternalTaskPojo;
24 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask;
25 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
26 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter;
27 import com.intellij.openapi.externalSystem.service.internal.ExternalSystemExecuteTaskTask;
28 import com.intellij.openapi.externalSystem.util.ExternalSystemBundle;
29 import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
30 import com.intellij.openapi.fileEditor.FileDocumentManager;
31 import com.intellij.openapi.options.SettingsEditor;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.util.Disposer;
34 import com.intellij.openapi.util.InvalidDataException;
35 import com.intellij.openapi.util.WriteExternalException;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.util.ExceptionUtil;
38 import com.intellij.util.containers.ContainerUtilRt;
39 import com.intellij.util.net.NetUtils;
40 import com.intellij.util.text.DateFormatUtil;
41 import com.intellij.util.xmlb.XmlSerializer;
42 import org.jdom.Element;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45
46 import java.io.*;
47 import java.util.List;
48
49 /**
50  * @author Denis Zhdanov
51  * @since 23.05.13 18:30
52  */
53 public class ExternalSystemRunConfiguration extends LocatableConfigurationBase {
54
55   private static final Logger LOG = Logger.getInstance("#" + ExternalSystemRunConfiguration.class.getName());
56
57   private ExternalSystemTaskExecutionSettings mySettings = new ExternalSystemTaskExecutionSettings();
58
59   public ExternalSystemRunConfiguration(@NotNull ProjectSystemId externalSystemId,
60                                         Project project,
61                                         ConfigurationFactory factory,
62                                         String name) {
63     super(project, factory, name);
64     mySettings.setExternalSystemIdString(externalSystemId.getId());
65   }
66
67   @Override
68   public String suggestedName() {
69     return AbstractExternalSystemTaskConfigurationType.generateName(getProject(), mySettings);
70   }
71
72   @Override
73   public RunConfiguration clone() {
74     ExternalSystemRunConfiguration result = (ExternalSystemRunConfiguration)super.clone();
75     result.mySettings = mySettings.clone();
76     return result;
77   }
78
79   @Override
80   public void readExternal(Element element) throws InvalidDataException {
81     super.readExternal(element);
82     Element e = element.getChild(ExternalSystemTaskExecutionSettings.TAG_NAME);
83     if (e != null) {
84       mySettings = XmlSerializer.deserialize(e, ExternalSystemTaskExecutionSettings.class);
85     }
86   }
87
88   @Override
89   public void writeExternal(Element element) throws WriteExternalException {
90     super.writeExternal(element);
91     element.addContent(XmlSerializer.serialize(mySettings));
92   }
93
94   @NotNull
95   public ExternalSystemTaskExecutionSettings getSettings() {
96     return mySettings;
97   }
98
99   @NotNull
100   @Override
101   public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
102     return new ExternalSystemRunConfigurationEditor(getProject(), mySettings.getExternalSystemId());
103   }
104
105   @Nullable
106   @Override
107   public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException {
108     return new MyRunnableState(mySettings, getProject(), DefaultDebugExecutor.EXECUTOR_ID.equals(executor.getId()), this, env);
109   }
110
111   public static class MyRunnableState implements RunProfileState {
112
113     @NotNull private final ExternalSystemTaskExecutionSettings mySettings;
114     @NotNull private final Project myProject;
115     @NotNull private final ExternalSystemRunConfiguration myConfiguration;
116     @NotNull private final ExecutionEnvironment myEnv;
117
118     private final int myDebugPort;
119
120     public MyRunnableState(@NotNull ExternalSystemTaskExecutionSettings settings,
121                            @NotNull Project project,
122                            boolean debug,
123                            @NotNull ExternalSystemRunConfiguration configuration,
124                            @NotNull ExecutionEnvironment env) {
125       mySettings = settings;
126       myProject = project;
127       myConfiguration = configuration;
128       myEnv = env;
129       int port;
130       if (debug) {
131         try {
132           port = NetUtils.findAvailableSocketPort();
133         }
134         catch (IOException e) {
135           LOG.warn("Unexpected I/O exception occurred on attempt to find a free port to use for external system task debugging", e);
136           port = 0;
137         }
138       }
139       else {
140         port = 0;
141       }
142       myDebugPort = port;
143     }
144
145     public int getDebugPort() {
146       return myDebugPort;
147     }
148
149     @Nullable
150     @Override
151     public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException {
152       if (myProject.isDisposed()) return null;
153
154       ExternalSystemUtil.updateRecentTasks(new ExternalTaskExecutionInfo(mySettings.clone(), executor.getId()), myProject);
155       final List<ExternalTaskPojo> tasks = ContainerUtilRt.newArrayList();
156       for (String taskName : mySettings.getTaskNames()) {
157         tasks.add(new ExternalTaskPojo(taskName, mySettings.getExternalProjectPath(), null));
158       }
159       if (tasks.isEmpty()) {
160         throw new ExecutionException(ExternalSystemBundle.message("run.error.undefined.task"));
161       }
162       String debuggerSetup = null;
163       if (myDebugPort > 0) {
164         debuggerSetup = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=" + myDebugPort;
165       }
166
167       ApplicationManager.getApplication().assertIsDispatchThread();
168       FileDocumentManager.getInstance().saveAllDocuments();
169
170       final ExternalSystemExecuteTaskTask task = new ExternalSystemExecuteTaskTask(mySettings.getExternalSystemId(),
171                                                                                    myProject,
172                                                                                    tasks,
173                                                                                    mySettings.getVmOptions(),
174                                                                                    mySettings.getScriptParameters(),
175                                                                                    debuggerSetup);
176
177       final MyProcessHandler processHandler = new MyProcessHandler(task);
178       final ExternalSystemExecutionConsoleManager<ExternalSystemRunConfiguration> consoleManager = getConsoleManagerFor(task);
179
180       final ExecutionConsole consoleView =
181         consoleManager.attachExecutionConsole(task, myProject, myConfiguration, executor, myEnv, processHandler);
182       Disposer.register(myProject, consoleView);
183
184       ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
185         @Override
186         public void run() {
187           final String startDateTime = DateFormatUtil.formatTimeWithSeconds(System.currentTimeMillis());
188           final String greeting;
189           if (mySettings.getTaskNames().size() > 1) {
190             greeting = ExternalSystemBundle
191               .message("run.text.starting.multiple.task", startDateTime, StringUtil.join(mySettings.getTaskNames(), " "));
192           }
193           else {
194             greeting =
195               ExternalSystemBundle.message("run.text.starting.single.task", startDateTime, StringUtil.join(mySettings.getTaskNames(), " "));
196           }
197           processHandler.notifyTextAvailable(greeting, ProcessOutputTypes.SYSTEM);
198           task.execute(new ExternalSystemTaskNotificationListenerAdapter() {
199
200             private boolean myResetGreeting = true;
201
202             @Override
203             public void onTaskOutput(@NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) {
204               if (myResetGreeting) {
205                 processHandler.notifyTextAvailable("\r", ProcessOutputTypes.SYSTEM);
206                 myResetGreeting = false;
207               }
208
209               consoleManager.onOutput(text, stdOut ? ProcessOutputTypes.STDOUT : ProcessOutputTypes.STDERR);
210             }
211
212             @Override
213             public void onFailure(@NotNull ExternalSystemTaskId id, @NotNull Exception e) {
214               String exceptionMessage = ExceptionUtil.getMessage(e);
215               String text = exceptionMessage == null ? e.toString() : exceptionMessage;
216               processHandler.notifyTextAvailable(text + '\n', ProcessOutputTypes.STDERR);
217               processHandler.notifyProcessTerminated(1);
218             }
219
220             @Override
221             public void onEnd(@NotNull ExternalSystemTaskId id) {
222               final String endDateTime = DateFormatUtil.formatTimeWithSeconds(System.currentTimeMillis());
223               final String farewell;
224               if (mySettings.getTaskNames().size() > 1) {
225                 farewell = ExternalSystemBundle
226                   .message("run.text.ended.multiple.task", endDateTime, StringUtil.join(mySettings.getTaskNames(), " "));
227               }
228               else {
229                 farewell =
230                   ExternalSystemBundle.message("run.text.ended.single.task", endDateTime, StringUtil.join(mySettings.getTaskNames(), " "));
231               }
232               processHandler.notifyTextAvailable(farewell, ProcessOutputTypes.SYSTEM);
233               processHandler.notifyProcessTerminated(0);
234             }
235           });
236         }
237       });
238       DefaultExecutionResult result = new DefaultExecutionResult(consoleView, processHandler);
239       result.setRestartActions(consoleManager.getRestartActions());
240       return result;
241     }
242   }
243
244   private static class MyProcessHandler extends ProcessHandler {
245     private final ExternalSystemExecuteTaskTask myTask;
246     @Nullable private volatile OutputStream myOutputStream;
247
248     public MyProcessHandler(ExternalSystemExecuteTaskTask task) {
249       myTask = task;
250     }
251
252     @Override
253     protected void destroyProcessImpl() {
254     }
255
256     @Override
257     protected void detachProcessImpl() {
258       myTask.cancel();
259       notifyProcessDetached();
260     }
261
262     @Override
263     public boolean detachIsDefault() {
264       return true;
265     }
266
267     @Nullable
268     @Override
269     public OutputStream getProcessInput() {
270       return null;
271     }
272
273     @Override
274     public void notifyProcessTerminated(int exitCode) {
275       super.notifyProcessTerminated(exitCode);
276     }
277   }
278
279   @NotNull
280   private static ExternalSystemExecutionConsoleManager<ExternalSystemRunConfiguration> getConsoleManagerFor(@NotNull ExternalSystemTask task) {
281     for (ExternalSystemExecutionConsoleManager executionConsoleManager : ExternalSystemExecutionConsoleManager.EP_NAME.getExtensions()) {
282       if (executionConsoleManager.isApplicableFor(task))
283         //noinspection unchecked
284         return executionConsoleManager;
285     }
286
287     return new DefaultExternalSystemExecutionConsoleManager();
288   }
289
290 }