Skip to content

Commit 7a942be

Browse files
committed
Stop shuffling output files around so often
This commit is an architectural change inside of Cargo itself in the way that it handles the output format of builds. Previously when a build start, all existing directories and files would be renamed to `old-foo` folders. The build would then `rename` all files back into the right location as they were seen as fresh and needed for the build. The benefit of a system such as this is a rock-solid guarantee that the build tree contains exactly what it would if we were to start the build from a totally clean directory each time. There are some downsides, however: * In #800, it was discovered that this method has an unfortunate interaction with Docker. Docker apparently will mount many filesystems which `rename` will not work across. * I have seen countless flaky failures on windows due to an attempt to remove a file that was still in use somehow. I've never been able to truly track down why these failures are happening, however. The new system for managing output files is to build up a list of all known files at the start of a build, whitelist any necessary files when the build is being prepared, and then wipe out all unknown files right before the build begins. This is not quite as close to the guarantee as the benefits reaped before because on the second build all build files will still be in their final output locations, they may just get updated as part of the build as well. This seems like an acceptable compromise, however. Closes #800
1 parent bfcc65c commit 7a942be

File tree

4 files changed

+128
-179
lines changed

4 files changed

+128
-179
lines changed

src/cargo/ops/cargo_rustc/custom_build.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,9 @@ pub struct BuildState {
3939
pub fn prepare(pkg: &Package, target: &Target, req: PlatformRequirement,
4040
cx: &mut Context) -> CargoResult<(Work, Work, Freshness)> {
4141
let kind = match req { PlatformPlugin => KindHost, _ => KindTarget, };
42-
let (script_output, build_output, old_build_output) = {
43-
let target = cx.layout(pkg, kind);
42+
let (script_output, build_output) = {
4443
(cx.layout(pkg, KindHost).build(pkg),
45-
target.build_out(pkg),
46-
target.proxy().old_build(pkg).join("out"))
44+
cx.layout(pkg, KindTarget).build_out(pkg))
4745
};
4846

4947
// Building the command to execute
@@ -99,7 +97,6 @@ pub fn prepare(pkg: &Package, target: &Target, req: PlatformRequirement,
9997
let build_state = cx.build_state.clone();
10098
let id = pkg.get_package_id().clone();
10199
let all = (id.clone(), pkg_name.clone(), build_state.clone(),
102-
old_build_output.clone(),
103100
build_output.clone());
104101

105102
try!(fs::mkdir_recursive(&cx.layout(pkg, KindTarget).build(pkg), USER_RWX));
@@ -115,14 +112,12 @@ pub fn prepare(pkg: &Package, target: &Target, req: PlatformRequirement,
115112
//
116113
// If we have an old build directory, then just move it into place,
117114
// otherwise create it!
118-
try!(if old_build_output.exists() {
119-
fs::rename(&old_build_output, &build_output)
120-
} else {
121-
fs::mkdir(&build_output, USER_RWX)
122-
}.chain_error(|| {
123-
internal("failed to create script output directory for \
124-
build command")
125-
}));
115+
if !build_output.exists() {
116+
try!(fs::mkdir(&build_output, USER_RWX).chain_error(|| {
117+
internal("failed to create script output directory for \
118+
build command")
119+
}));
120+
}
126121

127122
// For all our native lib dependencies, pick up their metadata to pass
128123
// along to this custom build command.
@@ -183,10 +178,8 @@ pub fn prepare(pkg: &Package, target: &Target, req: PlatformRequirement,
183178
try!(fingerprint::prepare_build_cmd(cx, pkg, Some(target)));
184179
let dirty = proc(tx: Sender<String>) { try!(work(tx.clone())); dirty(tx) };
185180
let fresh = proc(tx) {
186-
let (id, pkg_name, build_state, old_build_output, build_output) = all;
181+
let (id, pkg_name, build_state, build_output) = all;
187182
let new_loc = build_output.dir_path().join("output");
188-
try!(fs::rename(&old_build_output.dir_path().join("output"), &new_loc));
189-
try!(fs::rename(&old_build_output, &build_output));
190183
let mut f = try!(File::open(&new_loc).map_err(|e| {
191184
human(format!("failed to read cached build command output: {}", e))
192185
}));

src/cargo/ops/cargo_rustc/fingerprint.rs

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::collections::hash_map::{Occupied, Vacant};
22
use std::hash::{Hash, Hasher};
33
use std::hash::sip::SipHasher;
4-
use std::io::{fs, File, USER_RWX, BufferedReader};
4+
use std::io::{mod, fs, File, BufferedReader};
5+
use std::io::fs::PathExtensions;
56

67
use core::{Package, Target};
78
use util;
@@ -43,10 +44,9 @@ pub fn prepare_target(cx: &mut Context, pkg: &Package, target: &Target,
4344
kind: Kind) -> CargoResult<Preparation> {
4445
let _p = profile::start(format!("fingerprint: {} / {}",
4546
pkg.get_package_id(), target));
46-
let (old, new) = dirs(cx, pkg, kind);
47-
let filename = filename(target);
48-
let old_loc = old.join(filename.as_slice());
49-
let new_loc = new.join(filename.as_slice());
47+
let new = dir(cx, pkg, kind);
48+
let loc = new.join(filename(target));
49+
cx.layout(pkg, kind).proxy().whitelist(&loc);
5050

5151
// We want to use the package fingerprint if we're either a doc target or a
5252
// path source. If we're a git/registry source, then the mtime of files may
@@ -58,13 +58,13 @@ pub fn prepare_target(cx: &mut Context, pkg: &Package, target: &Target,
5858
doc || !path
5959
};
6060

61-
info!("fingerprint at: {}", new_loc.display());
61+
info!("fingerprint at: {}", loc.display());
6262

6363
// First bit of the freshness calculation, whether the dep-info file
6464
// indicates that the target is fresh.
65-
let (old_dep_info, new_dep_info) = dep_info_loc(cx, pkg, target, kind);
65+
let dep_info = dep_info_loc(cx, pkg, target, kind);
6666
let are_files_fresh = use_pkg ||
67-
try!(calculate_target_fresh(pkg, &old_dep_info));
67+
try!(calculate_target_fresh(pkg, &dep_info));
6868

6969
// Second bit of the freshness calculation, whether rustc itself, the
7070
// target are fresh, and the enabled set of features are all fresh.
@@ -80,43 +80,38 @@ pub fn prepare_target(cx: &mut Context, pkg: &Package, target: &Target,
8080
} else {
8181
mk_fingerprint(cx, &(target, features))
8282
};
83-
let is_rustc_fresh = try!(is_fresh(&old_loc, rustc_fingerprint.as_slice()));
83+
let is_rustc_fresh = try!(is_fresh(&loc, rustc_fingerprint.as_slice()));
8484

85-
let (old_root, root) = {
85+
let root = {
8686
let layout = cx.layout(pkg, kind);
8787
if target.get_profile().is_custom_build() {
88-
(layout.old_build(pkg), layout.build(pkg))
88+
layout.build(pkg)
8989
} else if target.is_example() {
90-
(layout.old_examples().clone(), layout.examples().clone())
90+
layout.examples().clone()
9191
} else {
92-
(layout.old_root().clone(), layout.root().clone())
92+
layout.root().clone()
9393
}
9494
};
95-
let mut pairs = vec![(old_loc, new_loc.clone())];
9695
if !target.get_profile().is_doc() {
97-
pairs.push((old_dep_info, new_dep_info));
98-
9996
for filename in try!(cx.target_filenames(target)).iter() {
100-
let filename = filename.as_slice();
10197
let dst = root.join(filename);
102-
pairs.push((old_root.join(filename), root.join(filename)));
98+
cx.layout(pkg, kind).proxy().whitelist(&dst);
10399

104100
if target.get_profile().is_test() {
105-
cx.compilation.tests.push((target.get_name().into_string(), dst.clone()));
101+
cx.compilation.tests.push((target.get_name().into_string(), dst));
106102
} else if target.is_bin() {
107-
cx.compilation.binaries.push(dst.clone());
103+
cx.compilation.binaries.push(dst);
108104
} else if target.is_lib() {
109105
let pkgid = pkg.get_package_id().clone();
110106
match cx.compilation.libraries.entry(pkgid) {
111107
Occupied(entry) => entry.into_mut(),
112108
Vacant(entry) => entry.set(Vec::new()),
113-
}.push(root.join(filename));
109+
}.push(dst);
114110
}
115111
}
116112
}
117113

118-
Ok(prepare(is_rustc_fresh && are_files_fresh, new_loc, rustc_fingerprint,
119-
pairs))
114+
Ok(prepare(is_rustc_fresh && are_files_fresh, loc, rustc_fingerprint))
120115
}
121116

122117
/// Prepare the necessary work for the fingerprint of a build command.
@@ -147,76 +142,73 @@ pub fn prepare_build_cmd(cx: &mut Context, pkg: &Package,
147142
if pkg.get_manifest().get_build().len() == 0 && target.is_none() {
148143
return Ok((Fresh, proc(_) Ok(()), proc(_) Ok(())))
149144
}
150-
let (old, new) = dirs(cx, pkg, kind);
151-
let old_loc = old.join("build");
152-
let new_loc = new.join("build");
145+
let new = dir(cx, pkg, kind);
146+
let loc = new.join("build");
147+
cx.layout(pkg, kind).proxy().whitelist(&loc);
153148

154-
info!("fingerprint at: {}", new_loc.display());
149+
info!("fingerprint at: {}", loc.display());
155150

156151
let new_fingerprint = try!(calculate_build_cmd_fingerprint(cx, pkg));
157152
let new_fingerprint = mk_fingerprint(cx, &new_fingerprint);
158153

159-
let is_fresh = try!(is_fresh(&old_loc, new_fingerprint.as_slice()));
160-
let mut pairs = vec![(old_loc, new_loc.clone())];
154+
let is_fresh = try!(is_fresh(&loc, new_fingerprint.as_slice()));
161155

162156
// The new custom build command infrastructure handles its own output
163157
// directory as part of freshness.
164158
if target.is_none() {
165159
let native_dir = cx.layout(pkg, kind).native(pkg);
166-
pairs.push((cx.layout(pkg, kind).old_native(pkg), native_dir.clone()));
167160
cx.compilation.native_dirs.insert(pkg.get_package_id().clone(),
168161
native_dir);
169162
}
170163

171-
Ok(prepare(is_fresh, new_loc, new_fingerprint, pairs))
164+
Ok(prepare(is_fresh, loc, new_fingerprint))
172165
}
173166

174167
/// Prepare work for when a package starts to build
175168
pub fn prepare_init(cx: &mut Context, pkg: &Package, kind: Kind)
176169
-> (Work, Work) {
177-
let (_, new1) = dirs(cx, pkg, kind);
170+
let new1 = dir(cx, pkg, kind);
178171
let new2 = new1.clone();
179172

180-
let work1 = proc(_) { try!(fs::mkdir(&new1, USER_RWX)); Ok(()) };
181-
let work2 = proc(_) { try!(fs::mkdir(&new2, USER_RWX)); Ok(()) };
173+
let work1 = proc(_) {
174+
if !new1.exists() {
175+
try!(fs::mkdir(&new1, io::USER_DIR));
176+
}
177+
Ok(())
178+
};
179+
let work2 = proc(_) {
180+
if !new2.exists() {
181+
try!(fs::mkdir(&new2, io::USER_DIR));
182+
}
183+
Ok(())
184+
};
182185

183186
(work1, work2)
184187
}
185188

186189
/// Given the data to build and write a fingerprint, generate some Work
187190
/// instances to actually perform the necessary work.
188-
fn prepare(is_fresh: bool, loc: Path, fingerprint: String,
189-
to_copy: Vec<(Path, Path)>) -> Preparation {
191+
fn prepare(is_fresh: bool, loc: Path, fingerprint: String) -> Preparation {
190192
let write_fingerprint = proc(desc_tx) {
191193
drop(desc_tx);
192194
try!(File::create(&loc).write_str(fingerprint.as_slice()));
193195
Ok(())
194196
};
195197

196-
let move_old = proc(desc_tx) {
197-
drop(desc_tx);
198-
for &(ref src, ref dst) in to_copy.iter() {
199-
try!(fs::rename(src, dst));
200-
}
201-
Ok(())
202-
};
203-
204-
(if is_fresh {Fresh} else {Dirty}, write_fingerprint, move_old)
198+
(if is_fresh {Fresh} else {Dirty}, write_fingerprint, proc(_) Ok(()))
205199
}
206200

207201
/// Return the (old, new) location for fingerprints for a package
208-
pub fn dirs(cx: &Context, pkg: &Package, kind: Kind) -> (Path, Path) {
209-
let layout = cx.layout(pkg, kind);
210-
let layout = layout.proxy();
211-
(layout.old_fingerprint(pkg), layout.fingerprint(pkg))
202+
pub fn dir(cx: &Context, pkg: &Package, kind: Kind) -> Path {
203+
cx.layout(pkg, kind).proxy().fingerprint(pkg)
212204
}
213205

214206
/// Returns the (old, new) location for the dep info file of a target.
215207
pub fn dep_info_loc(cx: &Context, pkg: &Package, target: &Target,
216-
kind: Kind) -> (Path, Path) {
217-
let (old, new) = dirs(cx, pkg, kind);
218-
let filename = format!("dep-{}", filename(target));
219-
(old.join(filename.as_slice()), new.join(filename))
208+
kind: Kind) -> Path {
209+
let ret = dir(cx, pkg, kind).join(format!("dep-{}", filename(target)));
210+
cx.layout(pkg, kind).proxy().whitelist(&ret);
211+
return ret;
220212
}
221213

222214
fn is_fresh(loc: &Path, new_fingerprint: &str) -> CargoResult<bool> {

0 commit comments

Comments
 (0)