@@ -78,6 +78,32 @@ fn is_dir_empty(path: &Path) -> Result<bool, String> {
7878 Ok ( entries. next ( ) . is_none ( ) )
7979}
8080
81+ /// Recursively checks whether a directory tree contains any regular files.
82+ ///
83+ /// Returns `false` if the directory only contains empty subdirectories,
84+ /// which is important because remote execution tree artifacts only track
85+ /// files, not directories.
86+ fn dir_contains_files ( path : & Path ) -> bool {
87+ let entries = match std:: fs:: read_dir ( path) {
88+ Ok ( entries) => entries,
89+ Err ( _) => return false ,
90+ } ;
91+ for entry in entries. flatten ( ) {
92+ let file_type = match entry. file_type ( ) {
93+ Ok ( ft) => ft,
94+ Err ( _) => continue ,
95+ } ;
96+ if file_type. is_dir ( ) {
97+ if dir_contains_files ( & entry. path ( ) ) {
98+ return true ;
99+ }
100+ } else {
101+ return true ;
102+ }
103+ }
104+ false
105+ }
106+
81107/// A struct for generating runfiles directories to use when running Cargo build scripts.
82108pub struct RunfilesMaker {
83109 /// The output where a runfiles-like directory should be written.
@@ -248,17 +274,24 @@ impl RunfilesMaker {
248274 . iter ( )
249275 . any ( |suffix| dest. ends_with ( suffix) )
250276 {
251- if let Some ( parent) = abs_dest. parent ( ) {
252- if is_dir_empty ( parent) . map_err ( |e| {
277+ let mut dir = abs_dest. parent ( ) . map ( Path :: to_path_buf) ;
278+ while let Some ( parent) = dir {
279+ if parent == self . output_dir {
280+ break ;
281+ }
282+ if is_dir_empty ( & parent) . map_err ( |e| {
253283 format ! ( "Failed to determine if directory was empty with: {:?}" , e)
254284 } ) ? {
255- std:: fs:: remove_dir ( parent) . map_err ( |e| {
285+ std:: fs:: remove_dir ( & parent) . map_err ( |e| {
256286 format ! (
257287 "Failed to delete directory {} with {:?}" ,
258288 parent. display( ) ,
259289 e
260290 )
261291 } ) ?;
292+ dir = parent. parent ( ) . map ( Path :: to_path_buf) ;
293+ } else {
294+ break ;
262295 }
263296 }
264297 continue ;
@@ -273,6 +306,7 @@ impl RunfilesMaker {
273306 )
274307 } ) ?;
275308 }
309+
276310 Ok ( ( ) )
277311 }
278312
@@ -312,6 +346,20 @@ impl RunfilesMaker {
312346 self . drain_runfiles_dir_unix ( ) ?;
313347 }
314348
349+ // If the runfiles dir contains no files, add an empty file to avoid
350+ // an upstream Bazel bug where tree artifacts with only empty
351+ // subdirectories are considered "not created" in remote execution.
352+ // https://github.com/bazelbuild/bazel/issues/28286
353+ if !dir_contains_files ( & self . output_dir ) {
354+ std:: fs:: write ( self . output_dir . join ( ".empty" ) , "" ) . unwrap_or_else ( |e| {
355+ panic ! (
356+ "Failed to write empty file to runfiles dir `{}`\n {:?}" ,
357+ self . output_dir. display( ) ,
358+ e
359+ )
360+ } )
361+ }
362+
315363 // Due to the symlinks in `CARGO_MANIFEST_DIR`, some build scripts
316364 // may have placed symlinks over real files in `OUT_DIR`. To counter
317365 // this, all non-relative symlinks are resolved.
0 commit comments