mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +00:00
Auto merge of #8378 - jstasiak:backups, r=ehuss
Exclude the target directory from backups using CACHEDIR.TAG This patch follows the lead of #4386 (which excludes target directories from Time Machine backups) and is motived by the same reasons listen in #3884. CACHEDIR.TAG is an OS-independent mechanism supported by Borg, restic, GNU Tar and other backup/archiving solutions. See https://bford.info/cachedir/ for more information about the specification. This has been discussed in Rust Internals earlier this year[1] and it seems like it's an uncontroversial improvement so I went ahead with the patch. One thing I'm wondering is whether this should maybe cover the whole main target directory (right now it applies to `target/debug`, `target/release` etc. but not to target root). [1] https://internals.rust-lang.org/t/pre-rfc-put-cachedir-tag-into-target/12262/11
This commit is contained in:
commit
cf3bfc904d
@ -150,10 +150,11 @@ impl Layout {
|
||||
// If the root directory doesn't already exist go ahead and create it
|
||||
// here. Use this opportunity to exclude it from backups as well if the
|
||||
// system supports it since this is a freshly created folder.
|
||||
if !dest.as_path_unlocked().exists() {
|
||||
dest.create_dir()?;
|
||||
exclude_from_backups(dest.as_path_unlocked());
|
||||
}
|
||||
//
|
||||
paths::create_dir_all_excluded_from_backups_atomic(root.as_path_unlocked())?;
|
||||
// Now that the excluded from backups target root is created we can create the
|
||||
// actual destination (sub)subdirectory.
|
||||
paths::create_dir_all(dest.as_path_unlocked())?;
|
||||
|
||||
// For now we don't do any more finer-grained locking on the artifact
|
||||
// directory, so just lock the entire thing for the duration of this
|
||||
@ -219,32 +220,3 @@ impl Layout {
|
||||
&self.build
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn exclude_from_backups(_: &Path) {}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
/// Marks files or directories as excluded from Time Machine on macOS
|
||||
///
|
||||
/// This is recommended to prevent derived/temporary files from bloating backups.
|
||||
fn exclude_from_backups(path: &Path) {
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::{number, string, url};
|
||||
use std::ptr;
|
||||
|
||||
// For compatibility with 10.7 a string is used instead of global kCFURLIsExcludedFromBackupKey
|
||||
let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
|
||||
let path = url::CFURL::from_path(path, false);
|
||||
if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
|
||||
unsafe {
|
||||
url::CFURLSetResourcePropertyForKey(
|
||||
path.as_concrete_TypeRef(),
|
||||
is_excluded_key.as_concrete_TypeRef(),
|
||||
number::kCFBooleanTrue as *const _,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Errors are ignored, since it's an optional feature and failure
|
||||
// doesn't prevent Cargo from working
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use std::iter;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use filetime::FileTime;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
|
||||
use crate::util::errors::{CargoResult, CargoResultExt};
|
||||
|
||||
@ -457,3 +458,91 @@ pub fn strip_prefix_canonical<P: AsRef<Path>>(
|
||||
let canon_base = safe_canonicalize(base.as_ref());
|
||||
canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf())
|
||||
}
|
||||
|
||||
/// Creates an excluded from cache directory atomically with its parents as needed.
|
||||
///
|
||||
/// The atomicity only covers creating the leaf directory and exclusion from cache. Any missing
|
||||
/// parent directories will not be created in an atomic manner.
|
||||
///
|
||||
/// This function is idempotent and in addition to that it won't exclude ``p`` from cache if it
|
||||
/// already exists.
|
||||
pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef<Path>) -> CargoResult<()> {
|
||||
let path = p.as_ref();
|
||||
if path.is_dir() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let parent = path.parent().unwrap();
|
||||
let base = path.file_name().unwrap();
|
||||
create_dir_all(parent)?;
|
||||
// We do this in two steps (first create a temporary directory and exlucde
|
||||
// it from backups, then rename it to the desired name. If we created the
|
||||
// directory directly where it should be and then excluded it from backups
|
||||
// we would risk a situation where cargo is interrupted right after the directory
|
||||
// creation but before the exclusion the the directory would remain non-excluded from
|
||||
// backups because we only perform exclusion right after we created the directory
|
||||
// ourselves.
|
||||
//
|
||||
// We need the tempdir created in parent instead of $TMP, because only then we can be
|
||||
// easily sure that rename() will succeed (the new name needs to be on the same mount
|
||||
// point as the old one).
|
||||
let tempdir = TempFileBuilder::new().prefix(base).tempdir_in(parent)?;
|
||||
exclude_from_backups(&tempdir.path());
|
||||
// Previously std::fs::create_dir_all() (through paths::create_dir_all()) was used
|
||||
// here to create the directory directly and fs::create_dir_all() explicitly treats
|
||||
// the directory being created concurrently by another thread or process as success,
|
||||
// hence the check below to follow the existing behavior. If we get an error at
|
||||
// rename() and suddently the directory (which didn't exist a moment earlier) exists
|
||||
// we can infer from it it's another cargo process doing work.
|
||||
if let Err(e) = fs::rename(tempdir.path(), path) {
|
||||
if !path.exists() {
|
||||
return Err(anyhow::Error::from(e));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Marks the directory as excluded from archives/backups.
|
||||
///
|
||||
/// This is recommended to prevent derived/temporary files from bloating backups. There are two
|
||||
/// mechanisms used to achieve this right now:
|
||||
///
|
||||
/// * A dedicated resource property excluding from Time Machine backups on macOS
|
||||
/// * CACHEDIR.TAG files supported by various tools in a platform-independent way
|
||||
fn exclude_from_backups(path: &Path) {
|
||||
exclude_from_time_machine(path);
|
||||
let _ = std::fs::write(
|
||||
path.join("CACHEDIR.TAG"),
|
||||
"Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/",
|
||||
);
|
||||
// Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn exclude_from_time_machine(_: &Path) {}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
/// Marks files or directories as excluded from Time Machine on macOS
|
||||
fn exclude_from_time_machine(path: &Path) {
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::{number, string, url};
|
||||
use std::ptr;
|
||||
|
||||
// For compatibility with 10.7 a string is used instead of global kCFURLIsExcludedFromBackupKey
|
||||
let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
|
||||
let path = url::CFURL::from_path(path, false);
|
||||
if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
|
||||
unsafe {
|
||||
url::CFURLSetResourcePropertyForKey(
|
||||
path.as_concrete_TypeRef(),
|
||||
is_excluded_key.as_concrete_TypeRef(),
|
||||
number::kCFBooleanTrue as *const _,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Errors are ignored, since it's an optional feature and failure
|
||||
// doesn't prevent Cargo from working
|
||||
}
|
||||
|
@ -5083,3 +5083,23 @@ fn reduced_reproduction_8249() {
|
||||
p.cargo("check").run();
|
||||
p.cargo("check").run();
|
||||
}
|
||||
|
||||
#[cargo_test]
|
||||
fn target_directory_backup_exclusion() {
|
||||
let p = project()
|
||||
.file("Cargo.toml", &basic_bin_manifest("foo"))
|
||||
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
|
||||
.build();
|
||||
|
||||
// Newly created target/ should have CACHEDIR.TAG inside...
|
||||
p.cargo("build").run();
|
||||
let cachedir_tag = p.build_dir().join("CACHEDIR.TAG");
|
||||
assert!(cachedir_tag.is_file());
|
||||
assert!(fs::read_to_string(&cachedir_tag)
|
||||
.unwrap()
|
||||
.starts_with("Signature: 8a477f597d28d172789f06886806bc55"));
|
||||
// ...but if target/ already exists CACHEDIR.TAG should not be created in it.
|
||||
fs::remove_file(&cachedir_tag).unwrap();
|
||||
p.cargo("build").run();
|
||||
assert!(!&cachedir_tag.is_file());
|
||||
}
|
||||
|
@ -432,7 +432,9 @@ fn assert_all_clean(build_dir: &Path) {
|
||||
}) {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if let ".rustc_info.json" | ".cargo-lock" = path.file_name().unwrap().to_str().unwrap() {
|
||||
if let ".rustc_info.json" | ".cargo-lock" | "CACHEDIR.TAG" =
|
||||
path.file_name().unwrap().to_str().unwrap()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if path.is_symlink() || path.is_file() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user