debugger: Fix issues with running Zed-installed debugpy + hangs when downloading (#32034)

Closes #32018

Release Notes:

- Fixed issues with launching Python debug adapter downloaded by Zed.
You might need to clear the old install of Debugpy from
`$HOME/.local/share/zed/debug_adapters/Debugpy` (Linux) or
`$HOME/Library/Application Support/Zed/debug_adapters/Debugpy` (Mac).
This commit is contained in:
Piotr Osiewicz 2025-06-04 01:37:25 +02:00 committed by GitHub
parent 936ad0bf10
commit d23359e19a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 49 deletions

View file

@ -333,24 +333,6 @@ pub async fn download_adapter_from_github(
Ok(version_path)
}
pub async fn fetch_latest_adapter_version_from_github(
github_repo: GithubRepo,
delegate: &dyn DapDelegate,
) -> Result<AdapterVersion> {
let release = latest_github_release(
&format!("{}/{}", github_repo.repo_owner, github_repo.repo_name),
false,
false,
delegate.http_client(),
)
.await?;
Ok(AdapterVersion {
tag_name: release.tag_name,
url: release.zipball_url,
})
}
#[async_trait(?Send)]
pub trait DebugAdapter: 'static + Send + Sync {
fn name(&self) -> DebugAdapterName;

View file

@ -1,7 +1,8 @@
use crate::*;
use anyhow::Context as _;
use dap::adapters::latest_github_release;
use dap::{DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::{AsyncApp, SharedString};
use gpui::{AppContext, AsyncApp, SharedString};
use json_dotpath::DotPaths;
use language::{LanguageName, Toolchain};
use serde_json::Value;
@ -21,12 +22,13 @@ pub(crate) struct PythonDebugAdapter {
impl PythonDebugAdapter {
const ADAPTER_NAME: &'static str = "Debugpy";
const DEBUG_ADAPTER_NAME: DebugAdapterName =
DebugAdapterName(SharedString::new_static(Self::ADAPTER_NAME));
const ADAPTER_PACKAGE_NAME: &'static str = "debugpy";
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>,
@ -54,7 +56,7 @@ impl PythonDebugAdapter {
format!("--port={}", port),
])
} else {
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
let adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref());
let file_name_prefix = format!("{}_", Self::ADAPTER_NAME);
let debugpy_dir =
@ -107,22 +109,21 @@ impl PythonDebugAdapter {
repo_owner: "microsoft".into(),
};
adapters::fetch_latest_adapter_version_from_github(github_repo, delegate.as_ref()).await
fetch_latest_adapter_version_from_github(github_repo, delegate.as_ref()).await
}
async fn install_binary(
&self,
adapter_name: DebugAdapterName,
version: AdapterVersion,
delegate: &Arc<dyn DapDelegate>,
delegate: Arc<dyn DapDelegate>,
) -> Result<()> {
let version_path = adapters::download_adapter_from_github(
self.name(),
adapter_name,
version,
adapters::DownloadedFileType::Zip,
adapters::DownloadedFileType::GzipTar,
delegate.as_ref(),
)
.await?;
// only needed when you install the latest version for the first time
if let Some(debugpy_dir) =
util::fs::find_file_name_in_dir(version_path.as_path(), |file_name| {
@ -171,14 +172,13 @@ impl PythonDebugAdapter {
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?;
let arguments = Self::generate_debugpy_arguments(
&host,
port,
user_installed_path.as_deref(),
installed_in_venv,
)
.await?;
log::debug!(
"Starting debugpy adapter with command: {} {}",
@ -204,7 +204,7 @@ impl PythonDebugAdapter {
#[async_trait(?Send)]
impl DebugAdapter for PythonDebugAdapter {
fn name(&self) -> DebugAdapterName {
DebugAdapterName(Self::ADAPTER_NAME.into())
Self::DEBUG_ADAPTER_NAME
}
fn adapter_language_name(&self) -> Option<LanguageName> {
@ -635,7 +635,9 @@ impl DebugAdapter for PythonDebugAdapter {
if self.checked.set(()).is_ok() {
delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() {
self.install_binary(version, delegate).await?;
cx.background_spawn(Self::install_binary(self.name(), version, delegate.clone()))
.await
.context("Failed to install debugpy")?;
}
}
@ -644,6 +646,24 @@ impl DebugAdapter for PythonDebugAdapter {
}
}
async fn fetch_latest_adapter_version_from_github(
github_repo: GithubRepo,
delegate: &dyn DapDelegate,
) -> Result<AdapterVersion> {
let release = latest_github_release(
&format!("{}/{}", github_repo.repo_owner, github_repo.repo_name),
false,
false,
delegate.http_client(),
)
.await?;
Ok(AdapterVersion {
tag_name: release.tag_name,
url: release.tarball_url,
})
}
#[cfg(test)]
mod tests {
use super::*;
@ -651,20 +671,18 @@ mod tests {
#[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();
let user_args =
PythonDebugAdapter::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)
let venv_args = PythonDebugAdapter::generate_debugpy_arguments(&host, port, None, true)
.await
.unwrap();
@ -679,9 +697,4 @@ mod tests {
// 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");
}
}