Skip to main content

cargo_ff/
lib.rs

1pub mod types;
2
3mod cache;
4mod coalesce;
5mod discover;
6mod dispatch;
7mod exec;
8mod report;
9mod size;
10
11#[cfg(feature = "cli")]
12pub mod cli;
13
14pub use types::{Config, Edition, Error, FileFailure, Report, Result};
15
16#[doc(hidden)]
17pub mod __test_only {
18    use crate::types::{Config, CrateUnit, Result};
19    use crossbeam_channel::Sender;
20
21    pub fn discover_run(cfg: &Config, tx: Sender<CrateUnit>) -> Result<()> {
22        crate::discover::run(cfg, tx).map(drop)
23    }
24}
25
26use crossbeam_channel::bounded;
27use std::sync::Arc;
28use std::thread::{self, JoinHandle};
29
30pub fn run(cfg: &Config) -> Result<Report> {
31    if matches!(cfg.workers, Some(0)) {
32        return Err(Error::InvalidWorkers(0));
33    }
34
35    if cfg.warnings {
36        warn_if_stable_rustfmt();
37    }
38
39    let n = cfg
40        .workers
41        .or_else(|| thread::available_parallelism().ok().map(|p| p.get()))
42        .unwrap_or(1);
43    let cap = cfg.channel_capacity.unwrap_or(512);
44    let batch_size = cfg.batch_size.unwrap_or(3);
45    // Same cutoff used by the size proxy: any crate the proxy clamped
46    // to `HUGE_CUTOFF_BYTES` is by definition the threshold or above,
47    // so the comparison `>= HUGE_CUTOFF_BYTES` exactly catches them.
48    let solo_threshold = size::HUGE_CUTOFF_BYTES;
49
50    let (unit_tx, unit_rx) = bounded::<types::CrateUnit>(cap);
51    let queue = Arc::new(dispatch::PriorityQueue::new());
52    let (result_tx, result_rx) = bounded::<types::BatchResult>(cap);
53
54    let cfg_d = cfg.clone();
55    let producer = thread::spawn(move || discover::run(&cfg_d, unit_tx));
56
57    // Coalescer pushes batches into `queue`; closes it on exit so
58    // workers' `pop()` returns `None` once everything is drained.
59    let coalescer_q = queue.clone();
60    let coalescer = thread::spawn(move || {
61        let r = coalesce::run(unit_rx, &coalescer_q, batch_size, 4, solo_threshold);
62        coalescer_q.close();
63        r
64    });
65
66    let mut workers = Vec::with_capacity(n);
67    for _ in 0..n {
68        let q = queue.clone();
69        let tx = result_tx.clone();
70        let cfg_w = cfg.clone();
71        workers.push(thread::spawn(move || exec::worker(q, tx, &cfg_w)));
72    }
73    drop(result_tx);
74
75    let report = report::aggregate(result_rx);
76
77    let cache_opt = join_fallible(producer, "producer")?;
78    join_fallible(coalescer, "coalescer")?;
79    for w in workers {
80        join_void(w, "worker")?;
81    }
82
83    // Commit the skip cache. Drop pending entries for crates that had
84    // any --check failure so future runs re-fingerprint and surface them.
85    if let Some(mut cache) = cache_opt {
86        for f in &report.failures {
87            cache.invalidate(&f.manifest_dir);
88        }
89        let _ = cache.commit_and_save();
90    }
91
92    Ok(report)
93}
94
95fn join_fallible<T>(h: JoinHandle<Result<T>>, name: &'static str) -> Result<T> {
96    h.join().map_err(|_| Error::ThreadPanicked(name))?
97}
98
99fn join_void(h: JoinHandle<()>, name: &'static str) -> Result<()> {
100    h.join().map_err(|_| Error::ThreadPanicked(name))
101}
102
103/// `rustfmt --version` prints e.g. `rustfmt 1.8.0-nightly (…)`. If the
104/// `nightly` marker is absent, unstable `rustfmt.toml` options are silently
105/// dropped and output diverges from `cargo +nightly fmt`.
106fn warn_if_stable_rustfmt() {
107    use std::io::Write;
108    let Ok(out) = std::process::Command::new("rustfmt")
109        .arg("--version")
110        .output()
111    else {
112        return;
113    };
114    if !out.status.success() {
115        return;
116    }
117    let v = String::from_utf8_lossy(&out.stdout);
118    if !v.contains("nightly") {
119        let _ = writeln!(
120            std::io::stderr(),
121            "warning: rustfmt on PATH appears to be the stable channel ({}); \
122             unstable rustfmt.toml options will be silently ignored. \
123             Run via `cargo +nightly ff` for parity with `cargo +nightly fmt`.",
124            v.trim(),
125        );
126    }
127}