c39fdb22bf3b433f75fd6f34ed3c3886d00c308f
[idea/community.git] / python / src / com / jetbrains / python / debugger / PyDebugRunner.java
1 /*
2  * Copyright 2000-2015 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.jetbrains.python.debugger;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.execution.ExecutionException;
20 import com.intellij.execution.ExecutionResult;
21 import com.intellij.execution.Executor;
22 import com.intellij.execution.configurations.*;
23 import com.intellij.execution.console.LanguageConsoleBuilder;
24 import com.intellij.execution.executors.DefaultDebugExecutor;
25 import com.intellij.execution.process.ProcessHandler;
26 import com.intellij.execution.runners.ExecutionEnvironment;
27 import com.intellij.execution.runners.GenericProgramRunner;
28 import com.intellij.execution.ui.ExecutionConsole;
29 import com.intellij.execution.ui.RunContentDescriptor;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.fileEditor.FileDocumentManager;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.roots.ProjectRootManager;
34 import com.intellij.openapi.util.text.StringUtil;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.xdebugger.XDebugProcess;
37 import com.intellij.xdebugger.XDebugProcessStarter;
38 import com.intellij.xdebugger.XDebugSession;
39 import com.intellij.xdebugger.XDebuggerManager;
40 import com.jetbrains.python.PythonHelpersLocator;
41 import com.jetbrains.python.console.PythonConsoleView;
42 import com.jetbrains.python.console.PythonDebugConsoleCommunication;
43 import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
44 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
45 import com.jetbrains.python.run.AbstractPythonRunConfiguration;
46 import com.jetbrains.python.run.CommandLinePatcher;
47 import com.jetbrains.python.run.DebugAwareConfiguration;
48 import com.jetbrains.python.run.PythonCommandLineState;
49 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52
53 import java.io.File;
54 import java.net.ServerSocket;
55 import java.util.List;
56
57 /**
58  * @author yole
59  */
60 public class PyDebugRunner extends GenericProgramRunner {
61   public static final String PY_DEBUG_RUNNER = "PyDebugRunner";
62
63   @SuppressWarnings("SpellCheckingInspection")
64   public static final String DEBUGGER_MAIN = "pydev/pydevd.py";
65   public static final String CLIENT_PARAM = "--client";
66   public static final String PORT_PARAM = "--port";
67   public static final String FILE_PARAM = "--file";
68   public static final String IDE_PROJECT_ROOTS = "IDE_PROJECT_ROOTS";
69   @SuppressWarnings("SpellCheckingInspection")
70   public static final String GEVENT_SUPPORT = "GEVENT_SUPPORT";
71   public static boolean isModule = false;
72
73   @Override
74   @NotNull
75   public String getRunnerId() {
76     return PY_DEBUG_RUNNER;
77   }
78
79   @Override
80   public boolean canRun(@NotNull final String executorId, @NotNull final RunProfile profile) {
81     if (!DefaultDebugExecutor.EXECUTOR_ID.equals(executorId)) {
82       // If not debug at all
83       return false;
84     }
85     if (profile instanceof DebugAwareConfiguration) {
86       // if configuration knows whether debug is allowed
87       return ((DebugAwareConfiguration)profile).canRunUnderDebug();
88     }
89     return false;
90   }
91
92
93   protected XDebugSession createSession(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
94     throws ExecutionException {
95     FileDocumentManager.getInstance().saveAllDocuments();
96
97     final PythonCommandLineState pyState = (PythonCommandLineState)state;
98     final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
99     final int serverLocalPort = serverSocket.getLocalPort();
100     RunProfile profile = environment.getRunProfile();
101     final ExecutionResult result =
102       pyState.execute(environment.getExecutor(), createCommandLinePatchers(environment.getProject(), pyState, profile, serverLocalPort));
103
104     return XDebuggerManager.getInstance(environment.getProject()).
105       startSession(environment, new XDebugProcessStarter() {
106         @Override
107         @NotNull
108         public XDebugProcess start(@NotNull final XDebugSession session) {
109           PyDebugProcess pyDebugProcess =
110             createDebugProcess(session, serverSocket, result, pyState);
111
112           createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
113           return pyDebugProcess;
114         }
115       });
116   }
117
118   @NotNull
119   protected PyDebugProcess createDebugProcess(@NotNull XDebugSession session,
120                                               ServerSocket serverSocket,
121                                               ExecutionResult result,
122                                               PythonCommandLineState pyState) {
123     return new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler(),
124                               pyState.isMultiprocessDebug());
125   }
126
127   @Override
128   protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
129     throws ExecutionException {
130     XDebugSession session = createSession(state, environment);
131     initSession(session, state, environment.getExecutor());
132     return session.getRunContentDescriptor();
133   }
134
135   protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
136   }
137
138   public static int findIndex(List<String> paramList, String paramName) {
139     for (int i = 0; i < paramList.size(); i++) {
140       if (paramName.equals(paramList.get(i))) {
141         return i + 1;
142       }
143     }
144     return -1;
145   }
146
147   public static void createConsoleCommunicationAndSetupActions(@NotNull final Project project,
148                                                                @NotNull final ExecutionResult result,
149                                                                @NotNull PyDebugProcess debugProcess, @NotNull XDebugSession session) {
150     ExecutionConsole console = result.getExecutionConsole();
151     if (console instanceof PythonDebugLanguageConsoleView) {
152       ProcessHandler processHandler = result.getProcessHandler();
153
154       initDebugConsoleView(project, debugProcess, (PythonDebugLanguageConsoleView)console, processHandler, session);
155     }
156   }
157
158   public static PythonDebugConsoleCommunication initDebugConsoleView(Project project,
159                                                                      PyDebugProcess debugProcess,
160                                                                      PythonDebugLanguageConsoleView console,
161                                                                      ProcessHandler processHandler, final XDebugSession session) {
162     PythonConsoleView pythonConsoleView = console.getPydevConsoleView();
163     PythonDebugConsoleCommunication debugConsoleCommunication = new PythonDebugConsoleCommunication(project, debugProcess);
164
165     pythonConsoleView.setConsoleCommunication(debugConsoleCommunication);
166
167
168     PydevDebugConsoleExecuteActionHandler consoleExecuteActionHandler = new PydevDebugConsoleExecuteActionHandler(pythonConsoleView,
169                                                                                                                   processHandler,
170                                                                                                                   debugConsoleCommunication);
171     pythonConsoleView.setExecutionHandler(consoleExecuteActionHandler);
172
173     debugProcess.getSession().addSessionListener(consoleExecuteActionHandler);
174     new LanguageConsoleBuilder(pythonConsoleView).processHandler(processHandler).initActions(consoleExecuteActionHandler, "py");
175
176
177     debugConsoleCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
178       @Override
179       public void commandExecuted(boolean more) {
180         session.rebuildViews();
181       }
182
183       @Override
184       public void inputRequested() {
185       }
186     });
187
188     return debugConsoleCommunication;
189   }
190
191   @Nullable
192   private static CommandLinePatcher createRunConfigPatcher(RunProfileState state, RunProfile profile) {
193     CommandLinePatcher runConfigPatcher = null;
194     if (state instanceof PythonCommandLineState && profile instanceof AbstractPythonRunConfiguration) {
195       runConfigPatcher = (AbstractPythonRunConfiguration)profile;
196     }
197     return runConfigPatcher;
198   }
199
200   public static CommandLinePatcher[] createCommandLinePatchers(final Project project, final PythonCommandLineState state,
201                                                                RunProfile profile,
202                                                                final int serverLocalPort) {
203     return new CommandLinePatcher[]{createDebugServerPatcher(project, state, serverLocalPort), createRunConfigPatcher(state, profile)};
204   }
205
206   private static CommandLinePatcher createDebugServerPatcher(final Project project,
207                                                              final PythonCommandLineState pyState,
208                                                              final int serverLocalPort) {
209     return new CommandLinePatcher() {
210
211       private void patchExeParams(ParametersList parametersList) {
212         // we should remove '-m' parameter, but notify debugger of it
213         // but we can't remove one parameter from group, so we create new parameters group
214         ParamsGroup newExeParams = new ParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
215         int exeParamsIndex = parametersList.getParamsGroups().indexOf(
216           parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS));
217         ParamsGroup exeParamsOld = parametersList.removeParamsGroup(exeParamsIndex);
218         isModule = false;
219         for (String param : exeParamsOld.getParameters()) {
220           if (!param.equals("-m")) {
221             newExeParams.addParameter(param);
222           }
223           else {
224             isModule = true;
225           }
226         }
227
228         parametersList.addParamsGroupAt(exeParamsIndex, newExeParams);
229       }
230
231
232       @Override
233       public void patchCommandLine(GeneralCommandLine commandLine) {
234         // script name is the last parameter; all other params are for python interpreter; insert just before name
235         ParametersList parametersList = commandLine.getParametersList();
236
237         @SuppressWarnings("ConstantConditions") @NotNull
238         ParamsGroup debugParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_DEBUGGER);
239
240         patchExeParams(parametersList);
241
242         @SuppressWarnings("ConstantConditions") @NotNull
243         ParamsGroup exeParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
244
245         final PythonSdkFlavor flavor = pyState.getSdkFlavor();
246         if (flavor != null) {
247           assert exeParams != null;
248           for (String option : flavor.getExtraDebugOptions()) {
249             exeParams.addParameter(option);
250           }
251         }
252
253         assert debugParams != null;
254         fillDebugParameters(project, debugParams, serverLocalPort, pyState, commandLine);
255       }
256     };
257   }
258
259   private static void fillDebugParameters(@NotNull Project project,
260                                           @NotNull ParamsGroup debugParams,
261                                           int serverLocalPort,
262                                           @NotNull PythonCommandLineState pyState,
263                                           @NotNull GeneralCommandLine generalCommandLine) {
264     debugParams.addParameter(PythonHelpersLocator.getHelperPath(DEBUGGER_MAIN));
265     if (pyState.isMultiprocessDebug()) {
266       //noinspection SpellCheckingInspection
267       debugParams.addParameter("--multiproc");
268     }
269
270     if (isModule) {
271       debugParams.addParameter("--module");
272     }
273
274     if (ApplicationManager.getApplication().isUnitTestMode()) {
275       debugParams.addParameter("--DEBUG");
276     }
277
278     if (PyDebuggerOptionsProvider.getInstance(project).isSaveCallSignatures()) {
279       debugParams.addParameter("--save-signatures");
280     }
281
282     if (PyDebuggerOptionsProvider.getInstance(project).isSupportGeventDebugging()) {
283       generalCommandLine.getEnvironment().put(GEVENT_SUPPORT, "True");
284     }
285
286     addProjectRootsToEnv(project, generalCommandLine);
287
288     final String[] debuggerArgs = new String[]{
289       CLIENT_PARAM, "127.0.0.1",
290       PORT_PARAM, String.valueOf(serverLocalPort),
291       FILE_PARAM
292     };
293     for (String s : debuggerArgs) {
294       debugParams.addParameter(s);
295     }
296   }
297
298   private static void addProjectRootsToEnv(@NotNull Project project, @NotNull GeneralCommandLine commandLine) {
299
300     List<String> roots = Lists.newArrayList();
301     for (VirtualFile contentRoot : ProjectRootManager.getInstance(project).getContentRoots()) {
302       roots.add(contentRoot.getPath());
303     }
304
305     commandLine.getEnvironment().put(IDE_PROJECT_ROOTS, StringUtil.join(roots, File.pathSeparator));
306   }
307 }