|
| 1 | +//! Derive a content identity for this native build so local caches notice source changes. |
| 2 | +//! |
| 3 | +//! The identity hashes every file under the listed crate entries plus the workspace lockfile, |
| 4 | +//! keyed by `/`-separated paths relative to the crate root so it matches across platforms. |
| 5 | +//! Entries that do not exist (for example the lockfile outside a workspace checkout) are left |
| 6 | +//! out of both the hash and the `rerun-if-changed` list, because Cargo reruns a build script on |
| 7 | +//! every build when a watched path is missing. |
| 8 | +
|
| 9 | +use sha2::{Digest, Sha256}; |
| 10 | +use std::path::{Component, Path, PathBuf}; |
| 11 | + |
| 12 | +const HASHED_ENTRIES: [&str; 3] = ["Cargo.toml", "src", "function_names"]; |
| 13 | +const WORKSPACE_LOCKFILE: &str = "../../Cargo.lock"; |
| 14 | + |
| 15 | +fn main() -> Result<(), String> { |
| 16 | + let crate_dir = |
| 17 | + PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").map_err(|error| error.to_string())?); |
| 18 | + let mut files: Vec<(String, PathBuf)> = Vec::new(); |
| 19 | + for entry in HASHED_ENTRIES { |
| 20 | + let path = crate_dir.join(entry); |
| 21 | + if path.exists() { |
| 22 | + println!("cargo:rerun-if-changed={}", path.display()); |
| 23 | + collect_files(&crate_dir, &path, &mut files)?; |
| 24 | + } |
| 25 | + } |
| 26 | + files.sort(); |
| 27 | + let lockfile = crate_dir.join(WORKSPACE_LOCKFILE); |
| 28 | + if lockfile.is_file() { |
| 29 | + println!("cargo:rerun-if-changed={}", lockfile.display()); |
| 30 | + files.push((WORKSPACE_LOCKFILE.to_owned(), lockfile)); |
| 31 | + } |
| 32 | + let mut digest = Sha256::new(); |
| 33 | + for (label, file) in &files { |
| 34 | + digest.update(label.as_bytes()); |
| 35 | + digest.update([0]); |
| 36 | + digest.update(std::fs::read(file).map_err(|error| error.to_string())?); |
| 37 | + digest.update([0]); |
| 38 | + } |
| 39 | + let identity = format!("{:x}", digest.finalize()); |
| 40 | + println!( |
| 41 | + "cargo:rustc-env=SQLBUILD_NATIVE_SOURCE_HASH={}", |
| 42 | + &identity[..16] |
| 43 | + ); |
| 44 | + Ok(()) |
| 45 | +} |
| 46 | + |
| 47 | +fn portable_label(crate_dir: &Path, path: &Path) -> Result<String, String> { |
| 48 | + let relative = path |
| 49 | + .strip_prefix(crate_dir) |
| 50 | + .map_err(|error| error.to_string())?; |
| 51 | + let parts: Vec<String> = relative |
| 52 | + .components() |
| 53 | + .map(|component| match component { |
| 54 | + Component::Normal(part) => Ok(part.to_string_lossy().into_owned()), |
| 55 | + other => Err(format!("unexpected path component {other:?}")), |
| 56 | + }) |
| 57 | + .collect::<Result<_, _>>()?; |
| 58 | + Ok(parts.join("/")) |
| 59 | +} |
| 60 | + |
| 61 | +fn collect_files( |
| 62 | + crate_dir: &Path, |
| 63 | + path: &Path, |
| 64 | + files: &mut Vec<(String, PathBuf)>, |
| 65 | +) -> Result<(), String> { |
| 66 | + if path.is_file() { |
| 67 | + files.push((portable_label(crate_dir, path)?, path.to_path_buf())); |
| 68 | + return Ok(()); |
| 69 | + } |
| 70 | + if !path.is_dir() { |
| 71 | + return Ok(()); |
| 72 | + } |
| 73 | + for entry in std::fs::read_dir(path).map_err(|error| error.to_string())? { |
| 74 | + collect_files( |
| 75 | + crate_dir, |
| 76 | + &entry.map_err(|error| error.to_string())?.path(), |
| 77 | + files, |
| 78 | + )?; |
| 79 | + } |
| 80 | + Ok(()) |
| 81 | +} |
0 commit comments