diff --git a/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java b/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java
index 1e348cf533eb..85ab3d943cd6 100644
--- a/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java
+++ b/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java
@@ -5,7 +5,6 @@ import com.intellij.execution.filters.InputFilter;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
-import com.jetbrains.python.debugger.PyRunCythonExtensionsFilter;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
@@ -21,9 +20,6 @@ public class PyEduConsoleInputFilterProvider implements ConsoleInputFilterProvid
if (outputType.equals(ConsoleViewContentType.SYSTEM_OUTPUT) && !text.contains("exit code")) {
return Collections.emptyList();
}
- if (text.startsWith(PyRunCythonExtensionsFilter.WARNING_MESSAGE_BEGIN)) {
- return Collections.emptyList();
- }
if (text.startsWith("pydev debugger")) {
return Collections.emptyList();
}
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
index 1003280fa7d8..de8c7cae4560 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
@@ -146,6 +146,7 @@ CMD_INPUT_REQUESTED = 147
CMD_GET_DESCRIPTION = 148
CMD_PROCESS_CREATED = 149
+CMD_SHOW_CYTHON_WARNING = 150
CMD_VERSION = 501
CMD_RETURN = 502
@@ -204,6 +205,7 @@ ID_TO_MEANING = {
'148': 'CMD_GET_DESCRIPTION',
'149': 'CMD_PROCESS_CREATED',
+ '150': 'CMD_SHOW_CYTHON_WARNING',
'501': 'CMD_VERSION',
'502': 'CMD_RETURN',
@@ -590,6 +592,12 @@ class NetCommandFactory:
cmdText = ''
return NetCommand(CMD_PROCESS_CREATED, 0, cmdText)
+ def make_show_cython_warning_message(self):
+ try:
+ return NetCommand(CMD_SHOW_CYTHON_WARNING, 0, '')
+ except:
+ return self.make_error_message(0, get_exception_traceback_str())
+
def make_custom_frame_created_message(self, frameId, frameDescription):
frameDescription = pydevd_xml.make_valid_xml_value(frameDescription)
cmdText = '' % (frameDescription, frameId)
@@ -828,7 +836,6 @@ class NetCommandFactory:
except:
return self.make_error_message(0, get_exception_traceback_str())
-
def make_exit_message(self):
try:
net = NetCommand(CMD_EXIT, 0, '')
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py
index 224e9b5acd77..59af01d41616 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py
@@ -105,6 +105,7 @@ USE_LIB_COPY = SUPPORT_GEVENT and \
INTERACTIVE_MODE_AVAILABLE = sys.platform in ('darwin', 'win32') or os.getenv('DISPLAY') is not None
+SHOW_CYTHON_WARNING = False
def protect_libraries_from_patching():
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch.py b/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch.py
index 18f579685706..4c0b033cdc58 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_trace_dispatch.py
@@ -3,7 +3,7 @@
import os
import sys
-from _pydevd_bundle.pydevd_constants import CYTHON_SUPPORTED
+from _pydevd_bundle.pydevd_constants import CYTHON_SUPPORTED, SHOW_CYTHON_WARNING
use_cython = os.getenv('PYDEVD_USE_CYTHON', None)
@@ -34,6 +34,7 @@ def delete_old_compiled_extensions():
log_error_once("warning: failed to delete old cython speedups. Please delete all *.so files from the directories "
"\"%s\" and \"%s\"" % (_pydevd_bundle_dir, _pydevd_frame_eval_dir))
+show_tracing_warning = False
if use_cython == 'YES':
# We must import the cython version if forcing cython
@@ -65,8 +66,11 @@ elif use_cython is None:
from _pydevd_bundle.pydevd_trace_dispatch_regular import trace_dispatch, global_cache_skips, global_cache_frame_skips # @UnusedImport
from _pydev_bundle.pydev_monkey import log_error_once
- log_error_once("warning: Debugger speedups using cython not found. Run '\"%s\" \"%s\" build_ext --inplace' to build." % (
- sys.executable, os.path.join(dirname, 'setup_cython.py')))
+ if SHOW_CYTHON_WARNING:
+ log_error_once("warning: Debugger speedups using cython not found. Run '\"%s\" \"%s\" build_ext --inplace' to build." % (
+ sys.executable, os.path.join(dirname, 'setup_cython.py')))
+ else:
+ show_tracing_warning = True
else:
raise RuntimeError('Unexpected value for PYDEVD_USE_CYTHON: %s (accepted: YES, NO)' % (use_cython,))
diff --git a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_eval_main.py b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_eval_main.py
index 47a7188d1a54..3eb6900e5a1c 100644
--- a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_eval_main.py
+++ b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_eval_main.py
@@ -1,6 +1,8 @@
import os
import sys
+from _pydevd_bundle.pydevd_constants import SHOW_CYTHON_WARNING
+
IS_PY36 = False
if sys.version_info[0] == 3 and sys.version_info[1] == 6:
IS_PY36 = True
@@ -9,6 +11,7 @@ frame_eval_func = None
stop_frame_eval = None
enable_cache_frames_without_breaks = None
dummy_trace_dispatch = None
+show_frame_eval_warning = False
USE_FRAME_EVAL = os.environ.get('PYDEVD_USE_FRAME_EVAL', None)
@@ -24,5 +27,9 @@ else:
from _pydev_bundle.pydev_monkey import log_error_once
dirname = os.path.dirname(os.path.dirname(__file__))
- log_error_once("warning: Debugger speedups using cython not found. Run '\"%s\" \"%s\" build_ext --inplace' to build." % (
- sys.executable, os.path.join(dirname, 'setup_cython.py')))
+
+ if SHOW_CYTHON_WARNING:
+ log_error_once("warning: Debugger speedups using cython not found. Run '\"%s\" \"%s\" build_ext --inplace' to build." % (
+ sys.executable, os.path.join(dirname, 'setup_cython.py')))
+ else:
+ show_frame_eval_warning = True
diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py
index 89b5a77daf5e..7441961f49da 100644
--- a/python/helpers/pydev/pydevd.py
+++ b/python/helpers/pydev/pydevd.py
@@ -35,8 +35,9 @@ from _pydevd_bundle.pydevd_comm import CMD_SET_BREAK, CMD_SET_NEXT_STATEMENT, CM
from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame
from _pydevd_bundle.pydevd_kill_all_pydevd_threads import kill_all_pydev_threads
-from _pydevd_bundle.pydevd_trace_dispatch import trace_dispatch as _trace_dispatch, global_cache_skips, global_cache_frame_skips
-from _pydevd_frame_eval.pydevd_frame_eval_main import frame_eval_func, stop_frame_eval, enable_cache_frames_without_breaks, dummy_trace_dispatch
+from _pydevd_bundle.pydevd_trace_dispatch import trace_dispatch as _trace_dispatch, global_cache_skips, global_cache_frame_skips, show_tracing_warning
+from _pydevd_frame_eval.pydevd_frame_eval_main import frame_eval_func, stop_frame_eval, enable_cache_frames_without_breaks, \
+ dummy_trace_dispatch, show_frame_eval_warning
from _pydevd_bundle.pydevd_utils import save_main_module
from pydevd_concurrency_analyser.pydevd_concurrency_logger import ThreadingLogger, AsyncioLogger, send_message, cur_time
from pydevd_concurrency_analyser.pydevd_thread_wrappers import wrap_threads
@@ -905,6 +906,10 @@ class PyDB:
# we need all data to be sent to IDE even after program finishes
CheckOutputThread(self).start()
+ if show_tracing_warning or show_frame_eval_warning:
+ cmd = self.cmd_factory.make_show_cython_warning_message()
+ self.writer.add_command(cmd)
+
def patch_threads(self):
try:
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java b/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java
index 2bbbec2859c2..6ba9745b9678 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java
@@ -39,5 +39,7 @@ public interface IPyDebugProcess extends PyFrameAccessor {
void consoleInputRequested(boolean isStarted);
+ void showCythonWarning();
+
XDebugSession getSession();
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
index c67c4dcb0525..b34043b38d08 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
@@ -55,6 +55,7 @@ public abstract class AbstractCommand {
public static final int INPUT_REQUESTED = 147;
public static final int PROCESS_CREATED = 149;
+ public static final int SHOW_CYTHON_WARNING = 150;
public static final int ERROR = 901;
@@ -206,6 +207,10 @@ public abstract class AbstractCommand {
return command == INPUT_REQUESTED;
}
+ public static boolean isShowWarningCommand(final int command) {
+ return command == SHOW_CYTHON_WARNING;
+ }
+
public static boolean isExitEvent(final int command) {
return command == EXIT;
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
index a62f80795170..22807f2ff80a 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
@@ -474,6 +474,9 @@ public class RemoteDebugger implements ProcessDebugger {
else if (ProcessCreatedCommand.isProcessCreatedCommand(frame.getCommand())) {
onProcessCreatedEvent();
}
+ else if (AbstractCommand.isShowWarningCommand(frame.getCommand())) {
+ myDebugProcess.showCythonWarning();
+ }
else {
placeResponse(frame.getSequence(), frame);
}
diff --git a/python/src/com/jetbrains/python/console/PyDebugConsoleBuilder.java b/python/src/com/jetbrains/python/console/PyDebugConsoleBuilder.java
index 11d2dff73390..3a9c73e11328 100644
--- a/python/src/com/jetbrains/python/console/PyDebugConsoleBuilder.java
+++ b/python/src/com/jetbrains/python/console/PyDebugConsoleBuilder.java
@@ -22,7 +22,6 @@ import com.intellij.execution.filters.TextConsoleBuilder;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
-import com.jetbrains.python.debugger.PyRunCythonExtensionsFilter;
import com.jetbrains.python.run.PythonTracebackFilter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -53,7 +52,6 @@ public class PyDebugConsoleBuilder extends TextConsoleBuilder {
protected ConsoleView createConsole() {
PythonDebugLanguageConsoleView consoleView = new PythonDebugLanguageConsoleView(myProject, mySdk);
consoleView.addMessageFilter(new PythonTracebackFilter(myProject));
- consoleView.addMessageFilter(new PyRunCythonExtensionsFilter(myProject));
return consoleView;
}
diff --git a/python/src/com/jetbrains/python/debugger/PyCythonExtensionWarning.java b/python/src/com/jetbrains/python/debugger/PyCythonExtensionWarning.java
new file mode 100644
index 000000000000..b52e9ebf7db6
--- /dev/null
+++ b/python/src/com/jetbrains/python/debugger/PyCythonExtensionWarning.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.debugger;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.process.*;
+import com.intellij.execution.util.ExecUtil;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationListener;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.help.HelpManager;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.python.PythonHelpersLocator;
+import com.jetbrains.python.run.AbstractPythonRunConfiguration;
+import com.jetbrains.python.sdk.PythonEnvUtil;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.event.HyperlinkEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+public class PyCythonExtensionWarning {
+ private static final Logger LOG = Logger.getInstance("com.jetbrains.python.debugger.PyCythonExtensionWarning");
+ public static final String ERROR_TITLE = "Compile Cython Extensions Error";
+ private static final String CYTHON_WARNING_GROUP_ID = "CythonWarning";
+ private static final String WARNING_MESSAGE = "Cython extension speed ups Python debugging
" +
+ "Install " +
+ "How does it work";
+ public static final String SETUP_CYTHON_PATH = "pydev/setup_cython.py";
+ public static final String[] CYTHON_ARGS = {"build_ext", "--inplace"};
+
+
+ public static void showCythonExtensionWarning(@NotNull Project project) {
+ Notification notification =
+ new Notification(CYTHON_WARNING_GROUP_ID, "Python Debugger Extension Available", WARNING_MESSAGE,
+ NotificationType.INFORMATION, createListener(project));
+ notification.notify(project);
+ }
+
+ private static NotificationListener.Adapter createListener(@NotNull Project project) {
+ return new NotificationListener.Adapter() {
+ @Override
+ protected void hyperlinkActivated(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
+ switch (event.getDescription()) {
+ case "install":
+ compileCythonExtension(project);
+ notification.expire();
+ return;
+ case "docs":
+ // TODO: help id for cython speedups help page
+ HelpManager.getInstance().invokeHelp("");
+ }
+ }
+ };
+ }
+
+ private static void showErrorDialog(Project project, String message) {
+ Messages.showMessageDialog(project, message, ERROR_TITLE, null);
+ }
+
+ private static void compileCythonExtension(@NotNull Project project) {
+ try {
+ final RunManager runManager = RunManager.getInstance(project);
+ final RunnerAndConfigurationSettings selectedConfiguration = runManager.getSelectedConfiguration();
+ if (selectedConfiguration == null) {
+ throw new ExecutionException("Python Run Configuration should be selected");
+ }
+ final RunConfiguration configuration = selectedConfiguration.getConfiguration();
+ if (!(configuration instanceof AbstractPythonRunConfiguration)) {
+ throw new ExecutionException("Python Run Configuration should be selected");
+ }
+ AbstractPythonRunConfiguration runConfiguration = (AbstractPythonRunConfiguration)configuration;
+ final String sdkPath = runConfiguration.getSdkHome();
+ final String helpersPath = PythonHelpersLocator.getHelpersRoot().getPath();
+
+ final List cmdline = new ArrayList<>();
+ cmdline.add(sdkPath);
+ cmdline.add(FileUtil.join(helpersPath, FileUtil.toSystemDependentName(SETUP_CYTHON_PATH)));
+ cmdline.addAll(Arrays.asList(CYTHON_ARGS));
+ LOG.info("Compile Cython Extensions " + StringUtil.join(cmdline, " "));
+
+ final Map environment = new HashMap<>(System.getenv());
+ PythonEnvUtil.setPythonUnbuffered(environment);
+ PythonEnvUtil.setPythonDontWriteBytecode(environment);
+ if (sdkPath != null) {
+ PythonEnvUtil.resetHomePathChanges(sdkPath, environment);
+ }
+ GeneralCommandLine commandLine = new GeneralCommandLine(cmdline).withEnvironment(environment);
+
+ final boolean canCreate = FileUtil.ensureCanCreateFile(new File(helpersPath));
+ final boolean useSudo = !canCreate && !SystemInfo.isWindows;
+ Process process;
+ if (useSudo) {
+ process = ExecUtil.sudo(commandLine, "Please enter your password to compile cython extensions: ");
+ }
+ else {
+ process = commandLine.createProcess();
+ }
+
+ ProgressManager.getInstance().run(new Task.Backgroundable(project, "Compile Cython Extensions") {
+ @Override
+ public void run(@NotNull ProgressIndicator indicator) {
+ final CapturingProcessHandler handler =
+ new CapturingProcessHandler(process, commandLine.getCharset(), commandLine.getCommandLineString());
+ handler.addProcessListener(new ProcessAdapter() {
+ @Override
+ public void onTextAvailable(ProcessEvent event, Key outputType) {
+ if (outputType == ProcessOutputTypes.STDOUT || outputType == ProcessOutputTypes.STDERR) {
+ for (String line : StringUtil.splitByLines(event.getText())) {
+ if (isSignificantOutput(line)) {
+ indicator.setText2(line.trim());
+ }
+ }
+ }
+ }
+
+ private boolean isSignificantOutput(String line) {
+ return line.trim().length() > 3;
+ }
+ });
+ final ProcessOutput result = handler.runProcessWithProgressIndicator(indicator);
+ final int exitCode = result.getExitCode();
+ if (exitCode != 0) {
+ final String message = StringUtil.isEmptyOrSpaces(result.getStdout()) && StringUtil.isEmptyOrSpaces(result.getStderr())
+ ? "Permission denied"
+ : "Non-zero exit code (" + exitCode + "): \n" + result.getStderr();
+ UIUtil.invokeLaterIfNeeded(() -> showErrorDialog(project, message));
+ }
+ }
+ });
+ }
+ catch (IOException | ExecutionException e) {
+ showErrorDialog(project, e.getMessage());
+ }
+ }
+}
diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
index 6f3bc5287074..3d10501f1781 100644
--- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
+++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
@@ -119,6 +119,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
private PyStackFrame myConsoleContextFrame = null;
private PyReferrersLoader myReferrersProvider;
private final List myFrameListeners = ContainerUtil.createLockFreeCopyOnWriteList();
+ private boolean isCythonWarningShown = false;
public PyDebugProcess(@NotNull XDebugSession session,
@NotNull ServerSocket serverSocket,
@@ -382,6 +383,14 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
}
+ @Override
+ public void showCythonWarning() {
+ if (!isCythonWarningShown) {
+ PyCythonExtensionWarning.showCythonExtensionWarning(getSession().getProject());
+ isCythonWarningShown = true;
+ }
+ }
+
protected void afterConnect() {
}
diff --git a/python/src/com/jetbrains/python/debugger/PyRunCythonExtensionsFilter.java b/python/src/com/jetbrains/python/debugger/PyRunCythonExtensionsFilter.java
deleted file mode 100644
index 3369cc28d8a6..000000000000
--- a/python/src/com/jetbrains/python/debugger/PyRunCythonExtensionsFilter.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2000-2016 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.debugger;
-
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.RunnerAndConfigurationSettings;
-import com.intellij.execution.configurations.GeneralCommandLine;
-import com.intellij.execution.configurations.RunConfiguration;
-import com.intellij.execution.filters.Filter;
-import com.intellij.execution.filters.HyperlinkInfo;
-import com.intellij.execution.process.*;
-import com.intellij.execution.util.ExecUtil;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.ProgressManager;
-import com.intellij.openapi.progress.Task;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.Key;
-import com.intellij.openapi.util.SystemInfo;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.ui.UIUtil;
-import com.jetbrains.python.PythonHelpersLocator;
-import com.jetbrains.python.run.AbstractPythonRunConfiguration;
-import com.jetbrains.python.sdk.PythonEnvUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.*;
-
-public class PyRunCythonExtensionsFilter implements Filter {
- public static final String ERROR_TITLE = "Compile Cython Extensions Error";
- public static final String WARNING_MESSAGE_BEGIN = "warning: Debugger speedups using cython not found. Run \'";
- public static final String WARNING_MESSAGE_END = "\' to build.";
- public static final String SETUP_CYTHON_PATH = "pydev/setup_cython.py";
- public static final String[] CYTHON_ARGS = {"build_ext", "--inplace"};
- private static final Logger LOG = Logger.getInstance("com.jetbrains.python.debugger.PyRunCythonExtensionsFilter");
- private final @NotNull Project myProject;
- private volatile boolean isApplied = false;
-
- public PyRunCythonExtensionsFilter(@NotNull Project project) {
- myProject = project;
- }
-
- @Nullable
- @Override
- public Result applyFilter(String line, int entireLength) {
- final int indexOfWarning = line.indexOf(WARNING_MESSAGE_BEGIN);
- if (indexOfWarning != -1) {
- final int indexOfLinkBeginning = indexOfWarning + WARNING_MESSAGE_BEGIN.length();
- final int textStartOffset = entireLength - line.length();
- final int indexOfLinkEnd = line.indexOf(WARNING_MESSAGE_END);
- return new Result(textStartOffset + indexOfLinkBeginning, textStartOffset + indexOfLinkEnd, new DebuggerExtensionsHyperlinkInfo());
- }
- return null;
- }
-
- private void showErrorDialog(String message) {
- Messages.showMessageDialog(myProject, message, ERROR_TITLE, null);
- }
-
- private class DebuggerExtensionsHyperlinkInfo implements HyperlinkInfo {
- @Override
- public void navigate(Project project) {
- try {
- if (isApplied) {
- return;
- }
- final RunManager runManager = RunManager.getInstance(myProject);
- final RunnerAndConfigurationSettings selectedConfiguration = runManager.getSelectedConfiguration();
- if (selectedConfiguration == null) {
- throw new ExecutionException("Python Run Configuration should be selected");
- }
- final RunConfiguration configuration = selectedConfiguration.getConfiguration();
- if (!(configuration instanceof AbstractPythonRunConfiguration)) {
- throw new ExecutionException("Python Run Configuration should be selected");
- }
- AbstractPythonRunConfiguration runConfiguration = (AbstractPythonRunConfiguration)configuration;
- final String sdkPath = runConfiguration.getSdkHome();
- final String helpersPath = PythonHelpersLocator.getHelpersRoot().getPath();
-
- final List cmdline = new ArrayList<>();
- cmdline.add(sdkPath);
- cmdline.add(FileUtil.join(helpersPath, FileUtil.toSystemDependentName(SETUP_CYTHON_PATH)));
- cmdline.addAll(Arrays.asList(CYTHON_ARGS));
- LOG.info("Compile Cython Extensions " + StringUtil.join(cmdline, " "));
-
- final Map environment = new HashMap<>(System.getenv());
- PythonEnvUtil.setPythonUnbuffered(environment);
- PythonEnvUtil.setPythonDontWriteBytecode(environment);
- if (sdkPath != null) {
- PythonEnvUtil.resetHomePathChanges(sdkPath, environment);
- }
- GeneralCommandLine commandLine = new GeneralCommandLine(cmdline).withEnvironment(environment);
-
- final boolean canCreate = FileUtil.ensureCanCreateFile(new File(helpersPath));
- final boolean useSudo = !canCreate && !SystemInfo.isWindows;
- Process process;
- if (useSudo) {
- process = ExecUtil.sudo(commandLine, "Please enter your password to compile cython extensions: ");
- }
- else {
- process = commandLine.createProcess();
- }
-
- ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Compile Cython Extensions") {
- @Override
- public void run(@NotNull ProgressIndicator indicator) {
- isApplied = true;
- final CapturingProcessHandler handler =
- new CapturingProcessHandler(process, commandLine.getCharset(), commandLine.getCommandLineString());
- handler.addProcessListener(new ProcessAdapter() {
- @Override
- public void onTextAvailable(ProcessEvent event, Key outputType) {
- if (outputType == ProcessOutputTypes.STDOUT || outputType == ProcessOutputTypes.STDERR) {
- for (String line : StringUtil.splitByLines(event.getText())) {
- if (isSignificantOutput(line)) {
- indicator.setText2(line.trim());
- }
- }
- }
- }
-
- private boolean isSignificantOutput(String line) {
- return line.trim().length() > 3;
- }
- });
- final ProcessOutput result = handler.runProcessWithProgressIndicator(indicator);
- final int exitCode = result.getExitCode();
- if (exitCode != 0) {
- final String message = StringUtil.isEmptyOrSpaces(result.getStdout()) && StringUtil.isEmptyOrSpaces(result.getStderr())
- ? "Permission denied"
- : "Non-zero exit code (" + exitCode + "): \n" + result.getStderr();
- UIUtil.invokeLaterIfNeeded(() -> showErrorDialog(message));
- }
- }
- });
- }
- catch (IOException | ExecutionException e) {
- showErrorDialog(e.getMessage());
- }
- }
- }
-}