whisker_dev_server/hotpatch/
shim_paths.rs1use anyhow::{Context, Result};
21use std::path::{Path, PathBuf};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ShimPaths {
26 pub rustc_shim: PathBuf,
27 pub linker_shim: PathBuf,
28}
29
30pub fn expected_shim_paths(workspace_root: &Path) -> ShimPaths {
34 let target_dir = std::env::var_os("CARGO_TARGET_DIR")
35 .map(PathBuf::from)
36 .unwrap_or_else(|| workspace_root.join("target"));
37 let bin = |name: &str| target_dir.join("debug").join(exe_name(name));
38 ShimPaths {
39 rustc_shim: bin("whisker-rustc-shim"),
40 linker_shim: bin("whisker-linker-shim"),
41 }
42}
43
44pub fn exe_name(name: &str) -> String {
47 if cfg!(windows) {
48 format!("{name}.exe")
49 } else {
50 name.to_string()
51 }
52}
53
54pub fn resolve_shim_paths(workspace_root: &Path) -> Result<ShimPaths> {
62 let paths = expected_shim_paths(workspace_root);
63 if paths.rustc_shim.is_file() && paths.linker_shim.is_file() {
64 return Ok(paths);
65 }
66 build_shims(workspace_root).context("build whisker-cli shim binaries")?;
67 let paths = expected_shim_paths(workspace_root);
68 anyhow::ensure!(
69 paths.rustc_shim.is_file(),
70 "expected `{}` to exist after `cargo build` of the shims",
71 paths.rustc_shim.display(),
72 );
73 anyhow::ensure!(
74 paths.linker_shim.is_file(),
75 "expected `{}` to exist after `cargo build` of the shims",
76 paths.linker_shim.display(),
77 );
78 Ok(paths)
79}
80
81fn build_shims(workspace_root: &Path) -> Result<()> {
82 let step = whisker_build::ui::step("setup", "whisker-cli shims");
89 let mut cmd = std::process::Command::new("cargo");
90 cmd.args([
91 "build",
92 "-p",
93 "whisker-cli",
94 "--bin",
95 "whisker-rustc-shim",
96 "--bin",
97 "whisker-linker-shim",
98 ])
99 .current_dir(workspace_root);
100 let status = step.pipe(&mut cmd).context("spawn cargo")?;
101 if !status.success() {
102 step.fail(format!("{status}"));
103 anyhow::bail!("cargo exited {status}");
104 }
105 step.done("");
106 Ok(())
107}
108
109#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn exe_name_appends_dot_exe_on_windows_otherwise_passes_through() {
119 let name = exe_name("foo");
120 if cfg!(windows) {
121 assert_eq!(name, "foo.exe");
122 } else {
123 assert_eq!(name, "foo");
124 }
125 }
126
127 #[test]
128 fn expected_paths_default_to_workspace_target_debug() {
129 let p = expected_shim_paths(Path::new("/tmp/ws"));
135 let rustc_basename = p.rustc_shim.file_name().and_then(|n| n.to_str()).unwrap();
136 let linker_basename = p.linker_shim.file_name().and_then(|n| n.to_str()).unwrap();
137 assert_eq!(rustc_basename, exe_name("whisker-rustc-shim"));
138 assert_eq!(linker_basename, exe_name("whisker-linker-shim"));
139 assert!(
140 p.rustc_shim.parent().unwrap().ends_with("debug"),
141 "expected …/debug/, got {}",
142 p.rustc_shim.display(),
143 );
144 assert!(p.linker_shim.parent().unwrap().ends_with("debug"));
145 }
146
147 #[test]
148 fn resolve_returns_existing_paths_without_rebuilding() {
149 let dir = unique_tempdir();
157 let target = dir.join("target");
158 std::fs::create_dir_all(target.join("debug")).unwrap();
159 let rustc = target.join("debug").join(exe_name("whisker-rustc-shim"));
160 let linker = target.join("debug").join(exe_name("whisker-linker-shim"));
161 std::fs::write(&rustc, b"#!/bin/sh\nexit 0\n").unwrap();
162 std::fs::write(&linker, b"#!/bin/sh\nexit 0\n").unwrap();
163
164 let prev = std::env::var_os("CARGO_TARGET_DIR");
167 std::env::set_var("CARGO_TARGET_DIR", &target);
168 let result = resolve_shim_paths(&dir);
169 match prev {
170 Some(p) => std::env::set_var("CARGO_TARGET_DIR", p),
171 None => std::env::remove_var("CARGO_TARGET_DIR"),
172 }
173
174 let paths = result.expect("resolve");
175 assert_eq!(paths.rustc_shim, rustc);
176 assert_eq!(paths.linker_shim, linker);
177
178 let _ = std::fs::remove_dir_all(&dir);
179 }
180
181 fn unique_tempdir() -> PathBuf {
182 use std::sync::atomic::{AtomicU64, Ordering};
183 static SEQ: AtomicU64 = AtomicU64::new(0);
184 let n = SEQ.fetch_add(1, Ordering::Relaxed);
185 let pid = std::process::id();
186 let p = std::env::temp_dir().join(format!("whisker-shim-paths-test-{pid}-{n}"));
187 let _ = std::fs::remove_dir_all(&p);
188 std::fs::create_dir_all(&p).unwrap();
189 p
190 }
191}