cargo_shim/
lib.rs

1#![deny(
2    missing_debug_implementations,
3    trivial_numeric_casts,
4    unstable_features,
5    unused_import_braces,
6    unused_qualifications
7)]
8
9extern crate cargo_metadata;
10#[macro_use]
11extern crate failure;
12#[macro_use]
13extern crate log;
14extern crate serde_json;
15#[macro_use]
16extern crate serde_derive;
17extern crate serde;
18extern crate ansi_term;
19
20use std::collections::{HashSet, HashMap};
21use std::process::{Command, Stdio};
22use std::path::{Path, PathBuf};
23use std::io::{self, BufRead, BufReader};
24use std::ffi::OsString;
25use std::hash::{Hash, Hasher};
26use std::ops::Deref;
27use std::cell::Cell;
28use std::env;
29use std::thread;
30use std::str;
31use std::error;
32use std::fmt;
33
34mod cargo;
35mod cargo_output;
36mod rustc_diagnostic;
37mod diagnostic_formatter;
38
39use self::cargo::cfg::{Cfg, CfgExpr};
40use self::cargo_output::{CargoOutput, PackageId};
41
42#[derive(Copy, Clone, PartialEq, Eq, Debug)]
43pub enum BuildType {
44    Debug,
45    Release
46}
47
48#[derive(Copy, Clone, PartialEq, Eq, Debug)]
49pub enum Profile {
50    Main,
51    Test,
52    Bench
53}
54
55#[derive(Copy, Clone, PartialEq, Eq, Debug)]
56pub enum TargetKind {
57    Lib,
58    CDyLib,
59    Bin,
60    Example,
61    Test,
62    Bench
63}
64
65#[derive(Clone, Debug)]
66pub struct CargoProject {
67    pub packages: Vec< CargoPackage >,
68    pub target_directory: String
69}
70
71#[derive(Clone, Debug)]
72pub struct CargoPackageId( PackageId );
73
74// TODO: Fix this upstream.
75impl PartialEq for CargoPackageId {
76    fn eq( &self, rhs: &CargoPackageId ) -> bool {
77        self.0.name() == rhs.0.name() &&
78        self.0.version() == rhs.0.version() &&
79        self.0.url() == rhs.0.url()
80    }
81}
82
83impl Eq for CargoPackageId {}
84
85impl Hash for CargoPackageId {
86    fn hash< H: Hasher >( &self, state: &mut H ) {
87        self.0.name().hash( state );
88        self.0.version().hash( state );
89        self.0.url().hash( state );
90    }
91}
92
93impl CargoPackageId {
94    fn new( id: &str ) -> Option< Self > {
95        let value = serde_json::Value::String( id.to_owned() );
96        match serde_json::from_value( value ).ok() {
97            Some( package_id ) => Some( CargoPackageId( package_id ) ),
98            None => None
99        }
100    }
101}
102
103impl Deref for CargoPackageId {
104    type Target = PackageId;
105    fn deref( &self ) -> &Self::Target {
106        &self.0
107    }
108}
109
110#[derive(Clone, PartialEq, Debug)]
111pub struct CargoPackage {
112    pub id: CargoPackageId,
113    pub name: String,
114    pub manifest_path: PathBuf,
115    pub crate_root: PathBuf,
116    pub targets: Vec< CargoTarget >,
117    pub dependencies: Vec< CargoDependency >,
118    pub is_workspace_member: bool,
119    pub is_default: bool
120}
121
122#[derive(Clone, PartialEq, Debug)]
123pub struct CargoTarget {
124    pub name: String,
125    pub kind: TargetKind,
126    pub source_directory: PathBuf
127}
128
129#[derive(Clone, PartialEq, Debug)]
130pub enum CargoDependencyKind {
131    Normal,
132    Development,
133    Build
134}
135
136#[derive(Clone, PartialEq, Debug)]
137pub enum CargoDependencyTarget {
138    Expr( CfgExpr ),
139    Target( String )
140}
141
142struct TargetCfg {
143    triplet: String,
144    map: HashMap< String, String >,
145    set: HashSet< String >
146}
147
148impl TargetCfg {
149    fn new( triplet: &str ) -> Self {
150        let rustc =
151            if cfg!( windows ) {
152                "rustc.exe"
153            } else {
154                "rustc"
155            };
156
157        // NOTE: Currently this will always emit the "debug_assertion" config
158        // even if we're compiling in release mode, and it won't contain "test"
159        // even if we're building tests.
160        //
161        // Cargo works the same way:
162        //    https://github.com/rust-lang/cargo/issues/5777
163
164        debug!( "Querying the target config..." );
165        let mut command = Command::new( rustc );
166        command.arg( "--target" );
167        command.arg( triplet );
168        command.arg( "--print" );
169        command.arg( "cfg" );
170
171        let mut map = HashMap::new();
172        let mut set = HashSet::new();
173
174        command.stdout( Stdio::piped() );
175        command.stderr( Stdio::inherit() );
176        command.stdin( Stdio::null() );
177        let result = command.output().expect( "could not launch rustc" );
178        if !result.status.success() {
179            panic!( "Failed to grab the target configuration from rustc!" );
180        }
181
182        for line in BufReader::new( result.stdout.as_slice() ).lines() {
183            let line = line.unwrap();
184            let line = line.trim();
185            if line.is_empty() {
186                continue;
187            }
188
189            if let Some( index ) = line.chars().position( |byte| byte == '=' ) {
190                let key: String = line.chars().take( index ).collect();
191                let mut value: String = line.chars().skip( index + 2 ).collect();
192                value.pop().unwrap();
193                debug!( "Target config: '{}' = '{}'", key, value );
194                map.insert( key, value );
195            } else {
196                debug!( "Target config: '{}'", line );
197                set.insert( line.to_owned() );
198            }
199        }
200
201        TargetCfg {
202            triplet: triplet.to_owned(),
203            map,
204            set
205        }
206    }
207
208    fn matches( &self, expr: &CfgExpr ) -> bool {
209        match *expr {
210            CfgExpr::Not( ref inner ) => !self.matches( &inner ),
211            CfgExpr::All( ref inner ) => inner.iter().all( |inner| self.matches( &inner ) ),
212            CfgExpr::Any( ref inner ) => inner.iter().any( |inner| self.matches( &inner ) ),
213            CfgExpr::Value( ref inner ) => self.matches_value( &inner )
214        }
215    }
216
217    fn matches_value( &self, value: &Cfg ) -> bool {
218        match *value {
219            Cfg::Name( ref name ) => {
220                self.set.contains( name )
221            },
222            Cfg::KeyPair( ref key, ref expected_value ) => {
223                self.map.get( key ).map( |value| value == expected_value ).unwrap_or( false )
224            }
225        }
226    }
227}
228
229impl CargoDependencyTarget {
230    fn matches( &self, target_cfg: &TargetCfg ) -> bool {
231        match *self {
232            CargoDependencyTarget::Target( ref target ) => *target == target_cfg.triplet,
233            CargoDependencyTarget::Expr( ref expr ) => target_cfg.matches( expr )
234        }
235    }
236}
237
238#[derive(Clone, PartialEq, Debug)]
239pub struct CargoDependency {
240    pub name: String,
241    pub kind: CargoDependencyKind,
242    pub target: Option< CargoDependencyTarget >,
243    pub resolved_to: Option< CargoPackageId >
244}
245
246#[derive(Debug)]
247pub enum Error {
248    CannotLaunchCargo( io::Error ),
249    CargoFailed( String ),
250    CannotParseCargoOutput( serde_json::Error )
251}
252
253
254impl error::Error for Error {
255    fn description( &self ) -> &str {
256        match *self {
257            Error::CannotLaunchCargo( _ ) => "cannot launch cargo",
258            Error::CargoFailed( _ ) => "cargo failed",
259            Error::CannotParseCargoOutput( _ ) => "cannot parse cargo output"
260        }
261    }
262}
263
264impl fmt::Display for Error {
265    fn fmt( &self, formatter: &mut fmt::Formatter ) -> fmt::Result {
266        use std::error::Error as StdError;
267        match *self {
268            Error::CannotLaunchCargo( ref err ) => write!( formatter, "{}: {}", self.description(), err ),
269            Error::CargoFailed( ref err ) => write!( formatter, "{}: {}", self.description(), err ),
270            Error::CannotParseCargoOutput( ref err ) => write!( formatter, "{}: {}", self.description(), err )
271        }
272    }
273}
274
275impl CargoProject {
276    pub fn new(
277        manifest_path: Option< &str >,
278        no_default_features: bool,
279        enable_all_features: bool,
280        features: &[String]
281    ) -> Result< CargoProject, Error >
282    {
283        let cwd = env::current_dir().expect( "cannot get current working directory" );
284        let cargo = env::var( "CARGO" ).unwrap_or_else( |_|
285            if cfg!( windows ) {
286                "cargo.exe"
287            } else {
288                "cargo"
289            }.to_owned()
290        );
291
292        let mut command = Command::new( cargo );
293        command.arg( "metadata" );
294
295        if no_default_features {
296            command.arg( "--no-default-features" );
297        }
298
299        if enable_all_features {
300            command.arg( "--all-features" );
301        }
302
303        if !features.is_empty() {
304            command.arg( "--features" );
305            command.arg( &features.join( " " ) );
306        }
307
308        command.arg( "--format-version" );
309        command.arg( "1" );
310
311        if let Some( manifest_path ) = manifest_path {
312            command.arg( "--manifest-path" );
313            command.arg( manifest_path );
314        }
315
316        if cfg!( unix ) {
317            command.arg( "--color" );
318            command.arg( "always" );
319        }
320
321        debug!( "Launching: {:?}", command );
322
323        let output = command.output().map_err( |err| Error::CannotLaunchCargo( err ) )?;
324        if !output.status.success() {
325            return Err( Error::CargoFailed( String::from_utf8_lossy( &output.stderr ).into_owned() ) );
326        }
327        let metadata = str::from_utf8( &output.stdout ).expect( "cargo output is not valid UTF-8" );
328        let metadata: cargo_metadata::Metadata =
329            serde_json::from_str( metadata ).map_err( |err| Error::CannotParseCargoOutput( err ) )?;
330
331        let mut workspace_members = HashSet::new();
332        for member in metadata.workspace_members {
333            workspace_members.insert( member.name().to_owned() );
334        }
335
336        let mut project = CargoProject {
337            target_directory: metadata.target_directory,
338            packages: metadata.packages.into_iter().map( |package| {
339                let manifest_path: PathBuf = package.manifest_path.into();
340                let is_workspace_member = workspace_members.contains( &*package.name );
341                CargoPackage {
342                    id: CargoPackageId::new( &package.id ).expect( "unparsable package id" ),
343                    name: package.name,
344                    crate_root: manifest_path.parent().unwrap().into(),
345                    manifest_path: manifest_path,
346                    is_workspace_member,
347                    is_default: false,
348                    targets: package.targets.into_iter().filter_map( |target| {
349                        Some( CargoTarget {
350                            name: target.name,
351                            kind: match target.kind[ 0 ].as_str() {
352                                "lib" => TargetKind::Lib,
353                                "rlib" => TargetKind::Lib,
354                                "cdylib" => TargetKind::CDyLib,
355                                "dylib" => TargetKind::Lib,
356                                "staticlib" => TargetKind::Lib,
357                                "bin" => TargetKind::Bin,
358                                "example" => TargetKind::Example,
359                                "test" => TargetKind::Test,
360                                "bench" => TargetKind::Bench,
361                                "custom-build" => return None,
362                                "proc-macro" => return None,
363                                _ => panic!( "Unknown target kind: '{}'", target.kind[ 0 ] )
364                            },
365                            source_directory: Into::< PathBuf >::into( target.src_path ).parent().unwrap().into()
366                        })
367                    }).collect(),
368                    dependencies: package.dependencies.into_iter().map( |dependency| {
369                        // TODO: Make the `target` field public in `cargo_metadata`.
370                        let json: serde_json::Value = serde_json::from_str( &serde_json::to_string( &dependency ).unwrap() ).unwrap();
371                        let target = match json.get( "target" ).unwrap() {
372                            &serde_json::Value::Null => None,
373                            &serde_json::Value::String( ref target ) => {
374                                let target = if target.starts_with( "cfg(" ) && target.ends_with( ")" ) {
375                                    let cfg = target[ 4..target.len() - 1 ].trim().parse().expect( "cannot parse target specification in a Cargo.toml" );
376                                    CargoDependencyTarget::Expr( cfg )
377                                } else {
378                                    CargoDependencyTarget::Target( target.clone() )
379                                };
380
381                                Some( target )
382                            },
383                            _ => unreachable!()
384                        };
385
386                        CargoDependency {
387                            name: dependency.name,
388                            kind: match dependency.kind {
389                                cargo_metadata::DependencyKind::Normal => CargoDependencyKind::Normal,
390                                cargo_metadata::DependencyKind::Development => CargoDependencyKind::Development,
391                                cargo_metadata::DependencyKind::Build => CargoDependencyKind::Build,
392                                other => panic!( "Unknown dependency kind: {:?}", other )
393                            },
394                            target,
395                            resolved_to: None
396                        }
397                    }).collect()
398                }
399            }).collect()
400        };
401
402        let mut package_map = HashMap::new();
403        for (index, package) in project.packages.iter().enumerate() {
404            package_map.insert( package.id.clone(), index );
405        }
406
407        for node in metadata.resolve.expect( "missing `resolve` metadata section" ).nodes {
408            let id = CargoPackageId::new( &node.id ).expect( "unparsable package id in the `resolve` metadata section" );
409            let package_index = *package_map.get( &id ).expect( "extra entry in the `resolve` metadata section" );
410            let package = &mut project.packages[ package_index ];
411            for dependency_id in node.dependencies {
412                let dependency_id = CargoPackageId::new( &dependency_id ).expect( "unparsable dependency package id" );
413
414                let mut dependency_found = false;
415                for dependency in package.dependencies.iter_mut().filter( |dep| dep.name == dependency_id.name() ) {
416                    assert!( dependency.resolved_to.is_none(), "duplicate dependency" );
417                    dependency.resolved_to = Some( dependency_id.clone() );
418                    dependency_found = true;
419                }
420
421                assert!( dependency_found, "dependency missing from packages" );
422            }
423        }
424
425        let mut default_package: Option< (usize, usize) > = None;
426        for (package_index, package) in project.packages.iter().enumerate() {
427            if !package.is_workspace_member {
428                continue;
429            }
430
431            let package_directory = package.manifest_path.parent().unwrap();
432            if !cwd.starts_with( package_directory ) {
433                continue;
434            }
435
436            let common_length = cwd.components().zip( package_directory.components() ).take_while( |&(a, b)| a == b ).count();
437            if default_package == None || default_package.unwrap().1 < common_length {
438                default_package = Some( (package_index, common_length) );
439            }
440        }
441
442        if let Some( (default_package_index, _) ) = default_package {
443            project.packages[ default_package_index ].is_default = true;
444        }
445
446        Ok( project )
447    }
448
449    pub fn default_package( &self ) -> Option< &CargoPackage > {
450        self.packages.iter().find( |package| package.is_default )
451    }
452
453    pub fn used_packages( &self, triplet: &str, main_package: &CargoPackage, profile: Profile ) -> Vec< &CargoPackage > {
454        let mut package_map = HashMap::new();
455        for (index, package) in self.packages.iter().enumerate() {
456            package_map.insert( package.id.clone(), index );
457        }
458
459        struct Entry< 'a > {
460            package: &'a CargoPackage,
461            is_used: Cell< bool >
462        }
463
464        let mut queue = Vec::new();
465        let entries: Vec< Entry > = self.packages.iter().enumerate().map( |(index, package)| {
466            let is_main_package = package == main_package;
467            if is_main_package {
468                queue.push( index );
469            }
470
471            Entry {
472                package,
473                is_used: Cell::new( is_main_package )
474            }
475        }).collect();
476
477        let target_cfg = TargetCfg::new( triplet );
478        while let Some( index ) = queue.pop() {
479            for dependency in &entries[ index ].package.dependencies {
480                if let Some( ref target ) = dependency.target {
481                    if !target.matches( &target_cfg ) {
482                        continue;
483                    }
484                }
485
486                match profile {
487                    Profile::Main => {
488                        match dependency.kind {
489                            CargoDependencyKind::Normal => {},
490                            CargoDependencyKind::Development |
491                            CargoDependencyKind::Build => continue
492                        }
493                    },
494                    Profile::Test |
495                    Profile::Bench => {
496                        match dependency.kind {
497                            CargoDependencyKind::Normal |
498                            CargoDependencyKind::Development => {},
499                            CargoDependencyKind::Build => continue
500                        }
501                    }
502                }
503
504                let dependency_id = match dependency.resolved_to {
505                    Some( ref dependency_id ) => dependency_id,
506                    None => continue
507                };
508
509                let dependency_index = *package_map.get( dependency_id ).unwrap();
510                if entries[ dependency_index ].is_used.get() {
511                    continue;
512                }
513
514                entries[ dependency_index ].is_used.set( true );
515                queue.push( dependency_index );
516            }
517        }
518
519        entries.into_iter().filter( |entry| entry.is_used.get() ).map( |entry| entry.package ).collect()
520    }
521}
522
523#[derive(Clone, Debug)]
524pub enum BuildTarget {
525    Lib( String, Profile ),
526    Bin( String, Profile ),
527    ExampleBin( String ),
528    IntegrationTest( String ),
529    IntegrationBench( String )
530}
531
532impl BuildTarget {
533    fn is_executable( &self ) -> bool {
534        match *self {
535            BuildTarget::Lib( _, Profile::Main ) => false,
536            _ => true
537        }
538    }
539}
540
541#[derive(Copy, Clone, PartialEq, Debug)]
542pub enum MessageFormat {
543    Human,
544    Json
545}
546
547#[derive(Clone, Debug)]
548pub struct BuildConfig {
549    pub build_target: BuildTarget,
550    pub build_type: BuildType,
551    pub triplet: Option< String >,
552    pub package: Option< String >,
553    pub features: Vec< String >,
554    pub no_default_features: bool,
555    pub enable_all_features: bool,
556    pub extra_paths: Vec< PathBuf >,
557    pub extra_rustflags: Vec< String >,
558    pub extra_environment: Vec< (String, String) >,
559    pub message_format: MessageFormat,
560    pub is_verbose: bool,
561    pub use_color: bool
562}
563
564fn profile_to_arg( profile: Profile ) -> &'static str {
565    match profile {
566        Profile::Main => "dev",
567        Profile::Test => "test",
568        Profile::Bench => "bench"
569    }
570}
571
572pub fn target_to_build_target( target: &CargoTarget, profile: Profile ) -> BuildTarget {
573    match target.kind {
574        TargetKind::Lib => BuildTarget::Lib( target.name.clone(), profile ),
575        TargetKind::CDyLib => BuildTarget::Lib( target.name.clone(), profile ),
576        TargetKind::Bin => BuildTarget::Bin( target.name.clone(), profile ),
577        TargetKind::Example => BuildTarget::ExampleBin( target.name.clone() ),
578        TargetKind::Test => BuildTarget::IntegrationTest( target.name.clone() ),
579        TargetKind::Bench => BuildTarget::IntegrationBench( target.name.clone() )
580    }
581}
582
583impl BuildConfig {
584    fn as_command( &self, should_build: bool ) -> Command {
585        let mut command = Command::new( "cargo" );
586        if should_build {
587            command.arg( "rustc" );
588        } else {
589            command.arg( "check" );
590        }
591
592        command.arg( "--message-format" );
593        command.arg( "json" );
594
595        if cfg!( unix ) && self.use_color {
596            command.arg( "--color" );
597            command.arg( "always" );
598        }
599
600        if let Some( ref triplet ) = self.triplet {
601            command.arg( "--target" ).arg( triplet.as_str() );
602        }
603
604        if let Some( ref package ) = self.package {
605            command.arg( "--package" ).arg( package.as_str() );
606        }
607
608        match self.build_type {
609            BuildType::Debug => {},
610            BuildType::Release => {
611                command.arg( "--release" );
612            }
613        }
614
615        match self.build_target {
616            BuildTarget::Lib( _, _ ) if !should_build => {
617                command.arg( "--lib" );
618            },
619            BuildTarget::Bin( ref name, _ ) if !should_build => {
620                command.arg( "--bin" ).arg( name.as_str() );
621            },
622            BuildTarget::Lib( _, profile ) => {
623                command
624                    .arg( "--profile" ).arg( profile_to_arg( profile ) )
625                    .arg( "--lib" );
626            },
627            BuildTarget::Bin( ref name, profile ) => {
628                command
629                    .arg( "--profile" ).arg( profile_to_arg( profile ) )
630                    .arg( "--bin" ).arg( name.as_str() );
631            },
632            BuildTarget::ExampleBin( ref name ) => {
633                command.arg( "--example" ).arg( name.as_str() );
634            },
635            BuildTarget::IntegrationTest( ref name ) => {
636                command.arg( "--test" ).arg( name.as_str() );
637            },
638            BuildTarget::IntegrationBench( ref name ) => {
639                command.arg( "--bench" ).arg( name.as_str() );
640            }
641        }
642
643        if self.no_default_features {
644            command.arg( "--no-default-features" );
645        }
646
647        if self.enable_all_features {
648            command.arg( "--all-features" );
649        }
650
651        if !self.features.is_empty() {
652            command.arg( "--features" );
653            command.arg( &self.features.join( " " ) );
654        }
655
656        if self.is_verbose {
657            command.arg( "--verbose" );
658        }
659
660        command
661    }
662
663    pub fn check( &self ) -> CargoResult {
664        let status = self.launch_cargo( false ).map( |(status, _)| status );
665        CargoResult {
666            status,
667            artifacts: Vec::new()
668        }
669    }
670
671    pub fn build< F >( &self, mut postprocess: Option< F > ) -> CargoResult
672        where F: for <'a> FnMut( Vec< PathBuf > ) -> Vec< PathBuf >
673    {
674        let mut result = self.build_internal( &mut postprocess );
675        if result.is_ok() == false {
676            return result;
677        }
678
679        // HACK: For some reason when you install emscripten for the first time
680        // the first build is always a dud (it produces no artifacts), so we retry once.
681        let is_emscripten = self.triplet.as_ref().map( |triplet| {
682            triplet == "wasm32-unknown-emscripten" || triplet == "asmjs-unknown-emscripten"
683        }).unwrap_or( false );
684
685        if is_emscripten && self.build_target.is_executable() {
686            let no_js_generated = result
687                .artifacts()
688                .iter()
689                .find( |artifact| artifact.extension().map( |ext| ext == "js" ).unwrap_or( false ) )
690                .is_none();
691
692            if no_js_generated {
693                debug!( "No artifacts were generated yet build succeeded; retrying..." );
694                result = self.build_internal( &mut postprocess );
695            }
696        }
697
698        return result;
699    }
700
701    fn launch_cargo( &self, should_build: bool ) -> Option< (i32, Vec< cargo_output::Artifact >) > {
702        let mut command = self.as_command( should_build );
703
704        let env_paths = env::var_os( "PATH" )
705            .map( |paths| env::split_paths( &paths ).collect() )
706            .unwrap_or( Vec::new() );
707
708        let mut paths = Vec::new();
709        paths.extend( self.extra_paths.clone().into_iter() );
710        paths.extend( env_paths.into_iter() );
711
712        let new_paths = env::join_paths( paths ).unwrap();
713        debug!( "Will launch cargo with PATH: {:?}", new_paths );
714        command.env( "PATH", new_paths );
715
716        let mut rustflags = OsString::new();
717        for flag in &self.extra_rustflags {
718            if !rustflags.is_empty() {
719                rustflags.push( " " );
720            }
721            rustflags.push( flag );
722        }
723
724        if let Some( env_rustflags ) = env::var_os( "RUSTFLAGS" ) {
725            if !rustflags.is_empty() {
726                rustflags.push( " " );
727            }
728            rustflags.push( env_rustflags );
729        }
730
731        debug!( "Will launch cargo with RUSTFLAGS: {:?}", rustflags );
732        command.env( "RUSTFLAGS", rustflags );
733
734        for &(ref key, ref value) in &self.extra_environment {
735            debug!( "Will launch cargo with variable \"{}\" set to \"{}\"", key, value );
736            command.env( key, value );
737        }
738
739        command.stdout( Stdio::piped() );
740        command.stderr( Stdio::piped() );
741
742        debug!( "Launching cargo: {:?}", command );
743        let mut child = match command.spawn() {
744            Ok( child ) => child,
745            Err( _ ) => return None
746        };
747
748        let stderr = BufReader::new( child.stderr.take().unwrap() );
749        let stdout = BufReader::new( child.stdout.take().unwrap() );
750
751        let is_verbose = self.is_verbose;
752        thread::spawn( move || {
753            let mut skip = 0;
754            for line in stderr.lines() {
755                let line = match line {
756                    Ok( line ) => line,
757                    Err( _ ) => break
758                };
759
760                if skip > 0 {
761                    skip -= 1;
762                    continue;
763                }
764
765                // This is really ugly, so let's skip it.
766                if line.trim() == "Caused by:" && !is_verbose {
767                    skip += 1;
768                    continue;
769                }
770
771                eprintln!( "{}", line );
772            }
773        });
774
775        let mut artifacts = Vec::new();
776        for line in stdout.lines() {
777            let line = match line {
778                Ok( line ) => line,
779                Err( _ ) => break
780            };
781
782            let line = line.trim();
783            if line.is_empty() {
784                continue;
785            }
786
787            let json: serde_json::Value = serde_json::from_str( &line ).expect( "failed to parse cargo output" );
788            let line = serde_json::to_string_pretty( &json ).unwrap();
789            if let Some( output ) = CargoOutput::parse( &line ) {
790                match output {
791                    CargoOutput::Message( message ) => {
792                        match self.message_format {
793                            MessageFormat::Human => diagnostic_formatter::print( self.use_color, &message ),
794                            MessageFormat::Json => {
795                                println!( "{}", serde_json::to_string( &message.to_json_value() ).unwrap() );
796                            }
797                        }
798                    },
799                    CargoOutput::Artifact( artifact ) => {
800                        for filename in &artifact.filenames {
801                            debug!( "Built artifact: {}", filename );
802                        }
803
804                        artifacts.push( artifact );
805                    },
806                    CargoOutput::BuildScriptExecuted( executed ) => {
807                        match self.message_format {
808                            MessageFormat::Human => {},
809                            MessageFormat::Json => {
810                                println!( "{}", serde_json::to_string( &executed.to_json_value() ).unwrap() );
811                            }
812                        }
813                    }
814                }
815            }
816        }
817
818        let result = child.wait();
819        let status = result.unwrap().code().expect( "failed to grab cargo status code" );
820        debug!( "Cargo finished with status: {}", status );
821
822        Some( (status, artifacts) )
823    }
824
825    fn build_internal< F >( &self, postprocess: &mut Option< F > ) -> CargoResult
826        where F: for <'a> FnMut( Vec< PathBuf > ) -> Vec< PathBuf >
827    {
828        let (status, mut artifacts) = match self.launch_cargo( true ) {
829            Some( result ) => result,
830            None => {
831                return CargoResult {
832                    status: None,
833                    artifacts: Vec::new()
834                }
835            }
836        };
837
838        fn has_extension< P: AsRef< Path > >( path: P, extension: &str ) -> bool {
839            path.as_ref().extension().map( |ext| ext == extension ).unwrap_or( false )
840        }
841
842        fn find_artifact( artifacts: &[cargo_output::Artifact], extension: &str ) -> Option< (usize, usize) > {
843            artifacts.iter().enumerate().filter_map( |(artifact_index, artifact)| {
844                if let Some( filename_index ) = artifact.filenames.iter().position( |filename| has_extension( filename, extension ) ) {
845                    Some( (artifact_index, filename_index) )
846                } else {
847                    None
848                }
849            }).next()
850        }
851
852        // For some reason when building tests cargo doesn't treat
853        // the `.wasm` file as an artifact.
854        if status == 0 && self.triplet.as_ref().map( |triplet| triplet == "wasm32-unknown-emscripten" ).unwrap_or( false ) {
855            match self.build_target {
856                BuildTarget::Bin( _, Profile::Test ) |
857                BuildTarget::Lib( _, Profile::Test ) |
858                BuildTarget::IntegrationTest( _ ) => {
859                    if find_artifact( &artifacts, "wasm" ).is_none() {
860                        if let Some( (artifact_index, filename_index) ) = find_artifact( &artifacts, "js" ) {
861                            let wasm_path = {
862                                let main_artifact = Path::new( &artifacts[ artifact_index ].filenames[ filename_index ] );
863                                let filename = main_artifact.file_name().unwrap();
864                                main_artifact.parent().unwrap().join( "deps" ).join( filename ).with_extension( "wasm" )
865                            };
866
867                            assert!( wasm_path.exists(), "internal error: wasm doesn't exist where I expected it to be" );
868                            artifacts[ artifact_index ].filenames.push( wasm_path.to_str().unwrap().to_owned() );
869                            debug!( "Found `.wasm` test artifact: {:?}", wasm_path );
870                        }
871                    }
872                },
873                _ => {}
874            }
875        }
876
877        let mut artifact_paths = Vec::new();
878        for mut artifact in &mut artifacts {
879            if let Some( ref mut callback ) = postprocess.as_mut() {
880                let filenames = artifact.filenames.iter().map( |filename| Path::new( &filename ).to_owned() ).collect();
881                let filenames = callback( filenames );
882                artifact.filenames = filenames.into_iter().map( |filename| filename.to_str().unwrap().to_owned() ).collect();
883            }
884        }
885
886        for mut artifact in artifacts {
887            if artifact.filenames.is_empty() {
888                continue;
889            }
890
891            match self.message_format {
892                MessageFormat::Human => {},
893                MessageFormat::Json => {
894                    println!( "{}", serde_json::to_string( &artifact.to_json_value() ).unwrap() );
895                }
896            }
897
898            for filename in artifact.filenames {
899                // NOTE: Since we extract the paths from the JSON
900                //       we get a list of artifacts as `String`s instead of `PathBuf`s.
901                artifact_paths.push( filename.into() )
902            }
903        }
904
905        CargoResult {
906            status: Some( status ),
907            artifacts: artifact_paths
908        }
909    }
910}
911
912#[derive(Debug)]
913pub struct CargoResult {
914    status: Option< i32 >,
915    artifacts: Vec< PathBuf >
916}
917
918impl CargoResult {
919    pub fn is_ok( &self ) -> bool {
920        self.status == Some( 0 )
921    }
922
923    pub fn artifacts( &self ) -> &[PathBuf] {
924        &self.artifacts
925    }
926}