extension_api: Add IPv6 support in DAP extensions API (#54984)

Fix forward for #52244

Release Notes:

- N/A
This commit is contained in:
Tom Houlé 2026-04-29 09:27:05 +02:00 committed by GitHub
parent ab69fc6d1e
commit c6c63e8a38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 401 additions and 54 deletions

View file

@ -20,15 +20,23 @@ interface dap {
attach(attach-request)
}
type ipv4-address = tuple<u8, u8, u8, u8>;
type ipv6-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>;
variant ip-address {
ipv4(ipv4-address),
ipv6(ipv6-address),
}
record tcp-arguments {
port: u16,
host: u32,
host: ip-address,
timeout: option<u64>,
}
record tcp-arguments-template {
port: option<u16>,
host: option<u32>,
host: option<ip-address>,
timeout: option<u64>,
}

View file

@ -16,7 +16,7 @@ use lsp::LanguageServerName;
use release_channel::ReleaseChannel;
use task::{DebugScenario, SpawnInTerminal, TaskTemplate, ZedDebugConfig};
use crate::wasm_host::wit::since_v0_6_0::dap::StartDebuggingRequestArgumentsRequest;
use latest::dap::StartDebuggingRequestArgumentsRequest;
use super::{WasmState, wasm_engine};
use anyhow::{Context as _, Result, anyhow};
@ -1072,18 +1072,19 @@ impl Extension {
Ok(Ok(dap_binary))
}
Extension::V0_6_0(ext) => {
let task: latest::DebugTaskDefinition = task.try_into()?;
let dap_binary = ext
.call_get_dap_binary(
store,
&adapter_name,
&task.try_into()?,
&task.into(),
user_installed_path.as_ref().and_then(|p| p.to_str()),
resource,
)
.await?
.map_err(|e| anyhow!("{e:?}"))?;
Ok(Ok(dap_binary))
Ok(Ok(dap_binary.into()))
}
Extension::V0_5_0(_)
| Extension::V0_4_0(_)
@ -1123,7 +1124,7 @@ impl Extension {
.await?
.map_err(|e| anyhow!("{e:?}"))?;
Ok(Ok(dap_binary))
Ok(Ok(dap_binary.into()))
}
Extension::V0_5_0(_)
| Extension::V0_4_0(_)
@ -1154,12 +1155,13 @@ impl Extension {
Ok(Ok(dap_binary.try_into()?))
}
Extension::V0_6_0(ext) => {
let config = config.into();
let config: latest::DebugConfig = config.into();
let dap_binary = ext
.call_dap_config_to_scenario(store, &config)
.call_dap_config_to_scenario(store, &config.into())
.await?
.map_err(|e| anyhow!("{e:?}"))?;
let dap_binary: latest::DebugScenario = dap_binary.into();
Ok(Ok(dap_binary.try_into()?))
}
Extension::V0_5_0(_)
@ -1199,18 +1201,20 @@ impl Extension {
Ok(dap_binary.map(TryInto::try_into).transpose()?)
}
Extension::V0_6_0(ext) => {
let build_config_template = build_config_template.into();
let build_config_template: latest::dap::TaskTemplate = build_config_template.into();
let dap_binary = ext
.call_dap_locator_create_scenario(
store,
&locator_name,
&build_config_template,
&build_config_template.into(),
&resolved_label,
&debug_adapter_name,
)
.await?;
Ok(dap_binary.map(TryInto::try_into).transpose()?)
Ok(dap_binary
.map(|s| latest::DebugScenario::from(s).try_into())
.transpose()?)
}
Extension::V0_5_0(_)
| Extension::V0_4_0(_)
@ -1242,12 +1246,14 @@ impl Extension {
Ok(Ok(dap_request.into()))
}
Extension::V0_6_0(ext) => {
let build_config_template = resolved_build_task.try_into()?;
let build_config_template: latest::dap::TaskTemplate =
resolved_build_task.try_into()?;
let dap_request = ext
.call_run_dap_locator(store, &locator_name, &build_config_template)
.call_run_dap_locator(store, &locator_name, &build_config_template.into())
.await?
.map_err(|e| anyhow!("{e:?}"))?;
let dap_request: latest::DebugRequest = dap_request.into();
Ok(Ok(dap_request.into()))
}
Extension::V0_5_0(_)

View file

@ -30,7 +30,6 @@ wasmtime::component::bindgen!({
"zed:extension/process": latest::zed::extension::process,
"zed:extension/slash-command": latest::zed::extension::slash_command,
"zed:extension/context-server": latest::zed::extension::context_server,
"zed:extension/dap": latest::zed::extension::dap,
},
});
@ -384,3 +383,328 @@ impl ExtensionImports for WasmState {
latest::ExtensionImports::make_file_executable(self, path).await
}
}
impl From<dap::TcpArguments> for latest::dap::TcpArguments {
fn from(value: dap::TcpArguments) -> Self {
let [a, b, c, d] = std::net::Ipv4Addr::from_bits(value.host).octets();
Self {
host: latest::dap::IpAddress::Ipv4((a, b, c, d)),
port: value.port,
timeout: value.timeout,
}
}
}
impl TryFrom<latest::dap::TcpArguments> for dap::TcpArguments {
type Error = anyhow::Error;
fn try_from(value: latest::dap::TcpArguments) -> Result<Self> {
let host = match value.host {
latest::dap::IpAddress::Ipv4((a, b, c, d)) => {
std::net::Ipv4Addr::new(a, b, c, d).to_bits()
}
latest::dap::IpAddress::Ipv6((a, b, c, d, e, f, g, h)) => {
let addr = std::net::Ipv6Addr::new(a, b, c, d, e, f, g, h);
anyhow::bail!(
"DAP returned IPv6 host {addr}, which the v0.6.0 extension API cannot represent; the extension must be updated to v0.8.0 or later"
);
}
};
Ok(Self {
host,
port: value.port,
timeout: value.timeout,
})
}
}
impl From<dap::TcpArgumentsTemplate> for latest::dap::TcpArgumentsTemplate {
fn from(value: dap::TcpArgumentsTemplate) -> Self {
Self {
host: value.host.map(|host| {
let [a, b, c, d] = std::net::Ipv4Addr::from_bits(host).octets();
latest::dap::IpAddress::Ipv4((a, b, c, d))
}),
port: value.port,
timeout: value.timeout,
}
}
}
impl From<latest::dap::TcpArgumentsTemplate> for dap::TcpArgumentsTemplate {
fn from(value: latest::dap::TcpArgumentsTemplate) -> Self {
Self {
host: value.host.and_then(|host| match host {
latest::dap::IpAddress::Ipv4((a, b, c, d)) => {
Some(std::net::Ipv4Addr::new(a, b, c, d).to_bits())
}
latest::dap::IpAddress::Ipv6((a, b, c, d, e, f, g, h)) => {
let addr = std::net::Ipv6Addr::new(a, b, c, d, e, f, g, h);
log::warn!(
"Dropping IPv6 host {addr} when handing TCP arguments back to a v0.6.0 extension; update the extension to v0.8.0 or later for IPv6 support"
);
None
}
}),
port: value.port,
timeout: value.timeout,
}
}
}
impl From<dap::LaunchRequest> for latest::dap::LaunchRequest {
fn from(value: dap::LaunchRequest) -> Self {
Self {
program: value.program,
cwd: value.cwd,
args: value.args,
envs: value.envs,
}
}
}
impl From<latest::dap::LaunchRequest> for dap::LaunchRequest {
fn from(value: latest::dap::LaunchRequest) -> Self {
Self {
program: value.program,
cwd: value.cwd,
args: value.args,
envs: value.envs,
}
}
}
impl From<dap::AttachRequest> for latest::dap::AttachRequest {
fn from(value: dap::AttachRequest) -> Self {
Self {
process_id: value.process_id,
}
}
}
impl From<latest::dap::AttachRequest> for dap::AttachRequest {
fn from(value: latest::dap::AttachRequest) -> Self {
Self {
process_id: value.process_id,
}
}
}
impl From<DebugRequest> for latest::DebugRequest {
fn from(value: DebugRequest) -> Self {
match value {
DebugRequest::Launch(req) => Self::Launch(req.into()),
DebugRequest::Attach(req) => Self::Attach(req.into()),
}
}
}
impl From<latest::DebugRequest> for DebugRequest {
fn from(value: latest::DebugRequest) -> Self {
match value {
latest::DebugRequest::Launch(req) => Self::Launch(req.into()),
latest::DebugRequest::Attach(req) => Self::Attach(req.into()),
}
}
}
impl From<DebugConfig> for latest::DebugConfig {
fn from(value: DebugConfig) -> Self {
Self {
label: value.label,
adapter: value.adapter,
request: value.request.into(),
stop_on_entry: value.stop_on_entry,
}
}
}
impl From<latest::DebugConfig> for DebugConfig {
fn from(value: latest::DebugConfig) -> Self {
Self {
label: value.label,
adapter: value.adapter,
request: value.request.into(),
stop_on_entry: value.stop_on_entry,
}
}
}
impl From<dap::TaskTemplate> for latest::dap::TaskTemplate {
fn from(value: dap::TaskTemplate) -> Self {
Self {
label: value.label,
command: value.command,
args: value.args,
env: value.env,
cwd: value.cwd,
}
}
}
impl From<latest::dap::TaskTemplate> for dap::TaskTemplate {
fn from(value: latest::dap::TaskTemplate) -> Self {
Self {
label: value.label,
command: value.command,
args: value.args,
env: value.env,
cwd: value.cwd,
}
}
}
impl From<dap::BuildTaskDefinition> for latest::dap::BuildTaskDefinition {
fn from(value: dap::BuildTaskDefinition) -> Self {
match value {
dap::BuildTaskDefinition::ByName(name) => Self::ByName(name),
dap::BuildTaskDefinition::Template(payload) => {
Self::Template(latest::dap::BuildTaskDefinitionTemplatePayload {
locator_name: payload.locator_name,
template: payload.template.into(),
})
}
}
}
}
impl From<latest::dap::BuildTaskDefinition> for dap::BuildTaskDefinition {
fn from(value: latest::dap::BuildTaskDefinition) -> Self {
match value {
latest::dap::BuildTaskDefinition::ByName(name) => Self::ByName(name),
latest::dap::BuildTaskDefinition::Template(payload) => {
Self::Template(dap::BuildTaskDefinitionTemplatePayload {
locator_name: payload.locator_name,
template: payload.template.into(),
})
}
}
}
}
impl From<DebugScenario> for latest::DebugScenario {
fn from(value: DebugScenario) -> Self {
Self {
label: value.label,
adapter: value.adapter,
build: value.build.map(Into::into),
config: value.config,
tcp_connection: value.tcp_connection.map(Into::into),
}
}
}
impl From<latest::DebugScenario> for DebugScenario {
fn from(value: latest::DebugScenario) -> Self {
Self {
label: value.label,
adapter: value.adapter,
build: value.build.map(Into::into),
config: value.config,
tcp_connection: value.tcp_connection.map(Into::into),
}
}
}
impl From<DebugTaskDefinition> for latest::DebugTaskDefinition {
fn from(value: DebugTaskDefinition) -> Self {
Self {
label: value.label,
adapter: value.adapter,
config: value.config,
tcp_connection: value.tcp_connection.map(Into::into),
}
}
}
impl From<latest::DebugTaskDefinition> for DebugTaskDefinition {
fn from(value: latest::DebugTaskDefinition) -> Self {
Self {
label: value.label,
adapter: value.adapter,
config: value.config,
tcp_connection: value.tcp_connection.map(Into::into),
}
}
}
impl From<dap::StartDebuggingRequestArgumentsRequest>
for latest::dap::StartDebuggingRequestArgumentsRequest
{
fn from(value: dap::StartDebuggingRequestArgumentsRequest) -> Self {
match value {
dap::StartDebuggingRequestArgumentsRequest::Launch => Self::Launch,
dap::StartDebuggingRequestArgumentsRequest::Attach => Self::Attach,
}
}
}
impl From<latest::dap::StartDebuggingRequestArgumentsRequest>
for dap::StartDebuggingRequestArgumentsRequest
{
fn from(value: latest::dap::StartDebuggingRequestArgumentsRequest) -> Self {
match value {
latest::dap::StartDebuggingRequestArgumentsRequest::Launch => Self::Launch,
latest::dap::StartDebuggingRequestArgumentsRequest::Attach => Self::Attach,
}
}
}
impl From<dap::StartDebuggingRequestArguments> for latest::dap::StartDebuggingRequestArguments {
fn from(value: dap::StartDebuggingRequestArguments) -> Self {
Self {
configuration: value.configuration,
request: value.request.into(),
}
}
}
impl From<latest::dap::StartDebuggingRequestArguments> for dap::StartDebuggingRequestArguments {
fn from(value: latest::dap::StartDebuggingRequestArguments) -> Self {
Self {
configuration: value.configuration,
request: value.request.into(),
}
}
}
impl From<DebugAdapterBinary> for latest::DebugAdapterBinary {
fn from(value: DebugAdapterBinary) -> Self {
Self {
command: value.command,
arguments: value.arguments,
envs: value.envs,
cwd: value.cwd,
connection: value.connection.map(Into::into),
request_args: value.request_args.into(),
}
}
}
impl TryFrom<latest::DebugAdapterBinary> for DebugAdapterBinary {
type Error = anyhow::Error;
fn try_from(value: latest::DebugAdapterBinary) -> Result<Self> {
Ok(Self {
command: value.command,
arguments: value.arguments,
envs: value.envs,
cwd: value.cwd,
connection: value.connection.map(TryInto::try_into).transpose()?,
request_args: value.request_args.into(),
})
}
}
impl zed::extension::dap::Host for WasmState {
async fn resolve_tcp_template(
&mut self,
template: dap::TcpArgumentsTemplate,
) -> wasmtime::Result<Result<dap::TcpArguments, String>> {
let result = latest::dap::Host::resolve_tcp_template(self, template.into()).await?;
Ok(
result
.and_then(|args| dap::TcpArguments::try_from(args).map_err(|err| err.to_string())),
)
}
}

View file

@ -1,10 +1,4 @@
use crate::wasm_host::wit::since_v0_6_0::{
dap::{
BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, StartDebuggingRequestArguments,
TcpArguments, TcpArgumentsTemplate,
},
slash_command::SlashCommandOutputSection,
};
use crate::wasm_host::wit::since_v0_6_0::slash_command::SlashCommandOutputSection;
use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
use crate::wasm_host::{WasmState, wit::ToWasmtimeResult};
use ::http_client::{AsyncBody, HttpRequestExt};
@ -24,7 +18,7 @@ use project::project_settings::ProjectSettings;
use semver::Version;
use std::{
env,
net::{IpAddr, Ipv4Addr},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
path::{Path, PathBuf},
str::FromStr,
sync::{Arc, OnceLock},
@ -104,43 +98,66 @@ impl From<StartDebuggingRequestArgumentsRequest>
}
}
}
impl TryFrom<StartDebuggingRequestArguments> for extension::StartDebuggingRequestArguments {
impl TryFrom<dap::StartDebuggingRequestArguments> for extension::StartDebuggingRequestArguments {
type Error = anyhow::Error;
fn try_from(value: StartDebuggingRequestArguments) -> Result<Self, Self::Error> {
fn try_from(value: dap::StartDebuggingRequestArguments) -> Result<Self, Self::Error> {
Ok(Self {
configuration: serde_json::from_str(&value.configuration)?,
request: value.request.into(),
})
}
}
impl From<TcpArguments> for extension::TcpArguments {
fn from(value: TcpArguments) -> Self {
impl From<dap::IpAddress> for IpAddr {
fn from(value: dap::IpAddress) -> Self {
match value {
dap::IpAddress::Ipv4((a, b, c, d)) => IpAddr::V4(Ipv4Addr::new(a, b, c, d)),
dap::IpAddress::Ipv6((a, b, c, d, e, f, g, h)) => {
IpAddr::V6(Ipv6Addr::new(a, b, c, d, e, f, g, h))
}
}
}
}
impl From<IpAddr> for dap::IpAddress {
fn from(value: IpAddr) -> Self {
match value {
IpAddr::V4(v4) => {
let [a, b, c, d] = v4.octets();
Self::Ipv4((a, b, c, d))
}
IpAddr::V6(v6) => {
let [a, b, c, d, e, f, g, h] = v6.segments();
Self::Ipv6((a, b, c, d, e, f, g, h))
}
}
}
}
impl From<dap::TcpArguments> for extension::TcpArguments {
fn from(value: dap::TcpArguments) -> Self {
Self {
host: IpAddr::V4(Ipv4Addr::from_bits(value.host)),
host: value.host.into(),
port: value.port,
timeout: value.timeout,
}
}
}
impl From<extension::TcpArgumentsTemplate> for TcpArgumentsTemplate {
impl From<extension::TcpArgumentsTemplate> for dap::TcpArgumentsTemplate {
fn from(value: extension::TcpArgumentsTemplate) -> Self {
Self {
host: value.host.and_then(|addr| match addr {
IpAddr::V4(v4) => Some(v4.to_bits()),
IpAddr::V6(_) => None,
}),
host: value.host.map(Into::into),
port: value.port,
timeout: value.timeout,
}
}
}
impl From<TcpArgumentsTemplate> for extension::TcpArgumentsTemplate {
fn from(value: TcpArgumentsTemplate) -> Self {
impl From<dap::TcpArgumentsTemplate> for extension::TcpArgumentsTemplate {
fn from(value: dap::TcpArgumentsTemplate) -> Self {
Self {
host: value.host.map(|bits| IpAddr::V4(Ipv4Addr::from_bits(bits))),
host: value.host.map(Into::into),
port: value.port,
timeout: value.timeout,
}
@ -238,11 +255,11 @@ impl TryFrom<DebugAdapterBinary> for extension::DebugAdapterBinary {
}
}
impl From<BuildTaskDefinition> for extension::BuildTaskDefinition {
fn from(value: BuildTaskDefinition) -> Self {
impl From<dap::BuildTaskDefinition> for extension::BuildTaskDefinition {
fn from(value: dap::BuildTaskDefinition) -> Self {
match value {
BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
BuildTaskDefinition::Template(build_task_template) => Self::Template {
dap::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
dap::BuildTaskDefinition::Template(build_task_template) => Self::Template {
task_template: build_task_template.template.into(),
locator_name: build_task_template.locator_name.map(SharedString::from),
},
@ -250,14 +267,14 @@ impl From<BuildTaskDefinition> for extension::BuildTaskDefinition {
}
}
impl From<extension::BuildTaskDefinition> for BuildTaskDefinition {
impl From<extension::BuildTaskDefinition> for dap::BuildTaskDefinition {
fn from(value: extension::BuildTaskDefinition) -> Self {
match value {
extension::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()),
extension::BuildTaskDefinition::Template {
task_template,
locator_name,
} => Self::Template(BuildTaskDefinitionTemplatePayload {
} => Self::Template(dap::BuildTaskDefinitionTemplatePayload {
template: task_template.into(),
locator_name: locator_name.map(String::from),
}),
@ -901,27 +918,19 @@ impl context_server::Host for WasmState {}
impl dap::Host for WasmState {
async fn resolve_tcp_template(
&mut self,
template: TcpArgumentsTemplate,
) -> wasmtime::Result<Result<TcpArguments, String>> {
template: dap::TcpArgumentsTemplate,
) -> wasmtime::Result<Result<dap::TcpArguments, String>> {
maybe!(async {
let (host, port, timeout) =
::dap::configure_tcp_connection(task::TcpArgumentsTemplate {
port: template.port,
host: template
.host
.map(|bits| IpAddr::V4(Ipv4Addr::from_bits(bits))),
host: template.host.map(Into::into),
timeout: template.timeout,
})
.await?;
let host_bits = match host {
IpAddr::V4(v4) => v4.to_bits(),
IpAddr::V6(_) => {
anyhow::bail!("IPv6 addresses are not supported in the extension API")
}
};
Ok(TcpArguments {
Ok(dap::TcpArguments {
port,
host: host_bits,
host: host.into(),
timeout,
})
})