Skip to main content

cargo_compatible/
lib.rs

1pub mod cli;
2pub mod compat;
3pub mod explain;
4pub mod index;
5pub mod manifest_edit;
6pub mod metadata;
7pub mod model;
8pub mod report;
9pub mod resolution;
10pub mod temp_workspace;
11
12use anyhow::Result;
13use clap::Parser;
14use cli::{Cli, Commands, OutputFormat, ResolveCommand};
15use compat::analyze_current_workspace;
16use explain::build_explain_report;
17use index::CratesIoIndex;
18use manifest_edit::{apply_manifest_suggestions, suggest_manifest_changes};
19use metadata::{load_workspace, select_packages};
20use report::{
21    render_explain_report, render_manifest_suggestions_report, render_resolve_report,
22    render_scan_report,
23};
24use resolution::{apply_candidate_lockfile, build_candidate_resolution};
25use std::fs;
26use std::path::PathBuf;
27
28pub fn run() -> Result<()> {
29    let cli = Cli::parse();
30    dispatch(cli)
31}
32
33fn dispatch(cli: Cli) -> Result<()> {
34    match cli.command {
35        Commands::Scan(command) => {
36            let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
37            let selection = select_packages(&workspace, &command.selection)?;
38            let report = analyze_current_workspace(&workspace, &selection)?;
39            print_output(command.format, render_scan_report(&report, command.format)?)?;
40        }
41        Commands::Resolve(command) => {
42            let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
43            let selection = select_packages(&workspace, &command.selection)?;
44            let report = build_candidate_resolution(&workspace, &selection, &command)?;
45            if let Some(path) = command.write_report.as_ref() {
46                fs::write(path, serde_json::to_vec_pretty(&report)?)?;
47            }
48            if let Some(path) = command.write_candidate.as_ref() {
49                if let Some(candidate) = report.candidate_lockfile.as_ref() {
50                    persist_candidate(path, candidate)?;
51                }
52            }
53            print_output(
54                command.format,
55                render_resolve_report(&report, command.format)?,
56            )?;
57        }
58        Commands::ApplyLock(command) => {
59            let workspace = load_workspace(command.manifest_path.as_deref())?;
60            let applied = apply_candidate_lockfile(
61                &workspace.workspace_root,
62                command
63                    .candidate_lockfile
64                    .unwrap_or_else(default_candidate_lockfile_path),
65            )?;
66            println!("{applied}");
67        }
68        Commands::SuggestManifest(command) => {
69            let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
70            let selection = select_packages(&workspace, &command.selection)?;
71            let resolution = build_candidate_resolution(
72                &workspace,
73                &selection,
74                &ResolveCommand {
75                    selection: command.selection.clone(),
76                    format: command.format,
77                    write_candidate: None,
78                    write_report: None,
79                },
80            )?;
81            let registry = CratesIoIndex::new()?;
82            let suggestions = suggest_manifest_changes(
83                &workspace,
84                &selection,
85                &resolution,
86                &registry,
87                command.allow_major,
88            )?;
89            if command.write_manifests {
90                apply_manifest_suggestions(&suggestions)?;
91            }
92            print_output(
93                command.format,
94                render_manifest_suggestions_report(
95                    &workspace,
96                    &selection,
97                    &resolution,
98                    &suggestions,
99                    command.format,
100                    command.write_manifests,
101                )?,
102            )?;
103        }
104        Commands::Explain(command) => {
105            let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
106            let selection = select_packages(&workspace, &command.selection)?;
107            let report = build_explain_report(&workspace, &selection, &command)?;
108            print_output(
109                command.format,
110                render_explain_report(&report, command.format)?,
111            )?;
112        }
113    }
114
115    Ok(())
116}
117
118fn persist_candidate(path: &PathBuf, contents: &str) -> Result<()> {
119    if let Some(parent) = path.parent() {
120        fs::create_dir_all(parent)?;
121    }
122    fs::write(path, contents)?;
123    Ok(())
124}
125
126fn print_output(format: OutputFormat, contents: String) -> Result<()> {
127    match format {
128        OutputFormat::Human | OutputFormat::Markdown | OutputFormat::Json => {
129            println!("{contents}");
130        }
131    }
132
133    Ok(())
134}
135
136fn default_candidate_lockfile_path() -> PathBuf {
137    PathBuf::from(".cargo-compatible/candidate/Cargo.lock")
138}