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
74impl 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 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 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 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 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 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 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}