pub mod build_dependencies;
pub mod codec;
pub mod occasion;
pub mod snapshot;
pub mod storage;
use std::{
hash::{DefaultHasher, Hash, Hasher},
sync::Arc,
};
use rspack_cacheable::{
cacheable,
utils::PortablePath,
with::{As, AsVec},
};
use rspack_fs::{IntermediateFileSystem, ReadableFileSystem};
use rspack_paths::ArcPathSet;
use rspack_workspace::rspack_pkg_version;
use self::{
build_dependencies::{BuildDeps, BuildDepsOptions},
codec::CacheCodec,
occasion::{MakeOccasion, MetaOccasion},
snapshot::{Snapshot, SnapshotOptions, SnapshotScope},
storage::{Storage, StorageOptions, create_storage},
};
use super::Cache;
use crate::{
Compilation, CompilerOptions, Logger,
compilation::build_module_graph::{BuildModuleGraphArtifact, BuildModuleGraphArtifactState},
};
#[cacheable]
#[derive(Debug, Clone, Hash)]
pub struct PersistentCacheOptions {
#[cacheable(with=AsVec<As<PortablePath>>)]
pub build_dependencies: BuildDepsOptions,
pub version: String,
pub snapshot: SnapshotOptions,
pub storage: StorageOptions,
}
#[derive(Debug)]
pub struct PersistentCache {
initialized: bool,
build_deps: BuildDeps,
snapshot: Arc<Snapshot>,
make_occasion: MakeOccasion,
meta_occasion: MetaOccasion,
async_mode: bool,
storage: Arc<dyn Storage>,
warnings: Vec<String>,
}
impl PersistentCache {
pub fn new(
compiler_path: &str,
option: &PersistentCacheOptions,
compiler_options: Arc<CompilerOptions>,
input_filesystem: Arc<dyn ReadableFileSystem>,
intermediate_filesystem: Arc<dyn IntermediateFileSystem>,
) -> Self {
let async_mode = compiler_options.mode.is_development();
let codec = Arc::new(CacheCodec::new(None));
let option_bytes = codec
.encode(option)
.expect("should persistent cache options can be serialized");
let version = {
let mut hasher = DefaultHasher::new();
compiler_path.hash(&mut hasher);
option_bytes.hash(&mut hasher);
rspack_pkg_version!().hash(&mut hasher);
compiler_options.name.hash(&mut hasher);
compiler_options.mode.hash(&mut hasher);
hex::encode(hasher.finish().to_ne_bytes())
};
let storage = create_storage(option.storage.clone(), version, intermediate_filesystem);
let snapshot = Arc::new(Snapshot::new(
option.snapshot.clone(),
input_filesystem.clone(),
storage.clone(),
codec.clone(),
));
Self {
initialized: false,
build_deps: BuildDeps::new(
&option.build_dependencies,
input_filesystem,
snapshot.clone(),
storage.clone(),
),
snapshot,
make_occasion: MakeOccasion::new(storage.clone(), codec.clone()),
meta_occasion: MetaOccasion::new(storage.clone(), codec),
warnings: Default::default(),
async_mode,
storage,
}
}
async fn initialize(&mut self) {
if self.initialized {
return;
}
self.initialized = true;
if let Err(err) = self.build_deps.validate().await {
self.warnings.push(err.to_string());
}
if let Err(err) = self.meta_occasion.recovery().await {
self.warnings.push(err.to_string());
}
}
async fn save(&mut self) {
let rx = match self.storage.trigger_save() {
Ok(rx) => rx,
Err(err) => {
self.warnings.push(err.to_string());
return;
}
};
if self.async_mode {
tokio::spawn(async {
if let Err(err) = rx.await.expect("should receive message") {
println!("persistent cache save failed. {err}");
}
});
} else if let Err(err) = rx.await.expect("should receive message") {
self.warnings.push(err.to_string());
}
}
}
#[async_trait::async_trait]
impl Cache for PersistentCache {
async fn before_compile(&mut self, compilation: &mut Compilation) -> bool {
self.initialize().await;
if !compilation.is_rebuild {
let mut is_hot_start = false;
let mut modified_paths = ArcPathSet::default();
let mut removed_paths = ArcPathSet::default();
let data = vec![
self.snapshot.calc_modified_paths(SnapshotScope::FILE).await,
self
.snapshot
.calc_modified_paths(SnapshotScope::CONTEXT)
.await,
self
.snapshot
.calc_modified_paths(SnapshotScope::MISSING)
.await,
];
for item in data {
match item {
Ok((a, b, c, _)) => {
is_hot_start = is_hot_start || a;
modified_paths.extend(b);
removed_paths.extend(c);
}
Err(err) => {
self.warnings.push(err.to_string());
return false;
}
};
}
tracing::debug!("cache::snapshot recovery {modified_paths:?} {removed_paths:?}",);
compilation.modified_files.extend(modified_paths);
compilation.removed_files.extend(removed_paths);
return is_hot_start;
}
false
}
async fn after_compile(&mut self, compilation: &Compilation) {
self.meta_occasion.save();
let (_, file_added, file_updated, file_removed) = compilation.file_dependencies();
let (_, context_added, context_updated, context_removed) = compilation.context_dependencies();
let (_, missing_added, missing_updated, missing_removed) = compilation.missing_dependencies();
let (_, build_added, build_updated, _) = compilation.build_dependencies();
self
.snapshot
.remove(SnapshotScope::FILE, file_removed.cloned());
self
.snapshot
.remove(SnapshotScope::CONTEXT, context_removed.cloned());
self
.snapshot
.remove(SnapshotScope::MISSING, missing_removed.cloned());
self
.snapshot
.add(SnapshotScope::FILE, file_added.chain(file_updated).cloned())
.await;
self
.snapshot
.add(
SnapshotScope::CONTEXT,
context_added.chain(context_updated).cloned(),
)
.await;
self
.snapshot
.add(
SnapshotScope::MISSING,
missing_added.chain(missing_updated).cloned(),
)
.await;
self.warnings.extend(
self
.build_deps
.add(build_added.chain(build_updated).cloned())
.await,
);
self.save().await;
let logger = compilation.get_logger("rspack.persistentCache");
for msg in std::mem::take(&mut self.warnings) {
logger.warn(msg);
}
}
async fn before_build_module_graph(&mut self, make_artifact: &mut BuildModuleGraphArtifact) {
if matches!(
make_artifact.state,
BuildModuleGraphArtifactState::Uninitialized
) {
match self.make_occasion.recovery().await {
Ok(artifact) => *make_artifact = artifact,
Err(err) => self.warnings.push(err.to_string()),
}
}
}
async fn after_build_module_graph(&mut self, make_artifact: &BuildModuleGraphArtifact) {
self.make_occasion.save(make_artifact);
}
}