debugger/setup/
mod.rs

1//! Debug adapter setup and installation
2//!
3//! This module provides functionality to install, manage, and verify DAP-compatible
4//! debug adapters across different platforms.
5
6pub mod adapters;
7pub mod detector;
8pub mod installer;
9pub mod registry;
10pub mod verifier;
11
12use crate::common::Result;
13use std::path::PathBuf;
14
15/// Options for the setup command
16#[derive(Debug, Clone)]
17pub struct SetupOptions {
18    /// Specific debugger to install
19    pub debugger: Option<String>,
20    /// Specific version to install
21    pub version: Option<String>,
22    /// List available debuggers
23    pub list: bool,
24    /// Check installed debuggers
25    pub check: bool,
26    /// Auto-detect project types and install appropriate debuggers
27    pub auto_detect: bool,
28    /// Uninstall instead of install
29    pub uninstall: bool,
30    /// Show installation path
31    pub path: bool,
32    /// Force reinstall
33    pub force: bool,
34    /// Dry run mode
35    pub dry_run: bool,
36    /// Output as JSON
37    pub json: bool,
38}
39
40/// Result of a setup operation
41#[derive(Debug, Clone, serde::Serialize)]
42pub struct SetupResult {
43    pub status: SetupStatus,
44    pub debugger: String,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub version: Option<String>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub path: Option<PathBuf>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub languages: Option<Vec<String>>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub message: Option<String>,
53}
54
55/// Status of a setup operation
56#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
57#[serde(rename_all = "snake_case")]
58pub enum SetupStatus {
59    Success,
60    AlreadyInstalled,
61    Uninstalled,
62    NotFound,
63    Failed,
64    DryRun,
65}
66
67/// Run the setup command
68pub async fn run(opts: SetupOptions) -> Result<()> {
69    if opts.list {
70        return list_debuggers(opts.json).await;
71    }
72
73    if opts.check {
74        return check_debuggers(opts.json).await;
75    }
76
77    if opts.auto_detect {
78        return auto_setup(opts).await;
79    }
80
81    // Need a debugger name for other operations
82    let debugger = match &opts.debugger {
83        Some(d) => d.clone(),
84        None => {
85            if opts.json {
86                println!(
87                    "{}",
88                    serde_json::json!({
89                        "status": "error",
90                        "message": "No debugger specified. Use --list to see available debuggers."
91                    })
92                );
93            } else {
94                println!("No debugger specified. Use --list to see available debuggers.");
95                println!();
96                println!("Available debuggers:");
97                for info in registry::all_debuggers() {
98                    println!(
99                        "  {:12} - {} ({})",
100                        info.id,
101                        info.description,
102                        info.languages.join(", ")
103                    );
104                }
105            }
106            return Ok(());
107        }
108    };
109
110    if opts.path {
111        return show_path(&debugger, opts.json).await;
112    }
113
114    if opts.uninstall {
115        return uninstall_debugger(&debugger, opts.json).await;
116    }
117
118    // Install the debugger
119    install_debugger(&debugger, opts).await
120}
121
122/// List all available debuggers and their status
123async fn list_debuggers(json: bool) -> Result<()> {
124    let debuggers = registry::all_debuggers();
125    let mut results = Vec::new();
126
127    for info in debuggers {
128        let installer = registry::get_installer(info.id);
129        let status = if let Some(inst) = &installer {
130            inst.status().await.ok()
131        } else {
132            None
133        };
134
135        let status_str = match &status {
136            Some(installer::InstallStatus::Installed { version, .. }) => {
137                if let Some(v) = version {
138                    format!("installed ({})", v)
139                } else {
140                    "installed".to_string()
141                }
142            }
143            Some(installer::InstallStatus::Broken { reason, .. }) => {
144                format!("broken: {}", reason)
145            }
146            Some(installer::InstallStatus::NotInstalled) | None => "not installed".to_string(),
147        };
148
149        if json {
150            results.push(serde_json::json!({
151                "id": info.id,
152                "name": info.name,
153                "description": info.description,
154                "languages": info.languages,
155                "platforms": info.platforms.iter().map(|p| p.to_string()).collect::<Vec<_>>(),
156                "primary": info.primary,
157                "status": status_str,
158                "path": status.as_ref().and_then(|s| match s {
159                    installer::InstallStatus::Installed { path, .. } => Some(path.display().to_string()),
160                    installer::InstallStatus::Broken { path, .. } => Some(path.display().to_string()),
161                    _ => None,
162                }),
163            }));
164        } else {
165            let status_indicator = match &status {
166                Some(installer::InstallStatus::Installed { .. }) => "✓",
167                Some(installer::InstallStatus::Broken { .. }) => "✗",
168                _ => " ",
169            };
170            println!(
171                "  {} {:12} {:20} {}",
172                status_indicator,
173                info.id,
174                status_str,
175                info.languages.join(", ")
176            );
177        }
178    }
179
180    if json {
181        println!("{}", serde_json::to_string_pretty(&results)?);
182    }
183
184    Ok(())
185}
186
187/// Check all installed debuggers
188async fn check_debuggers(json: bool) -> Result<()> {
189    let debuggers = registry::all_debuggers();
190    let mut results = Vec::new();
191    let mut found_any = false;
192
193    if !json {
194        println!("Checking installed debuggers...\n");
195    }
196
197    for info in debuggers {
198        let installer = match registry::get_installer(info.id) {
199            Some(i) => i,
200            None => continue,
201        };
202
203        let status = installer.status().await.ok();
204
205        if let Some(installer::InstallStatus::Installed { path, version }) = &status {
206            found_any = true;
207
208            // Verify the installation
209            let verify_result = installer.verify().await;
210            let working = verify_result.as_ref().map(|v| v.success).unwrap_or(false);
211
212            if json {
213                results.push(serde_json::json!({
214                    "id": info.id,
215                    "path": path.display().to_string(),
216                    "version": version,
217                    "working": working,
218                    "error": verify_result.as_ref().ok().and_then(|v| v.error.clone()),
219                }));
220            } else {
221                let status_icon = if working { "✓" } else { "✗" };
222                println!("{} {}", status_icon, info.id);
223                println!("  Path: {}", path.display());
224                if let Some(v) = version {
225                    println!("  Version: {}", v);
226                }
227                if !working {
228                    if let Ok(v) = &verify_result {
229                        if let Some(err) = &v.error {
230                            println!("  Error: {}", err);
231                        }
232                    }
233                }
234                println!();
235            }
236        }
237    }
238
239    if json {
240        println!("{}", serde_json::to_string_pretty(&results)?);
241    } else if !found_any {
242        println!("No debuggers installed.");
243        println!("Use 'debugger setup --list' to see available debuggers.");
244    }
245
246    Ok(())
247}
248
249/// Auto-detect project types and install appropriate debuggers
250async fn auto_setup(opts: SetupOptions) -> Result<()> {
251    let project_types = detector::detect_project_types(std::env::current_dir()?.as_path());
252
253    if project_types.is_empty() {
254        if opts.json {
255            println!(
256                "{}",
257                serde_json::json!({
258                    "status": "no_projects",
259                    "message": "No recognized project types found in current directory."
260                })
261            );
262        } else {
263            println!("No recognized project types found in current directory.");
264        }
265        return Ok(());
266    }
267
268    let debuggers: Vec<&str> = project_types
269        .iter()
270        .flat_map(|pt| detector::debuggers_for_project(pt))
271        .collect::<std::collections::HashSet<_>>()
272        .into_iter()
273        .collect();
274
275    if !opts.json {
276        println!(
277            "Detected project types: {}",
278            project_types
279                .iter()
280                .map(|p| format!("{:?}", p))
281                .collect::<Vec<_>>()
282                .join(", ")
283        );
284        println!(
285            "Will install debuggers: {}",
286            debuggers.join(", ")
287        );
288        println!();
289    }
290
291    let mut results = Vec::new();
292
293    for debugger in debuggers {
294        let result = install_debugger_inner(
295            debugger,
296            &SetupOptions {
297                debugger: Some(debugger.to_string()),
298                ..opts.clone()
299            },
300        )
301        .await;
302
303        if opts.json {
304            results.push(result);
305        }
306    }
307
308    if opts.json {
309        println!("{}", serde_json::to_string_pretty(&results)?);
310    }
311
312    Ok(())
313}
314
315/// Show the installation path for a debugger
316async fn show_path(debugger: &str, json: bool) -> Result<()> {
317    let installer = match registry::get_installer(debugger) {
318        Some(i) => i,
319        None => {
320            if json {
321                println!(
322                    "{}",
323                    serde_json::json!({
324                        "status": "not_found",
325                        "debugger": debugger,
326                        "message": format!("Unknown debugger: {}", debugger)
327                    })
328                );
329            } else {
330                println!("Unknown debugger: {}", debugger);
331            }
332            return Ok(());
333        }
334    };
335
336    let status = installer.status().await?;
337
338    match status {
339        installer::InstallStatus::Installed { path, version } => {
340            if json {
341                println!(
342                    "{}",
343                    serde_json::json!({
344                        "status": "installed",
345                        "debugger": debugger,
346                        "path": path.display().to_string(),
347                        "version": version,
348                    })
349                );
350            } else {
351                println!("{}", path.display());
352            }
353        }
354        installer::InstallStatus::Broken { path, reason } => {
355            if json {
356                println!(
357                    "{}",
358                    serde_json::json!({
359                        "status": "broken",
360                        "debugger": debugger,
361                        "path": path.display().to_string(),
362                        "reason": reason,
363                    })
364                );
365            } else {
366                println!("{} (broken: {})", path.display(), reason);
367            }
368        }
369        installer::InstallStatus::NotInstalled => {
370            if json {
371                println!(
372                    "{}",
373                    serde_json::json!({
374                        "status": "not_installed",
375                        "debugger": debugger,
376                    })
377                );
378            } else {
379                println!("{} is not installed", debugger);
380            }
381        }
382    }
383
384    Ok(())
385}
386
387/// Uninstall a debugger
388async fn uninstall_debugger(debugger: &str, json: bool) -> Result<()> {
389    let installer = match registry::get_installer(debugger) {
390        Some(i) => i,
391        None => {
392            if json {
393                println!(
394                    "{}",
395                    serde_json::json!({
396                        "status": "not_found",
397                        "debugger": debugger,
398                        "message": format!("Unknown debugger: {}", debugger)
399                    })
400                );
401            } else {
402                println!("Unknown debugger: {}", debugger);
403            }
404            return Ok(());
405        }
406    };
407
408    match installer.uninstall().await {
409        Ok(()) => {
410            if json {
411                println!(
412                    "{}",
413                    serde_json::json!({
414                        "status": "uninstalled",
415                        "debugger": debugger,
416                    })
417                );
418            } else {
419                println!("{} uninstalled", debugger);
420            }
421        }
422        Err(e) => {
423            if json {
424                println!(
425                    "{}",
426                    serde_json::json!({
427                        "status": "error",
428                        "debugger": debugger,
429                        "message": e.to_string(),
430                    })
431                );
432            } else {
433                println!("Failed to uninstall {}: {}", debugger, e);
434            }
435        }
436    }
437
438    Ok(())
439}
440
441/// Install a debugger
442async fn install_debugger(debugger: &str, opts: SetupOptions) -> Result<()> {
443    let result = install_debugger_inner(debugger, &opts).await;
444
445    if opts.json {
446        println!("{}", serde_json::to_string_pretty(&result)?);
447    }
448
449    Ok(())
450}
451
452/// Inner installation logic that returns a result struct
453async fn install_debugger_inner(debugger: &str, opts: &SetupOptions) -> SetupResult {
454    let installer = match registry::get_installer(debugger) {
455        Some(i) => i,
456        None => {
457            return SetupResult {
458                status: SetupStatus::NotFound,
459                debugger: debugger.to_string(),
460                version: None,
461                path: None,
462                languages: None,
463                message: Some(format!("Unknown debugger: {}", debugger)),
464            };
465        }
466    };
467
468    // Check current status
469    let status = match installer.status().await {
470        Ok(s) => s,
471        Err(e) => {
472            return SetupResult {
473                status: SetupStatus::Failed,
474                debugger: debugger.to_string(),
475                version: None,
476                path: None,
477                languages: None,
478                message: Some(format!("Failed to check status: {}", e)),
479            };
480        }
481    };
482
483    // Already installed?
484    if let installer::InstallStatus::Installed { path, version } = &status {
485        if !opts.force {
486            if !opts.json {
487                println!(
488                    "{} is already installed at {}",
489                    debugger,
490                    path.display()
491                );
492                if let Some(v) = version {
493                    println!("Version: {}", v);
494                }
495                println!("Use --force to reinstall.");
496            }
497            return SetupResult {
498                status: SetupStatus::AlreadyInstalled,
499                debugger: debugger.to_string(),
500                version: version.clone(),
501                path: Some(path.clone()),
502                languages: Some(
503                    installer
504                        .info()
505                        .languages
506                        .iter()
507                        .map(|s| s.to_string())
508                        .collect(),
509                ),
510                message: None,
511            };
512        }
513    }
514
515    // Dry run?
516    if opts.dry_run {
517        let method = installer.best_method().await;
518        if !opts.json {
519            println!("Would install {} using:", debugger);
520            match &method {
521                Ok(m) => println!("  Method: {:?}", m),
522                Err(e) => println!("  Error determining method: {}", e),
523            }
524        }
525        return SetupResult {
526            status: SetupStatus::DryRun,
527            debugger: debugger.to_string(),
528            version: opts.version.clone(),
529            path: None,
530            languages: Some(
531                installer
532                    .info()
533                    .languages
534                    .iter()
535                    .map(|s| s.to_string())
536                    .collect(),
537            ),
538            message: Some(format!("Method: {:?}", method)),
539        };
540    }
541
542    // Install
543    if !opts.json {
544        println!("Installing {}...", debugger);
545    }
546
547    let install_opts = installer::InstallOptions {
548        version: opts.version.clone(),
549        force: opts.force,
550    };
551
552    match installer.install(install_opts).await {
553        Ok(result) => {
554            // Update configuration
555            if let Err(e) = update_config(debugger, &result.path, &result.args).await {
556                if !opts.json {
557                    println!("Warning: Failed to update configuration: {}", e);
558                }
559            }
560
561            if !opts.json {
562                println!();
563                println!(
564                    "✓ {} {} installed to {}",
565                    installer.info().name,
566                    result.version.as_deref().unwrap_or(""),
567                    result.path.display()
568                );
569                println!();
570                println!(
571                    "Configuration updated. Use 'debugger start --adapter {} ./program' to debug.",
572                    debugger
573                );
574            }
575
576            SetupResult {
577                status: SetupStatus::Success,
578                debugger: debugger.to_string(),
579                version: result.version,
580                path: Some(result.path),
581                languages: Some(
582                    installer
583                        .info()
584                        .languages
585                        .iter()
586                        .map(|s| s.to_string())
587                        .collect(),
588                ),
589                message: None,
590            }
591        }
592        Err(e) => {
593            if !opts.json {
594                println!("✗ Failed to install {}: {}", debugger, e);
595            }
596            SetupResult {
597                status: SetupStatus::Failed,
598                debugger: debugger.to_string(),
599                version: None,
600                path: None,
601                languages: None,
602                message: Some(e.to_string()),
603            }
604        }
605    }
606}
607
608/// Update the configuration file with the installed adapter
609async fn update_config(debugger: &str, path: &std::path::Path, args: &[String]) -> Result<()> {
610    use crate::common::paths::{config_path, ensure_config_dir};
611    use std::io::Write;
612
613    ensure_config_dir()?;
614
615    let config_file = match config_path() {
616        Some(p) => p,
617        None => return Ok(()),
618    };
619
620    // Read existing config or create new
621    let mut content = if config_file.exists() {
622        std::fs::read_to_string(&config_file)?
623    } else {
624        String::new()
625    };
626
627    // Parse and update
628    let mut config: toml::Table = if content.is_empty() {
629        toml::Table::new()
630    } else {
631        content.parse().map_err(|e| {
632            crate::common::Error::ConfigParse(format!(
633                "Failed to parse {}: {}",
634                config_file.display(),
635                e
636            ))
637        })?
638    };
639
640    // Ensure adapters section exists
641    if !config.contains_key("adapters") {
642        config.insert("adapters".to_string(), toml::Value::Table(toml::Table::new()));
643    }
644
645    let adapters = config
646        .get_mut("adapters")
647        .and_then(|v| v.as_table_mut())
648        .expect("Expected 'adapters' to be a TOML table");
649
650    // Create adapter entry
651    let mut adapter_table = toml::Table::new();
652    adapter_table.insert(
653        "path".to_string(),
654        toml::Value::String(path.display().to_string()),
655    );
656    if !args.is_empty() {
657        adapter_table.insert(
658            "args".to_string(),
659            toml::Value::Array(args.iter().map(|s| toml::Value::String(s.clone())).collect()),
660        );
661    }
662
663    adapters.insert(debugger.to_string(), toml::Value::Table(adapter_table));
664
665    // Write back
666    content = toml::to_string_pretty(&config).unwrap_or_default();
667    let mut file = std::fs::File::create(&config_file)?;
668    file.write_all(content.as_bytes())?;
669
670    Ok(())
671}