diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index e4251a4..1a90414 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -12,7 +12,7 @@ use crate::{ }; use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD}; use clap::builder::OsStringValueParser; -use eyre::{Context, bail, ensure, eyre}; +use eyre::{Context, ensure, eyre}; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{DeserializeOwned, Error}, @@ -26,6 +26,7 @@ use std::{ io::Write, marker::PhantomData, path::{Path, PathBuf}, + process::ExitStatus, sync::OnceLock, }; @@ -365,13 +366,17 @@ impl ExternalJobCaching { .stdin(std::process::Stdio::null()); Ok(cmd) } - pub fn run eyre::Result<()>>( + pub fn run( self, command_line: Interned<[Interned]>, input_file_paths: impl IntoIterator>, output_file_paths: impl IntoIterator> + Clone, run_fn: F, - ) -> eyre::Result<()> { + exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report, + ) -> eyre::Result<()> + where + F: FnOnce(std::process::Command) -> eyre::Result>, + { let mut hasher = JobCacheHasher::default(); hasher.hash_iter(command_line.iter(), |hasher, arg| { hasher.hash_sized_os_str(arg) @@ -419,7 +424,26 @@ impl ExternalJobCaching { }) .expect("spawn shouldn't fail"); run_fn(cmd) - }); + })?; + if let Err(exit_status) = result { + // check if the user may have terminated it or something, don't cache the failure + let user_maybe_terminated; + #[cfg(unix)] + { + user_maybe_terminated = std::os::unix::process::ExitStatusExt::signal(&exit_status) + .is_some() + || exit_status.code().is_none_or(|code| code > 1); + } + #[cfg(not(unix))] + { + user_maybe_terminated = !exit_status.success(); + } + if user_maybe_terminated { + let _ = std::fs::remove_file(self.cache_json_path); + return Err(exit_status_to_error(exit_status)); + } + } + let result = result.map_err(exit_status_to_error); ExternalJobCacheV2 { version: ExternalJobCacheVersion::CURRENT, inputs_hash, @@ -444,16 +468,26 @@ impl ExternalJobCaching { .write_to_file(self.cache_json_path)?; result } - pub fn run_maybe_cached eyre::Result<()>>( + pub fn run_maybe_cached( this: Option, command_line: Interned<[Interned]>, input_file_paths: impl IntoIterator>, output_file_paths: impl IntoIterator> + Clone, run_fn: F, - ) -> eyre::Result<()> { + exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report, + ) -> eyre::Result<()> + where + F: FnOnce(std::process::Command) -> eyre::Result>, + { match this { - Some(this) => this.run(command_line, input_file_paths, output_file_paths, run_fn), - None => run_fn(Self::make_command(command_line)?), + Some(this) => this.run( + command_line, + input_file_paths, + output_file_paths, + run_fn, + exit_status_to_error, + ), + None => run_fn(Self::make_command(command_line)?)?.map_err(exit_status_to_error), } } } @@ -1119,10 +1153,12 @@ impl JobKind for ExternalCommandJobKind { } let status = acquired_job.run_command(cmd, |cmd| cmd.status())?; if !status.success() { - bail!("running {command_line:?} failed: {status}") + Ok(Err(status)) + } else { + Ok(Ok(())) } - Ok(()) }, + |status| eyre!("running {command_line:?} failed: {status}"), )?; Ok(job .output_paths()