debugger beta: Fix install detection for Debugpy in venv (#31339)
Based on my report on discord when chatting with Anthony and Remco: https://discord.com/channels/869392257814519848/1375129714645012530 Root Cause: Zed was incorrectly trying to execute a directory path instead of properly invoking the debugpy module when debugpy was installed via package managers (pip, conda, etc.) rather than downloaded from GitHub releases. Solution: - Automatic Detection: Zed now automatically detects whether debugpy is installed via pip/conda or downloaded from GitHub - Correct Invocation: For pip-installed debugpy, Zed now uses python -m debugpy.adapter instead of trying to execute file paths - Added a `installed_in_venv` flag to differentiate the setup properly - Backward Compatibility: GitHub-downloaded debugpy releases continue to work as before - Enhanced Logging: Added logging to show which debugpy installation method is being used (I had to verify it somehow) I verified with the following setups (can be confirmed with the debug logs): - `conda` with installed debugpy, went to installed instance - `uv` with installed debugpy, went to installed instance - `uv` without installed debugpy, went to github releases - Homebrew global python install, went to github releases Release Notes: - Fix issue where debugpy from different environments won't load as intended
This commit is contained in:
parent
7ec61ceec9
commit
05763b2fe3
3 changed files with 138 additions and 27 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4058,10 +4058,10 @@ dependencies = [
|
|||
"gpui",
|
||||
"json_dotpath",
|
||||
"language",
|
||||
"log",
|
||||
"paths",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"task",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
|
|
|
@ -28,10 +28,10 @@ futures.workspace = true
|
|||
gpui.workspace = true
|
||||
json_dotpath.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
paths.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
task.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
|
|
@ -8,6 +8,7 @@ use gpui::{AsyncApp, SharedString};
|
|||
use json_dotpath::DotPaths;
|
||||
use language::{LanguageName, Toolchain};
|
||||
use serde_json::Value;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsStr,
|
||||
|
@ -27,6 +28,60 @@ impl PythonDebugAdapter {
|
|||
const ADAPTER_PATH: &'static str = "src/debugpy/adapter";
|
||||
const LANGUAGE_NAME: &'static str = "Python";
|
||||
|
||||
async fn generate_debugpy_arguments(
|
||||
&self,
|
||||
host: &Ipv4Addr,
|
||||
port: u16,
|
||||
user_installed_path: Option<&Path>,
|
||||
installed_in_venv: bool,
|
||||
) -> Result<Vec<String>> {
|
||||
if let Some(user_installed_path) = user_installed_path {
|
||||
log::debug!(
|
||||
"Using user-installed debugpy adapter from: {}",
|
||||
user_installed_path.display()
|
||||
);
|
||||
Ok(vec![
|
||||
user_installed_path
|
||||
.join(Self::ADAPTER_PATH)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
format!("--host={}", host),
|
||||
format!("--port={}", port),
|
||||
])
|
||||
} else if installed_in_venv {
|
||||
log::debug!("Using venv-installed debugpy");
|
||||
Ok(vec![
|
||||
"-m".to_string(),
|
||||
"debugpy.adapter".to_string(),
|
||||
format!("--host={}", host),
|
||||
format!("--port={}", port),
|
||||
])
|
||||
} else {
|
||||
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
|
||||
let file_name_prefix = format!("{}_", Self::ADAPTER_NAME);
|
||||
|
||||
let debugpy_dir =
|
||||
util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
|
||||
file_name.starts_with(&file_name_prefix)
|
||||
})
|
||||
.await
|
||||
.context("Debugpy directory not found")?;
|
||||
|
||||
log::debug!(
|
||||
"Using GitHub-downloaded debugpy adapter from: {}",
|
||||
debugpy_dir.display()
|
||||
);
|
||||
Ok(vec![
|
||||
debugpy_dir
|
||||
.join(Self::ADAPTER_PATH)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
format!("--host={}", host),
|
||||
format!("--port={}", port),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn request_args(
|
||||
&self,
|
||||
task_definition: &DebugTaskDefinition,
|
||||
|
@ -93,24 +148,12 @@ impl PythonDebugAdapter {
|
|||
config: &DebugTaskDefinition,
|
||||
user_installed_path: Option<PathBuf>,
|
||||
toolchain: Option<Toolchain>,
|
||||
installed_in_venv: bool,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
|
||||
let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
let debugpy_dir = if let Some(user_installed_path) = user_installed_path {
|
||||
user_installed_path
|
||||
} else {
|
||||
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
|
||||
let file_name_prefix = format!("{}_", Self::ADAPTER_NAME);
|
||||
|
||||
util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
|
||||
file_name.starts_with(&file_name_prefix)
|
||||
})
|
||||
.await
|
||||
.context("Debugpy directory not found")?
|
||||
};
|
||||
|
||||
let python_path = if let Some(toolchain) = toolchain {
|
||||
Some(toolchain.path.to_string())
|
||||
} else {
|
||||
|
@ -128,16 +171,27 @@ impl PythonDebugAdapter {
|
|||
name
|
||||
};
|
||||
|
||||
let python_command = python_path.context("failed to find binary path for Python")?;
|
||||
log::debug!("Using Python executable: {}", python_command);
|
||||
|
||||
let arguments = self
|
||||
.generate_debugpy_arguments(
|
||||
&host,
|
||||
port,
|
||||
user_installed_path.as_deref(),
|
||||
installed_in_venv,
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::debug!(
|
||||
"Starting debugpy adapter with command: {} {}",
|
||||
python_command,
|
||||
arguments.join(" ")
|
||||
);
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: python_path.context("failed to find binary path for Python")?,
|
||||
arguments: vec![
|
||||
debugpy_dir
|
||||
.join(Self::ADAPTER_PATH)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
format!("--port={}", port),
|
||||
format!("--host={}", host),
|
||||
],
|
||||
command: python_command,
|
||||
arguments,
|
||||
connection: Some(adapters::TcpArguments {
|
||||
host,
|
||||
port,
|
||||
|
@ -558,6 +612,16 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
user_installed_path: Option<PathBuf>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
if let Some(local_path) = &user_installed_path {
|
||||
log::debug!(
|
||||
"Using user-installed debugpy adapter from: {}",
|
||||
local_path.display()
|
||||
);
|
||||
return self
|
||||
.get_installed_binary(delegate, &config, Some(local_path.clone()), None, false)
|
||||
.await;
|
||||
}
|
||||
|
||||
let toolchain = delegate
|
||||
.toolchain_store()
|
||||
.active_toolchain(
|
||||
|
@ -571,13 +635,18 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
if let Some(toolchain) = &toolchain {
|
||||
if let Some(path) = Path::new(&toolchain.path.to_string()).parent() {
|
||||
let debugpy_path = path.join("debugpy");
|
||||
if smol::fs::metadata(&debugpy_path).await.is_ok() {
|
||||
if delegate.fs().is_file(&debugpy_path).await {
|
||||
log::debug!(
|
||||
"Found debugpy in toolchain environment: {}",
|
||||
debugpy_path.display()
|
||||
);
|
||||
return self
|
||||
.get_installed_binary(
|
||||
delegate,
|
||||
&config,
|
||||
Some(debugpy_path.to_path_buf()),
|
||||
None,
|
||||
Some(toolchain.clone()),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
@ -591,7 +660,49 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
self.get_installed_binary(delegate, &config, user_installed_path, toolchain)
|
||||
self.get_installed_binary(delegate, &config, None, None, false)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{net::Ipv4Addr, path::PathBuf};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_debugpy_install_path_cases() {
|
||||
let adapter = PythonDebugAdapter::default();
|
||||
let host = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let port = 5678;
|
||||
|
||||
// Case 1: User-defined debugpy path (highest precedence)
|
||||
let user_path = PathBuf::from("/custom/path/to/debugpy");
|
||||
let user_args = adapter
|
||||
.generate_debugpy_arguments(&host, port, Some(&user_path), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Case 2: Venv-installed debugpy (uses -m debugpy.adapter)
|
||||
let venv_args = adapter
|
||||
.generate_debugpy_arguments(&host, port, None, true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(user_args[0].ends_with("src/debugpy/adapter"));
|
||||
assert_eq!(user_args[1], "--host=127.0.0.1");
|
||||
assert_eq!(user_args[2], "--port=5678");
|
||||
|
||||
assert_eq!(venv_args[0], "-m");
|
||||
assert_eq!(venv_args[1], "debugpy.adapter");
|
||||
assert_eq!(venv_args[2], "--host=127.0.0.1");
|
||||
assert_eq!(venv_args[3], "--port=5678");
|
||||
|
||||
// Note: Case 3 (GitHub-downloaded debugpy) is not tested since this requires mocking the Github API.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_adapter_path_constant() {
|
||||
assert_eq!(PythonDebugAdapter::ADAPTER_PATH, "src/debugpy/adapter");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue