debugger: Allow locators to generate full debug scenarios (#30014)

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Piotr Osiewicz 2025-05-06 18:39:49 +02:00 committed by GitHub
parent a378b3f300
commit bbfcd885ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 197 additions and 146 deletions

View file

@ -46,7 +46,7 @@ use std::{
path::{Path, PathBuf},
sync::{Arc, Once},
};
use task::{DebugScenario, SpawnInTerminal};
use task::{DebugScenario, SpawnInTerminal, TaskTemplate};
use util::{ResultExt as _, merge_json_value_into};
use worktree::Worktree;
@ -99,8 +99,7 @@ impl DapStore {
pub fn init(client: &AnyProtoClient, cx: &mut App) {
static ADD_LOCATORS: Once = Once::new();
ADD_LOCATORS.call_once(|| {
DapRegistry::global(cx)
.add_locator("cargo".into(), Arc::new(locators::cargo::CargoLocator {}))
DapRegistry::global(cx).add_locator(Arc::new(locators::cargo::CargoLocator {}))
});
client.add_entity_request_handler(Self::handle_run_debug_locator);
client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
@ -282,78 +281,38 @@ impl DapStore {
pub fn debug_scenario_for_build_task(
&self,
mut build: SpawnInTerminal,
unresoved_label: SharedString,
build: TaskTemplate,
adapter: SharedString,
cx: &mut App,
) -> Option<DebugScenario> {
build.args = build
.args
.into_iter()
.map(|arg| {
if arg.starts_with("$") {
arg.strip_prefix("$")
.and_then(|arg| build.env.get(arg).map(ToOwned::to_owned))
.unwrap_or_else(|| arg)
} else {
arg
}
})
.collect();
DapRegistry::global(cx)
.locators()
.values()
.find(|locator| locator.accepts(&build))
.map(|_| DebugScenario {
adapter,
label: format!("Debug `{}`", build.label).into(),
build: Some(unresoved_label),
request: None,
initialize_args: None,
tcp_connection: None,
stop_on_entry: None,
})
.find_map(|locator| locator.create_scenario(&build, &adapter))
}
pub fn run_debug_locator(
&mut self,
mut build_command: SpawnInTerminal,
locator_name: &str,
build_command: SpawnInTerminal,
cx: &mut Context<Self>,
) -> Task<Result<DebugRequest>> {
match &self.mode {
DapStoreMode::Local(_) => {
// Pre-resolve args with existing environment.
build_command.args = build_command
.args
.into_iter()
.map(|arg| {
if arg.starts_with("$") {
arg.strip_prefix("$")
.and_then(|arg| build_command.env.get(arg).map(ToOwned::to_owned))
.unwrap_or_else(|| arg)
} else {
arg
}
})
.collect();
let locators = DapRegistry::global(cx)
.locators()
.values()
.filter(|locator| locator.accepts(&build_command))
.cloned()
.collect::<Vec<_>>();
if !locators.is_empty() {
let locators = DapRegistry::global(cx).locators();
let locator = locators.get(locator_name);
if let Some(locator) = locator.cloned() {
cx.background_spawn(async move {
for locator in locators {
let result = locator
.run(build_command.clone())
.await
.log_with_level(log::Level::Error);
if let Some(result) = result {
return Ok(result);
}
let result = locator
.run(build_command.clone())
.await
.log_with_level(log::Level::Error);
if let Some(result) = result {
return Ok(result);
}
Err(anyhow!(
"None of the locators for task `{}` completed successfully",
build_command.label
@ -370,6 +329,7 @@ impl DapStore {
let request = ssh.upstream_client.request(proto::RunDebugLocators {
project_id: ssh.upstream_project_id,
build_command: Some(build_command.to_proto()),
locator: locator_name.to_owned(),
});
cx.background_spawn(async move {
let response = request.await?;
@ -789,8 +749,11 @@ impl DapStore {
.build_command
.ok_or_else(|| anyhow!("missing definition"))?;
let build_task = SpawnInTerminal::from_proto(task);
let locator = envelope.payload.locator;
let request = this
.update(&mut cx, |this, cx| this.run_debug_locator(build_task, cx))?
.update(&mut cx, |this, cx| {
this.run_debug_locator(&locator, build_task, cx)
})?
.await?;
Ok(request.to_proto())

View file

@ -1,12 +1,13 @@
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use dap::{DapLocator, DebugRequest};
use gpui::SharedString;
use serde_json::Value;
use smol::{
io::AsyncReadExt,
process::{Command, Stdio},
};
use task::SpawnInTerminal;
use task::{BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskTemplate};
pub(crate) struct CargoLocator;
@ -37,18 +38,51 @@ async fn find_best_executable(executables: &[String], test_name: &str) -> Option
}
#[async_trait]
impl DapLocator for CargoLocator {
fn accepts(&self, build_config: &SpawnInTerminal) -> bool {
fn name(&self) -> SharedString {
SharedString::new_static("rust-cargo-locator")
}
fn create_scenario(&self, build_config: &TaskTemplate, adapter: &str) -> Option<DebugScenario> {
if build_config.command != "cargo" {
return false;
return None;
}
let Some(command) = build_config.args.first().map(|s| s.as_str()) else {
return false;
};
if matches!(command, "check" | "run") {
return false;
let mut task_template = build_config.clone();
let cargo_action = task_template.args.first_mut()?;
if cargo_action == "check" {
return None;
}
!matches!(command, "test" | "bench")
|| build_config.args.iter().any(|arg| arg == "--no-run")
match cargo_action.as_ref() {
"run" => {
*cargo_action = "build".to_owned();
}
"test" | "bench" => {
let delimiter = task_template
.args
.iter()
.position(|arg| arg == "--")
.unwrap_or(task_template.args.len());
if !task_template.args[..delimiter]
.iter()
.any(|arg| arg == "--no-run")
{
task_template.args.insert(delimiter, "--no-run".to_owned());
}
}
_ => {}
}
let label = format!("Debug `{}`", build_config.label);
Some(DebugScenario {
adapter: adapter.to_owned().into(),
label: SharedString::from(label),
build: Some(BuildTaskDefinition::Template {
task_template,
locator_name: Some(self.name()),
}),
request: None,
initialize_args: None,
tcp_connection: None,
stop_on_entry: None,
})
}
async fn run(&self, build_config: SpawnInTerminal) -> Result<DebugRequest> {
@ -57,10 +91,19 @@ impl DapLocator for CargoLocator {
"Couldn't get cwd from debug config which is needed for locators"
));
};
let mut child = Command::new("cargo")
.args(&build_config.args)
.arg("--message-format=json")
let builder = ShellBuilder::new(true, &build_config.shell).non_interactive();
let (program, args) = builder.build(
"cargo".into(),
&build_config
.args
.iter()
.cloned()
.take_while(|arg| arg != "--")
.chain(Some("--message-format=json".to_owned()))
.collect(),
);
let mut child = Command::new(program)
.args(args)
.envs(build_config.env.iter().map(|(k, v)| (k.clone(), v.clone())))
.current_dir(cwd)
.stdout(Stdio::piped())
@ -89,7 +132,6 @@ impl DapLocator for CargoLocator {
if executables.is_empty() {
return Err(anyhow!("Couldn't get executable in cargo locator"));
};
let is_test = build_config.args.first().map_or(false, |arg| arg == "test");
let mut test_name = None;