1717
1818use std:: collections:: BTreeMap ;
1919use std:: env;
20- use std:: fs:: { create_dir_all, read_to_string, write} ;
20+ use std:: fs:: { create_dir_all, read_dir , read_to_string, remove_file , write} ;
2121use std:: path:: { Path , PathBuf } ;
2222use std:: process:: Command ;
2323
@@ -234,6 +234,13 @@ fn run_buildrs() -> Result<(), String> {
234234 . drain_runfiles_dir ( & out_dir_abs)
235235 . unwrap ( ) ;
236236
237+ // Remove non-deterministic configure-generated files from OUT_DIR before
238+ // Bazel captures it as a TreeArtifact. Files like config.log and
239+ // Makefile.config embed the Bazel sandbox path (which changes on every
240+ // action run), making the TreeArtifact hash non-deterministic and causing
241+ // cache misses for all downstream rustc compilations.
242+ remove_nondeterministic_out_dir_files ( & out_dir_abs) ;
243+
237244 // If out_dir is empty add an empty file to the directory to avoid an upstream Bazel bug
238245 // https://github.com/bazelbuild/bazel/issues/28286
239246 if out_dir_abs. read_dir ( ) . map ( |read| read. count ( ) ) . unwrap_or ( 0 ) == 0 {
@@ -256,6 +263,45 @@ fn run_buildrs() -> Result<(), String> {
256263 Ok ( ( ) )
257264}
258265
266+ /// Recursively walk `dir` and delete any file whose basename appears in
267+ /// `RULES_RUST_OUT_DIR_VOLATILE_BASENAMES` (colon-separated, set by the
268+ /// `//cargo/settings:out_dir_volatile_file_basenames` flag) or has a `.d` or
269+ /// `.pc` extension. Errors are silently ignored: if a file cannot be removed
270+ /// the worst outcome is a cache miss, not a build failure.
271+ fn remove_nondeterministic_out_dir_files ( dir : & Path ) {
272+ let volatile_basenames: Vec < String > = env:: var ( "RULES_RUST_OUT_DIR_VOLATILE_BASENAMES" )
273+ . map ( |v| v. split ( ':' ) . map ( String :: from) . collect ( ) )
274+ . unwrap_or_default ( ) ;
275+ remove_nondeterministic_out_dir_files_with_list ( dir, & volatile_basenames) ;
276+ }
277+
278+ fn remove_nondeterministic_out_dir_files_with_list ( dir : & Path , volatile_basenames : & [ String ] ) {
279+ let entries = match read_dir ( dir) {
280+ Ok ( e) => e,
281+ Err ( _) => return ,
282+ } ;
283+ for entry in entries. flatten ( ) {
284+ // Use file_type() which does not follow symlinks, so we never recurse
285+ // into symlink targets or traverse outside OUT_DIR.
286+ let Ok ( file_type) = entry. file_type ( ) else {
287+ continue ;
288+ } ;
289+ let path = entry. path ( ) ;
290+ if file_type. is_dir ( ) {
291+ remove_nondeterministic_out_dir_files_with_list ( & path, volatile_basenames) ;
292+ } else if file_type. is_file ( ) {
293+ if let Some ( name) = path. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
294+ if volatile_basenames. iter ( ) . any ( |b| b == name)
295+ || name. ends_with ( ".d" )
296+ || name. ends_with ( ".pc" )
297+ {
298+ let _ = remove_file ( & path) ;
299+ }
300+ }
301+ }
302+ }
303+ }
304+
259305fn should_symlink_exec_root ( ) -> bool {
260306 env:: var ( "RULES_RUST_SYMLINK_EXEC_ROOT" )
261307 . map ( |s| s == "1" )
@@ -446,6 +492,137 @@ fn main() {
446492#[ cfg( test) ]
447493mod test {
448494 use super :: * ;
495+ use std:: fs:: { create_dir_all, write} ;
496+
497+ fn make_temp_dir ( label : & str ) -> PathBuf {
498+ let nanos = std:: time:: SystemTime :: now ( )
499+ . duration_since ( std:: time:: UNIX_EPOCH )
500+ . unwrap ( )
501+ . subsec_nanos ( ) ;
502+ let dir = std:: env:: temp_dir ( ) . join ( format ! ( "rules_rust_bin_test_{}_{}" , label, nanos) ) ;
503+ create_dir_all ( & dir) . unwrap ( ) ;
504+ dir
505+ }
506+
507+ fn basenames ( names : & [ & str ] ) -> Vec < String > {
508+ names. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( )
509+ }
510+
511+ #[ test]
512+ fn remove_nondeterministic_named_files ( ) {
513+ let names = & [ "config.log" , "config.status" , "Makefile" , "commit_hash" ] ;
514+ let dir = make_temp_dir ( "named" ) ;
515+ for name in names {
516+ write ( dir. join ( name) , "content" ) . unwrap ( ) ;
517+ }
518+ write ( dir. join ( "libfoo.a" ) , "keep" ) . unwrap ( ) ;
519+
520+ remove_nondeterministic_out_dir_files_with_list ( & dir, & basenames ( names) ) ;
521+
522+ for name in names {
523+ assert ! (
524+ !dir. join( name) . exists( ) ,
525+ "{} should have been removed" ,
526+ name
527+ ) ;
528+ }
529+ assert ! ( dir. join( "libfoo.a" ) . exists( ) , "libfoo.a should be kept" ) ;
530+ std:: fs:: remove_dir_all ( & dir) . ok ( ) ;
531+ }
532+
533+ #[ test]
534+ fn remove_dot_d_and_pc_files ( ) {
535+ let dir = make_temp_dir ( "dotd" ) ;
536+ write ( dir. join ( "foo.d" ) , "deps" ) . unwrap ( ) ;
537+ write ( dir. join ( "bar.d" ) , "deps" ) . unwrap ( ) ;
538+ write ( dir. join ( "jemalloc.pc" ) , "prefix=/sandbox/out" ) . unwrap ( ) ;
539+ write ( dir. join ( "output.o" ) , "keep" ) . unwrap ( ) ;
540+
541+ remove_nondeterministic_out_dir_files_with_list ( & dir, & [ ] ) ;
542+
543+ assert ! ( !dir. join( "foo.d" ) . exists( ) , "foo.d should be removed" ) ;
544+ assert ! ( !dir. join( "bar.d" ) . exists( ) , "bar.d should be removed" ) ;
545+ assert ! (
546+ !dir. join( "jemalloc.pc" ) . exists( ) ,
547+ "jemalloc.pc should be removed"
548+ ) ;
549+ assert ! ( dir. join( "output.o" ) . exists( ) , "output.o should be kept" ) ;
550+ std:: fs:: remove_dir_all ( & dir) . ok ( ) ;
551+ }
552+
553+ #[ test]
554+ fn remove_nondeterministic_files_recursively ( ) {
555+ let dir = make_temp_dir ( "recurse" ) ;
556+ let sub = dir. join ( "subdir" ) ;
557+ create_dir_all ( & sub) . unwrap ( ) ;
558+ write ( sub. join ( "config.log" ) , "log" ) . unwrap ( ) ;
559+ write ( sub. join ( "foo.d" ) , "deps" ) . unwrap ( ) ;
560+ write ( sub. join ( "output.o" ) , "keep" ) . unwrap ( ) ;
561+ write ( dir. join ( "Makefile" ) , "top-level" ) . unwrap ( ) ;
562+
563+ remove_nondeterministic_out_dir_files_with_list (
564+ & dir,
565+ & basenames ( & [ "config.log" , "Makefile" ] ) ,
566+ ) ;
567+
568+ assert ! ( !sub. join( "config.log" ) . exists( ) ) ;
569+ assert ! ( !sub. join( "foo.d" ) . exists( ) ) ;
570+ assert ! ( sub. join( "output.o" ) . exists( ) ) ;
571+ assert ! ( !dir. join( "Makefile" ) . exists( ) ) ;
572+ std:: fs:: remove_dir_all ( & dir) . ok ( ) ;
573+ }
574+
575+ #[ test]
576+ fn remove_nondeterministic_nonexistent_dir_is_noop ( ) {
577+ let dir = std:: env:: temp_dir ( ) . join ( "rules_rust_bin_test_nonexistent_999999999" ) ;
578+ // Must not panic.
579+ remove_nondeterministic_out_dir_files_with_list ( & dir, & [ ] ) ;
580+ }
581+
582+ #[ test]
583+ fn remove_nondeterministic_custom_basenames ( ) {
584+ let dir = make_temp_dir ( "custom" ) ;
585+ write ( dir. join ( "custom_volatile.txt" ) , "bad" ) . unwrap ( ) ;
586+ write ( dir. join ( "config.log" ) , "keep_this" ) . unwrap ( ) ;
587+
588+ remove_nondeterministic_out_dir_files_with_list ( & dir, & basenames ( & [ "custom_volatile.txt" ] ) ) ;
589+
590+ assert ! (
591+ !dir. join( "custom_volatile.txt" ) . exists( ) ,
592+ "custom file should be removed"
593+ ) ;
594+ assert ! (
595+ dir. join( "config.log" ) . exists( ) ,
596+ "config.log should be kept with custom list"
597+ ) ;
598+ std:: fs:: remove_dir_all ( & dir) . ok ( ) ;
599+ }
600+
601+ #[ test]
602+ fn remove_nondeterministic_env_var_override ( ) {
603+ let dir = make_temp_dir ( "envvar" ) ;
604+ write ( dir. join ( "config.log" ) , "would be removed by default" ) . unwrap ( ) ;
605+ write ( dir. join ( "only_this.txt" ) , "should be removed" ) . unwrap ( ) ;
606+
607+ // Override via env var: only "only_this.txt" is volatile; config.log should survive.
608+ let prev = std:: env:: var ( "RULES_RUST_OUT_DIR_VOLATILE_BASENAMES" ) . ok ( ) ;
609+ std:: env:: set_var ( "RULES_RUST_OUT_DIR_VOLATILE_BASENAMES" , "only_this.txt" ) ;
610+ remove_nondeterministic_out_dir_files ( & dir) ;
611+ match prev {
612+ Some ( v) => std:: env:: set_var ( "RULES_RUST_OUT_DIR_VOLATILE_BASENAMES" , v) ,
613+ None => std:: env:: remove_var ( "RULES_RUST_OUT_DIR_VOLATILE_BASENAMES" ) ,
614+ }
615+
616+ assert ! (
617+ !dir. join( "only_this.txt" ) . exists( ) ,
618+ "only_this.txt should be removed"
619+ ) ;
620+ assert ! (
621+ dir. join( "config.log" ) . exists( ) ,
622+ "config.log should survive when not in env var list"
623+ ) ;
624+ std:: fs:: remove_dir_all ( & dir) . ok ( ) ;
625+ }
449626
450627 #[ test]
451628 fn rustc_cfg_parsing ( ) {
0 commit comments