Skip to main content

soma_observability/
binary_status.rs

1//! Running-binary freshness checks.
2//!
3//! This catches the common local failure mode where `~/.local/bin/<service>` is
4//! older than the checkout a developer is editing.
5
6use std::ffi::OsStr;
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::time::SystemTime;
10
11#[cfg(test)]
12#[path = "binary_status_tests.rs"]
13mod tests;
14
15const SOURCE_INPUTS: &[&str] = &[
16    "Cargo.toml",
17    "Cargo.lock",
18    "rust-toolchain.toml",
19    ".cargo/config.toml",
20    "src",
21    "config",
22    "migrations",
23    "assets",
24    "apps/web/out",
25];
26
27pub fn stale_binary_warning() -> Option<String> {
28    if std::env::var_os("SOMA_SUPPRESS_STALE_BINARY_WARNING").is_some() {
29        return None;
30    }
31
32    let exe = std::env::current_exe().ok()?;
33    let binary_mtime = fs::metadata(&exe).ok()?.modified().ok()?;
34    stale_binary_warning_at(binary_mtime, Path::new(env!("CARGO_MANIFEST_DIR")))
35}
36
37fn stale_binary_warning_at(binary_mtime: SystemTime, source_root: &Path) -> Option<String> {
38    if !source_root.is_dir() {
39        return None;
40    }
41
42    let newest = SOURCE_INPUTS
43        .iter()
44        .filter_map(|relative| {
45            newest_mtime_for_path(source_root.join(relative)).map(|mtime| (*relative, mtime))
46        })
47        .max_by_key(|(_, mtime)| *mtime)?;
48
49    stale_binary_warning_for(binary_mtime, [newest])
50}
51
52pub fn stale_binary_warning_for<'a>(
53    binary_mtime: SystemTime,
54    inputs: impl IntoIterator<Item = (&'a str, SystemTime)>,
55) -> Option<String> {
56    inputs
57        .into_iter()
58        .filter(|(_, input_mtime)| *input_mtime > binary_mtime)
59        .max_by_key(|(_, input_mtime)| *input_mtime)
60        .map(|(path, _)| warning_message(path))
61}
62
63pub fn warning_message(newer_input: &str) -> String {
64    format!(
65        "outdated soma binary: {newer_input} is newer than the running executable. Rebuild with `cargo build --release --bin soma` and install with `just install-local`."
66    )
67}
68
69fn newest_mtime_for_path(path: PathBuf) -> Option<SystemTime> {
70    let metadata = fs::metadata(&path).ok()?;
71    if metadata.is_file() {
72        return metadata.modified().ok();
73    }
74    if !metadata.is_dir() {
75        return None;
76    }
77
78    let mut newest = metadata.modified().ok();
79    let mut stack = vec![path];
80    while let Some(dir) = stack.pop() {
81        let Ok(entries) = fs::read_dir(dir) else {
82            continue;
83        };
84        for entry in entries.flatten() {
85            let path = entry.path();
86            if should_skip_path(&path) {
87                continue;
88            }
89            let Ok(metadata) = entry.metadata() else {
90                continue;
91            };
92            if metadata.is_dir() {
93                stack.push(path);
94            } else if metadata.is_file() {
95                if let Ok(mtime) = metadata.modified() {
96                    if newest.is_none_or(|current| mtime > current) {
97                        newest = Some(mtime);
98                    }
99                }
100            }
101        }
102    }
103    newest
104}
105
106fn should_skip_path(path: &Path) -> bool {
107    path.file_name()
108        .and_then(OsStr::to_str)
109        .is_some_and(|name| matches!(name, ".git" | "target" | "node_modules"))
110}