mod check;
mod maintenance;
mod register;
mod update;
use std::path::Path;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::time::Instant;
use crate::core::NormalizedPath;
use crate::hash::ContentHash;
use dashmap::DashMap;
use super::context::{ArtifactKey, CompileContext, ContextKey};
use super::scanner::IncludeDirective;
#[derive(Debug, Clone)]
pub struct FileEntry {
pub includes: Vec<IncludeDirective>,
pub scanned_at: Instant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextState {
Cold,
Warm,
Stale,
}
#[derive(Debug, Clone)]
pub struct ContextEntry {
pub context: CompileContext,
pub key_root: Option<NormalizedPath>,
pub resolved_includes: Vec<NormalizedPath>,
pub unresolved_includes: Vec<String>,
pub has_computed_includes: bool,
pub artifact_key: Option<ArtifactKey>,
pub last_file_hashes: Vec<(NormalizedPath, ContentHash)>,
pub last_accessed: Instant,
pub state: ContextState,
}
#[derive(Debug, Clone)]
pub enum CacheVerdict {
Hit { artifact_key: ArtifactKey },
SourceChanged { artifact_key: ArtifactKey },
HeadersChanged { changed: Vec<NormalizedPath> },
Cold,
NeedsPreprocessor,
}
#[derive(Debug, Clone)]
pub struct DepGraphStats {
pub file_count: usize,
pub context_count: usize,
pub checks: u64,
pub hits: u64,
pub misses: u64,
}
impl std::fmt::Debug for DepGraph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DepGraph")
.field("files", &self.files.len())
.field("contexts", &self.contexts.len())
.finish()
}
}
pub struct DepGraph {
pub(super) files: DashMap<NormalizedPath, FileEntry>,
pub(super) contexts: DashMap<ContextKey, ContextEntry>,
pub(super) rustc_externs: DashMap<ContextKey, Vec<(String, NormalizedPath)>>,
pub(super) path_key_cache: DashMap<PathKeyCacheKey, Arc<str>>,
pub(super) checks: AtomicU64,
pub(super) hits: AtomicU64,
pub(super) misses: AtomicU64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(super) struct PathKeyCacheKey {
pub(super) path: NormalizedPath,
pub(super) key_root: Option<NormalizedPath>,
}
#[derive(Debug, Clone, Copy)]
pub struct ContextRegistration {
pub key: ContextKey,
pub rebased_from_equivalent_root: bool,
}
pub(super) fn depgraph_update_profile_enabled() -> bool {
use std::sync::OnceLock;
static FLAG: OnceLock<bool> = OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("ZCCACHE_PROFILE_CC_MISS").is_some())
}
pub(super) fn rebase_project_path(
path: &NormalizedPath,
old_root: Option<&NormalizedPath>,
new_root: Option<&NormalizedPath>,
) -> NormalizedPath {
match (old_root, new_root) {
(Some(old_root), Some(new_root)) => path
.strip_prefix(old_root)
.map(|relative| new_root.join(relative))
.unwrap_or_else(|_| path.clone()),
_ => path.clone(),
}
}
pub(super) fn collect_rustc_extern_hashes<G>(
rustc_externs: &[(String, NormalizedPath)],
get_hash: &G,
) -> Option<Vec<(String, ContentHash)>>
where
G: Fn(&Path) -> Option<ContentHash>,
{
let mut extern_hashes = Vec::with_capacity(rustc_externs.len());
for (name, path) in rustc_externs {
extern_hashes.push((name.clone(), get_hash(path)?));
}
Some(extern_hashes)
}
pub(super) fn drifted_paths<'a, I, P>(
last_file_hashes: &[(NormalizedPath, ContentHash)],
current: I,
) -> Vec<NormalizedPath>
where
I: IntoIterator<Item = (P, &'a ContentHash)>,
P: AsRef<Path>,
{
if last_file_hashes.is_empty() {
return Vec::new();
}
let old_map: std::collections::HashMap<&Path, &ContentHash> = last_file_hashes
.iter()
.map(|(p, h)| (p.as_path(), h))
.collect();
let mut drifted: Vec<NormalizedPath> = Vec::new();
for (path, new_hash) in current {
let p = path.as_ref();
match old_map.get(p) {
Some(old_hash) if old_hash != &new_hash => {
drifted.push(p.into());
}
None => {
drifted.push(p.into());
}
_ => {}
}
}
drifted
}
pub(super) fn format_drift_for_log(drifted: &[NormalizedPath]) -> String {
if drifted.is_empty() {
return String::new();
}
let names: Vec<String> = drifted
.iter()
.take(5)
.map(|p| {
p.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| p.display().to_string())
})
.collect();
format!(", drifted=[{}]", names.join(","))
}
impl DepGraph {
#[must_use]
pub fn new() -> Self {
Self {
files: DashMap::new(),
contexts: DashMap::new(),
rustc_externs: DashMap::new(),
path_key_cache: DashMap::new(),
checks: AtomicU64::new(0),
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
}
}
pub fn cached_normalize_key_path(&self, path: &Path, key_root: Option<&Path>) -> Arc<str> {
crate::depgraph::context::normalize_key_path(path, key_root).into()
}
#[cfg(test)]
pub fn path_key_cache_len(&self) -> usize {
self.path_key_cache.len()
}
pub(super) fn rustc_extern_inputs(
&self,
key: &ContextKey,
) -> Option<Vec<(String, NormalizedPath)>> {
self.rustc_externs.get(key).map(|externs| externs.clone())
}
pub(crate) fn from_maps_with_rustc_externs(
files: DashMap<NormalizedPath, FileEntry>,
contexts: DashMap<ContextKey, ContextEntry>,
rustc_externs: DashMap<ContextKey, Vec<(String, NormalizedPath)>>,
) -> Self {
Self {
files,
contexts,
rustc_externs,
path_key_cache: DashMap::new(),
checks: AtomicU64::new(0),
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
}
}
}
impl Default for DepGraph {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests;