ferro_cli/doctor/checks/
toolchain.rs1use crate::doctor::check::{CheckResult, DoctorCheck};
7use crate::project::resolve_rust_base_image;
8use std::path::Path;
9use std::process::Command;
10
11pub struct ToolchainCheck;
12
13const NAME: &str = "toolchain_match";
14const DEFAULT_IMAGE: &str = "rust:1.88-slim-bookworm";
15
16impl DoctorCheck for ToolchainCheck {
17 fn name(&self) -> &'static str {
18 NAME
19 }
20 fn run(&self, root: &Path) -> CheckResult {
21 check_impl(root)
22 }
23}
24
25pub(crate) fn check_impl(root: &Path) -> CheckResult {
26 let image = resolve_rust_base_image(root);
27 let declared = if image == DEFAULT_IMAGE && !root.join("rust-toolchain.toml").exists() {
28 None
29 } else {
30 image
32 .strip_prefix("rust:")
33 .and_then(|s| s.strip_suffix("-slim-bookworm"))
34 .map(|s| s.to_string())
35 };
36
37 let installed = match Command::new("rustc").arg("--version").output() {
38 Ok(out) if out.status.success() => String::from_utf8_lossy(&out.stdout).trim().to_string(),
39 Ok(_) => return CheckResult::error(NAME, "rustc invocation failed"),
40 Err(_) => return CheckResult::error(NAME, "rustc not found in PATH"),
41 };
42
43 match declared {
44 None => CheckResult::warn(NAME, format!("{installed}; rust-toolchain.toml missing"))
45 .with_details("Declare a pinned channel for reproducible builds"),
46 Some(channel) => {
47 if installed.contains(&channel) {
48 CheckResult::ok(NAME, format!("{installed} matches channel {channel}"))
49 } else {
50 CheckResult::warn(
51 NAME,
52 format!("installed {installed} differs from declared channel {channel}"),
53 )
54 }
55 }
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use tempfile::TempDir;
63
64 #[test]
65 fn name_is_toolchain() {
66 assert_eq!(ToolchainCheck.name(), "toolchain_match");
67 }
68
69 #[test]
70 fn missing_rust_toolchain_warns_gracefully() {
71 let tmp = TempDir::new().unwrap();
72 let result = check_impl(tmp.path());
73 assert_eq!(result.name, "toolchain_match");
75 }
78}