Merge branch 'master' into traff/zip_helpers
[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.PythonHelper;
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 MODULE_PARAM = "--module";
69   public static final String IDE_PROJECT_ROOTS = "IDE_PROJECT_ROOTS";
70   @SuppressWarnings("SpellCheckingInspection")
71   public static final String GEVENT_SUPPORT = "GEVENT_SUPPORT";
72   public static boolean isModule = false;
73
74   @Override
75   @NotNull
76   public String getRunnerId() {
77     return PY_DEBUG_RUNNER;
78   }
79
80   @Override
81   public boolean canRun(@NotNull final String executorId, @NotNull final RunProfile profile) {
82     if (!DefaultDebugExecutor.EXECUTOR_ID.equals(executorId)) {
83       // If not debug at all
84       return false;
85     }
86     /**
87      * Any python configuration is debuggable unless it explicitly declares itself as DebugAwareConfiguration and denies it
88      * with canRunUnderDebug == false
89      */
90
91     if (profile instanceof WrappingRunConfiguration) {
92       // If configuration is wrapper -- unwrap it and check
93       return isDebuggable(((WrappingRunConfiguration<?>)profile).getPeer());
94     }
95     return isDebuggable(profile);
96   }
97
98   private static boolean isDebuggable(@NotNull final RunProfile profile) {
99     if (profile instanceof DebugAwareConfiguration) {
100       // if configuration knows whether debug is allowed
101       return ((DebugAwareConfiguration)profile).canRunUnderDebug();
102     }
103     if (profile instanceof AbstractPythonRunConfiguration) {
104       // Any python configuration is debuggable
105       return true;
106     }
107     // No even a python configuration
108     return false;
109   }
110
111
112   protected XDebugSession createSession(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
113     throws ExecutionException {
114     FileDocumentManager.getInstance().saveAllDocuments();
115
116     final PythonCommandLineState pyState = (PythonCommandLineState)state;
117     final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
118     final int serverLocalPort = serverSocket.getLocalPort();
119     RunProfile profile = environment.getRunProfile();
120     final ExecutionResult result =
121       pyState.execute(environment.getExecutor(), createCommandLinePatchers(environment.getProject(), pyState, profile, serverLocalPort));
122
123     return XDebuggerManager.getInstance(environment.getProject()).
124       startSession(environment, new XDebugProcessStarter() {
125         @Override
126         @NotNull
127         public XDebugProcess start(@NotNull final XDebugSession session) {
128           PyDebugProcess pyDebugProcess =
129             createDebugProcess(session, serverSocket, result, pyState);
130
131           createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
132           return pyDebugProcess;
133         }
134       });
135   }
136
137   @NotNull
138   protected PyDebugProcess createDebugProcess(@NotNull XDebugSession session,
139                                               ServerSocket serverSocket,
140                                               ExecutionResult result,
141                                               PythonCommandLineState pyState) {
142     return new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler(),
143                               pyState.isMultiprocessDebug());
144   }
145
146   @Override
147   protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
148     throws ExecutionException {
149     XDebugSession session = createSession(state, environment);
150     initSession(session, state, environment.getExecutor());
151     return session.getRunContentDescriptor();
152   }
153
154   protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
155   }
156
157   public static int findIndex(List<String> paramList, String paramName) {
158     for (int i = 0; i < paramList.size(); i++) {
159       if (paramName.equals(paramList.get(i))) {
160         return i + 1;
161       }
162     }
163     return -1;
164   }
165
166   public static void createConsoleCommunicationAndSetupActions(@NotNull final Project project,
167                                                                @NotNull final ExecutionResult result,
168                                                                @NotNull PyDebugProcess debugProcess, @NotNull XDebugSession session) {
169     ExecutionConsole console = result.getExecutionConsole();
170     if (console instanceof PythonDebugLanguageConsoleView) {
171       ProcessHandler processHandler = result.getProcessHandler();
172
173       initDebugConsoleView(project, debugProcess, (PythonDebugLanguageConsoleView)console, processHandler, session);
174     }
175   }
176
177   public static PythonDebugConsoleCommunication initDebugConsoleView(Project project,
178                                                                      PyDebugProcess debugProcess,
179                                                                      PythonDebugLanguageConsoleView console,
180                                                                      ProcessHandler processHandler, final XDebugSession session) {
181     PythonConsoleView pythonConsoleView = console.getPydevConsoleView();
182     PythonDebugConsoleCommunication debugConsoleCommunication = new PythonDebugConsoleCommunication(project, debugProcess);
183
184     pythonConsoleView.setConsoleCommunication(debugConsoleCommunication);
185
186
187     PydevDebugConsoleExecuteActionHandler consoleExecuteActionHandler = new PydevDebugConsoleExecuteActionHandler(pythonConsoleView,
188                                                                                                                   processHandler,
189                                                                                                                   debugConsoleCommunication);
190     pythonConsoleView.setExecutionHandler(consoleExecuteActionHandler);
191
192     debugProcess.getSession().addSessionListener(consoleExecuteActionHandler);
193     new LanguageConsoleBuilder(pythonConsoleView).processHandler(processHandler).initActions(consoleExecuteActionHandler, "py");
194
195
196     debugConsoleCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
197       @Override
198       public void commandExecuted(boolean more) {
199         session.rebuildViews();
200       }
201
202       @Override
203       public void inputRequested() {
204       }
205     });
206
207     return debugConsoleCommunication;
208   }
209
210   @Nullable
211   private static CommandLinePatcher createRunConfigPatcher(RunProfileState state, RunProfile profile) {
212     CommandLinePatcher runConfigPatcher = null;
213     if (state instanceof PythonCommandLineState && profile instanceof AbstractPythonRunConfiguration) {
214       runConfigPatcher = (AbstractPythonRunConfiguration)profile;
215     }
216     return runConfigPatcher;
217   }
218
219   public static CommandLinePatcher[] createCommandLinePatchers(final Project project, final PythonCommandLineState state,
220                                                                RunProfile profile,
221                                                                final int serverLocalPort) {
222     return new CommandLinePatcher[]{createDebugServerPatcher(project, state, serverLocalPort), createRunConfigPatcher(state, profile)};
223   }
224
225   private static CommandLinePatcher createDebugServerPatcher(final Project project,
226                                                              final PythonCommandLineState pyState,
227                                                              final int serverLocalPort) {
228     return new CommandLinePatcher() {
229
230       private void patchExeParams(ParametersList parametersList) {
231         // we should remove '-m' parameter, but notify debugger of it
232         // but we can't remove one parameter from group, so we create new parameters group
233         ParamsGroup newExeParams = new ParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
234         int exeParamsIndex = parametersList.getParamsGroups().indexOf(
235           parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS));
236         ParamsGroup exeParamsOld = parametersList.removeParamsGroup(exeParamsIndex);
237         isModule = false;
238         for (String param : exeParamsOld.getParameters()) {
239           if (!param.equals("-m")) {
240             newExeParams.addParameter(param);
241           }
242           else {
243             isModule = true;
244           }
245         }
246
247         parametersList.addParamsGroupAt(exeParamsIndex, newExeParams);
248       }
249
250
251       @Override
252       public void patchCommandLine(GeneralCommandLine commandLine) {
253         // script name is the last parameter; all other params are for python interpreter; insert just before name
254         ParametersList parametersList = commandLine.getParametersList();
255
256         @SuppressWarnings("ConstantConditions") @NotNull
257         ParamsGroup debugParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_DEBUGGER);
258
259         patchExeParams(parametersList);
260
261         @SuppressWarnings("ConstantConditions") @NotNull
262         ParamsGroup exeParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
263
264         final PythonSdkFlavor flavor = pyState.getSdkFlavor();
265         if (flavor != null) {
266           assert exeParams != null;
267           for (String option : flavor.getExtraDebugOptions()) {
268             exeParams.addParameter(option);
269           }
270         }
271
272         assert debugParams != null;
273         fillDebugParameters(project, debugParams, serverLocalPort, pyState, commandLine);
274       }
275     };
276   }
277
278   private static void fillDebugParameters(@NotNull Project project,
279                                           @NotNull ParamsGroup debugParams,
280                                           int serverLocalPort,
281                                           @NotNull PythonCommandLineState pyState,
282                                           @NotNull GeneralCommandLine cmd) {
283     PythonHelper.DEBUGGER.addToGroup(debugParams, cmd);
284
285     if (pyState.isMultiprocessDebug()) {
286       //noinspection SpellCheckingInspection
287       debugParams.addParameter("--multiproc");
288     }
289
290     if (isModule) {
291       debugParams.addParameter("--module");
292     }
293
294     if (ApplicationManager.getApplication().isUnitTestMode()) {
295       debugParams.addParameter("--DEBUG");
296     }
297
298     if (PyDebuggerOptionsProvider.getInstance(project).isSaveCallSignatures()) {
299       debugParams.addParameter("--save-signatures");
300     }
301
302     if (PyDebuggerOptionsProvider.getInstance(project).isSupportGeventDebugging()) {
303       cmd.getEnvironment().put(GEVENT_SUPPORT, "True");
304     }
305
306     addProjectRootsToEnv(project, cmd);
307
308     final String[] debuggerArgs = new String[]{
309       CLIENT_PARAM, "127.0.0.1",
310       PORT_PARAM, String.valueOf(serverLocalPort),
311       FILE_PARAM
312     };
313     for (String s : debuggerArgs) {
314       debugParams.addParameter(s);
315     }
316   }
317
318   private static void addProjectRootsToEnv(@NotNull Project project, @NotNull GeneralCommandLine commandLine) {
319
320     List<String> roots = Lists.newArrayList();
321     for (VirtualFile contentRoot : ProjectRootManager.getInstance(project).getContentRoots()) {
322       roots.add(contentRoot.getPath());
323     }
324
325     commandLine.getEnvironment().put(IDE_PROJECT_ROOTS, StringUtil.join(roots, File.pathSeparator));
326   }
327 }