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 ;
2020use std:: fs:: { self , File } ;
21+ use std:: io:: BufReader ;
22+ use std:: io:: prelude:: * ;
2123use std:: path:: { Path , PathBuf } ;
22- use std:: process:: Command ;
23- use std:: env ;
24+ use std:: process:: { Command , Stdio } ;
25+ use std:: str ;
2426
2527use build_helper:: { output, mtime, up_to_date} ;
2628use filetime:: FileTime ;
29+ use rustc_serialize:: json;
2730
2831use channel:: GitInfo ;
2932use 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: {:?}\n error: {}" , 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