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",
|
"gpui",
|
||||||
"json_dotpath",
|
"json_dotpath",
|
||||||
"language",
|
"language",
|
||||||
|
"log",
|
||||||
"paths",
|
"paths",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smol",
|
|
||||||
"task",
|
"task",
|
||||||
"util",
|
"util",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
|
|
|
@ -28,10 +28,10 @@ futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
json_dotpath.workspace = true
|
json_dotpath.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
|
log.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
smol.workspace = true
|
|
||||||
task.workspace = true
|
task.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
|
@ -8,6 +8,7 @@ use gpui::{AsyncApp, SharedString};
|
||||||
use json_dotpath::DotPaths;
|
use json_dotpath::DotPaths;
|
||||||
use language::{LanguageName, Toolchain};
|
use language::{LanguageName, Toolchain};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
|
@ -27,6 +28,60 @@ impl PythonDebugAdapter {
|
||||||
const ADAPTER_PATH: &'static str = "src/debugpy/adapter";
|
const ADAPTER_PATH: &'static str = "src/debugpy/adapter";
|
||||||
const LANGUAGE_NAME: &'static str = "Python";
|
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(
|
fn request_args(
|
||||||
&self,
|
&self,
|
||||||
task_definition: &DebugTaskDefinition,
|
task_definition: &DebugTaskDefinition,
|
||||||
|
@ -93,24 +148,12 @@ impl PythonDebugAdapter {
|
||||||
config: &DebugTaskDefinition,
|
config: &DebugTaskDefinition,
|
||||||
user_installed_path: Option<PathBuf>,
|
user_installed_path: Option<PathBuf>,
|
||||||
toolchain: Option<Toolchain>,
|
toolchain: Option<Toolchain>,
|
||||||
|
installed_in_venv: bool,
|
||||||
) -> Result<DebugAdapterBinary> {
|
) -> Result<DebugAdapterBinary> {
|
||||||
const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
|
const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
|
||||||
let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
|
let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
|
||||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
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 {
|
let python_path = if let Some(toolchain) = toolchain {
|
||||||
Some(toolchain.path.to_string())
|
Some(toolchain.path.to_string())
|
||||||
} else {
|
} else {
|
||||||
|
@ -128,16 +171,27 @@ impl PythonDebugAdapter {
|
||||||
name
|
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 {
|
Ok(DebugAdapterBinary {
|
||||||
command: python_path.context("failed to find binary path for Python")?,
|
command: python_command,
|
||||||
arguments: vec![
|
arguments,
|
||||||
debugpy_dir
|
|
||||||
.join(Self::ADAPTER_PATH)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
format!("--port={}", port),
|
|
||||||
format!("--host={}", host),
|
|
||||||
],
|
|
||||||
connection: Some(adapters::TcpArguments {
|
connection: Some(adapters::TcpArguments {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
@ -558,6 +612,16 @@ impl DebugAdapter for PythonDebugAdapter {
|
||||||
user_installed_path: Option<PathBuf>,
|
user_installed_path: Option<PathBuf>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Result<DebugAdapterBinary> {
|
) -> 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
|
let toolchain = delegate
|
||||||
.toolchain_store()
|
.toolchain_store()
|
||||||
.active_toolchain(
|
.active_toolchain(
|
||||||
|
@ -571,13 +635,18 @@ impl DebugAdapter for PythonDebugAdapter {
|
||||||
if let Some(toolchain) = &toolchain {
|
if let Some(toolchain) = &toolchain {
|
||||||
if let Some(path) = Path::new(&toolchain.path.to_string()).parent() {
|
if let Some(path) = Path::new(&toolchain.path.to_string()).parent() {
|
||||||
let debugpy_path = path.join("debugpy");
|
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
|
return self
|
||||||
.get_installed_binary(
|
.get_installed_binary(
|
||||||
delegate,
|
delegate,
|
||||||
&config,
|
&config,
|
||||||
Some(debugpy_path.to_path_buf()),
|
None,
|
||||||
Some(toolchain.clone()),
|
Some(toolchain.clone()),
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
.await;
|
.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
|
.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