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 ;
3029use std:: path:: { Path , PathBuf } ;
3130use 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).
510519fn 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