Skip to content

Commit 1b5f749

Browse files
committed
Exclude target/doc from backups as well
1 parent 50f2290 commit 1b5f749

File tree

4 files changed

+155
-80
lines changed

4 files changed

+155
-80
lines changed

src/cargo/core/compiler/layout.rs

Lines changed: 1 addition & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ use crate::core::compiler::CompileTarget;
102102
use crate::core::Workspace;
103103
use crate::util::paths;
104104
use crate::util::{CargoResult, FileLock};
105-
use std::fs;
106105
use std::path::{Path, PathBuf};
107-
use tempfile::Builder as TempFileBuilder;
108106

109107
/// Contains the paths of all target output locations.
110108
///
@@ -148,43 +146,12 @@ impl Layout {
148146
if let Some(target) = target {
149147
root.push(target.short_name());
150148
}
151-
// We need root to exist before we do the tempdir/rename dance inside it.
152-
if !root.as_path_unlocked().exists() {
153-
root.create_dir()?;
154-
}
155-
let dest_base = dest;
156149
let dest = root.join(dest);
157150
// If the root directory doesn't already exist go ahead and create it
158151
// here. Use this opportunity to exclude it from backups as well if the
159152
// system supports it since this is a freshly created folder.
160153
//
161-
// We do this in two steps (first create a temporary directory and exlucde
162-
// it from backups, then rename it to the desired name. If we created the
163-
// directory directly where it should be and then excluded it from backups
164-
// we would risk a situation where cargo is interrupted right after the directory
165-
// creation but before the exclusion the the directory would remain non-excluded from
166-
// backups because we only perform exclusion right after we created the directory
167-
// ourselves.
168-
if !dest.as_path_unlocked().exists() {
169-
// We need the tempdir created in root instead of $TMP, because only then we can be
170-
// easily sure that rename() will succeed (the new name needs to be on the same mount
171-
// point as the old one).
172-
let tempdir = TempFileBuilder::new()
173-
.prefix(dest_base)
174-
.tempdir_in(root.as_path_unlocked())?;
175-
exclude_from_backups(&tempdir.path());
176-
// Previously std::fs::create_dir_all() (through paths::create_dir_all()) was used
177-
// here to create the directory directly and fs::create_dir_all() explicitly treats
178-
// the directory being created concurrently by another thread or process as success,
179-
// hence the check below to follow the existing behavior. If we get an error at
180-
// rename() and suddently the directory (which didn't exist a moment earlier) exists
181-
// we can infer from it it's another cargo process doing work.
182-
if let Err(e) = fs::rename(tempdir.path(), dest.as_path_unlocked()) {
183-
if !dest.as_path_unlocked().exists() {
184-
return Err(anyhow::Error::from(e));
185-
}
186-
}
187-
}
154+
paths::create_dir_all_excluded_from_backups_atomic(dest.as_path_unlocked())?;
188155

189156
// For now we don't do any more finer-grained locking on the artifact
190157
// directory, so just lock the entire thing for the duration of this
@@ -250,48 +217,3 @@ impl Layout {
250217
&self.build
251218
}
252219
}
253-
254-
/// Marks the directory as excluded from archives/backups.
255-
///
256-
/// This is recommended to prevent derived/temporary files from bloating backups. There are two
257-
/// mechanisms used to achieve this right now:
258-
///
259-
/// * A dedicated resource property excluding from Time Machine backups on macOS
260-
/// * CACHEDIR.TAG files supported by various tools in a platform-independent way
261-
fn exclude_from_backups(path: &Path) {
262-
exclude_from_time_machine(path);
263-
let _ = std::fs::write(
264-
path.join("CACHEDIR.TAG"),
265-
"Signature: 8a477f597d28d172789f06886806bc55
266-
# This file is a cache directory tag created by cargo.
267-
# For information about cache directory tags see https://bford.info/cachedir/",
268-
);
269-
// Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
270-
}
271-
272-
#[cfg(not(target_os = "macos"))]
273-
fn exclude_from_time_machine(_: &Path) {}
274-
275-
#[cfg(target_os = "macos")]
276-
/// Marks files or directories as excluded from Time Machine on macOS
277-
fn exclude_from_time_machine(path: &Path) {
278-
use core_foundation::base::TCFType;
279-
use core_foundation::{number, string, url};
280-
use std::ptr;
281-
282-
// For compatibility with 10.7 a string is used instead of global kCFURLIsExcludedFromBackupKey
283-
let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
284-
let path = url::CFURL::from_path(path, false);
285-
if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
286-
unsafe {
287-
url::CFURLSetResourcePropertyForKey(
288-
path.as_concrete_TypeRef(),
289-
is_excluded_key.as_concrete_TypeRef(),
290-
number::kCFBooleanTrue as *const _,
291-
ptr::null_mut(),
292-
);
293-
}
294-
}
295-
// Errors are ignored, since it's an optional feature and failure
296-
// doesn't prevent Cargo from working
297-
}

src/cargo/core/compiler/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
557557
// Create the documentation directory ahead of time as rustdoc currently has
558558
// a bug where concurrent invocations will race to create this directory if
559559
// it doesn't already exist.
560-
paths::create_dir_all(&doc_dir)?;
560+
paths::create_dir_all_excluded_from_backups_atomic(&doc_dir)?;
561561

562562
rustdoc.arg("-o").arg(doc_dir);
563563

src/cargo/util/paths.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::iter;
77
use std::path::{Component, Path, PathBuf};
88

99
use filetime::FileTime;
10+
use tempfile::Builder as TempFileBuilder;
1011

1112
use crate::util::errors::{CargoResult, CargoResultExt};
1213

@@ -457,3 +458,91 @@ pub fn strip_prefix_canonical<P: AsRef<Path>>(
457458
let canon_base = safe_canonicalize(base.as_ref());
458459
canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf())
459460
}
461+
462+
/// Creates an excluded from cache directory atomically with its parents as needed.
463+
///
464+
/// The atomicity only covers creating the leaf directory and exclusion from cache. Any missing
465+
/// parent directories will not be created in an atomic manner.
466+
///
467+
/// This function is idempotent and in addition to that it won't exclude ``p`` from cache if it
468+
/// already exists.
469+
pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef<Path>) -> CargoResult<()> {
470+
let path = p.as_ref();
471+
if path.is_dir() {
472+
return Ok(());
473+
}
474+
475+
let parent = path.parent().unwrap();
476+
let base = path.file_name().unwrap();
477+
create_dir_all(parent)?;
478+
// We do this in two steps (first create a temporary directory and exlucde
479+
// it from backups, then rename it to the desired name. If we created the
480+
// directory directly where it should be and then excluded it from backups
481+
// we would risk a situation where cargo is interrupted right after the directory
482+
// creation but before the exclusion the the directory would remain non-excluded from
483+
// backups because we only perform exclusion right after we created the directory
484+
// ourselves.
485+
//
486+
// We need the tempdir created in parent instead of $TMP, because only then we can be
487+
// easily sure that rename() will succeed (the new name needs to be on the same mount
488+
// point as the old one).
489+
let tempdir = TempFileBuilder::new().prefix(base).tempdir_in(parent)?;
490+
exclude_from_backups(&tempdir.path());
491+
// Previously std::fs::create_dir_all() (through paths::create_dir_all()) was used
492+
// here to create the directory directly and fs::create_dir_all() explicitly treats
493+
// the directory being created concurrently by another thread or process as success,
494+
// hence the check below to follow the existing behavior. If we get an error at
495+
// rename() and suddently the directory (which didn't exist a moment earlier) exists
496+
// we can infer from it it's another cargo process doing work.
497+
if let Err(e) = fs::rename(tempdir.path(), path) {
498+
if !path.exists() {
499+
return Err(anyhow::Error::from(e));
500+
}
501+
}
502+
Ok(())
503+
}
504+
505+
/// Marks the directory as excluded from archives/backups.
506+
///
507+
/// This is recommended to prevent derived/temporary files from bloating backups. There are two
508+
/// mechanisms used to achieve this right now:
509+
///
510+
/// * A dedicated resource property excluding from Time Machine backups on macOS
511+
/// * CACHEDIR.TAG files supported by various tools in a platform-independent way
512+
fn exclude_from_backups(path: &Path) {
513+
exclude_from_time_machine(path);
514+
let _ = std::fs::write(
515+
path.join("CACHEDIR.TAG"),
516+
"Signature: 8a477f597d28d172789f06886806bc55
517+
# This file is a cache directory tag created by cargo.
518+
# For information about cache directory tags see https://bford.info/cachedir/",
519+
);
520+
// Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
521+
}
522+
523+
#[cfg(not(target_os = "macos"))]
524+
fn exclude_from_time_machine(_: &Path) {}
525+
526+
#[cfg(target_os = "macos")]
527+
/// Marks files or directories as excluded from Time Machine on macOS
528+
fn exclude_from_time_machine(path: &Path) {
529+
use core_foundation::base::TCFType;
530+
use core_foundation::{number, string, url};
531+
use std::ptr;
532+
533+
// For compatibility with 10.7 a string is used instead of global kCFURLIsExcludedFromBackupKey
534+
let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
535+
let path = url::CFURL::from_path(path, false);
536+
if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
537+
unsafe {
538+
url::CFURLSetResourcePropertyForKey(
539+
path.as_concrete_TypeRef(),
540+
is_excluded_key.as_concrete_TypeRef(),
541+
number::kCFBooleanTrue as *const _,
542+
ptr::null_mut(),
543+
);
544+
}
545+
}
546+
// Errors are ignored, since it's an optional feature and failure
547+
// doesn't prevent Cargo from working
548+
}

tests/testsuite/doc.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,3 +1551,67 @@ fn crate_versions_flag_is_overridden() {
15511551
.run();
15521552
asserts(output_documentation());
15531553
}
1554+
1555+
#[cargo_test]
1556+
fn target_directory_is_excluded_from_backups() {
1557+
let p = project()
1558+
.file(
1559+
"Cargo.toml",
1560+
r#"
1561+
[package]
1562+
name = "foo"
1563+
version = "0.0.1"
1564+
authors = []
1565+
build = "build.rs"
1566+
"#,
1567+
)
1568+
.file("build.rs", "fn main() {}")
1569+
.file("src/lib.rs", "pub fn foo() {}")
1570+
.build();
1571+
1572+
p.cargo("doc")
1573+
.with_stderr(
1574+
"\
1575+
[..] foo v0.0.1 ([CWD])
1576+
[..] foo v0.0.1 ([CWD])
1577+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1578+
",
1579+
)
1580+
.run();
1581+
let cachedir_tag = p.root().join("target/doc/CACHEDIR.TAG");
1582+
assert!(cachedir_tag.is_file());
1583+
assert!(fs::read_to_string(&cachedir_tag)
1584+
.unwrap()
1585+
.starts_with("Signature: 8a477f597d28d172789f06886806bc55"));
1586+
}
1587+
1588+
#[cargo_test]
1589+
fn target_directory_is_not_excluded_from_backups_if_it_already_exists() {
1590+
let p = project()
1591+
.file(
1592+
"Cargo.toml",
1593+
r#"
1594+
[package]
1595+
name = "foo"
1596+
version = "0.0.1"
1597+
authors = []
1598+
build = "build.rs"
1599+
"#,
1600+
)
1601+
.file("build.rs", "fn main() {}")
1602+
.file("src/lib.rs", "pub fn foo() {}")
1603+
.build();
1604+
fs::create_dir_all(p.root().join("target/doc")).unwrap();
1605+
1606+
p.cargo("doc")
1607+
.with_stderr(
1608+
"\
1609+
[..] foo v0.0.1 ([CWD])
1610+
[..] foo v0.0.1 ([CWD])
1611+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1612+
",
1613+
)
1614+
.run();
1615+
let cachedir_tag = p.root().join("target/doc/CACHEDIR.TAG");
1616+
assert!(!&cachedir_tag.is_file());
1617+
}

0 commit comments

Comments
 (0)