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.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;
54 import java.net.ServerSocket;
55 import java.util.List;
60 public class PyDebugRunner extends GenericProgramRunner {
61 public static final String PY_DEBUG_RUNNER = "PyDebugRunner";
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;
75 public String getRunnerId() {
76 return PY_DEBUG_RUNNER;
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
85 if (profile instanceof DebugAwareConfiguration) {
86 // if configuration knows whether debug is allowed
87 return ((DebugAwareConfiguration)profile).canRunUnderDebug();
93 protected XDebugSession createSession(@NotNull RunProfileState state, @NotNull final ExecutionEnvironment environment)
94 throws ExecutionException {
95 FileDocumentManager.getInstance().saveAllDocuments();
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));
104 return XDebuggerManager.getInstance(environment.getProject()).
105 startSession(environment, new XDebugProcessStarter() {
108 public XDebugProcess start(@NotNull final XDebugSession session) {
109 PyDebugProcess pyDebugProcess =
110 createDebugProcess(session, serverSocket, result, pyState);
112 createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
113 return pyDebugProcess;
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());
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();
135 protected void initSession(XDebugSession session, RunProfileState state, Executor executor) {
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))) {
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();
154 initDebugConsoleView(project, debugProcess, (PythonDebugLanguageConsoleView)console, processHandler, session);
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);
165 pythonConsoleView.setConsoleCommunication(debugConsoleCommunication);
168 PydevDebugConsoleExecuteActionHandler consoleExecuteActionHandler = new PydevDebugConsoleExecuteActionHandler(pythonConsoleView,
170 debugConsoleCommunication);
171 pythonConsoleView.setExecutionHandler(consoleExecuteActionHandler);
173 debugProcess.getSession().addSessionListener(consoleExecuteActionHandler);
174 new LanguageConsoleBuilder(pythonConsoleView).processHandler(processHandler).initActions(consoleExecuteActionHandler, "py");
177 debugConsoleCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
179 public void commandExecuted(boolean more) {
180 session.rebuildViews();
184 public void inputRequested() {
188 return debugConsoleCommunication;
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;
197 return runConfigPatcher;
200 public static CommandLinePatcher[] createCommandLinePatchers(final Project project, final PythonCommandLineState state,
202 final int serverLocalPort) {
203 return new CommandLinePatcher[]{createDebugServerPatcher(project, state, serverLocalPort), createRunConfigPatcher(state, profile)};
206 private static CommandLinePatcher createDebugServerPatcher(final Project project,
207 final PythonCommandLineState pyState,
208 final int serverLocalPort) {
209 return new CommandLinePatcher() {
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);
219 for (String param : exeParamsOld.getParameters()) {
220 if (!param.equals("-m")) {
221 newExeParams.addParameter(param);
228 parametersList.addParamsGroupAt(exeParamsIndex, newExeParams);
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();
237 @SuppressWarnings("ConstantConditions") @NotNull
238 ParamsGroup debugParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_DEBUGGER);
240 patchExeParams(parametersList);
242 @SuppressWarnings("ConstantConditions") @NotNull
243 ParamsGroup exeParams = parametersList.getParamsGroup(PythonCommandLineState.GROUP_EXE_OPTIONS);
245 final PythonSdkFlavor flavor = pyState.getSdkFlavor();
246 if (flavor != null) {
247 assert exeParams != null;
248 for (String option : flavor.getExtraDebugOptions()) {
249 exeParams.addParameter(option);
253 assert debugParams != null;
254 fillDebugParameters(project, debugParams, serverLocalPort, pyState, commandLine);
259 private static void fillDebugParameters(@NotNull Project project,
260 @NotNull ParamsGroup debugParams,
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");
271 debugParams.addParameter("--module");
274 if (ApplicationManager.getApplication().isUnitTestMode()) {
275 debugParams.addParameter("--DEBUG");
278 if (PyDebuggerOptionsProvider.getInstance(project).isSaveCallSignatures()) {
279 debugParams.addParameter("--save-signatures");
282 if (PyDebuggerOptionsProvider.getInstance(project).isSupportGeventDebugging()) {
283 generalCommandLine.getEnvironment().put(GEVENT_SUPPORT, "True");
286 addProjectRootsToEnv(project, generalCommandLine);
288 final String[] debuggerArgs = new String[]{
289 CLIENT_PARAM, "127.0.0.1",
290 PORT_PARAM, String.valueOf(serverLocalPort),
293 for (String s : debuggerArgs) {
294 debugParams.addParameter(s);
298 private static void addProjectRootsToEnv(@NotNull Project project, @NotNull GeneralCommandLine commandLine) {
300 List<String> roots = Lists.newArrayList();
301 for (VirtualFile contentRoot : ProjectRootManager.getInstance(project).getContentRoots()) {
302 roots.add(contentRoot.getPath());
305 commandLine.getEnvironment().put(IDE_PROJECT_ROOTS, StringUtil.join(roots, File.pathSeparator));