Skip to content

Commit 52e40a3

Browse files
fanyang89iainmcgin
andauthored
Use write-if-changed pattern in buffa-build to avoid unnecessary recompilation (#17)
Skip writing output files when their content is identical to what's already on disk, preventing timestamp changes that trigger needless downstream rebuilds. Co-authored-by: Iain McGinniss <309153+iainmcgin@users.noreply.github.com>
1 parent 63268e9 commit 52e40a3

File tree

1 file changed

+47
-5
lines changed

1 file changed

+47
-5
lines changed

buffa-build/src/lib.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
//! used instead — see [`Config::use_buf()`]. Alternatively, feed a
2727
//! pre-compiled descriptor set via [`Config::descriptor_set()`].
2828
29-
use std::io::Write;
3029
use std::path::{Path, PathBuf};
3130
use std::process::Command;
3231

@@ -460,8 +459,7 @@ impl Config {
460459
if let Some(parent) = path.parent() {
461460
std::fs::create_dir_all(parent)?;
462461
}
463-
let mut f = std::fs::File::create(&path)?;
464-
f.write_all(file.content.as_bytes())?;
462+
write_if_changed(&path, file.content.as_bytes())?;
465463
let package = file_to_package.get(&file.name).cloned().unwrap_or_default();
466464
output_entries.push((file.name, package));
467465
}
@@ -470,8 +468,7 @@ impl Config {
470468
if let Some(ref include_name) = self.include_file {
471469
let include_content = generate_include_file(&output_entries, relative_includes);
472470
let include_path = out_dir.join(include_name);
473-
let mut f = std::fs::File::create(&include_path)?;
474-
f.write_all(include_content.as_bytes())?;
471+
write_if_changed(&include_path, include_content.as_bytes())?;
475472
}
476473

477474
// Tell cargo to re-run if any proto file changes.
@@ -506,6 +503,18 @@ impl Default for Config {
506503
}
507504
}
508505

506+
/// Write `content` to `path` only if the file doesn't already exist with
507+
/// identical content. Avoids bumping timestamps on unchanged files, which
508+
/// prevents unnecessary downstream recompilation.
509+
fn write_if_changed(path: &Path, content: &[u8]) -> std::io::Result<()> {
510+
if let Ok(existing) = std::fs::read(path) {
511+
if existing == content {
512+
return Ok(());
513+
}
514+
}
515+
std::fs::write(path, content)
516+
}
517+
509518
/// Invoke `protoc` to produce a `FileDescriptorSet` (serialized bytes).
510519
fn invoke_protoc(
511520
files: &[PathBuf],
@@ -842,4 +851,37 @@ mod tests {
842851
);
843852
assert!(!out.contains("OUT_DIR"));
844853
}
854+
855+
#[test]
856+
fn write_if_changed_creates_new_file() {
857+
let dir = tempfile::tempdir().unwrap();
858+
let path = dir.path().join("new.rs");
859+
write_if_changed(&path, b"hello").unwrap();
860+
assert_eq!(std::fs::read(&path).unwrap(), b"hello");
861+
}
862+
863+
#[test]
864+
fn write_if_changed_skips_identical_content() {
865+
let dir = tempfile::tempdir().unwrap();
866+
let path = dir.path().join("same.rs");
867+
std::fs::write(&path, b"content").unwrap();
868+
let mtime_before = std::fs::metadata(&path).unwrap().modified().unwrap();
869+
870+
// Sleep briefly so any write would produce a different mtime.
871+
std::thread::sleep(std::time::Duration::from_millis(50));
872+
873+
write_if_changed(&path, b"content").unwrap();
874+
let mtime_after = std::fs::metadata(&path).unwrap().modified().unwrap();
875+
assert_eq!(mtime_before, mtime_after);
876+
}
877+
878+
#[test]
879+
fn write_if_changed_overwrites_different_content() {
880+
let dir = tempfile::tempdir().unwrap();
881+
let path = dir.path().join("changed.rs");
882+
std::fs::write(&path, b"old").unwrap();
883+
884+
write_if_changed(&path, b"new").unwrap();
885+
assert_eq!(std::fs::read(&path).unwrap(), b"new");
886+
}
845887
}

0 commit comments

Comments
 (0)