debugger: Install debugpy into user's venv if there's one selected (#35617)

Closes #35388


Release Notes:

- debugger: Fixed Python debug sessions failing to launch due to a
missing debugpy installation. Debugpy is now installed into user's venv
if there's one available.
This commit is contained in:
Piotr Osiewicz 2025-08-05 00:37:06 +02:00 committed by GitHub
parent 182edbf526
commit 91bbdb7002
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -126,38 +126,42 @@ impl PythonDebugAdapter {
} }
None None
} }
const BINARY_DIR: &str = if cfg!(target_os = "windows") {
"Scripts"
} else {
"bin"
};
async fn base_venv(&self, delegate: &dyn DapDelegate) -> Result<Arc<Path>, String> { async fn base_venv(&self, delegate: &dyn DapDelegate) -> Result<Arc<Path>, String> {
const BINARY_DIR: &str = if cfg!(target_os = "windows") {
"Scripts"
} else {
"bin"
};
self.python_venv_base self.python_venv_base
.get_or_init(move || async move { .get_or_init(move || async move {
let venv_base = Self::ensure_venv(delegate) let venv_base = Self::ensure_venv(delegate)
.await .await
.map_err(|e| format!("{e}"))?; .map_err(|e| format!("{e}"))?;
let pip_path = venv_base.join(BINARY_DIR).join("pip3"); Self::install_debugpy_into_venv(&venv_base).await?;
let installation_succeeded = util::command::new_smol_command(pip_path.as_path())
.arg("install")
.arg("debugpy")
.arg("-U")
.output()
.await
.map_err(|e| format!("{e}"))?
.status
.success();
if !installation_succeeded {
return Err("debugpy installation failed".into());
}
Ok(venv_base) Ok(venv_base)
}) })
.await .await
.clone() .clone()
} }
async fn install_debugpy_into_venv(venv_path: &Path) -> Result<(), String> {
let pip_path = venv_path.join(Self::BINARY_DIR).join("pip3");
let installation_succeeded = util::command::new_smol_command(pip_path.as_path())
.arg("install")
.arg("debugpy")
.arg("-U")
.output()
.await
.map_err(|e| format!("{e}"))?
.status
.success();
if !installation_succeeded {
return Err("debugpy installation failed".into());
}
Ok(())
}
async fn get_installed_binary( async fn get_installed_binary(
&self, &self,
delegate: &Arc<dyn DapDelegate>, delegate: &Arc<dyn DapDelegate>,
@ -629,11 +633,22 @@ impl DebugAdapter for PythonDebugAdapter {
.await; .await;
} }
let base_path = config
.config
.get("cwd")
.and_then(|cwd| {
cwd.as_str()
.map(Path::new)?
.strip_prefix(delegate.worktree_root_path())
.ok()
})
.unwrap_or_else(|| "".as_ref())
.into();
let toolchain = delegate let toolchain = delegate
.toolchain_store() .toolchain_store()
.active_toolchain( .active_toolchain(
delegate.worktree_id(), delegate.worktree_id(),
Arc::from("".as_ref()), base_path,
language::LanguageName::new(Self::LANGUAGE_NAME), language::LanguageName::new(Self::LANGUAGE_NAME),
cx, cx,
) )
@ -641,6 +656,10 @@ 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() {
if let Some(parent) = path.parent() {
Self::install_debugpy_into_venv(parent).await.ok();
}
let debugpy_path = path.join("debugpy"); let debugpy_path = path.join("debugpy");
if delegate.fs().is_file(&debugpy_path).await { if delegate.fs().is_file(&debugpy_path).await {
log::debug!( log::debug!(