debugger/setup/adapters/
codelldb.rs

1//! CodeLLDB installer
2//!
3//! Installs the CodeLLDB debug adapter from GitHub releases.
4
5use crate::common::{Error, Result};
6use crate::setup::installer::{
7    adapters_dir, arch_str, download_file, ensure_adapters_dir, extract_zip,
8    get_github_release, make_executable, platform_str, read_version_file,
9    write_version_file, InstallMethod, InstallOptions, InstallResult, InstallStatus, Installer,
10};
11use crate::setup::registry::{DebuggerInfo, Platform};
12use crate::setup::verifier::{verify_dap_adapter, VerifyResult};
13use async_trait::async_trait;
14
15static INFO: DebuggerInfo = DebuggerInfo {
16    id: "codelldb",
17    name: "CodeLLDB",
18    languages: &["c", "cpp", "rust"],
19    platforms: &[Platform::Linux, Platform::MacOS, Platform::Windows],
20    description: "Feature-rich LLDB-based debugger",
21    primary: false,
22};
23
24const GITHUB_REPO: &str = "vadimcn/codelldb";
25
26pub struct CodeLldbInstaller;
27
28#[async_trait]
29impl Installer for CodeLldbInstaller {
30    fn info(&self) -> &DebuggerInfo {
31        &INFO
32    }
33
34    async fn status(&self) -> Result<InstallStatus> {
35        let adapter_dir = adapters_dir().join("codelldb");
36        let binary_path = adapter_dir.join("extension").join("adapter").join(binary_name());
37
38        if binary_path.exists() {
39            let version = read_version_file(&adapter_dir);
40            return Ok(InstallStatus::Installed {
41                path: binary_path,
42                version,
43            });
44        }
45
46        // Check if available in PATH (unlikely but possible)
47        if let Ok(path) = which::which("codelldb") {
48            return Ok(InstallStatus::Installed {
49                path,
50                version: None,
51            });
52        }
53
54        Ok(InstallStatus::NotInstalled)
55    }
56
57    async fn best_method(&self) -> Result<InstallMethod> {
58        // CodeLLDB is always installed from GitHub releases
59        Ok(InstallMethod::GitHubRelease {
60            repo: GITHUB_REPO.to_string(),
61            asset_pattern: format!("codelldb-{}-{}.vsix", arch_str(), platform_str()),
62        })
63    }
64
65    async fn install(&self, opts: InstallOptions) -> Result<InstallResult> {
66        install_from_github(&opts).await
67    }
68
69    async fn uninstall(&self) -> Result<()> {
70        let adapter_dir = adapters_dir().join("codelldb");
71        if adapter_dir.exists() {
72            std::fs::remove_dir_all(&adapter_dir)?;
73            println!("Removed {}", adapter_dir.display());
74        } else {
75            println!("CodeLLDB is not installed");
76        }
77        Ok(())
78    }
79
80    async fn verify(&self) -> Result<VerifyResult> {
81        let status = self.status().await?;
82
83        match status {
84            InstallStatus::Installed { path, .. } => {
85                // CodeLLDB uses different arguments
86                verify_dap_adapter(&path, &[]).await
87            }
88            InstallStatus::Broken { reason, .. } => Ok(VerifyResult {
89                success: false,
90                capabilities: None,
91                error: Some(reason),
92            }),
93            InstallStatus::NotInstalled => Ok(VerifyResult {
94                success: false,
95                capabilities: None,
96                error: Some("Not installed".to_string()),
97            }),
98        }
99    }
100}
101
102fn binary_name() -> &'static str {
103    if cfg!(windows) {
104        "codelldb.exe"
105    } else {
106        "codelldb"
107    }
108}
109
110fn get_asset_pattern() -> Vec<String> {
111    let platform = platform_str();
112    let arch = arch_str();
113
114    // Map arch names to CodeLLDB naming convention
115    let codelldb_arch = match arch {
116        "x86_64" => "x86_64",
117        "aarch64" => "aarch64",
118        _ => arch,
119    };
120
121    // Map platform names
122    let codelldb_platform = match platform {
123        "darwin" => "darwin",
124        "linux" => "linux",
125        "windows" => "windows",
126        _ => platform,
127    };
128
129    vec![
130        format!("codelldb-{}-{}.vsix", codelldb_arch, codelldb_platform),
131        // Alternative naming patterns
132        format!("codelldb-{}-{}-*.vsix", codelldb_arch, codelldb_platform),
133    ]
134}
135
136async fn install_from_github(opts: &InstallOptions) -> Result<InstallResult> {
137    println!("Checking for existing installation... not found");
138    println!("Finding latest CodeLLDB release...");
139
140    let release = get_github_release(GITHUB_REPO, opts.version.as_deref()).await?;
141    let version = release.tag_name.trim_start_matches('v').to_string();
142    println!("Found version: {}", version);
143
144    // Find appropriate asset
145    let patterns = get_asset_pattern();
146    let asset = release
147        .find_asset(&patterns.iter().map(|s| s.as_str()).collect::<Vec<_>>())
148        .ok_or_else(|| {
149            Error::Internal(format!(
150                "No CodeLLDB release found for {} {}. Available assets: {:?}",
151                arch_str(),
152                platform_str(),
153                release.assets.iter().map(|a| &a.name).collect::<Vec<_>>()
154            ))
155        })?;
156
157    // Create temp directory for download
158    let temp_dir = tempfile::tempdir()?;
159    let archive_path = temp_dir.path().join(&asset.name);
160
161    println!(
162        "Downloading {}... {:.1} MB",
163        asset.name,
164        asset.size as f64 / 1_000_000.0
165    );
166    download_file(&asset.browser_download_url, &archive_path).await?;
167
168    println!("Extracting...");
169
170    // Create installation directory
171    let adapter_dir = ensure_adapters_dir()?.join("codelldb");
172    if adapter_dir.exists() {
173        std::fs::remove_dir_all(&adapter_dir)?;
174    }
175    std::fs::create_dir_all(&adapter_dir)?;
176
177    // Extract vsix (it's just a zip file)
178    extract_zip(&archive_path, &adapter_dir)?;
179
180    // Find and make the binary executable
181    let binary_path = adapter_dir.join("extension").join("adapter").join(binary_name());
182    if !binary_path.exists() {
183        return Err(Error::Internal(format!(
184            "codelldb binary not found at expected location: {}",
185            binary_path.display()
186        )));
187    }
188    make_executable(&binary_path)?;
189
190    // Also make libcodelldb executable on Unix
191    #[cfg(unix)]
192    {
193        let lib_path = adapter_dir.join("extension").join("adapter");
194        for entry in std::fs::read_dir(&lib_path)? {
195            if let Ok(entry) = entry {
196                let path = entry.path();
197                if path.extension().map(|e| e == "so" || e == "dylib").unwrap_or(false) {
198                    make_executable(&path)?;
199                }
200            }
201        }
202
203        // Make lldb and lldb-server executable if present
204        let lldb_dir = adapter_dir.join("extension").join("lldb");
205        if lldb_dir.exists() {
206            for subdir in &["bin", "lib"] {
207                let dir = lldb_dir.join(subdir);
208                if dir.exists() {
209                    if let Ok(entries) = std::fs::read_dir(&dir) {
210                        for entry in entries {
211                            if let Ok(entry) = entry {
212                                let path = entry.path();
213                                if path.is_file() {
214                                    // Log warning but don't fail installation
215                                    if let Err(e) = make_executable(&path) {
216                                        eprintln!(
217                                            "Warning: could not make {} executable: {}",
218                                            path.display(),
219                                            e
220                                        );
221                                    }
222                                }
223                            }
224                        }
225                    }
226                }
227            }
228        }
229    }
230
231    // Write version file
232    write_version_file(&adapter_dir, &version)?;
233
234    println!("Setting permissions... done");
235    println!("Verifying installation...");
236
237    Ok(InstallResult {
238        path: binary_path,
239        version: Some(version),
240        args: Vec::new(),
241    })
242}