whisker_dev_server/hotpatch/
validate.rs1use anyhow::{Context, Result};
27use std::path::Path;
28use std::process::Command;
29
30use super::wrapper::CapturedRustcInvocation;
31
32pub fn validate_environment(
35 captured: &CapturedRustcInvocation,
36 current_rustc: &Path,
37) -> Result<()> {
38 if let Some(triple) = extract_target_triple(&captured.args) {
39 ensure_target_supported(current_rustc, &triple)?;
40 }
41 Ok(())
42}
43
44pub fn extract_target_triple(args: &[String]) -> Option<String> {
48 let mut iter = args.iter();
49 while let Some(arg) = iter.next() {
50 if arg == "--target" {
51 return iter.next().cloned();
52 }
53 if let Some(rest) = arg.strip_prefix("--target=") {
54 return Some(rest.to_string());
55 }
56 }
57 None
58}
59
60pub fn ensure_target_supported(rustc: &Path, triple: &str) -> Result<()> {
64 let output = Command::new(rustc)
65 .args(["--print=target-list"])
66 .output()
67 .with_context(|| format!("spawn `{} --print=target-list`", rustc.display()))?;
68 if !output.status.success() {
69 anyhow::bail!(
70 "`{} --print=target-list` exited {}",
71 rustc.display(),
72 output.status,
73 );
74 }
75 let stdout = String::from_utf8_lossy(&output.stdout);
76 if stdout.lines().any(|line| line.trim() == triple) {
77 Ok(())
78 } else {
79 anyhow::bail!(
80 "rustc at {} doesn't support target triple `{triple}` \
81 — check `rustup target list --installed` and re-run \
82 the fat build under the same toolchain",
83 rustc.display(),
84 )
85 }
86}
87
88#[cfg(test)]
93mod tests {
94 use super::*;
95
96 fn s(v: &[&str]) -> Vec<String> {
97 v.iter().map(|s| s.to_string()).collect()
98 }
99
100 fn captured_with(args: Vec<String>) -> CapturedRustcInvocation {
101 CapturedRustcInvocation {
102 crate_name: "demo".into(),
103 args,
104 timestamp_micros: 0,
105 }
106 }
107
108 fn rustc_path() -> std::path::PathBuf {
109 std::path::PathBuf::from(std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()))
110 }
111
112 fn host_triple() -> String {
116 let out = Command::new(rustc_path())
117 .args(["-vV"])
118 .output()
119 .expect("rustc -vV");
120 let stdout = String::from_utf8_lossy(&out.stdout);
121 stdout
122 .lines()
123 .find_map(|l| l.strip_prefix("host: "))
124 .map(|s| s.trim().to_string())
125 .expect("rustc -vV reports a host:")
126 }
127
128 #[test]
131 fn extract_target_triple_from_separated_form() {
132 let args = s(&[
133 "--edition=2021",
134 "--target",
135 "aarch64-apple-darwin",
136 "src/lib.rs",
137 ]);
138 assert_eq!(
139 extract_target_triple(&args).as_deref(),
140 Some("aarch64-apple-darwin")
141 );
142 }
143
144 #[test]
145 fn extract_target_triple_from_equals_form() {
146 let args = s(&["--target=x86_64-unknown-linux-gnu"]);
147 assert_eq!(
148 extract_target_triple(&args).as_deref(),
149 Some("x86_64-unknown-linux-gnu")
150 );
151 }
152
153 #[test]
154 fn extract_target_triple_returns_none_when_absent() {
155 let args = s(&["--edition=2021", "src/lib.rs"]);
156 assert_eq!(extract_target_triple(&args), None);
157 }
158
159 #[test]
162 fn ensure_target_supported_accepts_the_host_triple() {
163 let triple = host_triple();
168 ensure_target_supported(&rustc_path(), &triple).expect("host triple supported");
169 }
170
171 #[test]
172 fn ensure_target_supported_rejects_a_made_up_triple() {
173 let result = ensure_target_supported(&rustc_path(), "totally-not-a-real-triple-9999");
174 assert!(result.is_err());
175 let msg = format!("{:#}", result.unwrap_err());
176 assert!(msg.contains("doesn't support target triple"), "got: {msg}",);
177 }
178
179 #[test]
180 fn ensure_target_supported_surfaces_a_missing_rustc_as_err() {
181 let result = ensure_target_supported(
182 std::path::Path::new("/no/such/rustc/anywhere"),
183 "any-triple",
184 );
185 assert!(result.is_err());
186 }
187
188 #[test]
191 fn validate_passes_when_no_target_triple_in_args() {
192 let captured = captured_with(s(&["--edition=2021", "src/lib.rs"]));
194 validate_environment(&captured, &rustc_path()).expect("ok");
195 }
196
197 #[test]
198 fn validate_passes_with_the_host_triple() {
199 let triple = host_triple();
200 let captured = captured_with(s(&["--target", &triple, "src/lib.rs"]));
201 validate_environment(&captured, &rustc_path()).expect("ok");
202 }
203
204 #[test]
205 fn validate_fails_when_target_triple_is_unsupported() {
206 let captured = captured_with(s(&["--target", "made-up-arch", "src/lib.rs"]));
207 let err = validate_environment(&captured, &rustc_path()).unwrap_err();
208 let msg = format!("{:#}", err);
209 assert!(msg.contains("made-up-arch"), "{msg}");
210 }
211}