16
16
//! compiler. This module is also responsible for assembling the sysroot as it
17
17
//! goes along from the output of the previous stage.
18
18
19
- use std:: collections :: HashMap ;
19
+ use std:: env ;
20
20
use std:: fs:: { self , File } ;
21
+ use std:: io:: BufReader ;
22
+ use std:: io:: prelude:: * ;
21
23
use std:: path:: { Path , PathBuf } ;
22
- use std:: process:: Command ;
23
- use std:: env ;
24
+ use std:: process:: { Command , Stdio } ;
25
+ use std:: str ;
24
26
25
27
use build_helper:: { output, mtime, up_to_date} ;
26
28
use filetime:: FileTime ;
29
+ use rustc_serialize:: json;
27
30
28
31
use channel:: GitInfo ;
29
32
use util:: { exe, libdir, is_dylib, copy} ;
@@ -84,8 +87,9 @@ pub fn std(build: &Build, target: &str, compiler: &Compiler) {
84
87
}
85
88
}
86
89
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) ) ;
89
93
}
90
94
91
95
/// Link all libstd rlibs/dylibs into the sysroot location.
@@ -106,11 +110,8 @@ pub fn std_link(build: &Build,
106
110
compiler. host,
107
111
target_compiler. host,
108
112
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) ) ;
114
115
115
116
if target. contains ( "musl" ) && !target. contains ( "mips" ) {
116
117
copy_musl_third_party_objects ( build, target, & libdir) ;
@@ -201,8 +202,9 @@ pub fn test(build: &Build, target: &str, compiler: &Compiler) {
201
202
}
202
203
cargo. arg ( "--manifest-path" )
203
204
. 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) ) ;
206
208
}
207
209
208
210
/// Same as `std_link`, only for libtest
@@ -216,9 +218,8 @@ pub fn test_link(build: &Build,
216
218
compiler. host,
217
219
target_compiler. host,
218
220
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) ) ;
222
223
}
223
224
224
225
/// Build the compiler.
@@ -294,8 +295,9 @@ pub fn rustc(build: &Build, target: &str, compiler: &Compiler) {
294
295
if let Some ( ref s) = build. config . rustc_default_ar {
295
296
cargo. env ( "CFG_DEFAULT_AR" , s) ;
296
297
}
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) ) ;
299
301
}
300
302
301
303
/// Same as `std_link`, only for librustc
@@ -309,9 +311,8 @@ pub fn rustc_link(build: &Build,
309
311
compiler. host,
310
312
target_compiler. host,
311
313
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) ) ;
315
316
}
316
317
317
318
/// 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) {
397
398
398
399
/// Link some files into a rustc sysroot.
399
400
///
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 ( ) {
417
409
continue
418
410
}
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) ) ) ;
433
412
copy ( & path, & sysroot_dst. join ( path. file_name ( ) . unwrap ( ) ) ) ;
434
413
}
435
414
}
@@ -490,40 +469,148 @@ pub fn tool(build: &Build, stage: u32, target: &str, tool: &str) {
490
469
build. run ( & mut cargo) ;
491
470
}
492
471
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) ,
523
481
} ;
524
482
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) ) ;
528
614
}
615
+ t ! ( t!( File :: create( stamp) ) . write_all( & new_contents) ) ;
529
616
}
0 commit comments