1use crate::error::{Error, Result}; 2use crate::gen::fs; 3use crate::paths; 4use std::io; 5use std::path::Path; 6 7pub(crate) fn write(path: impl AsRef<Path>, content: &[u8]) -> Result<()> { 8 let path = path.as_ref(); 9 10 let mut create_dir_error = None; 11 if fs::exists(path) { 12 if let Ok(existing) = fs::read(path) { 13 if existing == content { 14 // Avoid bumping modified time with unchanged contents. 15 return Ok(()); 16 } 17 } 18 best_effort_remove(path); 19 } else { 20 let parent = path.parent().unwrap(); 21 create_dir_error = fs::create_dir_all(parent).err(); 22 } 23 24 match fs::write(path, content) { 25 // As long as write succeeded, ignore any create_dir_all error. 26 Ok(()) => Ok(()), 27 // If create_dir_all and write both failed, prefer the first error. 28 Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))), 29 } 30} 31 32pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { 33 let original = original.as_ref(); 34 let link = link.as_ref(); 35 36 let mut create_dir_error = None; 37 if fs::exists(link) { 38 best_effort_remove(link); 39 } else { 40 let parent = link.parent().unwrap(); 41 create_dir_error = fs::create_dir_all(parent).err(); 42 } 43 44 match paths::symlink_or_copy(original, link) { 45 // As long as symlink_or_copy succeeded, ignore any create_dir_all error. 46 Ok(()) => Ok(()), 47 Err(err) => { 48 if err.kind() == io::ErrorKind::AlreadyExists { 49 // This is fine, a different simultaneous build script already 50 // created the same link or copy. The cxx_build target directory 51 // is laid out such that the same path never refers to two 52 // different targets during the same multi-crate build, so if 53 // some other build script already created the same path then we 54 // know it refers to the identical target that the current build 55 // script was trying to create. 56 Ok(()) 57 } else { 58 // If create_dir_all and symlink_or_copy both failed, prefer the 59 // first error. 60 Err(Error::Fs(create_dir_error.unwrap_or(err))) 61 } 62 } 63 } 64} 65 66pub(crate) fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { 67 let original = original.as_ref(); 68 let link = link.as_ref(); 69 70 let mut create_dir_error = None; 71 if fs::exists(link) { 72 best_effort_remove(link); 73 } else { 74 let parent = link.parent().unwrap(); 75 create_dir_error = fs::create_dir_all(parent).err(); 76 } 77 78 match fs::symlink_dir(original, link) { 79 // As long as symlink_dir succeeded, ignore any create_dir_all error. 80 Ok(()) => Ok(()), 81 // If create_dir_all and symlink_dir both failed, prefer the first error. 82 Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))), 83 } 84} 85 86fn best_effort_remove(path: &Path) { 87 use std::fs; 88 89 if cfg!(windows) { 90 // On Windows, the correct choice of remove_file vs remove_dir needs to 91 // be used according to what the symlink *points to*. Trying to use 92 // remove_file to remove a symlink which points to a directory fails 93 // with "Access is denied". 94 if let Ok(metadata) = fs::metadata(path) { 95 if metadata.is_dir() { 96 let _ = fs::remove_dir_all(path); 97 } else { 98 let _ = fs::remove_file(path); 99 } 100 } else if fs::symlink_metadata(path).is_ok() { 101 // The symlink might exist but be dangling, in which case there is 102 // no standard way to determine what "kind" of symlink it is. Try 103 // deleting both ways. 104 if fs::remove_dir_all(path).is_err() { 105 let _ = fs::remove_file(path); 106 } 107 } 108 } else { 109 // On non-Windows, we check metadata not following symlinks. All 110 // symlinks are removed using remove_file. 111 if let Ok(metadata) = fs::symlink_metadata(path) { 112 if metadata.is_dir() { 113 let _ = fs::remove_dir_all(path); 114 } else { 115 let _ = fs::remove_file(path); 116 } 117 } 118 } 119} 120