Skip to content

Commit a9fd1c2

Browse files
committed
Change Config::target_dir to return Filesystem
This is a shared directory among multiple Cargo processes so access to it needs to be properly synchronized. This commit changes the API of `Config::target_dir` and then propagates the changes outward as necessary. One fallout of this change is now we allow release/debug builds to proceed in parallel.
1 parent fae9c53 commit a9fd1c2

File tree

11 files changed

+135
-72
lines changed

11 files changed

+135
-72
lines changed

src/cargo/ops/cargo_clean.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,24 @@ pub fn clean(manifest_path: &Path, opts: &CleanOptions) -> CargoResult<()> {
2121

2222
// If we have a spec, then we need to delete some packages, otherwise, just
2323
// remove the whole target directory and be done with it!
24+
//
25+
// Note that we don't bother grabbing a lock here as we're just going to
26+
// blow it all away anyway.
2427
if opts.spec.is_empty() {
28+
let target_dir = target_dir.into_path_unlocked();
2529
return rm_rf(&target_dir);
2630
}
2731

2832
let (resolve, packages) = try!(ops::fetch(manifest_path, opts.config));
2933

3034
let dest = if opts.release {"release"} else {"debug"};
31-
let host_layout = Layout::new(opts.config, &root, None, dest);
32-
let target_layout = opts.target.map(|target| {
33-
Layout::new(opts.config, &root, Some(target), dest)
34-
});
35+
let host_layout = try!(Layout::new(opts.config, &root, None, dest));
36+
let target_layout = match opts.target {
37+
Some(target) => {
38+
Some(try!(Layout::new(opts.config, &root, Some(target), dest)))
39+
}
40+
None => None,
41+
};
3542

3643
let cx = try!(Context::new(&resolve, &packages, opts.config,
3744
host_layout, target_layout,

src/cargo/ops/cargo_doc.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ pub fn doc(manifest_path: &Path,
5050
}
5151
};
5252

53+
// Don't bother locking here as if this is getting deleted there's
54+
// nothing we can do about it and otherwise if it's getting overwritten
55+
// then that's also ok!
5356
let target_dir = options.compile_opts.config.target_dir(&package);
5457
let path = target_dir.join("doc").join(&name).join("index.html");
58+
let path = path.into_path_unlocked();
5559
if fs::metadata(&path).is_ok() {
5660
let mut shell = options.compile_opts.config.shell();
5761
match open_docs(&path) {

src/cargo/ops/cargo_install.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ pub fn install(root: Option<&str>,
8282
let target_dir = if source_id.is_path() {
8383
config.target_dir(&pkg)
8484
} else {
85-
config.cwd().join("target-install")
85+
Filesystem::new(config.cwd().join("target-install"))
8686
};
87-
config.set_target_dir(&target_dir);
87+
config.set_target_dir(target_dir.clone());
8888
let compile = try!(ops::compile_pkg(&pkg, Some(source), opts).chain_error(|| {
8989
human(format!("failed to compile `{}`, intermediate artifacts can be \
9090
found at `{}`", pkg, target_dir.display()))
@@ -108,6 +108,9 @@ pub fn install(root: Option<&str>,
108108
}
109109

110110
if !source_id.is_path() {
111+
// Don't bother grabbing a lock as we're going to blow it all away
112+
// anyway.
113+
let target_dir = target_dir.into_path_unlocked();
111114
try!(fs::remove_dir_all(&target_dir));
112115
}
113116

src/cargo/ops/cargo_package.rs

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
use std::io::prelude::*;
21
use std::fs::{self, File};
3-
use std::path::{self, Path, PathBuf};
2+
use std::io::SeekFrom;
3+
use std::io::prelude::*;
4+
use std::path::{self, Path};
45

56
use tar::{Archive, Builder, Header};
67
use flate2::{GzBuilder, Compression};
78
use flate2::read::GzDecoder;
89

910
use core::{SourceId, Package, PackageId};
1011
use sources::PathSource;
11-
use util::{self, CargoResult, human, internal, ChainError, Config};
12+
use util::{self, CargoResult, human, internal, ChainError, Config, FileLock};
1213
use ops;
1314

1415
pub fn package(manifest_path: &Path,
1516
config: &Config,
1617
verify: bool,
1718
list: bool,
18-
metadata: bool) -> CargoResult<Option<PathBuf>> {
19+
metadata: bool) -> CargoResult<Option<FileLock>> {
1920
let path = manifest_path.parent().unwrap();
2021
let id = try!(SourceId::for_path(path));
2122
let mut src = PathSource::new(path, &id, config);
@@ -39,29 +40,37 @@ pub fn package(manifest_path: &Path,
3940

4041
let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
4142
let dir = config.target_dir(&pkg).join("package");
42-
let dst = dir.join(&filename);
43-
if fs::metadata(&dst).is_ok() {
44-
return Ok(Some(dst))
45-
}
43+
let mut dst = match dir.open_ro(&filename, config, "packaged crate") {
44+
Ok(f) => return Ok(Some(f)),
45+
Err(..) => {
46+
let tmp = format!(".{}", filename);
47+
try!(dir.open_rw(&tmp, config, "package scratch space"))
48+
}
49+
};
4650

4751
// Package up and test a temporary tarball and only move it to the final
4852
// location if it actually passes all our tests. Any previously existing
4953
// tarball can be assumed as corrupt or invalid, so we just blow it away if
5054
// it exists.
5155
try!(config.shell().status("Packaging", pkg.package_id().to_string()));
52-
let tmp_dst = dir.join(format!(".{}", filename));
53-
let _ = fs::remove_file(&tmp_dst);
54-
try!(tar(&pkg, &src, config, &tmp_dst, &filename).chain_error(|| {
56+
try!(dst.file().set_len(0));
57+
try!(tar(&pkg, &src, config, dst.file(), &filename).chain_error(|| {
5558
human("failed to prepare local package for uploading")
5659
}));
5760
if verify {
58-
try!(run_verify(config, &pkg, &tmp_dst).chain_error(|| {
61+
try!(dst.seek(SeekFrom::Start(0)));
62+
try!(run_verify(config, &pkg, dst.file()).chain_error(|| {
5963
human("failed to verify package tarball")
6064
}))
6165
}
62-
try!(fs::rename(&tmp_dst, &dst).chain_error(|| {
63-
human("failed to move temporary tarball into final location")
64-
}));
66+
try!(dst.seek(SeekFrom::Start(0)));
67+
{
68+
let src_path = dst.path();
69+
let dst_path = dst.parent().join(&filename);
70+
try!(fs::rename(&src_path, &dst_path).chain_error(|| {
71+
human("failed to move temporary tarball into final location")
72+
}));
73+
}
6574
Ok(Some(dst))
6675
}
6776

@@ -103,26 +112,17 @@ fn check_metadata(pkg: &Package, config: &Config) -> CargoResult<()> {
103112
fn tar(pkg: &Package,
104113
src: &PathSource,
105114
config: &Config,
106-
dst: &Path,
115+
dst: &File,
107116
filename: &str) -> CargoResult<()> {
108-
if fs::metadata(&dst).is_ok() {
109-
bail!("destination already exists: {}", dst.display())
110-
}
111-
112-
try!(fs::create_dir_all(dst.parent().unwrap()));
113-
114-
let tmpfile = try!(File::create(dst));
115-
116117
// Prepare the encoder and its header
117118
let filename = Path::new(filename);
118119
let encoder = GzBuilder::new().filename(try!(util::path2bytes(filename)))
119-
.write(tmpfile, Compression::Best);
120+
.write(dst, Compression::Best);
120121

121122
// Put all package files into a compressed archive
122123
let mut ar = Builder::new(encoder);
123124
let root = pkg.root();
124125
for file in try!(src.list_files(pkg)).iter() {
125-
if &**file == dst { continue }
126126
let relative = util::without_prefix(&file, &root).unwrap();
127127
try!(check_filename(relative));
128128
let relative = try!(relative.to_str().chain_error(|| {
@@ -173,11 +173,13 @@ fn tar(pkg: &Package,
173173
Ok(())
174174
}
175175

176-
fn run_verify(config: &Config, pkg: &Package, tar: &Path)
176+
fn run_verify(config: &Config,
177+
pkg: &Package,
178+
tar: &File)
177179
-> CargoResult<()> {
178180
try!(config.shell().status("Verifying", pkg));
179181

180-
let f = try!(GzDecoder::new(try!(File::open(tar))));
182+
let f = try!(GzDecoder::new(tar));
181183
let dst = pkg.root().join(&format!("target/package/{}-{}",
182184
pkg.name(), pkg.version()));
183185
if fs::metadata(&dst).is_ok() {

src/cargo/ops/cargo_rustc/layout.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use std::io;
5050
use std::path::{PathBuf, Path};
5151

5252
use core::{Package, Target};
53-
use util::Config;
53+
use util::{Config, FileLock, CargoResult, Filesystem};
5454
use util::hex::short_hash;
5555

5656
pub struct Layout {
@@ -60,6 +60,7 @@ pub struct Layout {
6060
build: PathBuf,
6161
fingerprint: PathBuf,
6262
examples: PathBuf,
63+
_lock: FileLock,
6364
}
6465

6566
pub struct LayoutProxy<'a> {
@@ -68,8 +69,10 @@ pub struct LayoutProxy<'a> {
6869
}
6970

7071
impl Layout {
71-
pub fn new(config: &Config, pkg: &Package, triple: Option<&str>,
72-
dest: &str) -> Layout {
72+
pub fn new(config: &Config,
73+
pkg: &Package,
74+
triple: Option<&str>,
75+
dest: &str) -> CargoResult<Layout> {
7376
let mut path = config.target_dir(pkg);
7477
// Flexible target specifications often point at filenames, so interpret
7578
// the target triple as a Path and then just use the file stem as the
@@ -78,18 +81,25 @@ impl Layout {
7881
path.push(Path::new(triple).file_stem().unwrap());
7982
}
8083
path.push(dest);
81-
Layout::at(path)
84+
Layout::at(config, path)
8285
}
8386

84-
pub fn at(root: PathBuf) -> Layout {
85-
Layout {
87+
pub fn at(config: &Config, root: Filesystem) -> CargoResult<Layout> {
88+
// For now we don't do any more finer-grained locking on the artifact
89+
// directory, so just lock the entire thing for the duration of this
90+
// compile.
91+
let lock = try!(root.open_rw(".cargo-lock", config, "build directory"));
92+
let root = root.into_path_unlocked();
93+
94+
Ok(Layout {
8695
deps: root.join("deps"),
8796
native: root.join("native"),
8897
build: root.join("build"),
8998
fingerprint: root.join(".fingerprint"),
9099
examples: root.join("examples"),
91100
root: root,
92-
}
101+
_lock: lock,
102+
})
93103
}
94104

95105
pub fn prepare(&mut self) -> io::Result<()> {

src/cargo/ops/cargo_rustc/mod.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ use std::env;
33
use std::ffi::{OsStr, OsString};
44
use std::fs;
55
use std::io::prelude::*;
6-
use std::path::{self, PathBuf, Path};
6+
use std::path::{self, PathBuf};
77
use std::sync::Arc;
88

99
use core::{Package, PackageId, PackageSet, Target, Resolve};
1010
use core::{Profile, Profiles};
11-
use util::{self, CargoResult, human, Filesystem};
11+
use util::{self, CargoResult, human};
1212
use util::{Config, internal, ChainError, profile, join_paths};
1313

1414
use self::job::{Job, Work};
@@ -80,17 +80,13 @@ pub fn compile_targets<'a, 'cfg: 'a>(pkg_targets: &'a PackagesToBuild<'a>,
8080

8181
let dest = if build_config.release {"release"} else {"debug"};
8282
let root = try!(packages.get(resolve.root()));
83-
let host_layout = Layout::new(config, root, None, &dest);
84-
let target_layout = build_config.requested_target.as_ref().map(|target| {
85-
layout::Layout::new(config, root, Some(&target), &dest)
86-
});
87-
88-
// For now we don't do any more finer-grained locking on the artifact
89-
// directory, so just lock the entire thing for the duration of this
90-
// compile.
91-
let fs = Filesystem::new(host_layout.root().to_path_buf());
92-
let path = Path::new(".cargo-lock");
93-
let _lock = try!(fs.open_rw(path, config, "build directory"));
83+
let host_layout = try!(Layout::new(config, root, None, &dest));
84+
let target_layout = match build_config.requested_target.as_ref() {
85+
Some(target) => {
86+
Some(try!(layout::Layout::new(config, root, Some(&target), &dest)))
87+
}
88+
None => None,
89+
};
9490

9591
let mut cx = try!(Context::new(resolve, packages, config,
9692
host_layout, target_layout,

src/cargo/ops/registry.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashMap;
22
use std::env;
3-
use std::fs;
3+
use std::fs::{self, File};
44
use std::io::prelude::*;
55
use std::iter::repeat;
66
use std::path::{Path, PathBuf};
@@ -51,7 +51,7 @@ pub fn publish(manifest_path: &Path,
5151

5252
// Upload said tarball to the specified destination
5353
try!(config.shell().status("Uploading", pkg.package_id().to_string()));
54-
try!(transmit(&pkg, &tarball, &mut registry));
54+
try!(transmit(&pkg, tarball.file(), &mut registry));
5555

5656
Ok(())
5757
}
@@ -74,7 +74,7 @@ fn verify_dependencies(pkg: &Package, registry_src: &SourceId)
7474
Ok(())
7575
}
7676

77-
fn transmit(pkg: &Package, tarball: &Path, registry: &mut Registry)
77+
fn transmit(pkg: &Package, tarball: &File, registry: &mut Registry)
7878
-> CargoResult<()> {
7979
let deps = pkg.dependencies().iter().map(|dep| {
8080
NewCrateDependency {

src/cargo/util/config.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub struct Config {
3030
cwd: PathBuf,
3131
rustc: PathBuf,
3232
rustdoc: PathBuf,
33-
target_dir: RefCell<Option<PathBuf>>,
33+
target_dir: RefCell<Option<Filesystem>>,
3434
}
3535

3636
impl Config {
@@ -110,14 +110,14 @@ impl Config {
110110

111111
pub fn cwd(&self) -> &Path { &self.cwd }
112112

113-
pub fn target_dir(&self, pkg: &Package) -> PathBuf {
113+
pub fn target_dir(&self, pkg: &Package) -> Filesystem {
114114
self.target_dir.borrow().clone().unwrap_or_else(|| {
115-
pkg.root().join("target")
115+
Filesystem::new(pkg.root().join("target"))
116116
})
117117
}
118118

119-
pub fn set_target_dir(&self, path: &Path) {
120-
*self.target_dir.borrow_mut() = Some(path.to_owned());
119+
pub fn set_target_dir(&self, path: Filesystem) {
120+
*self.target_dir.borrow_mut() = Some(path);
121121
}
122122

123123
fn get(&self, key: &str) -> CargoResult<Option<ConfigValue>> {
@@ -327,9 +327,9 @@ impl Config {
327327

328328
fn scrape_target_dir_config(&mut self) -> CargoResult<()> {
329329
if let Some(dir) = env::var_os("CARGO_TARGET_DIR") {
330-
*self.target_dir.borrow_mut() = Some(self.cwd.join(dir));
330+
*self.target_dir.borrow_mut() = Some(Filesystem::new(self.cwd.join(dir)));
331331
} else if let Some(val) = try!(self.get_path("build.target-dir")) {
332-
*self.target_dir.borrow_mut() = Some(val.val);
332+
*self.target_dir.borrow_mut() = Some(Filesystem::new(val.val));
333333
}
334334
Ok(())
335335
}

src/cargo/util/flock.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fs::{self, File, OpenOptions};
22
use std::io::*;
33
use std::io;
4-
use std::path::{Path, PathBuf};
4+
use std::path::{Path, PathBuf, Display};
55

66
use term::color::CYAN;
77
use fs2::{FileExt, lock_contended_error};
@@ -102,7 +102,7 @@ impl Drop for FileLock {
102102
/// The `Path` of a filesystem cannot be learned unless it's done in a locked
103103
/// fashion, and otherwise functions on this structure are prepared to handle
104104
/// concurrent invocations across multiple instances of Cargo.
105-
#[derive(Clone)]
105+
#[derive(Clone, Debug)]
106106
pub struct Filesystem {
107107
root: PathBuf,
108108
}
@@ -119,6 +119,11 @@ impl Filesystem {
119119
Filesystem::new(self.root.join(other))
120120
}
121121

122+
/// Like `Path::push`, pushes a new path component onto this filesystem.
123+
pub fn push<T: AsRef<Path>>(&mut self, other: T) {
124+
self.root.push(other);
125+
}
126+
122127
/// Consumes this filesystem and returns the underlying `PathBuf`.
123128
///
124129
/// Note that this is a relatively dangerous operation and should be used
@@ -135,6 +140,12 @@ impl Filesystem {
135140
return create_dir_all(&self.root);
136141
}
137142

143+
/// Returns an adaptor that can be used to print the path of this
144+
/// filesystem.
145+
pub fn display(&self) -> Display {
146+
self.root.display()
147+
}
148+
138149
/// Opens exclusive access to a file, returning the locked version of a
139150
/// file.
140151
///

0 commit comments

Comments
 (0)