pub mod build_dependencies;
pub mod codec;
pub mod context;
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, Skip},
};
use rspack_fs::{IntermediateFileSystem, ReadableFileSystem};
use rspack_workspace::rspack_pkg_version;
use self::{
build_dependencies::{BuildDeps, BuildDepsOptions},
codec::CacheCodec,
context::CacheContext,
occasion::{MakeOccasion, MetaOccasion, MinimizeOccasion},
snapshot::{Snapshot, SnapshotOptions},
storage::{StorageOptions, create_storage},
};
use super::Cache;
use crate::{Compilation, CompilerOptions, Logger};
#[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,
pub portable: bool,
#[cacheable(with=Skip)]
pub readonly: bool,
}
#[derive(Debug)]
pub struct PersistentCache {
initialized: bool,
ctx: CacheContext,
build_deps: BuildDeps,
snapshot: Arc<Snapshot>,
make_occasion: MakeOccasion,
meta_occasion: MetaOccasion,
minimize_occasion: MinimizeOccasion,
}
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 project_root = if option.portable {
Some(compiler_options.context.as_path().to_path_buf())
} else {
None
};
let codec = Arc::new(CacheCodec::new(project_root));
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(),
codec.clone(),
));
Self {
initialized: false,
ctx: CacheContext::new(storage, option.readonly),
build_deps: BuildDeps::new(
&option.build_dependencies,
input_filesystem,
snapshot.clone(),
),
snapshot,
make_occasion: MakeOccasion::new(codec.clone()),
meta_occasion: MetaOccasion::new(codec.clone()),
minimize_occasion: MinimizeOccasion::new(codec),
}
}
async fn initialize(&mut self) {
if self.initialized {
return;
}
self.initialized = true;
self.ctx.load_build_deps(&mut self.build_deps).await;
self.ctx.load_occasion(&self.meta_occasion).await;
}
}
#[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 {
return false;
}
if let Some((is_hot_start, modified_paths, removed_paths)) =
self.ctx.load_snapshot(&self.snapshot).await
{
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.ctx.save_occasion(&self.meta_occasion, &());
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
.ctx
.save_snapshot(
&self.snapshot,
(
file_added.chain(file_updated).cloned(),
file_removed.cloned(),
),
(
context_added.chain(context_updated).cloned(),
context_removed.cloned(),
),
(
missing_added.chain(missing_updated).cloned(),
missing_removed.cloned(),
),
)
.await;
self
.ctx
.save_build_deps(
&mut self.build_deps,
build_added.chain(build_updated).cloned(),
)
.await;
self.ctx.save_storage();
let logger = compilation.get_logger("rspack.persistentCache");
for msg in self.ctx.reset() {
logger.warn(msg);
}
}
async fn before_build_module_graph(&mut self, compilation: &mut Compilation) {
if compilation.is_rebuild {
return;
}
if let Some(artifact) = self.ctx.load_occasion(&self.make_occasion).await {
*compilation.build_module_graph_artifact = artifact;
for (module, _) in compilation
.build_module_graph_artifact
.get_module_graph()
.modules()
{
compilation.exports_info_artifact.new_exports_info(*module);
}
}
}
async fn after_build_module_graph(&mut self, compilation: &Compilation) {
self.ctx.save_occasion(
&self.make_occasion,
&compilation.build_module_graph_artifact,
);
}
async fn before_process_assets(&mut self, compilation: &mut Compilation) {
if compilation.is_rebuild {
return;
}
let artifact = self
.ctx
.load_occasion(&self.minimize_occasion)
.await
.unwrap_or_default();
compilation.minimize_persistent_cache_artifact = Some(artifact);
}
async fn after_process_assets(&mut self, compilation: &Compilation) {
if let Some(artifact) = &compilation.minimize_persistent_cache_artifact {
self.ctx.save_occasion(&self.minimize_occasion, artifact);
}
}
async fn close(&self) {
self.ctx.flush_storage().await;
}
}