|
1 | | -use anyhow::Result; |
2 | | -use vergen_gitcl::{Emitter, GitclBuilder}; |
| 1 | +use std::collections::BTreeSet; |
| 2 | +use std::path::{Path, PathBuf}; |
| 3 | + |
| 4 | +use anyhow::{Context, Result}; |
3 | 5 |
|
4 | 6 | pub fn main() -> Result<()> { |
5 | | - Emitter::default() |
6 | | - .add_instructions( |
7 | | - &GitclBuilder::default() |
8 | | - .describe(true, false, None) |
9 | | - .build()?, |
10 | | - )? |
11 | | - .emit()?; |
| 7 | + // Compute checksum of pavexc_rustdoc_cache and its local dependencies. |
| 8 | + // This checksum is used as part of the cache fingerprint to ensure |
| 9 | + // the cache invalidates when the caching logic or serialized types change. |
| 10 | + let base_path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); |
| 11 | + let cache_crate_path = base_path.join("pavexc_rustdoc_cache"); |
| 12 | + |
| 13 | + // Find all local crates that pavexc_rustdoc_cache depends on (transitively) |
| 14 | + let crates_to_checksum = collect_local_dependencies(&cache_crate_path)?; |
| 15 | + |
| 16 | + let mut combined_hasher = xxhash_rust::xxh64::Xxh64::new(24); |
| 17 | + for crate_path in &crates_to_checksum { |
| 18 | + let checksum = checksum_directory(crate_path)?; |
| 19 | + combined_hasher.update(&checksum.to_le_bytes()); |
| 20 | + |
| 21 | + // Rerun if any of these crates change |
| 22 | + println!("cargo::rerun-if-changed={}/src", crate_path.display()); |
| 23 | + println!("cargo::rerun-if-changed={}/Cargo.toml", crate_path.display()); |
| 24 | + } |
| 25 | + |
| 26 | + let checksum = combined_hasher.digest(); |
| 27 | + println!("cargo::rustc-env=RUSTDOC_CACHE_SOURCE_HASH={checksum:x}"); |
| 28 | + |
12 | 29 | Ok(()) |
13 | 30 | } |
| 31 | + |
| 32 | +/// Collect all local path dependencies of a crate, including the crate itself. |
| 33 | +/// This is done recursively to capture transitive local dependencies. |
| 34 | +fn collect_local_dependencies(crate_path: &Path) -> Result<BTreeSet<PathBuf>> { |
| 35 | + let mut visited = BTreeSet::new(); |
| 36 | + let mut to_visit = vec![crate_path.to_path_buf()]; |
| 37 | + |
| 38 | + while let Some(current) = to_visit.pop() { |
| 39 | + let canonical = current |
| 40 | + .canonicalize() |
| 41 | + .with_context(|| format!("Failed to canonicalize path: {}", current.display()))?; |
| 42 | + |
| 43 | + if !visited.insert(canonical.clone()) { |
| 44 | + continue; |
| 45 | + } |
| 46 | + |
| 47 | + // Parse Cargo.toml to find path dependencies |
| 48 | + let cargo_toml_path = canonical.join("Cargo.toml"); |
| 49 | + let cargo_toml_content = std::fs::read_to_string(&cargo_toml_path) |
| 50 | + .with_context(|| format!("Failed to read {}", cargo_toml_path.display()))?; |
| 51 | + |
| 52 | + let cargo_toml: toml::Table = toml::from_str(&cargo_toml_content) |
| 53 | + .with_context(|| format!("Failed to parse {}", cargo_toml_path.display()))?; |
| 54 | + |
| 55 | + // Check [dependencies] section for path dependencies |
| 56 | + if let Some(toml::Value::Table(deps)) = cargo_toml.get("dependencies") { |
| 57 | + for (_name, value) in deps { |
| 58 | + if let Some(path) = value.get("path").and_then(|p| p.as_str()) { |
| 59 | + let dep_path = canonical.join(path); |
| 60 | + if dep_path.exists() { |
| 61 | + to_visit.push(dep_path); |
| 62 | + } |
| 63 | + } |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + Ok(visited) |
| 69 | +} |
| 70 | + |
| 71 | +/// Checksum the contents of a crate directory. |
| 72 | +fn checksum_directory(root_path: &Path) -> Result<u64> { |
| 73 | + let paths = get_file_paths(root_path)?; |
| 74 | + |
| 75 | + let mut hasher = xxhash_rust::xxh64::Xxh64::new(24); |
| 76 | + for path in paths { |
| 77 | + let contents = std::fs::read(&path) |
| 78 | + .with_context(|| format!("Failed to read file at `{}`", path.display()))?; |
| 79 | + hasher.update(&contents); |
| 80 | + // Include the file path in the hash to detect renames |
| 81 | + if let Ok(relative) = path.strip_prefix(root_path) { |
| 82 | + hasher.update(relative.to_string_lossy().as_bytes()); |
| 83 | + } |
| 84 | + } |
| 85 | + Ok(hasher.digest()) |
| 86 | +} |
| 87 | + |
| 88 | +/// Get all source files in a crate directory. |
| 89 | +fn get_file_paths(root_dir: &Path) -> Result<BTreeSet<PathBuf>> { |
| 90 | + let root_dir = root_dir |
| 91 | + .canonicalize() |
| 92 | + .context("Failed to canonicalize the path to the root directory")?; |
| 93 | + |
| 94 | + let patterns = vec!["src/**/*.rs", "Cargo.toml"]; |
| 95 | + |
| 96 | + let glob_walker = globwalk::GlobWalkerBuilder::from_patterns(&root_dir, &patterns).build()?; |
| 97 | + |
| 98 | + let included_files: BTreeSet<PathBuf> = glob_walker |
| 99 | + .into_iter() |
| 100 | + .filter_map(|entry| { |
| 101 | + let Ok(entry) = entry else { |
| 102 | + return None; |
| 103 | + }; |
| 104 | + if !entry.file_type().is_file() { |
| 105 | + return None; |
| 106 | + } |
| 107 | + Some(entry.into_path()) |
| 108 | + }) |
| 109 | + .collect(); |
| 110 | + Ok(included_files) |
| 111 | +} |
0 commit comments