b6e88780f2ba18a591e810e2ea89ea4801544ebd
[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.*;
20 import com.intellij.execution.configurations.*;
21 import com.intellij.execution.console.LanguageConsoleBuilder;
22 import com.intellij.execution.executors.DefaultDebugExecutor;
23 import com.intellij.execution.process.ProcessHandler;
24 import com.intellij.execution.runners.ExecutionEnvironment;
25 import com.intellij.execution.runners.GenericProgramRunner;
26 import com.intellij.execution.ui.ExecutionConsole;
27 import com.intellij.execution.ui.RunContentDescriptor;
28 import com.intellij.openapi.application.ApplicationManager;
29 import com.intellij.openapi.fileEditor.FileDocumentManager;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.projectRoots.Sdk;
32 import com.intellij.openapi.roots.OrderRootType;
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.remote.RemoteSdkAdditionalData;
37 import com.intellij.xdebugger.XDebugProcess;
38 import com.intellij.xdebugger.XDebugProcessStarter;
39 import com.intellij.xdebugger.XDebugSession;
40 import com.intellij.xdebugger.XDebuggerManager;
41 import com.jetbrains.python.PythonHelper;
42 import com.jetbrains.python.console.PydevConsoleRunnerFactory;
43 import com.jetbrains.python.console.PythonConsoleView;
44 import com.jetbrains.python.console.PythonDebugConsoleCommunication;
45 import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
46 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
47 import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
48 import com.jetbrains.python.run.AbstractPythonRunConfiguration;
49 import com.jetbrains.python.run.CommandLinePatcher;
50 import com.jetbrains.python.run.DebugAwareConfiguration;
51 import com.jetbrains.python.run.PythonCommandLineState;
52 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 import java.io.File;
57 import java.net.ServerSocket;
58 import java.util.List;
59 import java.util.Map;
60
61 /**
62  * @author yole
63  */
64 public class PyDebugRunner extends GenericProgramRunner {
65   public static final String PY_DEBUG_RUNNER = "PyDebugRunner";
66
67   @SuppressWarnings("SpellCheckingInspection")
68   public static final String DEBUGGER_MAIN = "pydev/pydevd.py";
69   public static final String CLIENT_PARAM = "--client";
70   public static final String PORT_PARAM = "--port";
71   public static final String FILE_PARAM = "--file";
72   public static final String MODULE_PARAM = "--module";
73   public static final String MULTIPROCESS_PARAM = "--multiprocess";
74   public static final String IDE_PROJECT_ROOTS = "IDE_PROJECT_ROOTS";
75   public static final String LIBRARY_ROOTS = "LIBRARY_ROOTS";
76   public static final String PYTHON_ASYNCIO_DEBUG = "PYTHONASYNCIODEBUG";
77   @SuppressWarnings("SpellCheckingInspection")
78   public static final String GEVENT_SUPPORT = "GEVENT_SUPPORT";
79   public static final String PYDEVD_FILTERS = "PYDEVD_FILTERS";
80   public static final String PYDEVD_FILTER_LIBRARIES = "PYDEVD_FILTER_LIBRARIES";
81   public static boolean isModule = false;
82
83   @Override
84   @NotNull
85   public String getRunnerId() {
86     return PY_DEBUG_RUNNER;
87   }
88
89   @Override
90   public boolean canRun(@NotNull final String executorId, @NotNull final RunProfile profile) {
91     if (!DefaultDebugExecutor.EXECUTOR_ID.equals(executorId)) {
92       // If not debug at all
93       return false;
94     }
95     /**
96      * Any python configuration is debuggable unless it explicitly declares itself as DebugAwareConfiguration and denies it
97      * with canRunUnderDebug == false
98      */
99
100     if (profile instanceof WrappingRunConfiguration) {
101       // If configuration is wrapper -- unwrap it and check
102       return isDebuggable(((WrappingRunConfiguration<?>)profile).getPeer());
103     }
104     return isDebuggable(profile);
105   }
106
107   private static boolean isDebuggable(@NotNull final RunProfile profile) {
108     if (profile instanceof DebugAwareConfiguration) {
109       // if configuration knows whether debug is allowed
110       return ((DebugAwareConfiguration)profile).canRunUnderDebug();
111     }
112     if (profile instanceof AbstractPythonRunConfiguration) {
113       // Any python configuration is debuggable
114       return true;
115     }
116     // No even a python configuration
117     return false;
118   }
119
120
121   protected XDebugSession createSession(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
122     throws ExecutionException {
123     FileDocumentManager.getInstance().saveAllDocuments();
124
125     final PythonCommandLineState pyState = (PythonCommandLineState)state;
126
127     Sdk sdk = pyState.getSdk();
128     PyDebugSessionFactory sessionCreator = PyDebugSessionFactory.findExtension(sdk);
129     if (sessionCreator != null) {
130       return sessionCreator.createSession(this, pyState, environment);
131     }
132
133     final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
134     final int serverLocalPort = serverSocket.getLocalPort();
135     RunProfile profile = environment.getRunProfile();
136     final ExecutionResult result =
137       pyState.execute(environment.getExecutor(), createCommandLinePatchers(environment.getProject(), pyState, profile, serverLocalPort));
138
139     return XDebuggerManager.getInstance(environment.getProject()).
140       startSession(environment, new XDebugProcessStarter() {
141         @Override
142         @NotNull
143         public XDebugProcess start(@NotNull final XDebugSession session) {
144           PyDebugProcess pyDebugProcess =
145             createDebugProcess(session, serverSocket, result, pyState);
146
147           createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
148           return pyDebugProcess;
149         }
150       });
151   }
152
153   @NotNull
154   protected PyDebugProcess createDebugProcess(@NotNull XDebugSession session,
155                                               ServerSocket serverSocket,
156                                               ExecutionResult result,
157                                               PythonCommandLineState pyState) {
158     return new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler(),
159                               pyState.isMultiprocessDebug());
160   }
161
162   @Override
163   protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
164     throws ExecutionException {
165     XDebugSession session = createSession(state, environment);
166     initSession(session, state, environment.getExecutor());
167     return session.getRunContentDescriptor();
168   }
169
170   protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
171   }
172
173   public static int findIndex(List<String> paramList, String paramName) {
174     for (int i = 0; i < paramList.size(); i++) {
175       if (paramName.equals(paramList.get(i))) {
176         return i + 1;
177       }
178     }
179     return -1;
180   }
181
182   public static void createConsoleCommunicationAndSetupActions(@NotNull final Project project,
183                                                                @NotNull final ExecutionResult result,
184                                                                @NotNull PyDebugProcess debugProcess, @NotNull XDebugSession session) {
185     ExecutionConsole console = result.getExecutionConsole();
186     if (console instanceof PythonDebugLanguageConsoleView) {
187       ProcessHandler processHandler = result.getProcessHandler();
188
189       initDebugConsoleView(project, debugProcess, (PythonDebugLanguageConsoleView)console, processHandler, session);
190     }
191   }
192
193   public static PythonDebugConsoleCommunication initDebugConsoleView(Project project,
194                                                                      PyDebugProcess debugProcess,
195                                                                      PythonDebugLanguageConsoleView console,
196                                                                      ProcessHandler processHandler, final XDebugSession session) {
197     PythonConsoleView pythonConsoleView = console.getPydevConsoleView();
198     PythonDebugConsoleCommunication debugConsoleCommunication = new PythonDebugConsoleCommunication(project, debugProcess);
199
200     pythonConsoleView.setConsoleCommunication(debugConsoleCommunication);
201
202
203     PydevDebugConsoleExecuteActionHandler consoleExecuteActionHandler = new PydevDebugConsoleExecuteActionHandler(pythonConsoleView,
204                                                                                                                   processHandler,
205                                                                                                                   debugConsoleCommunication);
206     pythonConsoleView.setExecutionHandler(consoleExecuteActionHandler);
207
208     debugProcess.getSession().addSessionListener(consoleExecuteActionHandler);
209     new LanguageConsoleBuilder(pythonConsoleView).processHandler(processHandler).initActions(consoleExecuteActionHandler, "py");
210
211
212     debugConsoleCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
213       @Override
214       public void commandExecuted(boolean more) {
215         session.rebuildViews();
216       }
217
218       @Override
219       public void inputRequested() {
220       }
221     });
222
223     return debugConsoleCommunication;
224   }
225
226   @Nullable
227   public static CommandLinePatcher createRunConfigPatcher(RunProfileState state, RunProfile profile) {
228     CommandLinePatcher runConfigPatcher = null;
229     if (state instanceof PythonCommandLineState && profile instanceof AbstractPythonRunConfiguration) {
230       runConfigPatcher = (AbstractPythonRunConfiguration)profile;
231     }
232     return runConfigPatcher;
233   }
234
235   public CommandLinePatcher[] createCommandLinePatchers(final Project project, final PythonCommandLineState state,
236                                                                RunProfile profile,
237                                                                final int serverLocalPort) {
238     return new CommandLinePatcher[]{createDebugServerPatcher(project, state, serverLocalPort), createRunConfigPatcher(state, profile)};
239   }
240
241   private CommandLinePatcher createDebugServerPatcher(final Project project,
242                                                              final PythonCommandLineState pyState,
243                                                              final int serverLocalPort) {
244     return new CommandLinePatcher() {
245
246       private void patchExeParams(ParametersList parametersList) {
247         // we should remove '-m' parameter, but notify debugger of it
248         // but we can't remove one parameter from group, so we create new parameters group
249         ParamsGroup newExeParams = new ParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
250         int exeParamsIndex = parametersList.getParamsGroups().indexOf(
251           parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS));
252         ParamsGroup exeParamsOld = parametersList.removeParamsGroup(exeParamsIndex);
253         isModule = false;
254         for (String param : exeParamsOld.getParameters()) {
255           if (!param.equals("-m")) {
256             newExeParams.addParameter(param);
257           }
258           else {
259             isModule = true;
260           }
261         }
262
263         parametersList.addParamsGroupAt(exeParamsIndex, newExeParams);
264       }
265
266
267       @Override
268       public void patchCommandLine(GeneralCommandLine commandLine) {
269         // script name is the last parameter; all other params are for python interpreter; insert just before name
270         ParametersList parametersList = commandLine.getParametersList();
271
272         @SuppressWarnings("ConstantConditions") @NotNull
273         ParamsGroup debugParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_DEBUGGER);
274
275         patchExeParams(parametersList);
276
277         @SuppressWarnings("ConstantConditions") @NotNull
278         ParamsGroup exeParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
279
280         final PythonSdkFlavor flavor = pyState.getSdkFlavor();
281         if (flavor != null) {
282           assert exeParams != null;
283           for (String option : flavor.getExtraDebugOptions()) {
284             exeParams.addParameter(option);
285           }
286         }
287
288         assert debugParams != null;
289         fillDebugParameters(project, debugParams, serverLocalPort, pyState, commandLine);
290       }
291     };
292   }
293
294   private void fillDebugParameters(@NotNull Project project,
295                                    @NotNull ParamsGroup debugParams,
296                                    int serverLocalPort,
297                                    @NotNull PythonCommandLineState pyState,
298                                    @NotNull GeneralCommandLine cmd) {
299     PythonHelper.DEBUGGER.addToGroup(debugParams, cmd);
300
301     configureDebugParameters(project, debugParams, pyState, cmd);
302
303
304     configureDebugEnvironment(project, cmd.getEnvironment());
305
306     configureDebugConnectionParameters(debugParams, serverLocalPort);
307   }
308
309   public static void configureDebugEnvironment(@NotNull Project project, Map<String, String> environment) {
310     if (PyDebuggerOptionsProvider.getInstance(project).isSupportGeventDebugging()) {
311       environment.put(GEVENT_SUPPORT, "True");
312     }
313
314     PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
315     if (debuggerSettings.isSteppingFiltersEnabled()) {
316       environment.put(PYDEVD_FILTERS, debuggerSettings.getSteppingFiltersForProject(project));
317     }
318     if (debuggerSettings.isLibrariesFilterEnabled()) {
319       environment.put(PYDEVD_FILTER_LIBRARIES, "True");
320     }
321
322     PydevConsoleRunnerFactory.putIPythonEnvFlag(project, environment);
323
324     addProjectRootsToEnv(project, environment);
325     addSdkRootsToEnv(project, environment);
326   }
327
328   protected void configureDebugParameters(@NotNull Project project,
329                                           @NotNull ParamsGroup debugParams,
330                                           @NotNull PythonCommandLineState pyState,
331                                           @NotNull GeneralCommandLine cmd) {
332     if (pyState.isMultiprocessDebug()) {
333       //noinspection SpellCheckingInspection
334       debugParams.addParameter("--multiproc");
335     }
336
337     configureCommonDebugParameters(project, debugParams);
338   }
339
340   public static void configureCommonDebugParameters(@NotNull Project project,
341                                                     @NotNull ParamsGroup debugParams) {
342     if (isModule) {
343       debugParams.addParameter("--module");
344     }
345
346     if (ApplicationManager.getApplication().isUnitTestMode()) {
347       debugParams.addParameter("--DEBUG");
348     }
349
350     if (PyDebuggerOptionsProvider.getInstance(project).isSaveCallSignatures()) {
351       debugParams.addParameter("--save-signatures");
352     }
353
354     if (PyDebuggerOptionsProvider.getInstance(project).isSupportQtDebugging()) {
355       debugParams.addParameter("--qt-support");
356     }
357   }
358
359   private static void configureDebugConnectionParameters(@NotNull ParamsGroup debugParams, int serverLocalPort) {
360     final String[] debuggerArgs = new String[]{
361       CLIENT_PARAM, "127.0.0.1",
362       PORT_PARAM, String.valueOf(serverLocalPort),
363       FILE_PARAM
364     };
365     for (String s : debuggerArgs) {
366       debugParams.addParameter(s);
367     }
368   }
369
370   private static void addProjectRootsToEnv(@NotNull Project project, @NotNull Map<String, String> environment) {
371
372     List<String> roots = Lists.newArrayList();
373     for (VirtualFile contentRoot : ProjectRootManager.getInstance(project).getContentRoots()) {
374       roots.add(contentRoot.getPath());
375     }
376
377     environment.put(IDE_PROJECT_ROOTS, StringUtil.join(roots, File.pathSeparator));
378   }
379
380   private static void addSdkRootsToEnv(@NotNull Project project, @NotNull Map<String, String> environment) {
381     final RunManager runManager = RunManager.getInstance(project);
382     final RunnerAndConfigurationSettings selectedConfiguration = runManager.getSelectedConfiguration();
383     if (selectedConfiguration != null) {
384       final RunConfiguration configuration = selectedConfiguration.getConfiguration();
385       if (configuration instanceof AbstractPythonRunConfiguration) {
386         AbstractPythonRunConfiguration runConfiguration = (AbstractPythonRunConfiguration)configuration;
387         final Sdk sdk = runConfiguration.getSdk();
388         if (sdk != null) {
389           List<String> roots = Lists.newArrayList();
390           for (VirtualFile contentRoot : sdk.getSdkModificator().getRoots(OrderRootType.CLASSES)) {
391             roots.add(contentRoot.getPath());
392           }
393           environment.put(LIBRARY_ROOTS, StringUtil.join(roots, File.pathSeparator));
394         }
395       }
396     }
397   }
398 }