2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.jetbrains.python.debugger;
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.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.PydevConsoleRunnerFactory;
42 import com.jetbrains.python.console.PythonConsoleView;
43 import com.jetbrains.python.console.PythonDebugConsoleCommunication;
44 import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
45 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
46 import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
47 import com.jetbrains.python.run.AbstractPythonRunConfiguration;
48 import com.jetbrains.python.run.CommandLinePatcher;
49 import com.jetbrains.python.run.DebugAwareConfiguration;
50 import com.jetbrains.python.run.PythonCommandLineState;
51 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
56 import java.net.ServerSocket;
57 import java.util.List;
63 public class PyDebugRunner extends GenericProgramRunner {
64 public static final String PY_DEBUG_RUNNER = "PyDebugRunner";
66 @SuppressWarnings("SpellCheckingInspection")
67 public static final String DEBUGGER_MAIN = "pydev/pydevd.py";
68 public static final String CLIENT_PARAM = "--client";
69 public static final String PORT_PARAM = "--port";
70 public static final String FILE_PARAM = "--file";
71 public static final String MODULE_PARAM = "--module";
72 public static final String MULTIPROCESS_PARAM = "--multiprocess";
73 public static final String IDE_PROJECT_ROOTS = "IDE_PROJECT_ROOTS";
74 public static final String LIBRARY_ROOTS = "LIBRARY_ROOTS";
75 public static final String PYTHON_ASYNCIO_DEBUG = "PYTHONASYNCIODEBUG";
76 @SuppressWarnings("SpellCheckingInspection")
77 public static final String GEVENT_SUPPORT = "GEVENT_SUPPORT";
78 public static final String PYDEVD_FILTERS = "PYDEVD_FILTERS";
79 public static final String PYDEVD_FILTER_LIBRARIES = "PYDEVD_FILTER_LIBRARIES";
80 public static boolean isModule = false;
84 public String getRunnerId() {
85 return PY_DEBUG_RUNNER;
89 public boolean canRun(@NotNull final String executorId, @NotNull final RunProfile profile) {
90 if (!DefaultDebugExecutor.EXECUTOR_ID.equals(executorId)) {
91 // If not debug at all
95 * Any python configuration is debuggable unless it explicitly declares itself as DebugAwareConfiguration and denies it
96 * with canRunUnderDebug == false
99 if (profile instanceof WrappingRunConfiguration) {
100 // If configuration is wrapper -- unwrap it and check
101 return isDebuggable(((WrappingRunConfiguration<?>)profile).getPeer());
103 return isDebuggable(profile);
106 private static boolean isDebuggable(@NotNull final RunProfile profile) {
107 if (profile instanceof DebugAwareConfiguration) {
108 // if configuration knows whether debug is allowed
109 return ((DebugAwareConfiguration)profile).canRunUnderDebug();
111 if (profile instanceof AbstractPythonRunConfiguration) {
112 // Any python configuration is debuggable
115 // No even a python configuration
120 protected XDebugSession createSession(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
121 throws ExecutionException {
122 FileDocumentManager.getInstance().saveAllDocuments();
124 final PythonCommandLineState pyState = (PythonCommandLineState)state;
126 Sdk sdk = pyState.getSdk();
127 PyDebugSessionFactory sessionCreator = PyDebugSessionFactory.findExtension(sdk);
128 if (sessionCreator != null) {
129 return sessionCreator.createSession(this, pyState, environment);
132 final ServerSocket serverSocket = PythonCommandLineState.createServerSocket();
133 final int serverLocalPort = serverSocket.getLocalPort();
134 RunProfile profile = environment.getRunProfile();
135 final ExecutionResult result =
136 pyState.execute(environment.getExecutor(), createCommandLinePatchers(environment.getProject(), pyState, profile, serverLocalPort));
138 return XDebuggerManager.getInstance(environment.getProject()).
139 startSession(environment, new XDebugProcessStarter() {
142 public XDebugProcess start(@NotNull final XDebugSession session) {
143 PyDebugProcess pyDebugProcess =
144 createDebugProcess(session, serverSocket, result, pyState);
146 createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
147 return pyDebugProcess;
153 protected PyDebugProcess createDebugProcess(@NotNull XDebugSession session,
154 ServerSocket serverSocket,
155 ExecutionResult result,
156 PythonCommandLineState pyState) {
157 return new PyDebugProcess(session, serverSocket, result.getExecutionConsole(), result.getProcessHandler(),
158 pyState.isMultiprocessDebug());
162 protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
163 throws ExecutionException {
164 XDebugSession session = createSession(state, environment);
165 initSession(session, state, environment.getExecutor());
166 return session.getRunContentDescriptor();
169 protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
172 public static int findIndex(List<String> paramList, String paramName) {
173 for (int i = 0; i < paramList.size(); i++) {
174 if (paramName.equals(paramList.get(i))) {
181 public static void createConsoleCommunicationAndSetupActions(@NotNull final Project project,
182 @NotNull final ExecutionResult result,
183 @NotNull PyDebugProcess debugProcess, @NotNull XDebugSession session) {
184 ExecutionConsole console = result.getExecutionConsole();
185 if (console instanceof PythonDebugLanguageConsoleView) {
186 ProcessHandler processHandler = result.getProcessHandler();
188 initDebugConsoleView(project, debugProcess, (PythonDebugLanguageConsoleView)console, processHandler, session);
192 public static PythonDebugConsoleCommunication initDebugConsoleView(Project project,
193 PyDebugProcess debugProcess,
194 PythonDebugLanguageConsoleView console,
195 ProcessHandler processHandler, final XDebugSession session) {
196 PythonConsoleView pythonConsoleView = console.getPydevConsoleView();
197 PythonDebugConsoleCommunication debugConsoleCommunication = new PythonDebugConsoleCommunication(project, debugProcess);
199 pythonConsoleView.setConsoleCommunication(debugConsoleCommunication);
202 PydevDebugConsoleExecuteActionHandler consoleExecuteActionHandler = new PydevDebugConsoleExecuteActionHandler(pythonConsoleView,
204 debugConsoleCommunication);
205 pythonConsoleView.setExecutionHandler(consoleExecuteActionHandler);
207 debugProcess.getSession().addSessionListener(consoleExecuteActionHandler);
208 new LanguageConsoleBuilder(pythonConsoleView).processHandler(processHandler).initActions(consoleExecuteActionHandler, "py");
211 debugConsoleCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
213 public void commandExecuted(boolean more) {
214 session.rebuildViews();
218 public void inputRequested() {
222 return debugConsoleCommunication;
226 public static CommandLinePatcher createRunConfigPatcher(RunProfileState state, RunProfile profile) {
227 CommandLinePatcher runConfigPatcher = null;
228 if (state instanceof PythonCommandLineState && profile instanceof AbstractPythonRunConfiguration) {
229 runConfigPatcher = (AbstractPythonRunConfiguration)profile;
231 return runConfigPatcher;
234 public CommandLinePatcher[] createCommandLinePatchers(final Project project, final PythonCommandLineState state,
236 final int serverLocalPort) {
237 return new CommandLinePatcher[]{createDebugServerPatcher(project, state, serverLocalPort), createRunConfigPatcher(state, profile)};
240 private CommandLinePatcher createDebugServerPatcher(final Project project,
241 final PythonCommandLineState pyState,
242 final int serverLocalPort) {
243 return new CommandLinePatcher() {
245 private void patchExeParams(ParametersList parametersList) {
246 // we should remove '-m' parameter, but notify debugger of it
247 // but we can't remove one parameter from group, so we create new parameters group
248 ParamsGroup newExeParams = new ParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
249 int exeParamsIndex = parametersList.getParamsGroups().indexOf(
250 parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS));
251 ParamsGroup exeParamsOld = parametersList.removeParamsGroup(exeParamsIndex);
253 for (String param : exeParamsOld.getParameters()) {
254 if (!param.equals("-m")) {
255 newExeParams.addParameter(param);
262 parametersList.addParamsGroupAt(exeParamsIndex, newExeParams);
267 public void patchCommandLine(GeneralCommandLine commandLine) {
268 // script name is the last parameter; all other params are for python interpreter; insert just before name
269 ParametersList parametersList = commandLine.getParametersList();
271 @SuppressWarnings("ConstantConditions") @NotNull
272 ParamsGroup debugParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_DEBUGGER);
274 patchExeParams(parametersList);
276 @SuppressWarnings("ConstantConditions") @NotNull
277 ParamsGroup exeParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
279 final PythonSdkFlavor flavor = pyState.getSdkFlavor();
280 if (flavor != null) {
281 assert exeParams != null;
282 for (String option : flavor.getExtraDebugOptions()) {
283 exeParams.addParameter(option);
287 assert debugParams != null;
288 fillDebugParameters(project, debugParams, serverLocalPort, pyState, commandLine);
293 private void fillDebugParameters(@NotNull Project project,
294 @NotNull ParamsGroup debugParams,
296 @NotNull PythonCommandLineState pyState,
297 @NotNull GeneralCommandLine cmd) {
298 PythonHelper.DEBUGGER.addToGroup(debugParams, cmd);
300 configureDebugParameters(project, debugParams, pyState, cmd);
303 configureDebugEnvironment(project, cmd.getEnvironment());
305 configureDebugConnectionParameters(debugParams, serverLocalPort);
308 public static void configureDebugEnvironment(@NotNull Project project, Map<String, String> environment) {
309 if (PyDebuggerOptionsProvider.getInstance(project).isSupportGeventDebugging()) {
310 environment.put(GEVENT_SUPPORT, "True");
313 PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
314 if (debuggerSettings.isSteppingFiltersEnabled()) {
315 environment.put(PYDEVD_FILTERS, debuggerSettings.getSteppingFiltersForProject(project));
317 if (debuggerSettings.isLibrariesFilterEnabled()) {
318 environment.put(PYDEVD_FILTER_LIBRARIES, "True");
321 PydevConsoleRunnerFactory.putIPythonEnvFlag(project, environment);
323 addProjectRootsToEnv(project, environment);
324 addSdkRootsToEnv(project, environment);
327 protected void configureDebugParameters(@NotNull Project project,
328 @NotNull ParamsGroup debugParams,
329 @NotNull PythonCommandLineState pyState,
330 @NotNull GeneralCommandLine cmd) {
331 if (pyState.isMultiprocessDebug()) {
332 //noinspection SpellCheckingInspection
333 debugParams.addParameter("--multiproc");
336 configureCommonDebugParameters(project, debugParams);
339 public static void configureCommonDebugParameters(@NotNull Project project,
340 @NotNull ParamsGroup debugParams) {
342 debugParams.addParameter(MODULE_PARAM);
345 if (ApplicationManager.getApplication().isUnitTestMode()) {
346 debugParams.addParameter("--DEBUG");
349 if (PyDebuggerOptionsProvider.getInstance(project).isSaveCallSignatures()) {
350 debugParams.addParameter("--save-signatures");
353 if (PyDebuggerOptionsProvider.getInstance(project).isSupportQtDebugging()) {
354 debugParams.addParameter("--qt-support");
358 private static void configureDebugConnectionParameters(@NotNull ParamsGroup debugParams, int serverLocalPort) {
359 final String[] debuggerArgs = new String[]{
360 CLIENT_PARAM, "127.0.0.1",
361 PORT_PARAM, String.valueOf(serverLocalPort),
364 for (String s : debuggerArgs) {
365 debugParams.addParameter(s);
369 private static void addProjectRootsToEnv(@NotNull Project project, @NotNull Map<String, String> environment) {
371 List<String> roots = Lists.newArrayList();
372 for (VirtualFile contentRoot : ProjectRootManager.getInstance(project).getContentRoots()) {
373 roots.add(contentRoot.getPath());
376 environment.put(IDE_PROJECT_ROOTS, StringUtil.join(roots, File.pathSeparator));
379 private static void addSdkRootsToEnv(@NotNull Project project, @NotNull Map<String, String> environment) {
380 final RunManager runManager = RunManager.getInstance(project);
381 final RunnerAndConfigurationSettings selectedConfiguration = runManager.getSelectedConfiguration();
382 if (selectedConfiguration != null) {
383 final RunConfiguration configuration = selectedConfiguration.getConfiguration();
384 if (configuration instanceof AbstractPythonRunConfiguration) {
385 AbstractPythonRunConfiguration runConfiguration = (AbstractPythonRunConfiguration)configuration;
386 final Sdk sdk = runConfiguration.getSdk();
388 List<String> roots = Lists.newArrayList();
389 for (VirtualFile contentRoot : sdk.getSdkModificator().getRoots(OrderRootType.CLASSES)) {
390 roots.add(contentRoot.getPath());
392 environment.put(LIBRARY_ROOTS, StringUtil.join(roots, File.pathSeparator));