use std::path::{Path, PathBuf};
use std::time::SystemTime;
pub fn write_if_changed(path: &Path, content: &str) -> std::io::Result<bool> {
if path.exists() {
if let Ok(existing) = std::fs::read_to_string(path) {
if existing == content {
return Ok(false);
}
}
}
std::fs::write(path, content)?;
Ok(true)
}
pub fn is_output_fresh(source: &Path, output: &Path) -> bool {
let source_mtime = match std::fs::metadata(source).and_then(|m| m.modified()) {
Ok(t) => t,
Err(_) => return false,
};
let output_mtime = match std::fs::metadata(output).and_then(|m| m.modified()) {
Ok(t) => t,
Err(_) => return false,
};
output_mtime >= source_mtime
}
pub fn is_meta_fresh(source: &Path) -> bool {
let meta_path = crate::metadata::meta_cache_path(source);
is_output_fresh(source, &meta_path)
}
pub fn is_codegen_cache_valid(
source: &str,
source_path: &Path,
output_path: &Path,
dep_roots: &[PathBuf],
) -> bool {
crate::compiler::incremental::is_codegen_cache_valid(
source,
source_path,
output_path,
dep_roots,
)
}
fn is_mod_wj_source(source_path: &Path) -> bool {
source_path.file_name().and_then(|n| n.to_str()) == Some("mod.wj")
}
fn is_mod_items_output(output_path: &Path) -> bool {
output_path.file_name().and_then(|n| n.to_str()) == Some("_mod_items.rs")
}
fn merged_mod_rs_for_mod_wj(
source_path: &Path,
src_base: &Path,
output_dir: &Path,
) -> Option<PathBuf> {
let relative = source_path.strip_prefix(src_base).ok()?;
let parent = relative.parent().unwrap_or_else(|| Path::new(""));
let mut mod_rs = output_dir.to_path_buf();
if !parent.as_os_str().is_empty() {
mod_rs.push(parent);
}
mod_rs.push("mod.rs");
if mod_rs.exists() {
return Some(mod_rs);
}
let lib_rs = output_dir.join("lib.rs");
if parent.as_os_str().is_empty() && lib_rs.exists() {
return Some(lib_rs);
}
Some(mod_rs)
}
pub fn is_library_codegen_cache_valid(
source: &str,
source_path: &Path,
output_path: &Path,
src_base: &Path,
output_dir: &Path,
dep_roots: &[PathBuf],
) -> bool {
if is_mod_wj_source(source_path) && is_mod_items_output(output_path) && !output_path.exists() {
if !crate::compiler::incremental::fingerprint_matches_cached(source, source_path, dep_roots)
{
return false;
}
if let Some(mod_rs) = merged_mod_rs_for_mod_wj(source_path, src_base, output_dir) {
return mod_rs.exists() && is_output_fresh(source_path, &mod_rs);
}
return false;
}
is_codegen_cache_valid(source, source_path, output_path, dep_roots)
}
pub fn is_library_codegen_cache_valid_with_dep_epoch(
source: &str,
source_path: &Path,
output_path: &Path,
src_base: &Path,
output_dir: &Path,
dep_epoch: u64,
) -> bool {
if is_mod_wj_source(source_path) && is_mod_items_output(output_path) && !output_path.exists() {
if !crate::compiler::incremental::fingerprint_matches_cached_with_dep_epoch(
source,
source_path,
dep_epoch,
) {
return false;
}
if let Some(mod_rs) = merged_mod_rs_for_mod_wj(source_path, src_base, output_dir) {
return mod_rs.exists() && is_output_fresh(source_path, &mod_rs);
}
return false;
}
crate::compiler::incremental::is_codegen_cache_valid_with_dep_epoch(
source,
source_path,
output_path,
dep_epoch,
)
}
pub fn compute_dirty_files(
wj_files: &[(PathBuf, String)],
src_base: &Path,
output: &Path,
dep_roots: &[PathBuf],
) -> (Vec<usize>, usize) {
let compiler_changed = !is_compiler_stamp_fresh(output);
if compiler_changed {
return ((0..wj_files.len()).collect(), 0);
}
let mut dirty_indices = Vec::new();
let mut skipped = 0;
for (i, (file, source)) in wj_files.iter().enumerate() {
let output_file =
match crate::project_paths::resolve_wj_output_path_library(src_base, file, output) {
Ok(p) => p,
Err(_) => {
dirty_indices.push(i);
continue;
}
};
if is_library_codegen_cache_valid(source, file, &output_file, src_base, output, dep_roots) {
skipped += 1;
} else {
dirty_indices.push(i);
}
}
(dirty_indices, skipped)
}
pub fn compute_rebuild_set(
wj_files: &[(PathBuf, String)],
src_base: &Path,
output: &Path,
dep_roots: &[PathBuf],
dependency_graph: &crate::compiler::incremental::DependencyGraph,
) -> std::collections::HashSet<usize> {
let (dirty_indices, _) = compute_dirty_files(wj_files, src_base, output, dep_roots);
let dirty: std::collections::HashSet<usize> = dirty_indices.into_iter().collect();
dependency_graph.transitive_dependents(&dirty)
}
pub fn is_compiler_stamp_fresh(output: &Path) -> bool {
crate::compiler::incremental::is_compiler_stamp_fresh(output)
}
pub fn write_compiler_stamp(output: &Path) -> std::io::Result<()> {
crate::compiler::incremental::write_compiler_stamp(output)
}
pub fn all_sources_fresh(
wj_files: &[(PathBuf, String)],
src_base: &Path,
output: &Path,
dep_metadata_paths: &[PathBuf],
) -> bool {
if !is_compiler_stamp_fresh(output) {
return false;
}
let mut max_dep_mtime = SystemTime::UNIX_EPOCH;
for dep_path in dep_metadata_paths {
if let Ok(meta) = std::fs::metadata(dep_path) {
if let Ok(mtime) = meta.modified() {
if mtime > max_dep_mtime {
max_dep_mtime = mtime;
}
}
}
}
for (file, source) in wj_files {
let output_file =
match crate::project_paths::resolve_wj_output_path_library(src_base, file, output) {
Ok(p) => p,
Err(_) => return false,
};
if !is_library_codegen_cache_valid(
source,
file,
&output_file,
src_base,
output,
dep_metadata_paths,
) {
return false;
}
if let Ok(out_meta) = std::fs::metadata(&output_file) {
if let Ok(out_mtime) = out_meta.modified() {
if max_dep_mtime > out_mtime {
return false;
}
}
}
}
true
}
pub fn is_library_source_under_root(source_path: &Path, src_base: &Path) -> bool {
source_path.strip_prefix(src_base).is_ok()
}
pub fn find_stale_codegen_outputs(
wj_files: &[(PathBuf, String)],
src_base: &Path,
output: &Path,
dep_roots: &[PathBuf],
) -> Vec<PathBuf> {
find_stale_codegen_outputs_with_dep_epoch(wj_files, src_base, output, dep_roots, None)
}
pub fn find_stale_codegen_outputs_with_dep_epoch(
wj_files: &[(PathBuf, String)],
src_base: &Path,
output: &Path,
dep_roots: &[PathBuf],
dep_epoch: Option<u64>,
) -> Vec<PathBuf> {
let mut stale = Vec::new();
for (file, source) in wj_files {
if !is_library_source_under_root(file, src_base) {
continue;
}
let source = std::fs::read_to_string(file).unwrap_or(source.clone());
let output_file =
match crate::project_paths::resolve_wj_output_path_library(src_base, file, output) {
Ok(p) => p,
Err(_) => {
stale.push(file.clone());
continue;
}
};
let cache_valid = if let Some(epoch) = dep_epoch {
is_library_codegen_cache_valid_with_dep_epoch(
&source,
file,
&output_file,
src_base,
output,
epoch,
)
} else {
is_library_codegen_cache_valid(&source, file, &output_file, src_base, output, dep_roots)
};
if !cache_valid {
stale.push(file.clone());
}
}
stale
}
pub(crate) fn clean_nested_cargo_toml(output_dir: &Path) {
fn visit(dir: &Path, root: &Path) {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_dir() {
if path.file_name().and_then(|n| n.to_str()) == Some("target") {
continue;
}
visit(&path, root);
} else if path.file_name().and_then(|n| n.to_str()) == Some("Cargo.toml")
&& path.parent() != Some(root)
{
let _ = std::fs::remove_file(&path);
}
}
}
visit(output_dir, output_dir);
}