Skip to main content

debugger/setup/adapters/
lldb.rs

1//! lldb-dap installer
2//!
3//! Installs the LLVM lldb-dap debug adapter.
4
5use crate::common::{Error, Result};
6use crate::setup::installer::{
7    adapters_dir, ensure_adapters_dir, make_executable, InstallMethod, InstallOptions,
8    InstallResult, InstallStatus, Installer, PackageManager,
9};
10use crate::setup::registry::{DebuggerInfo, Platform};
11use crate::setup::verifier::{verify_dap_adapter, VerifyResult};
12use async_trait::async_trait;
13use std::path::PathBuf;
14
15static INFO: DebuggerInfo = DebuggerInfo {
16    id: "lldb",
17    name: "lldb-dap",
18    languages: &["c", "cpp", "rust", "swift"],
19    platforms: &[Platform::Linux, Platform::MacOS],
20    description: "LLVM's native DAP adapter",
21    primary: true,
22};
23
24pub struct LldbInstaller;
25
26#[async_trait]
27impl Installer for LldbInstaller {
28    fn info(&self) -> &DebuggerInfo {
29        &INFO
30    }
31
32    async fn status(&self) -> Result<InstallStatus> {
33        // Check our managed installation first
34        let adapter_dir = adapters_dir().join("lldb-dap");
35        let managed_path = adapter_dir.join("bin").join(binary_name());
36
37        if managed_path.exists() {
38            let version = crate::setup::installer::read_version_file(&adapter_dir);
39            return Ok(InstallStatus::Installed {
40                path: managed_path,
41                version,
42            });
43        }
44
45        // Check if available in PATH
46        if let Ok(path) = which::which("lldb-dap") {
47            let version = get_version(&path).await;
48            return Ok(InstallStatus::Installed { path, version });
49        }
50
51        // Also check for lldb-vscode (older name)
52        if let Ok(path) = which::which("lldb-vscode") {
53            let version = get_version(&path).await;
54            return Ok(InstallStatus::Installed { path, version });
55        }
56
57        Ok(InstallStatus::NotInstalled)
58    }
59
60    async fn best_method(&self) -> Result<InstallMethod> {
61        // Check if already in PATH
62        if let Ok(path) = which::which("lldb-dap") {
63            return Ok(InstallMethod::AlreadyInstalled { path });
64        }
65        if let Ok(path) = which::which("lldb-vscode") {
66            return Ok(InstallMethod::AlreadyInstalled { path });
67        }
68
69        let platform = Platform::current();
70        let managers = PackageManager::detect();
71
72        match platform {
73            Platform::MacOS => {
74                // macOS: Xcode command line tools or Homebrew
75                if managers.contains(&PackageManager::Homebrew) {
76                    return Ok(InstallMethod::PackageManager {
77                        manager: PackageManager::Homebrew,
78                        package: "llvm".to_string(),
79                    });
80                }
81                // Check for Xcode lldb-dap
82                let xcode_path = PathBuf::from("/usr/bin/lldb-dap");
83                if xcode_path.exists() {
84                    return Ok(InstallMethod::AlreadyInstalled { path: xcode_path });
85                }
86            }
87            Platform::Linux => {
88                // Linux: package managers or LLVM releases
89                if managers.contains(&PackageManager::Apt) {
90                    return Ok(InstallMethod::PackageManager {
91                        manager: PackageManager::Apt,
92                        package: "lldb".to_string(),
93                    });
94                }
95                if managers.contains(&PackageManager::Dnf) {
96                    return Ok(InstallMethod::PackageManager {
97                        manager: PackageManager::Dnf,
98                        package: "lldb".to_string(),
99                    });
100                }
101                if managers.contains(&PackageManager::Pacman) {
102                    return Ok(InstallMethod::PackageManager {
103                        manager: PackageManager::Pacman,
104                        package: "lldb".to_string(),
105                    });
106                }
107
108                // Fallback to GitHub releases
109                return Ok(InstallMethod::GitHubRelease {
110                    repo: "llvm/llvm-project".to_string(),
111                    asset_pattern: "LLVM-*-Linux-*.tar.xz".to_string(),
112                });
113            }
114            Platform::Windows => {
115                return Ok(InstallMethod::NotSupported {
116                    reason: "lldb-dap is not well-supported on Windows. Use codelldb instead."
117                        .to_string(),
118                });
119            }
120        }
121
122        Ok(InstallMethod::NotSupported {
123            reason: "No installation method found for this platform".to_string(),
124        })
125    }
126
127    async fn install(&self, opts: InstallOptions) -> Result<InstallResult> {
128        let method = self.best_method().await?;
129
130        match method {
131            InstallMethod::AlreadyInstalled { path } => {
132                let version = get_version(&path).await;
133                Ok(InstallResult {
134                    path,
135                    version,
136                    args: Vec::new(),
137                })
138            }
139            InstallMethod::PackageManager { manager, package } => {
140                install_via_package_manager(manager, &package, &opts).await
141            }
142            InstallMethod::GitHubRelease { .. } => {
143                install_from_github(&opts).await
144            }
145            InstallMethod::NotSupported { reason } => {
146                Err(Error::Internal(format!("Cannot install lldb-dap: {}", reason)))
147            }
148            _ => Err(Error::Internal("Unexpected installation method".to_string())),
149        }
150    }
151
152    async fn uninstall(&self) -> Result<()> {
153        let adapter_dir = adapters_dir().join("lldb-dap");
154        if adapter_dir.exists() {
155            std::fs::remove_dir_all(&adapter_dir)?;
156            println!("Removed {}", adapter_dir.display());
157        } else {
158            println!("lldb-dap is not installed in managed location");
159            if let Ok(path) = which::which("lldb-dap") {
160                println!("System installation found at: {}", path.display());
161                println!("Use your system package manager to uninstall.");
162            }
163        }
164        Ok(())
165    }
166
167    async fn verify(&self) -> Result<VerifyResult> {
168        let status = self.status().await?;
169
170        match status {
171            InstallStatus::Installed { path, .. } => {
172                verify_dap_adapter(&path, &[]).await
173            }
174            InstallStatus::Broken { reason, .. } => Ok(VerifyResult {
175                success: false,
176                capabilities: None,
177                error: Some(reason),
178            }),
179            InstallStatus::NotInstalled => Ok(VerifyResult {
180                success: false,
181                capabilities: None,
182                error: Some("Not installed".to_string()),
183            }),
184        }
185    }
186}
187
188fn binary_name() -> &'static str {
189    if cfg!(windows) {
190        "lldb-dap.exe"
191    } else {
192        "lldb-dap"
193    }
194}
195
196async fn get_version(path: &PathBuf) -> Option<String> {
197    let output = tokio::process::Command::new(path)
198        .arg("--version")
199        .output()
200        .await
201        .ok()?;
202
203    if output.status.success() {
204        let stdout = String::from_utf8_lossy(&output.stdout);
205        // Parse version from output like "lldb version 17.0.6"
206        stdout
207            .lines()
208            .next()
209            .and_then(|line| line.split_whitespace().last())
210            .map(|s| s.to_string())
211    } else {
212        None
213    }
214}
215
216async fn install_via_package_manager(
217    manager: PackageManager,
218    package: &str,
219    _opts: &InstallOptions,
220) -> Result<InstallResult> {
221    println!("Installing {} via {:?}...", package, manager);
222
223    let command = manager.install_command(package);
224    println!("Running: {}", command);
225
226    crate::setup::installer::run_command(&command).await?;
227
228    // Find the installed binary
229    let path = which::which("lldb-dap")
230        .or_else(|_| which::which("lldb-vscode"))
231        .map_err(|_| {
232            Error::Internal(
233                "lldb-dap not found after installation. You may need to add LLVM to your PATH."
234                    .to_string(),
235            )
236        })?;
237
238    let version = get_version(&path).await;
239
240    Ok(InstallResult {
241        path,
242        version,
243        args: Vec::new(),
244    })
245}
246
247async fn install_from_github(opts: &InstallOptions) -> Result<InstallResult> {
248    use crate::setup::installer::{
249        arch_str, download_file, extract_tar_gz, extract_tar_xz, get_github_release, platform_str,
250        write_version_file,
251    };
252
253    println!("Checking for existing installation... not found");
254    println!("Finding latest LLVM release...");
255
256    let release = get_github_release("llvm/llvm-project", opts.version.as_deref()).await?;
257    println!("Found version: {}", release.tag_name);
258
259    // Find appropriate asset
260    let platform = platform_str();
261    let arch = arch_str();
262
263    let asset_patterns = vec![
264        format!("LLVM-*-{}-{}.tar.xz", arch, platform),
265        format!("clang+llvm-*-{}-*{}.tar.xz", arch, platform),
266    ];
267
268    let asset = release
269        .find_asset(&asset_patterns.iter().map(|s| s.as_str()).collect::<Vec<_>>())
270        .ok_or_else(|| {
271            Error::Internal(format!(
272                "No LLVM release found for {} {}. Available assets: {:?}",
273                arch,
274                platform,
275                release.assets.iter().map(|a| &a.name).collect::<Vec<_>>()
276            ))
277        })?;
278
279    // Create temp directory for download
280    let temp_dir = tempfile::tempdir()?;
281    let archive_path = temp_dir.path().join(&asset.name);
282
283    println!("Downloading {}... {:.1} MB", asset.name, asset.size as f64 / 1_000_000.0);
284    download_file(&asset.browser_download_url, &archive_path).await?;
285
286    println!("Extracting...");
287    // Use appropriate extractor based on file extension
288    if asset.name.ends_with(".tar.xz") {
289        extract_tar_xz(&archive_path, temp_dir.path())?;
290    } else {
291        extract_tar_gz(&archive_path, temp_dir.path())?;
292    }
293
294    // Find the extracted directory
295    let extracted_dir = std::fs::read_dir(temp_dir.path())?
296        .filter_map(|e| e.ok())
297        .find(|e| e.path().is_dir() && e.file_name().to_string_lossy().starts_with("LLVM"))
298        .or_else(|| {
299            std::fs::read_dir(temp_dir.path())
300                .ok()?
301                .filter_map(|e| e.ok())
302                .find(|e| e.path().is_dir() && e.file_name().to_string_lossy().starts_with("clang"))
303        })
304        .ok_or_else(|| Error::Internal("Could not find extracted LLVM directory".to_string()))?;
305
306    // Find lldb-dap binary
307    let lldb_dap_src = extracted_dir.path().join("bin").join(binary_name());
308    if !lldb_dap_src.exists() {
309        return Err(Error::Internal(format!(
310            "lldb-dap not found in LLVM distribution at {}",
311            lldb_dap_src.display()
312        )));
313    }
314
315    // Create installation directory
316    let adapter_dir = ensure_adapters_dir()?.join("lldb-dap");
317    let bin_dir = adapter_dir.join("bin");
318    std::fs::create_dir_all(&bin_dir)?;
319
320    // Copy lldb-dap and required libraries
321    let dest_path = bin_dir.join(binary_name());
322    std::fs::copy(&lldb_dap_src, &dest_path)?;
323    make_executable(&dest_path)?;
324
325    // Write version file
326    write_version_file(&adapter_dir, &release.tag_name)?;
327
328    println!("Setting permissions... done");
329    println!("Verifying installation...");
330
331    Ok(InstallResult {
332        path: dest_path,
333        version: Some(release.tag_name),
334        args: Vec::new(),
335    })
336}