debugger: Mark DebugAdapterBinary::program as optional (#32534)

This allows us to support debugging with a debug adapter not managed by
Zed. Note that this is not a user facing change, as DebugAdapterBinary
is used to determine how to spawn a debugger. Thus, this should not
break any configs or anything like that.

Closes #ISSUE

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2025-06-11 12:38:12 +02:00 committed by GitHub
parent a3cc063107
commit 6c4728f00f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 80 additions and 53 deletions

View file

@ -671,7 +671,7 @@ async fn test_remote_server_debugger(
}); });
session.update(cx_a, |session, _| { session.update(cx_a, |session, _| {
assert_eq!(session.binary().command, "ssh"); assert_eq!(session.binary().command.as_deref(), Some("ssh"));
}); });
let shutdown_session = workspace.update(cx_a, |workspace, cx| { let shutdown_session = workspace.update(cx_a, |workspace, cx| {

View file

@ -181,7 +181,7 @@ impl DebugTaskDefinition {
/// Created from a [DebugTaskDefinition], this struct describes how to spawn the debugger to create a previously-configured debug session. /// Created from a [DebugTaskDefinition], this struct describes how to spawn the debugger to create a previously-configured debug session.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DebugAdapterBinary { pub struct DebugAdapterBinary {
pub command: String, pub command: Option<String>,
pub arguments: Vec<String>, pub arguments: Vec<String>,
pub envs: HashMap<String, String>, pub envs: HashMap<String, String>,
pub cwd: Option<PathBuf>, pub cwd: Option<PathBuf>,
@ -437,7 +437,7 @@ impl DebugAdapter for FakeAdapter {
_: &mut AsyncApp, _: &mut AsyncApp,
) -> Result<DebugAdapterBinary> { ) -> Result<DebugAdapterBinary> {
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: "command".into(), command: Some("command".into()),
arguments: vec![], arguments: vec![],
connection: None, connection: None,
envs: HashMap::default(), envs: HashMap::default(),

View file

@ -297,7 +297,7 @@ mod tests {
let client = DebugAdapterClient::start( let client = DebugAdapterClient::start(
crate::client::SessionId(1), crate::client::SessionId(1),
DebugAdapterBinary { DebugAdapterBinary {
command: "command".into(), command: Some("command".into()),
arguments: Default::default(), arguments: Default::default(),
envs: Default::default(), envs: Default::default(),
connection: None, connection: None,
@ -367,7 +367,7 @@ mod tests {
let client = DebugAdapterClient::start( let client = DebugAdapterClient::start(
crate::client::SessionId(1), crate::client::SessionId(1),
DebugAdapterBinary { DebugAdapterBinary {
command: "command".into(), command: Some("command".into()),
arguments: Default::default(), arguments: Default::default(),
envs: Default::default(), envs: Default::default(),
connection: None, connection: None,
@ -420,7 +420,7 @@ mod tests {
let client = DebugAdapterClient::start( let client = DebugAdapterClient::start(
crate::client::SessionId(1), crate::client::SessionId(1),
DebugAdapterBinary { DebugAdapterBinary {
command: "command".into(), command: Some("command".into()),
arguments: Default::default(), arguments: Default::default(),
envs: Default::default(), envs: Default::default(),
connection: None, connection: None,

View file

@ -85,10 +85,12 @@ impl Transport {
TcpTransport::start(binary, cx) TcpTransport::start(binary, cx)
.await .await
.map(|(transports, tcp)| (transports, Self::Tcp(tcp))) .map(|(transports, tcp)| (transports, Self::Tcp(tcp)))
.context("Tried to connect to a debug adapter via TCP transport layer")
} else { } else {
StdioTransport::start(binary, cx) StdioTransport::start(binary, cx)
.await .await
.map(|(transports, stdio)| (transports, Self::Stdio(stdio))) .map(|(transports, stdio)| (transports, Self::Stdio(stdio)))
.context("Tried to connect to a debug adapter via stdin/stdout transport layer")
} }
} }
@ -567,7 +569,7 @@ pub struct TcpTransport {
pub port: u16, pub port: u16,
pub host: Ipv4Addr, pub host: Ipv4Addr,
pub timeout: u64, pub timeout: u64,
process: Mutex<Child>, process: Option<Mutex<Child>>,
} }
impl TcpTransport { impl TcpTransport {
@ -596,17 +598,23 @@ impl TcpTransport {
let host = connection_args.host; let host = connection_args.host;
let port = connection_args.port; let port = connection_args.port;
let mut command = util::command::new_std_command(&binary.command); let mut process = if let Some(command) = &binary.command {
let mut command = util::command::new_std_command(&command);
if let Some(cwd) = &binary.cwd { if let Some(cwd) = &binary.cwd {
command.current_dir(cwd); command.current_dir(cwd);
} }
command.args(&binary.arguments); command.args(&binary.arguments);
command.envs(&binary.envs); command.envs(&binary.envs);
let mut process = Child::spawn(command, Stdio::null()) Some(
.with_context(|| "failed to start debug adapter.")?; Child::spawn(command, Stdio::null())
.with_context(|| "failed to start debug adapter.")?,
)
} else {
None
};
let address = SocketAddrV4::new(host, port); let address = SocketAddrV4::new(host, port);
@ -624,15 +632,18 @@ impl TcpTransport {
match TcpStream::connect(address).await { match TcpStream::connect(address).await {
Ok(stream) => return Ok((process, stream.split())), Ok(stream) => return Ok((process, stream.split())),
Err(_) => { Err(_) => {
if let Ok(Some(_)) = process.try_status() { if let Some(p) = &mut process {
let output = process.into_inner().output().await?; if let Ok(Some(_)) = p.try_status() {
let output = if output.stderr.is_empty() { let output = process.take().unwrap().into_inner().output().await?;
String::from_utf8_lossy(&output.stdout).to_string() let output = if output.stderr.is_empty() {
} else { String::from_utf8_lossy(&output.stdout).to_string()
String::from_utf8_lossy(&output.stderr).to_string() } else {
}; String::from_utf8_lossy(&output.stderr).to_string()
anyhow::bail!("{output}\nerror: process exited before debugger attached."); };
anyhow::bail!("{output}\nerror: process exited before debugger attached.");
}
} }
cx.background_executor().timer(Duration::from_millis(100)).await; cx.background_executor().timer(Duration::from_millis(100)).await;
} }
} }
@ -645,13 +656,13 @@ impl TcpTransport {
host, host,
port port
); );
let stdout = process.stdout.take(); let stdout = process.as_mut().and_then(|p| p.stdout.take());
let stderr = process.stderr.take(); let stderr = process.as_mut().and_then(|p| p.stderr.take());
let this = Self { let this = Self {
port, port,
host, host,
process: Mutex::new(process), process: process.map(Mutex::new),
timeout, timeout,
}; };
@ -670,14 +681,18 @@ impl TcpTransport {
} }
async fn kill(&self) { async fn kill(&self) {
let mut process = self.process.lock().await; if let Some(process) = &self.process {
Child::kill(&mut process); let mut process = process.lock().await;
Child::kill(&mut process);
}
} }
} }
impl Drop for TcpTransport { impl Drop for TcpTransport {
fn drop(&mut self) { fn drop(&mut self) {
self.process.get_mut().kill(); if let Some(mut p) = self.process.take() {
p.get_mut().kill();
}
} }
} }
@ -688,7 +703,12 @@ pub struct StdioTransport {
impl StdioTransport { impl StdioTransport {
#[allow(dead_code, reason = "This is used in non test builds of Zed")] #[allow(dead_code, reason = "This is used in non test builds of Zed")]
async fn start(binary: &DebugAdapterBinary, _: AsyncApp) -> Result<(TransportPipe, Self)> { async fn start(binary: &DebugAdapterBinary, _: AsyncApp) -> Result<(TransportPipe, Self)> {
let mut command = util::command::new_std_command(&binary.command); let Some(binary_command) = &binary.command else {
bail!(
"When using the `stdio` transport, the path to a debug adapter binary must be set by Zed."
);
};
let mut command = util::command::new_std_command(&binary_command);
if let Some(cwd) = &binary.cwd { if let Some(cwd) = &binary.cwd {
command.current_dir(cwd); command.current_dir(cwd);
@ -700,7 +720,7 @@ impl StdioTransport {
let mut process = Child::spawn(command, Stdio::piped()).with_context(|| { let mut process = Child::spawn(command, Stdio::piped()).with_context(|| {
format!( format!(
"failed to spawn command `{} {}`.", "failed to spawn command `{} {}`.",
binary.command, binary_command,
binary.arguments.join(" ") binary.arguments.join(" ")
) )
})?; })?;
@ -715,7 +735,7 @@ impl StdioTransport {
if stderr.is_none() { if stderr.is_none() {
bail!( bail!(
"Failed to connect to stderr for debug adapter command {}", "Failed to connect to stderr for debug adapter command {}",
&binary.command &binary_command
); );
} }

View file

@ -359,7 +359,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
}; };
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: command.unwrap(), command: Some(command.unwrap()),
cwd: Some(delegate.worktree_root_path().to_path_buf()), cwd: Some(delegate.worktree_root_path().to_path_buf()),
arguments: vec![ arguments: vec![
"--settings".into(), "--settings".into(),

View file

@ -183,7 +183,7 @@ impl DebugAdapter for GdbDebugAdapter {
}; };
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: gdb_path, command: Some(gdb_path),
arguments: vec!["-i=dap".into()], arguments: vec!["-i=dap".into()],
envs: HashMap::default(), envs: HashMap::default(),
cwd: Some(delegate.worktree_root_path().to_path_buf()), cwd: Some(delegate.worktree_root_path().to_path_buf()),

View file

@ -463,7 +463,7 @@ impl DebugAdapter for GoDebugAdapter {
}; };
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: minidelve_path.to_string_lossy().into_owned(), command: Some(minidelve_path.to_string_lossy().into_owned()),
arguments, arguments,
cwd: Some(cwd), cwd: Some(cwd),
envs: HashMap::default(), envs: HashMap::default(),

View file

@ -69,12 +69,14 @@ impl JsDebugAdapter {
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: delegate command: Some(
.node_runtime() delegate
.binary_path() .node_runtime()
.await? .binary_path()
.to_string_lossy() .await?
.into_owned(), .to_string_lossy()
.into_owned(),
),
arguments: vec![ arguments: vec![
adapter_path adapter_path
.join(Self::ADAPTER_PATH) .join(Self::ADAPTER_PATH)

View file

@ -72,12 +72,14 @@ impl PhpDebugAdapter {
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: delegate command: Some(
.node_runtime() delegate
.binary_path() .node_runtime()
.await? .binary_path()
.to_string_lossy() .await?
.into_owned(), .to_string_lossy()
.into_owned(),
),
arguments: vec![ arguments: vec![
adapter_path adapter_path
.join(Self::ADAPTER_PATH) .join(Self::ADAPTER_PATH)

View file

@ -187,7 +187,7 @@ impl PythonDebugAdapter {
); );
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: python_command, command: Some(python_command),
arguments, arguments,
connection: Some(adapters::TcpArguments { connection: Some(adapters::TcpArguments {
host, host,

View file

@ -175,7 +175,7 @@ impl DebugAdapter for RubyDebugAdapter {
arguments.extend(ruby_config.args); arguments.extend(ruby_config.args);
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: rdbg_path.to_string_lossy().to_string(), command: Some(rdbg_path.to_string_lossy().to_string()),
arguments, arguments,
connection: Some(dap::adapters::TcpArguments { connection: Some(dap::adapters::TcpArguments {
host, host,

View file

@ -50,7 +50,7 @@ interface dap {
} }
record debug-adapter-binary { record debug-adapter-binary {
command: string, command: option<string>,
arguments: list<string>, arguments: list<string>,
envs: env-vars, envs: env-vars,
cwd: option<string>, cwd: option<string>,

View file

@ -258,14 +258,17 @@ impl DapStore {
let (program, args) = wrap_for_ssh( let (program, args) = wrap_for_ssh(
&ssh_command, &ssh_command,
Some((&binary.command, &binary.arguments)), binary
.command
.as_ref()
.map(|command| (command, &binary.arguments)),
binary.cwd.as_deref(), binary.cwd.as_deref(),
binary.envs, binary.envs,
None, None,
); );
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {
command: program, command: Some(program),
arguments: args, arguments: args,
envs: HashMap::default(), envs: HashMap::default(),
cwd: None, cwd: None,

View file

@ -496,7 +496,7 @@ message GetDebugAdapterBinary {
} }
message DebugAdapterBinary { message DebugAdapterBinary {
string command = 1; optional string command = 1;
repeated string arguments = 2; repeated string arguments = 2;
map<string, string> envs = 3; map<string, string> envs = 3;
optional string cwd = 4; optional string cwd = 4;