use crate::common::{Error, Result};
use crate::setup::installer::{InstallMethod, InstallOptions, InstallResult, InstallStatus, Installer};
use crate::setup::registry::{DebuggerInfo, Platform};
use crate::setup::verifier::{verify_dap_adapter, VerifyResult};
use async_trait::async_trait;
use std::path::PathBuf;
use super::gdb_common::{get_gdb_version, is_gdb_version_sufficient};
static INFO: DebuggerInfo = DebuggerInfo {
id: "cuda-gdb",
name: "CUDA-GDB",
languages: &["cuda", "c", "cpp"],
platforms: &[Platform::Linux],
description: "NVIDIA CUDA debugger for GPU code",
primary: true,
};
pub struct CudaGdbInstaller;
async fn has_native_dap_support(cuda_gdb_path: &PathBuf) -> bool {
if let Some(version) = get_gdb_version(cuda_gdb_path).await {
if !is_gdb_version_sufficient(&version) {
return false;
}
} else {
return false;
}
let output = tokio::process::Command::new(cuda_gdb_path)
.args(["-i=dap", "-batch", "-ex", "quit"])
.output()
.await;
match output {
Ok(result) => {
let stderr = String::from_utf8_lossy(&result.stderr);
!stderr.contains("unrecognized") && !stderr.contains("Interpreter")
}
Err(_) => false,
}
}
#[async_trait]
impl Installer for CudaGdbInstaller {
fn info(&self) -> &DebuggerInfo {
&INFO
}
async fn status(&self) -> Result<InstallStatus> {
if Platform::current() != Platform::Linux {
return Ok(InstallStatus::NotInstalled);
}
let Some(cuda_gdb_path) = find_cuda_gdb() else {
return Ok(InstallStatus::NotInstalled);
};
let version = get_gdb_version(&cuda_gdb_path).await;
if has_native_dap_support(&cuda_gdb_path).await {
return Ok(InstallStatus::Installed {
path: cuda_gdb_path,
version,
});
}
if let Some(cdt_adapter) = find_cdt_gdb_adapter() {
return Ok(InstallStatus::Installed {
path: cdt_adapter,
version,
});
}
Ok(InstallStatus::Broken {
path: cuda_gdb_path,
reason: "cuda-gdb found but lacks native DAP support. Install cdt-gdb-adapter: npm install -g cdt-gdb-adapter".to_string(),
})
}
async fn best_method(&self) -> Result<InstallMethod> {
if Platform::current() != Platform::Linux {
return Ok(InstallMethod::NotSupported {
reason: "CUDA-GDB GPU debugging is only supported on Linux".to_string(),
});
}
let Some(cuda_gdb_path) = find_cuda_gdb() else {
return Ok(InstallMethod::NotSupported {
reason: "CUDA-GDB not found. Install NVIDIA CUDA Toolkit from https://developer.nvidia.com/cuda-downloads".to_string(),
});
};
if has_native_dap_support(&cuda_gdb_path).await {
return Ok(InstallMethod::AlreadyInstalled { path: cuda_gdb_path });
}
if let Some(cdt_adapter) = find_cdt_gdb_adapter() {
return Ok(InstallMethod::AlreadyInstalled { path: cdt_adapter });
}
Ok(InstallMethod::NotSupported {
reason: "cuda-gdb lacks native DAP support. Install cdt-gdb-adapter: npm install -g cdt-gdb-adapter".to_string(),
})
}
async fn install(&self, _opts: InstallOptions) -> Result<InstallResult> {
let method = self.best_method().await?;
match method {
InstallMethod::AlreadyInstalled { path } => {
let cuda_gdb_path = find_cuda_gdb().ok_or_else(|| {
Error::Internal("CUDA-GDB not found".to_string())
})?;
let version = get_gdb_version(&cuda_gdb_path).await;
if has_native_dap_support(&cuda_gdb_path).await {
Ok(InstallResult {
path: cuda_gdb_path,
version,
args: vec!["-i=dap".to_string()],
})
} else {
Ok(InstallResult {
path,
version,
args: vec![format!("--config={{\"gdb\":\"{}\"}}", cuda_gdb_path.display())],
})
}
}
InstallMethod::NotSupported { reason } => {
Err(Error::Internal(format!("Cannot install CUDA-GDB: {}", reason)))
}
_ => Err(Error::Internal("Unexpected installation method".to_string())),
}
}
async fn uninstall(&self) -> Result<()> {
println!("CUDA-GDB is part of NVIDIA CUDA Toolkit. Uninstall the toolkit to remove it.");
Ok(())
}
async fn verify(&self) -> Result<VerifyResult> {
let status = self.status().await?;
match status {
InstallStatus::Installed { path, .. } => {
let cuda_gdb_path = find_cuda_gdb().ok_or_else(|| {
Error::Internal("CUDA-GDB not found".to_string())
})?;
if has_native_dap_support(&cuda_gdb_path).await {
verify_dap_adapter(&path, &["-i=dap".to_string()]).await
} else {
verify_dap_adapter(
&path,
&[format!("--config={{\"gdb\":\"{}\"}}", cuda_gdb_path.display())],
).await
}
}
InstallStatus::Broken { reason, .. } => Ok(VerifyResult {
success: false,
capabilities: None,
error: Some(reason),
}),
InstallStatus::NotInstalled => Ok(VerifyResult {
success: false,
capabilities: None,
error: Some("Not installed".to_string()),
}),
}
}
}
fn find_cuda_gdb() -> Option<PathBuf> {
if let Ok(entries) = std::fs::read_dir("/usr/local") {
let mut cuda_paths: Vec<_> = entries
.flatten()
.filter_map(|e| {
let name = e.file_name().to_string_lossy().to_string();
if name.starts_with("cuda-") {
let cuda_gdb = e.path().join("bin/cuda-gdb");
if cuda_gdb.exists() {
let version = name.strip_prefix("cuda-").unwrap_or("0.0").to_string();
return Some((version, cuda_gdb));
}
}
None
})
.collect();
cuda_paths.sort_by(|a, b| {
let parse_version = |s: &str| -> (u32, u32) {
let parts: Vec<&str> = s.split('.').collect();
let major = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0);
let minor = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(0);
(major, minor)
};
parse_version(&b.0).cmp(&parse_version(&a.0))
});
if let Some((_, path)) = cuda_paths.first() {
return Some(path.clone());
}
}
let default_path = PathBuf::from("/usr/local/cuda/bin/cuda-gdb");
if default_path.exists() {
return Some(default_path);
}
let arch_path = PathBuf::from("/opt/cuda/bin/cuda-gdb");
if arch_path.exists() {
return Some(arch_path);
}
if let Ok(cuda_home) = std::env::var("CUDA_HOME") {
let cuda_home_path = PathBuf::from(cuda_home).join("bin/cuda-gdb");
if cuda_home_path.exists() {
return Some(cuda_home_path);
}
}
which::which("cuda-gdb").ok()
}
fn find_cdt_gdb_adapter() -> Option<PathBuf> {
if let Ok(path) = which::which("cdtDebugAdapter") {
return Some(path);
}
if let Ok(home) = std::env::var("HOME") {
let nvm_path = PathBuf::from(&home).join(".nvm/versions/node");
if nvm_path.exists() {
if let Ok(entries) = std::fs::read_dir(&nvm_path) {
for entry in entries.flatten() {
let bin_path = entry.path().join("bin/cdtDebugAdapter");
if bin_path.exists() {
return Some(bin_path);
}
}
}
}
let npm_global = PathBuf::from(&home).join(".npm-global/bin/cdtDebugAdapter");
if npm_global.exists() {
return Some(npm_global);
}
let npm_prefix = PathBuf::from(&home).join("node_modules/.bin/cdtDebugAdapter");
if npm_prefix.exists() {
return Some(npm_prefix);
}
}
let system_path = PathBuf::from("/usr/local/bin/cdtDebugAdapter");
if system_path.exists() {
return Some(system_path);
}
None
}