use std::path::Path;
use std::process::Command;
use anyhow::{Context, Result, bail};
pub fn run(command: &str, artifact: &Path) -> Result<()> {
let abs = std::path::absolute(artifact)
.with_context(|| format!("resolving {}", artifact.display()))?;
let cmd = command.replace("$f", "e(&abs.to_string_lossy()));
let status = if cfg!(windows) {
Command::new("cmd").args(["/C", &cmd]).status()
} else {
Command::new("sh").args(["-c", &cmd]).status()
}
.with_context(|| format!("spawning sign command: {cmd}"))?;
if !status.success() {
let code = status
.code()
.map_or_else(|| "signal".to_string(), |c| c.to_string());
bail!(
"sign command failed for {} (exit {code}): {command}",
artifact.display()
);
}
println!("sign: {}", artifact.display());
Ok(())
}
#[cfg(unix)]
fn quote(s: &str) -> String {
format!("'{}'", s.replace('\'', r"'\''"))
}
#[cfg(windows)]
fn quote(s: &str) -> String {
format!("\"{s}\"")
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
fn scratch(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("embala-sign-{}-{name}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn substitutes_f_with_quoted_path_including_spaces() {
let artifact = scratch("subst").join("with space.txt");
std::fs::write(&artifact, b"x").unwrap();
run("printf M >> $f", &artifact).unwrap();
assert_eq!(std::fs::read(&artifact).unwrap(), b"xM");
}
#[test]
fn noop_command_succeeds() {
run("true", Path::new("/nonexistent/artifact")).unwrap();
}
#[test]
fn failing_command_surfaces_an_error() {
let err = run("false", Path::new("/nonexistent/artifact"))
.unwrap_err()
.to_string();
assert!(err.contains("sign command failed"), "{err}");
}
}