use anyhow::{Context, Result};
use std::path::Path;
use std::process::Command;
use super::wrapper::CapturedRustcInvocation;
pub fn validate_environment(
captured: &CapturedRustcInvocation,
current_rustc: &Path,
) -> Result<()> {
if let Some(triple) = extract_target_triple(&captured.args) {
ensure_target_supported(current_rustc, &triple)?;
}
Ok(())
}
pub fn extract_target_triple(args: &[String]) -> Option<String> {
let mut iter = args.iter();
while let Some(arg) = iter.next() {
if arg == "--target" {
return iter.next().cloned();
}
if let Some(rest) = arg.strip_prefix("--target=") {
return Some(rest.to_string());
}
}
None
}
pub fn ensure_target_supported(rustc: &Path, triple: &str) -> Result<()> {
let output = Command::new(rustc)
.args(["--print=target-list"])
.output()
.with_context(|| format!("spawn `{} --print=target-list`", rustc.display()))?;
if !output.status.success() {
anyhow::bail!(
"`{} --print=target-list` exited {}",
rustc.display(),
output.status,
);
}
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.lines().any(|line| line.trim() == triple) {
Ok(())
} else {
anyhow::bail!(
"rustc at {} doesn't support target triple `{triple}` \
— check `rustup target list --installed` and re-run \
the fat build under the same toolchain",
rustc.display(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|s| s.to_string()).collect()
}
fn captured_with(args: Vec<String>) -> CapturedRustcInvocation {
CapturedRustcInvocation {
crate_name: "demo".into(),
args,
timestamp_micros: 0,
}
}
fn rustc_path() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()))
}
fn host_triple() -> String {
let out = Command::new(rustc_path())
.args(["-vV"])
.output()
.expect("rustc -vV");
let stdout = String::from_utf8_lossy(&out.stdout);
stdout
.lines()
.find_map(|l| l.strip_prefix("host: "))
.map(|s| s.trim().to_string())
.expect("rustc -vV reports a host:")
}
#[test]
fn extract_target_triple_from_separated_form() {
let args = s(&[
"--edition=2021",
"--target",
"aarch64-apple-darwin",
"src/lib.rs",
]);
assert_eq!(
extract_target_triple(&args).as_deref(),
Some("aarch64-apple-darwin")
);
}
#[test]
fn extract_target_triple_from_equals_form() {
let args = s(&["--target=x86_64-unknown-linux-gnu"]);
assert_eq!(
extract_target_triple(&args).as_deref(),
Some("x86_64-unknown-linux-gnu")
);
}
#[test]
fn extract_target_triple_returns_none_when_absent() {
let args = s(&["--edition=2021", "src/lib.rs"]);
assert_eq!(extract_target_triple(&args), None);
}
#[test]
fn ensure_target_supported_accepts_the_host_triple() {
let triple = host_triple();
ensure_target_supported(&rustc_path(), &triple).expect("host triple supported");
}
#[test]
fn ensure_target_supported_rejects_a_made_up_triple() {
let result = ensure_target_supported(&rustc_path(), "totally-not-a-real-triple-9999");
assert!(result.is_err());
let msg = format!("{:#}", result.unwrap_err());
assert!(msg.contains("doesn't support target triple"), "got: {msg}",);
}
#[test]
fn ensure_target_supported_surfaces_a_missing_rustc_as_err() {
let result = ensure_target_supported(
std::path::Path::new("/no/such/rustc/anywhere"),
"any-triple",
);
assert!(result.is_err());
}
#[test]
fn validate_passes_when_no_target_triple_in_args() {
let captured = captured_with(s(&["--edition=2021", "src/lib.rs"]));
validate_environment(&captured, &rustc_path()).expect("ok");
}
#[test]
fn validate_passes_with_the_host_triple() {
let triple = host_triple();
let captured = captured_with(s(&["--target", &triple, "src/lib.rs"]));
validate_environment(&captured, &rustc_path()).expect("ok");
}
#[test]
fn validate_fails_when_target_triple_is_unsupported() {
let captured = captured_with(s(&["--target", "made-up-arch", "src/lib.rs"]));
let err = validate_environment(&captured, &rustc_path()).unwrap_err();
let msg = format!("{:#}", err);
assert!(msg.contains("made-up-arch"), "{msg}");
}
}