1use 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 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 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 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 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 if managers.contains(&PackageManager::Homebrew) {
76 return Ok(InstallMethod::PackageManager {
77 manager: PackageManager::Homebrew,
78 package: "llvm".to_string(),
79 });
80 }
81 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 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 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 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 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 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 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 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 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 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 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 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(&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}