use core::str;
use std::{collections::BTreeSet, path::PathBuf};
use anyhow::Context;
use camino::Utf8Path;
#[tracing::instrument("Checksum crate files", level = tracing::Level::DEBUG)]
pub(crate) fn checksum_crate(root_path: &Utf8Path) -> Result<u64, anyhow::Error> {
let paths = get_file_paths(root_path)?;
let mut hasher = xxhash_rust::xxh64::Xxh64::new(24);
for path in paths {
let contents = std::fs::read(&path)
.with_context(|| format!("Failed to read file at `{}`", path.display()))?;
hasher.update(&contents);
}
Ok(hasher.digest())
}
fn get_file_paths(root_dir: &Utf8Path) -> Result<BTreeSet<PathBuf>, anyhow::Error> {
#[derive(serde::Deserialize)]
struct CargoManifest {
package: Package,
}
#[derive(serde::Deserialize)]
struct Package {
include: Option<Vec<String>>,
exclude: Option<Vec<String>>,
}
let root_dir = root_dir
.canonicalize()
.context("Failed to canonicalize the path to the root directory")?;
let toml_content = fs_err::read_to_string(root_dir.join("Cargo.toml"))?;
let manifest: CargoManifest = toml::from_str(&toml_content)?;
let default_include = vec![
"src/**".to_string(),
"Cargo.toml".to_string(),
];
let CargoManifest {
package: Package { include, exclude },
} = manifest;
let include_patterns = include.unwrap_or(default_include);
let exclude_patterns = exclude.unwrap_or_default();
let patterns: Vec<_> = include_patterns
.into_iter()
.chain(exclude_patterns.into_iter().map(|p| format!("!{p}")))
.collect();
let glob_walker = globwalk::GlobWalkerBuilder::from_patterns(&root_dir, &patterns).build()?;
let included_files: BTreeSet<PathBuf> = glob_walker
.into_iter()
.filter_map(|entry| {
let Ok(entry) = entry else {
return None;
};
if !entry.file_type().is_file() {
return None;
}
Some(entry.into_path())
})
.collect();
Ok(included_files)
}