Skip to content

Commit e925003

Browse files
committed
rustbuild: Fix copying duplicate crates into the sysroot
After compiling a project (e.g. libstd, libtest, or librustc) rustbuild needs to copy over all artifacts into the sysroot of the compiler it's assembling. Unfortunately rustbuild doesn't know precisely what files to copy! Today it has a heuristic where it just looks at the most recent version of all files that look like rlibs/dylibs and copies those over. This unfortunately leads to bugs with different versions of the same craet as seen in #42261. This commit updates rustbuild's strategy of copying artifacts to work off the list of artifacts produced by `cargo build --message-format=json`. The build system will now parse json messages coming out of Cargo to watch for files being generated, and then it'll only copy over those precise files. Note that there's still a bit of weird logic where Cargo prints that it's creating `libstd.rlib` where we actually want `libstd-xxxxx.rlib`, so we still do a bit of "most recent file" probing for those. This commit should take care of the crates.io dependency issues, however, as they're all copied over precisely. Closes #42261
1 parent 2f278c5 commit e925003

File tree

2 files changed

+178
-84
lines changed

2 files changed

+178
-84
lines changed

src/bootstrap/bin/rustc.rs

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ fn main() {
5656
}
5757
}
5858

59+
// Drop `--error-format json` because despite our desire for json messages
60+
// from Cargo we don't want any from rustc itself.
61+
if let Some(n) = args.iter().position(|n| n == "--error-format") {
62+
args.remove(n);
63+
args.remove(n);
64+
}
65+
5966
// Detect whether or not we're a build script depending on whether --target
6067
// is passed (a bit janky...)
6168
let target = args.windows(2)

src/bootstrap/compile.rs

+171-84
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@
1616
//! compiler. This module is also responsible for assembling the sysroot as it
1717
//! goes along from the output of the previous stage.
1818
19-
use std::collections::HashMap;
19+
use std::env;
2020
use std::fs::{self, File};
21+
use std::io::BufReader;
22+
use std::io::prelude::*;
2123
use std::path::{Path, PathBuf};
22-
use std::process::Command;
23-
use std::env;
24+
use std::process::{Command, Stdio};
25+
use std::str;
2426

2527
use build_helper::{output, mtime, up_to_date};
2628
use filetime::FileTime;
29+
use rustc_serialize::json;
2730

2831
use channel::GitInfo;
2932
use util::{exe, libdir, is_dylib, copy};
@@ -84,8 +87,9 @@ pub fn std(build: &Build, target: &str, compiler: &Compiler) {
8487
}
8588
}
8689

87-
build.run(&mut cargo);
88-
update_mtime(build, &libstd_stamp(build, &compiler, target));
90+
run_cargo(build,
91+
&mut cargo,
92+
&libstd_stamp(build, &compiler, target));
8993
}
9094

9195
/// Link all libstd rlibs/dylibs into the sysroot location.
@@ -106,11 +110,8 @@ pub fn std_link(build: &Build,
106110
compiler.host,
107111
target_compiler.host,
108112
target);
109-
let libdir = build.sysroot_libdir(&target_compiler, target);
110-
let out_dir = build.cargo_out(&compiler, Mode::Libstd, target);
111-
112-
t!(fs::create_dir_all(&libdir));
113-
add_to_sysroot(&out_dir, &libdir);
113+
let libdir = build.sysroot_libdir(target_compiler, target);
114+
add_to_sysroot(&libdir, &libstd_stamp(build, compiler, target));
114115

115116
if target.contains("musl") && !target.contains("mips") {
116117
copy_musl_third_party_objects(build, target, &libdir);
@@ -201,8 +202,9 @@ pub fn test(build: &Build, target: &str, compiler: &Compiler) {
201202
}
202203
cargo.arg("--manifest-path")
203204
.arg(build.src.join("src/libtest/Cargo.toml"));
204-
build.run(&mut cargo);
205-
update_mtime(build, &libtest_stamp(build, compiler, target));
205+
run_cargo(build,
206+
&mut cargo,
207+
&libtest_stamp(build, compiler, target));
206208
}
207209

208210
/// Same as `std_link`, only for libtest
@@ -216,9 +218,8 @@ pub fn test_link(build: &Build,
216218
compiler.host,
217219
target_compiler.host,
218220
target);
219-
let libdir = build.sysroot_libdir(&target_compiler, target);
220-
let out_dir = build.cargo_out(&compiler, Mode::Libtest, target);
221-
add_to_sysroot(&out_dir, &libdir);
221+
add_to_sysroot(&build.sysroot_libdir(target_compiler, target),
222+
&libtest_stamp(build, compiler, target));
222223
}
223224

224225
/// Build the compiler.
@@ -294,8 +295,9 @@ pub fn rustc(build: &Build, target: &str, compiler: &Compiler) {
294295
if let Some(ref s) = build.config.rustc_default_ar {
295296
cargo.env("CFG_DEFAULT_AR", s);
296297
}
297-
build.run(&mut cargo);
298-
update_mtime(build, &librustc_stamp(build, compiler, target));
298+
run_cargo(build,
299+
&mut cargo,
300+
&librustc_stamp(build, compiler, target));
299301
}
300302

301303
/// Same as `std_link`, only for librustc
@@ -309,9 +311,8 @@ pub fn rustc_link(build: &Build,
309311
compiler.host,
310312
target_compiler.host,
311313
target);
312-
let libdir = build.sysroot_libdir(&target_compiler, target);
313-
let out_dir = build.cargo_out(&compiler, Mode::Librustc, target);
314-
add_to_sysroot(&out_dir, &libdir);
314+
add_to_sysroot(&build.sysroot_libdir(target_compiler, target),
315+
&librustc_stamp(build, compiler, target));
315316
}
316317

317318
/// Cargo's output path for the standard library in a given stage, compiled
@@ -397,39 +398,17 @@ pub fn assemble_rustc(build: &Build, stage: u32, host: &str) {
397398

398399
/// Link some files into a rustc sysroot.
399400
///
400-
/// For a particular stage this will link all of the contents of `out_dir`
401-
/// into the sysroot of the `host` compiler, assuming the artifacts are
402-
/// compiled for the specified `target`.
403-
fn add_to_sysroot(out_dir: &Path, sysroot_dst: &Path) {
404-
// Collect the set of all files in the dependencies directory, keyed
405-
// off the name of the library. We assume everything is of the form
406-
// `foo-<hash>.{rlib,so,...}`, and there could be multiple different
407-
// `<hash>` values for the same name (of old builds).
408-
let mut map = HashMap::new();
409-
for file in t!(fs::read_dir(out_dir.join("deps"))).map(|f| t!(f)) {
410-
let filename = file.file_name().into_string().unwrap();
411-
412-
// We're only interested in linking rlibs + dylibs, other things like
413-
// unit tests don't get linked in
414-
if !filename.ends_with(".rlib") &&
415-
!filename.ends_with(".lib") &&
416-
!is_dylib(&filename) {
401+
/// For a particular stage this will link the file listed in `stamp` into the
402+
/// `sysroot_dst` provided.
403+
fn add_to_sysroot(sysroot_dst: &Path, stamp: &Path) {
404+
t!(fs::create_dir_all(&sysroot_dst));
405+
let mut contents = Vec::new();
406+
t!(t!(File::open(stamp)).read_to_end(&mut contents));
407+
for part in contents.split(|b| *b == 0) {
408+
if part.is_empty() {
417409
continue
418410
}
419-
let file = file.path();
420-
let dash = filename.find("-").unwrap();
421-
let key = (filename[..dash].to_string(),
422-
file.extension().unwrap().to_owned());
423-
map.entry(key).or_insert(Vec::new())
424-
.push(file.clone());
425-
}
426-
427-
// For all hash values found, pick the most recent one to move into the
428-
// sysroot, that should be the one we just built.
429-
for (_, paths) in map {
430-
let (_, path) = paths.iter().map(|path| {
431-
(mtime(&path).seconds(), path)
432-
}).max().unwrap();
411+
let path = Path::new(t!(str::from_utf8(part)));
433412
copy(&path, &sysroot_dst.join(path.file_name().unwrap()));
434413
}
435414
}
@@ -490,40 +469,148 @@ pub fn tool(build: &Build, stage: u32, target: &str, tool: &str) {
490469
build.run(&mut cargo);
491470
}
492471

493-
/// Updates the mtime of a stamp file if necessary, only changing it if it's
494-
/// older than some other library file in the same directory.
495-
///
496-
/// We don't know what file Cargo is going to output (because there's a hash in
497-
/// the file name) but we know where it's going to put it. We use this helper to
498-
/// detect changes to that output file by looking at the modification time for
499-
/// all files in a directory and updating the stamp if any are newer.
500-
///
501-
/// Note that we only consider Rust libraries as that's what we're interested in
502-
/// propagating changes from. Files like executables are tracked elsewhere.
503-
fn update_mtime(build: &Build, path: &Path) {
504-
let entries = match path.parent().unwrap().join("deps").read_dir() {
505-
Ok(entries) => entries,
506-
Err(_) => return,
507-
};
508-
let files = entries.map(|e| t!(e)).filter(|e| t!(e.file_type()).is_file());
509-
let files = files.filter(|e| {
510-
let filename = e.file_name();
511-
let filename = filename.to_str().unwrap();
512-
filename.ends_with(".rlib") ||
513-
filename.ends_with(".lib") ||
514-
is_dylib(&filename)
515-
});
516-
let max = files.max_by_key(|entry| {
517-
let meta = t!(entry.metadata());
518-
FileTime::from_last_modification_time(&meta)
519-
});
520-
let max = match max {
521-
Some(max) => max,
522-
None => return,
472+
fn run_cargo(build: &Build, cargo: &mut Command, stamp: &Path) {
473+
// Instruct Cargo to give us json messages on stdout, critically leaving
474+
// stderr as piped so we can get those pretty colors.
475+
cargo.arg("--message-format").arg("json")
476+
.stdout(Stdio::piped());
477+
build.verbose(&format!("running: {:?}", cargo));
478+
let mut child = match cargo.spawn() {
479+
Ok(child) => child,
480+
Err(e) => panic!("failed to execute command: {:?}\nerror: {}", cargo, e),
523481
};
524482

525-
if mtime(&max.path()) > mtime(path) {
526-
build.verbose(&format!("updating {:?} as {:?} changed", path, max.path()));
527-
t!(File::create(path));
483+
// `target_root_dir` looks like $dir/$target/release
484+
let target_root_dir = stamp.parent().unwrap();
485+
// `target_deps_dir` looks like $dir/$target/release/deps
486+
let target_deps_dir = target_root_dir.join("deps");
487+
// `host_root_dir` looks like $dir/release
488+
let host_root_dir = target_root_dir.parent().unwrap() // chop off `release`
489+
.parent().unwrap() // chop off `$target`
490+
.join(target_root_dir.file_name().unwrap());
491+
492+
// Spawn Cargo slurping up its JSON output. We'll start building up the
493+
// `deps` array of all files it generated along with a `toplevel` array of
494+
// files we need to probe for later.
495+
let mut deps = Vec::new();
496+
let mut toplevel = Vec::new();
497+
let stdout = BufReader::new(child.stdout.take().unwrap());
498+
for line in stdout.lines() {
499+
let line = t!(line);
500+
let json = if line.starts_with("{") {
501+
t!(line.parse::<json::Json>())
502+
} else {
503+
// If this was informational, just print it out and continue
504+
println!("{}", line);
505+
continue
506+
};
507+
if json.find("reason").and_then(|j| j.as_string()) != Some("compiler-artifact") {
508+
continue
509+
}
510+
for filename in json["filenames"].as_array().unwrap() {
511+
let filename = filename.as_string().unwrap();
512+
// Skip files like executables
513+
if !filename.ends_with(".rlib") &&
514+
!filename.ends_with(".lib") &&
515+
!is_dylib(&filename) {
516+
continue
517+
}
518+
519+
let filename = Path::new(filename);
520+
521+
// If this was an output file in the "host dir" we don't actually
522+
// worry about it, it's not relevant for us.
523+
if filename.starts_with(&host_root_dir) {
524+
continue
525+
526+
// If this was output in the `deps` dir then this is a precise file
527+
// name (hash included) so we start tracking it.
528+
} else if filename.starts_with(&target_deps_dir) {
529+
deps.push(filename.to_path_buf());
530+
531+
// Otherwise this was a "top level artifact" which right now doesn't
532+
// have a hash in the name, but there's a version of this file in
533+
// the `deps` folder which *does* have a hash in the name. That's
534+
// the one we'll want to we'll probe for it later.
535+
} else {
536+
toplevel.push((filename.file_stem().unwrap()
537+
.to_str().unwrap().to_string(),
538+
filename.extension().unwrap().to_owned()
539+
.to_str().unwrap().to_string()));
540+
}
541+
}
542+
}
543+
544+
// Make sure Cargo actually succeeded after we read all of its stdout.
545+
let status = t!(child.wait());
546+
if !status.success() {
547+
panic!("command did not execute successfully: {:?}\n\
548+
expected success, got: {}",
549+
cargo,
550+
status);
551+
}
552+
553+
// Ok now we need to actually find all the files listed in `toplevel`. We've
554+
// got a list of prefix/extensions and we basically just need to find the
555+
// most recent file in the `deps` folder corresponding to each one.
556+
let contents = t!(target_deps_dir.read_dir())
557+
.map(|e| t!(e))
558+
.map(|e| (e.path(), e.file_name().into_string().unwrap(), t!(e.metadata())))
559+
.collect::<Vec<_>>();
560+
for (prefix, extension) in toplevel {
561+
let candidates = contents.iter().filter(|&&(_, ref filename, _)| {
562+
filename.starts_with(&prefix[..]) &&
563+
filename[prefix.len()..].starts_with("-") &&
564+
filename.ends_with(&extension[..])
565+
});
566+
let max = candidates.max_by_key(|&&(_, _, ref metadata)| {
567+
FileTime::from_last_modification_time(metadata)
568+
});
569+
let path_to_add = match max {
570+
Some(triple) => triple.0.to_str().unwrap(),
571+
None => panic!("no output generated for {:?} {:?}", prefix, extension),
572+
};
573+
if is_dylib(path_to_add) {
574+
let candidate = format!("{}.lib", path_to_add);
575+
let candidate = PathBuf::from(candidate);
576+
if candidate.exists() {
577+
deps.push(candidate);
578+
}
579+
}
580+
deps.push(path_to_add.into());
581+
}
582+
583+
// Now we want to update the contents of the stamp file, if necessary. First
584+
// we read off the previous contents along with its mtime. If our new
585+
// contents (the list of files to copy) is different or if any dep's mtime
586+
// is newer then we rewrite the stamp file.
587+
deps.sort();
588+
let mut stamp_contents = Vec::new();
589+
if let Ok(mut f) = File::open(stamp) {
590+
t!(f.read_to_end(&mut stamp_contents));
591+
}
592+
let stamp_mtime = mtime(&stamp);
593+
let mut new_contents = Vec::new();
594+
let mut max = None;
595+
let mut max_path = None;
596+
for dep in deps {
597+
let mtime = mtime(&dep);
598+
if Some(mtime) > max {
599+
max = Some(mtime);
600+
max_path = Some(dep.clone());
601+
}
602+
new_contents.extend(dep.to_str().unwrap().as_bytes());
603+
new_contents.extend(b"\0");
604+
}
605+
let max = max.unwrap();
606+
let max_path = max_path.unwrap();
607+
if stamp_contents == new_contents && max < stamp_mtime {
608+
return
609+
}
610+
if max != stamp_mtime {
611+
build.verbose(&format!("updating {:?} as {:?} changed", stamp, max_path));
612+
} else {
613+
build.verbose(&format!("updating {:?} as deps changed", stamp));
528614
}
615+
t!(t!(File::create(stamp)).write_all(&new_contents));
529616
}

0 commit comments

Comments
 (0)