mod helper;
use std::{collections::VecDeque, path::PathBuf, sync::Arc};
use rspack_error::Result;
use rspack_fs::ReadableFileSystem;
use rspack_paths::{ArcPath, ArcPathSet, AssertUtf8};
use rustc_hash::FxHashSet as HashSet;
use self::helper::{Helper, is_node_package_path};
use super::{
snapshot::{Snapshot, SnapshotScope},
storage::Storage,
};
pub const SCOPE: &str = "build_dependencies";
pub type BuildDepsOptions = Vec<PathBuf>;
#[derive(Debug)]
pub struct BuildDeps {
added: ArcPathSet,
pending: ArcPathSet,
snapshot: Arc<Snapshot>,
fs: Arc<dyn ReadableFileSystem>,
}
impl BuildDeps {
pub fn new(
options: &BuildDepsOptions,
fs: Arc<dyn ReadableFileSystem>,
snapshot: Arc<Snapshot>,
) -> Self {
Self {
added: Default::default(),
pending: options.iter().map(|v| ArcPath::from(v.as_path())).collect(),
snapshot,
fs,
}
}
pub fn reset(&self, storage: &mut dyn Storage) {
storage.reset(SnapshotScope::BUILD.name());
}
pub async fn add(
&mut self,
storage: &mut dyn Storage,
data: impl Iterator<Item = ArcPath>,
) -> Vec<String> {
let mut helper = Helper::new(self.fs.clone());
let mut new_deps = HashSet::default();
let mut queue = VecDeque::new();
queue.extend(std::mem::take(&mut self.pending));
queue.extend(data);
while let Some(current) = queue.pop_front() {
if !self.added.insert(current.clone()) {
continue;
}
new_deps.insert(current.clone());
if is_node_package_path(¤t) {
continue;
}
if let Some(children) = helper.resolve(current.assert_utf8()).await {
queue.extend(children.iter().map(|item| item.as_path().into()));
}
}
self
.snapshot
.add(storage, SnapshotScope::BUILD, new_deps.into_iter())
.await;
helper.into_warnings()
}
pub async fn validate(&mut self, storage: &dyn Storage) -> Result<bool> {
let (_, modified_files, removed_files, no_changed_files) = self
.snapshot
.calc_modified_paths(storage, SnapshotScope::BUILD)
.await?;
if !modified_files.is_empty() || !removed_files.is_empty() {
tracing::info!(
"BuildDependencies: cache invalidate by modified_files {modified_files:?} and removed_files {removed_files:?}"
);
return Ok(false);
}
self.added = no_changed_files;
Ok(true)
}
}
#[cfg(test)]
mod test {
use std::{path::PathBuf, sync::Arc};
use rspack_fs::{MemoryFileSystem, WritableFileSystem};
use super::{
super::{
codec::CacheCodec,
snapshot::{Snapshot, SnapshotOptions, SnapshotScope},
storage::{MemoryStorage, Storage},
},
BuildDeps,
};
#[tokio::test]
async fn build_dependencies_test() {
let scope = SnapshotScope::BUILD.name();
let fs = Arc::new(MemoryFileSystem::default());
fs.create_dir_all("/configs/test".into()).await.unwrap();
fs.write("/configs/a.js".into(), r#"console.log('a')"#.as_bytes())
.await
.unwrap();
fs.write(
"/configs/test/b.js".into(),
r#"console.log('b')"#.as_bytes(),
)
.await
.unwrap();
fs.write(
"/configs/test/b1.js".into(),
r#"console.log('b1')"#.as_bytes(),
)
.await
.unwrap();
fs.write("/configs/c.txt".into(), r#"123"#.as_bytes())
.await
.unwrap();
fs.write("/a.js".into(), r#"require("./b")"#.as_bytes())
.await
.unwrap();
fs.write("/b.js".into(), r#"require("./c"); console.log("#.as_bytes())
.await
.unwrap();
fs.write("/c.js".into(), r#"console.log('c')"#.as_bytes())
.await
.unwrap();
fs.write("/index.js".into(), r#"import "./a""#.as_bytes())
.await
.unwrap();
let options = vec![PathBuf::from("/index.js"), PathBuf::from("/configs")];
let mut storage = MemoryStorage::default();
let codec = Arc::new(CacheCodec::new(None));
let snapshot = Arc::new(Snapshot::new(SnapshotOptions::default(), fs.clone(), codec));
let mut build_deps = BuildDeps::new(&options, fs.clone(), snapshot.clone());
let warnings = build_deps.add(&mut storage, vec![].into_iter()).await;
assert_eq!(warnings.len(), 1);
let data = storage.load(scope).await.expect("should load success");
assert_eq!(data.len(), 9);
let mut build_deps = BuildDeps::new(&options, fs.clone(), snapshot.clone());
fs.write("/b.js".into(), r#"require("./c")"#.as_bytes())
.await
.unwrap();
let validate_result = build_deps
.validate(&storage)
.await
.expect("should validate success");
assert!(!validate_result);
storage.reset(scope);
let data = storage.load(scope).await.expect("should load success");
assert_eq!(data.len(), 0);
let warnings = build_deps.add(&mut storage, vec![].into_iter()).await;
assert_eq!(warnings.len(), 0);
let data = storage.load(scope).await.expect("should load success");
assert_eq!(data.len(), 10);
}
}