use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
fn write(path: &Path, content: &str) {
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, content).unwrap();
}
fn workspace(root: &Path) {
write(
&root.join("Cargo.toml"),
"[workspace]\nmembers = [\"app\", \"regular\", \"bdep\"]\nresolver = \"2\"\n",
);
write(
&root.join("regular/Cargo.toml"),
"[package]\nname = \"regular\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
);
write(
&root.join("regular/src/lib.rs"),
"pub fn regular() -> u32 { 7 }\n",
);
write(
&root.join("bdep/Cargo.toml"),
"[package]\nname = \"bdep\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
);
write(&root.join("bdep/src/lib.rs"), "pub fn answer() -> u32 { 42 }\n");
write(
&root.join("app/Cargo.toml"),
"[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nregular = { path = \"../regular\" }\n\n[build-dependencies]\nbdep = { path = \"../bdep\" }\n",
);
write(
&root.join("app/build.rs"),
"fn main() {\n println!(\"cargo:rustc-env=BDEP_ANSWER={}\", bdep::answer());\n println!(\"cargo:rerun-if-changed=build.rs\");\n}\n",
);
write(
&root.join("app/src/main.rs"),
"fn main() { println!(\"{} {}\", env!(\"BDEP_ANSWER\"), regular::regular()); }\n",
);
}
fn mitts_build(root: &Path, cache: &Path) -> (bool, String) {
let out = Command::new(env!("CARGO_BIN_EXE_mitts"))
.arg("build")
.arg("--manifest-path")
.arg(root.join("app/Cargo.toml"))
.arg("--cache-dir")
.arg(cache)
.env("MITTS_TIMINGS", "1")
.env("MITTS_LOG_COMPILE", "1")
.env("CI", "true")
.output()
.unwrap();
(
out.status.success(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
fn script_events(stderr: &str, event: &str) -> Vec<String> {
stderr
.lines()
.filter_map(|l| l.strip_prefix(&format!("[mitts::build_script] {event} ")))
.map(str::to_owned)
.collect()
}
#[test]
fn build_script_key_and_externs_use_build_dependencies_only() {
let temp = TempDir::new().unwrap();
let root = temp.path().join("ws");
workspace(&root);
let cache = temp.path().join("cache");
let (ok, cold) = mitts_build(&root, &cache);
assert!(ok, "cold build failed:\n{cold}");
let key_line = cold
.lines()
.find(|l| l.starts_with("[mitts::build_script_key] app "))
.unwrap_or_else(|| panic!("no key line for app:\n{cold}"));
assert!(
key_line.contains("build_dep:bdep="),
"the build-dependency must key the script: {key_line}"
);
assert!(
!key_line.contains("build_dep:regular="),
"a regular dependency must not key the script: {key_line}"
);
assert!(
cold.lines()
.any(|l| l.starts_with("[mitts::build_script] miss app ")),
"the cold build must run app's script:\n{cold}"
);
let (ok, warm) = mitts_build(&root, &cache);
assert!(ok, "warm build failed:\n{warm}");
let misses = script_events(&warm, "miss");
assert!(
misses.is_empty(),
"a warm build must not re-run any build script, re-ran: {misses:?}\n{warm}"
);
assert!(
script_events(&warm, "hit")
.iter()
.any(|l| l.starts_with("app ")),
"app's script must be served from the cache:\n{warm}"
);
}