mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-21 05:51:25 +07:00
PY-73432 Run Python debugger in server mode by default
GitOrigin-RevId: 3cdce22120868761d823402bd6bcf660224591b8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
37366e6018
commit
84a016fd62
@@ -230,6 +230,14 @@ fun TargetEnvironment.LocalPortBinding.getTargetEnvironmentValue(): TargetEnviro
|
||||
resolvedPortBinding.targetEndpoint
|
||||
}
|
||||
|
||||
fun TargetEnvironment.TargetPortBinding.getTargetEnvironmentValue(): TargetEnvironmentFunction<HostPort> =
|
||||
TraceableTargetEnvironmentFunction { targetEnvironment ->
|
||||
val targetPortBinding = this@getTargetEnvironmentValue
|
||||
val resolvedPortBinding = (targetEnvironment.targetPortBindings[targetPortBinding]
|
||||
?: throw IllegalStateException("Target port binding \"$targetPortBinding\" cannot be found"))
|
||||
resolvedPortBinding.targetEndpoint
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun TargetEnvironment.downloadFromTarget(localPath: Path, progressIndicator: ProgressIndicator) {
|
||||
val localFileDir = localPath.parent
|
||||
|
||||
@@ -456,9 +456,12 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
<registryKey defaultValue="true" description="Halt variable resolve threads on step/resume" restartRequired="false"
|
||||
key="python.debug.halt.variable.resolve.threads.on.step.resume"/>
|
||||
|
||||
<registryKey defaultValue="false" description="Use a single port for communication between PyCharm and the debugger"
|
||||
<registryKey defaultValue="true" description="Use a single port for communication between PyCharm and the debugger"
|
||||
restartRequired="false" key="python.debug.use.single.port"/>
|
||||
|
||||
<registryKey defaultValue="29781" description="Default Python debugger port" restartRequired="false"
|
||||
key="python.debug.default.port" />
|
||||
|
||||
<!-- typing -->
|
||||
<multiHostInjector implementation="com.jetbrains.python.codeInsight.typing.PyTypingAnnotationInjector"/>
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.registry.RegistryManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.ExperimentalUI;
|
||||
@@ -111,6 +110,8 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
|
||||
private static final @NonNls String PYTHON3_PYCACHE_PREFIX_OPTION = "pycache_prefix=";
|
||||
|
||||
private static final int DEFAULT_DEBUGGER_PORT = 29781;
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(PyDebugRunner.class);
|
||||
|
||||
@Override
|
||||
@@ -168,33 +169,57 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
final @NotNull ExecutionEnvironment environment) {
|
||||
PythonCommandLineState pyState = (PythonCommandLineState)state;
|
||||
RunProfile profile = environment.getRunProfile();
|
||||
var clientId = ClientId.getCurrentOrNull();
|
||||
return Promises
|
||||
.runAsync(() -> {
|
||||
int serverLocalPort = findAvailableSocketPort();
|
||||
try {
|
||||
PythonDebuggerScriptTargetedCommandLineBuilder debuggerScriptCommandLineBuilder =
|
||||
new PythonDebuggerScriptTargetedCommandLineBuilder(environment.getProject(), pyState, profile, serverLocalPort);
|
||||
ExecutionResult result = pyState.execute(environment.getExecutor(), debuggerScriptCommandLineBuilder);
|
||||
ServerSocket serverSocket = debuggerScriptCommandLineBuilder.getServerSocketForDebugging();
|
||||
if (serverSocket == null) {
|
||||
LOG.error("The server socket has not been created after the target environment preparation" +
|
||||
", trying to fallback and create the server socket on the loopback address");
|
||||
serverSocket = createServerSocketOnLoopbackAddress(serverLocalPort);
|
||||
|
||||
if (Registry.is("python.debug.use.single.port")) {
|
||||
int port = Registry.intValue("python.debug.default.port", DEFAULT_DEBUGGER_PORT);
|
||||
TargetEnvironment.TargetPortBinding targetPortBinding =
|
||||
new TargetEnvironment.TargetPortBinding(port, port);
|
||||
return Promises
|
||||
.runAsync(() -> {
|
||||
try {
|
||||
var debuggerScriptCommandLineBuilder = new PythonDebuggerServerModeTargetedCommandLineBuilder(
|
||||
environment.getProject(), pyState, profile, targetPortBinding);
|
||||
return pyState.execute(environment.getExecutor(), debuggerScriptCommandLineBuilder);
|
||||
}
|
||||
return Pair.create(serverSocket, result);
|
||||
}
|
||||
catch (ExecutionException err) {
|
||||
throw new RuntimeException(err.getMessage(), err);
|
||||
}
|
||||
})
|
||||
.thenAsync(pair -> AppUIExecutor.onUiThread().submit(() -> {
|
||||
try (AccessToken ignored = ClientId.withClientId(clientId)) {
|
||||
ServerSocket serverSocket = pair.getFirst();
|
||||
ExecutionResult result = pair.getSecond();
|
||||
return createXDebugSession(environment, pyState, serverSocket, result);
|
||||
}
|
||||
}));
|
||||
catch (ExecutionException err) {
|
||||
throw new RuntimeException(err);
|
||||
}
|
||||
})
|
||||
.thenAsync(result -> AppUIExecutor.onUiThread().submit(() -> {
|
||||
return createXDebugSession(environment, port, result);
|
||||
}));
|
||||
}
|
||||
else {
|
||||
var clientId = ClientId.getCurrentOrNull();
|
||||
return Promises
|
||||
.runAsync(() -> {
|
||||
int serverLocalPort = findAvailableSocketPort();
|
||||
try {
|
||||
TargetEnvironment.LocalPortBinding localPortBinding =
|
||||
new TargetEnvironment.LocalPortBinding(serverLocalPort, null);
|
||||
var debuggerScriptCommandLineBuilder = new PythonDebuggerClientModeTargetedCommandLineBuilder(
|
||||
environment.getProject(), pyState, profile, localPortBinding);
|
||||
ExecutionResult result = pyState.execute(environment.getExecutor(), debuggerScriptCommandLineBuilder);
|
||||
ServerSocket serverSocket = debuggerScriptCommandLineBuilder.getServerSocketForDebugging();
|
||||
if (serverSocket == null) {
|
||||
LOG.error("The server socket has not been created after the target environment preparation" +
|
||||
", trying to fallback and create the server socket on the loopback address");
|
||||
serverSocket = createServerSocketOnLoopbackAddress(serverLocalPort);
|
||||
}
|
||||
return Pair.create(serverSocket, result);
|
||||
}
|
||||
catch (ExecutionException err) {
|
||||
throw new RuntimeException(err.getMessage(), err);
|
||||
}
|
||||
})
|
||||
.thenAsync(pair -> AppUIExecutor.onUiThread().submit(() -> {
|
||||
try (AccessToken ignored = ClientId.withClientId(clientId)) {
|
||||
ServerSocket serverSocket = pair.getFirst();
|
||||
ExecutionResult result = pair.getSecond();
|
||||
return createXDebugSession(environment, pyState, serverSocket, result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull ServerSocket createServerSocketOnLoopbackAddress(int serverLocalPort) {
|
||||
@@ -237,6 +262,26 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
return session;
|
||||
}
|
||||
|
||||
private @NotNull XDebugSession createXDebugSession(@NotNull ExecutionEnvironment environment,
|
||||
int serverPort, ExecutionResult result) throws ExecutionException {
|
||||
XDebugSession session = XDebuggerManager.getInstance(environment.getProject()).
|
||||
startSession(environment, new XDebugProcessStarter() {
|
||||
@Override
|
||||
public @NotNull XDebugProcess start(@NotNull XDebugSession session) {
|
||||
PyDebugProcess pyDebugProcess = createDebugProcess(session, serverPort, result);
|
||||
createConsoleCommunicationAndSetupActions(environment.getProject(), result, pyDebugProcess, session);
|
||||
return pyDebugProcess;
|
||||
}
|
||||
});
|
||||
|
||||
if (ExperimentalUI.isNewUI()) {
|
||||
session.getUI().getDefaults().initContentAttraction(DebuggerContentInfo.CONSOLE_CONTENT,
|
||||
XDebuggerUIConstants.LAYOUT_VIEW_FINISH_CONDITION,
|
||||
new LayoutAttractionPolicy.FocusOnce());
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be deprecated.
|
||||
* <p>
|
||||
@@ -280,6 +325,10 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
pyState.isMultiprocessDebug());
|
||||
}
|
||||
|
||||
protected @NotNull PyDebugProcess createDebugProcess(@NotNull XDebugSession session, int serverPort, ExecutionResult result) {
|
||||
return new PyDebugProcess(session, result.getExecutionConsole(), result.getProcessHandler(), "localhost", serverPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling this method with an SSH interpreter would lead to an error, because this method is expected to be executed from EDT,
|
||||
* and therefore it would try to create an SSH connection in EDT.
|
||||
@@ -595,7 +644,12 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
configureDebugEnvironment(project, new TargetEnvironmentController(debuggerScript.getEnvs(), request), runProfile,
|
||||
isLocalTarget);
|
||||
|
||||
configureClientModeDebugConnectionParameters(debuggerScript, serverPortOnTarget);
|
||||
if (Registry.is("python.debug.use.single.port")) {
|
||||
configureServerModeDebugConnectionParameters(debuggerScript, serverPortOnTarget);
|
||||
}
|
||||
else {
|
||||
configureClientModeDebugConnectionParameters(debuggerScript, serverPortOnTarget);
|
||||
}
|
||||
|
||||
originalPythonScript.accept(new PythonExecution.Visitor() {
|
||||
@Override
|
||||
@@ -687,21 +741,19 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
}
|
||||
|
||||
private static void applyRegistryFlags(@NotNull EnvironmentController environmentController) {
|
||||
var registryManager = RegistryManager.getInstance();
|
||||
|
||||
if (registryManager.is("python.debug.low.impact.monitoring.api")) {
|
||||
if (Registry.is("python.debug.low.impact.monitoring.api")) {
|
||||
environmentController.putFixedValue(USE_LOW_IMPACT_MONITORING, "True");
|
||||
}
|
||||
|
||||
if (!registryManager.is("python.debug.enable.cython.speedups")) {
|
||||
if (!Registry.is("python.debug.enable.cython.speedups")) {
|
||||
environmentController.putFixedValue(PYDEVD_USE_CYTHON, "NO");
|
||||
}
|
||||
|
||||
if (registryManager.is("python.debug.enable.diagnostic.prints")) {
|
||||
if (Registry.is("python.debug.enable.diagnostic.prints")) {
|
||||
environmentController.putFixedValue(PYCHARM_DEBUG, "True");
|
||||
}
|
||||
|
||||
if (registryManager.is("python.debug.halt.variable.resolve.threads.on.step.resume")) {
|
||||
if (Registry.is("python.debug.halt.variable.resolve.threads.on.step.resume")) {
|
||||
environmentController.putFixedValue(HALT_VARIABLE_RESOLVE_THREADS_ON_STEP_RESUME, "True");
|
||||
}
|
||||
}
|
||||
@@ -856,10 +908,10 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
* @param serverPortOnTarget the server
|
||||
*/
|
||||
static void configureServerModeDebugConnectionParameters(@NotNull PythonExecution debuggerScript,
|
||||
@NotNull Function<TargetEnvironment, Integer> serverPortOnTarget) {
|
||||
@NotNull Function<TargetEnvironment, HostPort> serverPortOnTarget) {
|
||||
// --port
|
||||
debuggerScript.addParameter(PORT_PARAM);
|
||||
debuggerScript.addParameter(serverPortOnTarget.andThen(Object::toString));
|
||||
debuggerScript.addParameter(serverPortOnTarget.andThen(HostPort::getPort).andThen(Object::toString));
|
||||
// --file
|
||||
debuggerScript.addParameter(FILE_PARAM);
|
||||
}
|
||||
@@ -891,45 +943,25 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
}
|
||||
}
|
||||
|
||||
private class PythonDebuggerScriptTargetedCommandLineBuilder implements PythonScriptTargetedCommandLineBuilder {
|
||||
private abstract class PythonDebuggerTargetedCommandLineBuilder implements PythonScriptTargetedCommandLineBuilder {
|
||||
private final @NotNull Project myProject;
|
||||
private final @NotNull PythonCommandLineState myPyState;
|
||||
private final @NotNull RunProfile myProfile;
|
||||
private final int myIdeDebugServerLocalPort;
|
||||
private volatile @Nullable ServerSocket myServerSocketForDebugging;
|
||||
|
||||
private PythonDebuggerScriptTargetedCommandLineBuilder(@NotNull Project project,
|
||||
@NotNull PythonCommandLineState pyState,
|
||||
@NotNull RunProfile profile,
|
||||
int ideDebugServerPort) {
|
||||
private PythonDebuggerTargetedCommandLineBuilder(@NotNull Project project,
|
||||
@NotNull PythonCommandLineState pyState,
|
||||
@NotNull RunProfile profile) {
|
||||
myProject = project;
|
||||
myPyState = pyState;
|
||||
myProfile = profile;
|
||||
myIdeDebugServerLocalPort = ideDebugServerPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PythonExecution build(@NotNull HelpersAwareTargetEnvironmentRequest helpersAwareTargetRequest,
|
||||
public final @NotNull PythonExecution build(@NotNull HelpersAwareTargetEnvironmentRequest helpersAwareTargetRequest,
|
||||
@NotNull PythonExecution pythonScript) {
|
||||
TargetEnvironment.LocalPortBinding ideServerPortBinding = new TargetEnvironment.LocalPortBinding(myIdeDebugServerLocalPort, null);
|
||||
helpersAwareTargetRequest.getTargetEnvironmentRequest().getLocalPortBindings().add(ideServerPortBinding);
|
||||
|
||||
helpersAwareTargetRequest.getTargetEnvironmentRequest().onEnvironmentPrepared((environment, indicator) -> {
|
||||
try {
|
||||
myServerSocketForDebugging = createServerSocketForDebugging(environment, ideServerPortBinding);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("Unable to create server socket for debugging", e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
Function<TargetEnvironment, HostPort> ideServerPortBindingValue =
|
||||
TargetEnvironmentFunctions.getTargetEnvironmentValue(ideServerPortBinding);
|
||||
|
||||
PythonScriptExecution debuggerScript =
|
||||
prepareDebuggerScriptExecution(myProject, ideServerPortBindingValue, myPyState, pythonScript, myProfile,
|
||||
helpersAwareTargetRequest);
|
||||
Function<TargetEnvironment, HostPort> portBinding = createPortBinding(helpersAwareTargetRequest);
|
||||
PythonExecution debuggerScript = prepareDebuggerScriptExecution(myProject, portBinding, myPyState, pythonScript, myProfile,
|
||||
helpersAwareTargetRequest);
|
||||
|
||||
var configuredInterpreterParameters = myPyState.getConfiguredInterpreterParameters();
|
||||
|
||||
@@ -946,6 +978,9 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
return debuggerScript;
|
||||
}
|
||||
|
||||
protected abstract @NotNull Function<TargetEnvironment, HostPort> createPortBinding(
|
||||
@NotNull HelpersAwareTargetEnvironmentRequest helpersAwareTargetRequest);
|
||||
|
||||
private List<String> createInterpreterParametersToPreventPycGenerationInHelpersDir(@NotNull List<String>
|
||||
existingInterpreterParameters) {
|
||||
var sdk = myPyState.getSdk();
|
||||
@@ -997,6 +1032,41 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
Files.createDirectories(pycacheDir);
|
||||
return pycacheDir.toAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for creating a command line configured for debugging Python scripts in the client mode, when
|
||||
* the debugger process connects to an IDE.
|
||||
*/
|
||||
private final class PythonDebuggerClientModeTargetedCommandLineBuilder extends PythonDebuggerTargetedCommandLineBuilder {
|
||||
private final @NotNull TargetEnvironment.LocalPortBinding myLocalPortBinding;
|
||||
|
||||
private volatile @Nullable ServerSocket myServerSocketForDebugging;
|
||||
|
||||
private PythonDebuggerClientModeTargetedCommandLineBuilder(@NotNull Project project,
|
||||
@NotNull PythonCommandLineState pyState,
|
||||
@NotNull RunProfile profile,
|
||||
@NotNull TargetEnvironment.LocalPortBinding localPortBinding) {
|
||||
super(project, pyState, profile);
|
||||
myLocalPortBinding = localPortBinding;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull Function<TargetEnvironment, HostPort> createPortBinding(@NotNull HelpersAwareTargetEnvironmentRequest helpersAwareTargetRequest) {
|
||||
helpersAwareTargetRequest.getTargetEnvironmentRequest().getLocalPortBindings().add(myLocalPortBinding);
|
||||
helpersAwareTargetRequest.getTargetEnvironmentRequest().onEnvironmentPrepared((environment, indicator) -> {
|
||||
try {
|
||||
myServerSocketForDebugging = createServerSocketForDebugging(environment, myLocalPortBinding);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("Unable to create server socket for debugging", e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
helpersAwareTargetRequest.getTargetEnvironmentRequest().getLocalPortBindings().add(myLocalPortBinding);
|
||||
return TargetEnvironmentFunctions.getTargetEnvironmentValue(myLocalPortBinding);
|
||||
}
|
||||
|
||||
private static @NotNull ServerSocket createServerSocketForDebugging(@NotNull TargetEnvironment environment,
|
||||
@NotNull TargetEnvironment.LocalPortBinding ideServerPortBinding)
|
||||
@@ -1025,4 +1095,26 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
|
||||
return myServerSocketForDebugging;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for creating a command line configured for debugging Python scripts in the server mode, when
|
||||
* an IDE process connects to the debugger.
|
||||
*/
|
||||
private final class PythonDebuggerServerModeTargetedCommandLineBuilder extends PythonDebuggerTargetedCommandLineBuilder {
|
||||
private final @NotNull TargetEnvironment.TargetPortBinding myTargetPortBinding;
|
||||
|
||||
private PythonDebuggerServerModeTargetedCommandLineBuilder(@NotNull Project project,
|
||||
@NotNull PythonCommandLineState pyState,
|
||||
@NotNull RunProfile profile,
|
||||
@NotNull TargetEnvironment.TargetPortBinding targetPortBinding) {
|
||||
super(project, pyState, profile);
|
||||
myTargetPortBinding = targetPortBinding;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull Function<TargetEnvironment, HostPort> createPortBinding(@NotNull HelpersAwareTargetEnvironmentRequest helpersAwareTargetRequest) {
|
||||
helpersAwareTargetRequest.getTargetEnvironmentRequest().getTargetPortBindings().add(myTargetPortBinding);
|
||||
return TargetEnvironmentFunctions.getTargetEnvironmentValue(myTargetPortBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.debugger
|
||||
|
||||
import com.intellij.execution.target.HostPort
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironment
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.execution.target.value.constant
|
||||
@@ -14,7 +15,7 @@ class PyDebugRunnerTest {
|
||||
AutoCloseableSoftAssertions().use { softAssertions ->
|
||||
val debuggerScriptExecution = PythonScriptExecution()
|
||||
val localTargetEnvironment = LocalTargetEnvironment(LocalTargetEnvironmentRequest())
|
||||
PyDebugRunner.configureServerModeDebugConnectionParameters(debuggerScriptExecution, constant(8787))
|
||||
PyDebugRunner.configureServerModeDebugConnectionParameters(debuggerScriptExecution, constant(HostPort("", 8787)))
|
||||
softAssertions
|
||||
.assertThat(debuggerScriptExecution.pythonScriptPath)
|
||||
.isNull()
|
||||
|
||||
Reference in New Issue
Block a user