use super::super::*;
use super::report::verify_and_report;
use super::types::VerifyContext;
pub(crate) fn check_file_once(
filename: &str,
output_mode: OutputMode,
verbosity: Verbosity,
layer: u8,
) -> bool {
let source = match fs::read_to_string(filename) {
Ok(s) => s,
Err(e) => {
eprintln!("Error: {filename}: {e}");
return true;
}
};
let output = compile(&source, filename);
crate::timing::print_pipeline_timing(
&output,
crate::timing::TimingOptions {
filename,
output_mode,
verbosity,
project: None,
config_line: None,
verify_ms: None,
show_total: false,
show_phase_failures: true,
},
);
let CompilationResult {
file,
resolved: _,
typed,
mut diagnostics,
mut has_errors,
..
} = output;
verify_and_report(VerifyContext {
filename,
source: &source,
typed: &typed,
file: &file,
diagnostics: &mut diagnostics,
has_errors: &mut has_errors,
output_mode,
verbosity,
verify_options: assura_config::VerifyOptions {
layer,
solver: assura_smt::SolverChoice::Z3,
..Default::default()
},
show_cores: false,
});
has_errors
}
pub(crate) fn content_hash(source: &str) -> String {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
source.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
pub(crate) fn run_watch_loop(
filename: &str,
output_mode: OutputMode,
verbosity: Verbosity,
layer: u8,
) -> ! {
use notify::{Event, EventKind, RecursiveMode, Watcher};
let path = Path::new(filename).canonicalize().unwrap_or_else(|e| {
eprintln!("Error: cannot resolve path {filename}: {e}");
process::exit(2);
});
let mut incremental = assura_smt::IncrementalCompiler::new();
let mut last_hash = String::new();
eprintln!("[watch] Checking {filename}...");
eprintln!();
if let Ok(source) = fs::read_to_string(filename) {
last_hash = content_hash(&source);
incremental.register_module(filename.to_string(), last_hash.clone());
}
let _had_errors = check_file_once(filename, output_mode, verbosity, layer);
incremental.mark_checked(filename, 1);
eprintln!();
eprintln!("[watch] Watching {filename} for changes. Press Ctrl+C to stop.");
let (tx, rx) = mpsc::channel();
let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
if let Ok(event) = res {
if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) {
let _ = tx.send(());
}
}
})
.unwrap_or_else(|e| {
eprintln!("Error: failed to create file watcher: {e}");
process::exit(2);
});
let watch_dir = path.parent().unwrap_or(&path);
watcher
.watch(watch_dir, RecursiveMode::NonRecursive)
.unwrap_or_else(|e| {
eprintln!("Error: failed to watch {}: {e}", watch_dir.display());
process::exit(2);
});
let mut iteration: u64 = 2;
loop {
let _ = rx.recv();
while rx.recv_timeout(Duration::from_millis(100)).is_ok() {}
let new_hash = fs::read_to_string(filename)
.map(|s| content_hash(&s))
.unwrap_or_default();
if new_hash == last_hash && !new_hash.is_empty() {
if verbosity == Verbosity::Verbose {
eprintln!("[watch] File saved but content unchanged, skipping re-check.");
}
continue;
}
last_hash = new_hash;
incremental.mark_changed(filename);
if verbosity == Verbosity::Verbose {
let dirty = incremental.dirty_modules();
eprintln!(
"[watch] {} dirty module(s): {}",
dirty.len(),
dirty.join(", ")
);
}
eprint!("\x1B[2J\x1B[H");
eprintln!("[watch] File changed, re-checking {filename}...");
eprintln!();
let _had_errors = check_file_once(filename, output_mode, verbosity, layer);
incremental.mark_checked(filename, iteration);
iteration += 1;
eprintln!();
eprintln!("[watch] Watching for changes. Press Ctrl+C to stop.");
}
}