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()); - } - } - } -}