use std::{collections::VecDeque, path::PathBuf, sync::Arc};
use rspack_error::Result;
use rspack_fs::ReadableFileSystem;
use rspack_paths::{ArcPath, ArcPathSet, AssertUtf8};
use super::{BuildDependenciesSnapshot, Snapshot};
use crate::{
CompilationLogger,
cache::persistent::{
build_dependencies::{Helper, is_node_package_path},
codec::CacheCodec,
},
};
pub type BuildDepsOptions = Vec<PathBuf>;
#[derive(Debug)]
pub enum BuildDepsValidationResult {
Valid {
tracked_files: usize,
},
Invalid {
modified_files: ArcPathSet,
removed_files: ArcPathSet,
},
}
#[derive(Debug)]
pub struct BuildDeps {
pending: ArcPathSet,
data: BuildDependenciesSnapshot,
fs: Arc<dyn ReadableFileSystem>,
logger: CompilationLogger,
}
impl BuildDeps {
pub fn new(
options: &BuildDepsOptions,
fs: Arc<dyn ReadableFileSystem>,
logger: CompilationLogger,
) -> Self {
Self {
pending: options
.iter()
.map(|path| ArcPath::from(path.as_path()))
.collect(),
data: Default::default(),
fs,
logger,
}
}
pub async fn create_snapshot(
&mut self,
codec: &CacheCodec,
snapshot: &Snapshot,
data: impl Iterator<Item = ArcPath>,
) -> Result<Vec<u8>> {
let mut helper = Helper::new(self.fs.clone(), self.logger.clone());
let mut added = ArcPathSet::default();
let mut queue = VecDeque::new();
queue.extend(self.pending.iter().cloned());
queue.extend(data);
while let Some(current) = queue.pop_front() {
if self.data.dependencies.contains(¤t) || !added.insert(current.clone()) {
continue;
}
if is_node_package_path(¤t) {
continue;
}
if let Some(children) = helper.resolve(current.assert_utf8()).await {
queue.extend(
children
.into_iter()
.map(|path| ArcPath::from(path.as_path())),
);
}
}
let snapshots = snapshot.add(added.iter().cloned()).await;
self.data.dependencies.extend(added);
self.data.snapshots.extend(snapshots);
self.pending.clear();
codec.encode(&self.data)
}
pub async fn validate_snapshot(
&mut self,
codec: &CacheCodec,
snapshot: &Snapshot,
data: Option<&[u8]>,
) -> Result<BuildDepsValidationResult> {
let Some(data) = data else {
return Ok(BuildDepsValidationResult::Valid { tracked_files: 0 });
};
let data = codec.decode::<BuildDependenciesSnapshot>(data)?;
let (modified_files, removed_files) = snapshot.calc_modified_paths(&data.snapshots).await;
if !modified_files.is_empty() || !removed_files.is_empty() {
return Ok(BuildDepsValidationResult::Invalid {
modified_files,
removed_files,
});
}
let tracked_files = data.dependencies.len();
self.data = data;
Ok(BuildDepsValidationResult::Valid { tracked_files })
}
}