55//! Utility for bundling target binaries as tarfiles.
66
77use anyhow:: { anyhow, bail, Context , Result } ;
8+ use futures:: stream:: { self , StreamExt , TryStreamExt } ;
9+ use indicatif:: { MultiProgress , ProgressBar , ProgressStyle } ;
810use omicron_package:: { parse, SubCommand } ;
9- use omicron_zone_package:: package:: Package ;
11+ use omicron_zone_package:: package:: { Package , Progress } ;
1012use rayon:: prelude:: * ;
1113use serde_derive:: Deserialize ;
1214use std:: collections:: BTreeMap ;
1315use std:: env;
1416use std:: fs:: create_dir_all;
1517use std:: path:: { Path , PathBuf } ;
16- use std:: process :: Command ;
18+ use std:: sync :: Arc ;
1719use structopt:: StructOpt ;
20+ use tokio:: process:: Command ;
1821
1922/// Describes the configuration for a set of packages.
2023#[ derive( Deserialize , Debug ) ]
@@ -47,50 +50,90 @@ struct Args {
4750 subcommand : SubCommand ,
4851}
4952
50- fn run_cargo_on_package (
53+ async fn run_cargo_on_packages < I , S > (
5154 subcmd : & str ,
52- package : & str ,
55+ packages : I ,
5356 release : bool ,
54- ) -> Result < ( ) > {
57+ ) -> Result < ( ) >
58+ where
59+ I : IntoIterator < Item = S > ,
60+ S : AsRef < std:: ffi:: OsStr > ,
61+ {
5562 let mut cmd = Command :: new ( "cargo" ) ;
5663 // We rely on the rust-toolchain.toml file for toolchain information,
5764 // rather than specifying one within the packaging tool.
58- cmd. arg ( subcmd) . arg ( "-p" ) . arg ( package) ;
65+ cmd. arg ( subcmd) ;
66+ for package in packages {
67+ cmd. arg ( "-p" ) . arg ( package) ;
68+ }
5969 if release {
6070 cmd. arg ( "--release" ) ;
6171 }
62- let status =
63- cmd. status ( ) . context ( format ! ( "Failed to run command: ({:?})" , cmd) ) ?;
72+ let status = cmd
73+ . status ( )
74+ . await
75+ . context ( format ! ( "Failed to run command: ({:?})" , cmd) ) ?;
6476 if !status. success ( ) {
65- bail ! ( "Failed to build package: {}" , package ) ;
77+ bail ! ( "Failed to build packages" ) ;
6678 }
6779
6880 Ok ( ( ) )
6981}
7082
71- async fn do_check ( config : & Config ) -> Result < ( ) > {
72- for ( package_name, package) in & config. packages {
73- if let Some ( rust_pkg) = & package. rust {
74- println ! ( "Checking {}" , package_name) ;
75- run_cargo_on_package ( "check" , & package_name, rust_pkg. release ) ?;
76- }
83+ async fn do_for_all_rust_packages (
84+ config : & Config ,
85+ command : & str ,
86+ ) -> Result < ( ) > {
87+ // First, filter out all Rust packages from the configuration that should be
88+ // built, and partition them into "release" and "debug" categories.
89+ let ( release_pkgs, debug_pkgs) : ( Vec < _ > , _ ) = config
90+ . packages
91+ . iter ( )
92+ . filter_map ( |( name, pkg) | {
93+ pkg. rust . as_ref ( ) . map ( |rust_pkg| ( name, rust_pkg. release ) )
94+ } )
95+ . partition ( |( _, release) | * release) ;
96+
97+ // Execute all the release / debug packages at the same time.
98+ if !release_pkgs. is_empty ( ) {
99+ run_cargo_on_packages (
100+ command,
101+ release_pkgs. iter ( ) . map ( |( name, _) | name) ,
102+ true ,
103+ )
104+ . await ?;
105+ }
106+ if !debug_pkgs. is_empty ( ) {
107+ run_cargo_on_packages (
108+ command,
109+ debug_pkgs. iter ( ) . map ( |( name, _) | name) ,
110+ false ,
111+ )
112+ . await ?;
77113 }
78114 Ok ( ( ) )
79115}
80116
117+ async fn do_check ( config : & Config ) -> Result < ( ) > {
118+ do_for_all_rust_packages ( config, "check" ) . await
119+ }
120+
121+ async fn do_build ( config : & Config ) -> Result < ( ) > {
122+ do_for_all_rust_packages ( config, "build" ) . await
123+ }
124+
81125async fn do_package ( config : & Config , output_directory : & Path ) -> Result < ( ) > {
82126 create_dir_all ( & output_directory)
83127 . map_err ( |err| anyhow ! ( "Cannot create output directory: {}" , err) ) ?;
84128
85- for ( package_name, package) in & config. packages {
86- if let Some ( rust_pkg) = & package. rust {
87- println ! ( "Building: {}" , package_name) ;
88- run_cargo_on_package ( "build" , package_name, rust_pkg. release ) ?;
89- }
90- package. create ( & output_directory) . await ?;
91- }
129+ let ui = ProgressUI :: new ( ) ;
130+ let ui_refs = vec ! [ ui. clone( ) ; config. packages. len( ) ] ;
131+
132+ do_build ( & config) . await ?;
92133
93134 for ( package_name, package) in & config. external_packages {
135+ let progress = ui. add_package ( package_name. to_string ( ) , 1 ) ;
136+ progress. set_message ( "finding package" . to_string ( ) ) ;
94137 let path = package. get_output_path ( & output_directory) ;
95138 if !path. exists ( ) {
96139 bail ! (
@@ -108,8 +151,34 @@ in improving the cross-repository meta-build system, please contact sean@.",
108151 . to_string_lossy( ) ,
109152 ) ;
110153 }
154+ progress. finish ( ) ;
111155 }
112156
157+ stream:: iter ( & config. packages )
158+ // It's a pain to clone a value into closures - see
159+ // https://github.com/rust-lang/rfcs/issues/2407 - so in the meantime,
160+ // we explicitly create the references to the UI we need for each
161+ // package.
162+ . zip ( stream:: iter ( ui_refs) )
163+ // We convert the stream type to operate on Results, so we may invoke
164+ // "try_for_each_concurrent" more easily.
165+ . map ( Ok :: < _ , anyhow:: Error > )
166+ . try_for_each_concurrent (
167+ None ,
168+ |( ( package_name, package) , ui) | async move {
169+ let total_work = package. get_total_work ( ) ;
170+ let progress =
171+ ui. add_package ( package_name. to_string ( ) , total_work) ;
172+ progress. set_message ( "bundle package" . to_string ( ) ) ;
173+ package
174+ . create_with_progress ( & progress, & output_directory)
175+ . await ?;
176+ progress. finish ( ) ;
177+ Ok ( ( ) )
178+ } ,
179+ )
180+ . await ?;
181+
113182 Ok ( ( ) )
114183}
115184
@@ -221,6 +290,72 @@ fn do_uninstall(
221290 Ok ( ( ) )
222291}
223292
293+ fn in_progress_style ( ) -> ProgressStyle {
294+ ProgressStyle :: default_bar ( )
295+ . template (
296+ "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}" ,
297+ )
298+ . unwrap ( )
299+ . progress_chars ( "#>." )
300+ }
301+
302+ fn completed_progress_style ( ) -> ProgressStyle {
303+ ProgressStyle :: default_bar ( )
304+ . template ( "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg:.green}" )
305+ . unwrap ( )
306+ . progress_chars ( "#>." )
307+ }
308+
309+ // Struct managing display of progress to UI.
310+ struct ProgressUI {
311+ multi : MultiProgress ,
312+ style : ProgressStyle ,
313+ }
314+
315+ struct PackageProgress {
316+ pb : ProgressBar ,
317+ service_name : String ,
318+ }
319+
320+ impl PackageProgress {
321+ fn finish ( & self ) {
322+ self . pb . set_style ( completed_progress_style ( ) ) ;
323+ self . pb . finish_with_message ( format ! ( "{}: done" , self . service_name) ) ;
324+ self . pb . tick ( ) ;
325+ }
326+ }
327+
328+ impl Progress for PackageProgress {
329+ fn set_message ( & self , message : impl Into < std:: borrow:: Cow < ' static , str > > ) {
330+ self . pb . set_message ( format ! (
331+ "{}: {}" ,
332+ self . service_name,
333+ message. into( )
334+ ) ) ;
335+ }
336+
337+ fn increment ( & self , delta : u64 ) {
338+ self . pb . inc ( delta) ;
339+ }
340+ }
341+
342+ impl ProgressUI {
343+ fn new ( ) -> Arc < Self > {
344+ Arc :: new ( Self {
345+ multi : MultiProgress :: new ( ) ,
346+ style : in_progress_style ( ) ,
347+ } )
348+ }
349+
350+ fn add_package ( & self , service_name : String , total : u64 ) -> PackageProgress {
351+ let pb = self . multi . add ( ProgressBar :: new ( total) ) ;
352+ pb. set_style ( self . style . clone ( ) ) ;
353+ pb. set_message ( service_name. clone ( ) ) ;
354+ pb. tick ( ) ;
355+ PackageProgress { pb, service_name }
356+ }
357+ }
358+
224359#[ tokio:: main]
225360async fn main ( ) -> Result < ( ) > {
226361 let args = Args :: from_args_safe ( ) . map_err ( |err| anyhow ! ( err) ) ?;
0 commit comments