1
- use crate :: cmd:: run_stage;
1
+ use crate :: cmd:: { cfg_spinner , run_stage} ;
2
2
use crate :: errors:: * ;
3
3
use console:: { style, Emoji } ;
4
+ use indicatif:: { MultiProgress , ProgressBar } ;
4
5
use std:: env;
5
6
use std:: fs;
6
- use std:: path:: PathBuf ;
7
+ use std:: path:: { Path , PathBuf } ;
8
+ use std:: thread:: { self , JoinHandle } ;
7
9
8
10
// Emojis for stages
9
11
static GENERATING : Emoji < ' _ , ' _ > = Emoji ( "🔨" , "" ) ;
10
12
static BUILDING : Emoji < ' _ , ' _ > = Emoji ( "🏗️ " , "" ) ; // Yes, there's a space here, for some reason it's needed...
11
- static FINALIZING : Emoji < ' _ , ' _ > = Emoji ( "📦" , "" ) ;
12
13
13
14
/// Returns the exit code if it's non-zero.
14
15
macro_rules! handle_exit_code {
15
16
( $code: expr) => {
16
17
let ( _, _, code) = $code;
17
18
if code != 0 {
18
- return Ok ( code) ;
19
+ return $crate :: errors :: Result :: Ok ( code) ;
19
20
}
20
21
} ;
21
22
}
22
23
23
- /// Actually builds the user's code, program arguments having been interpreted. This needs to know how many steps there are in total
24
- /// because the serving logic also uses it.
25
- pub fn build_internal ( dir : PathBuf , num_steps : u8 ) -> Result < i32 > {
26
- let mut target = dir;
27
- target. extend ( [ ".perseus" ] ) ;
28
-
29
- // Static generation
30
- handle_exit_code ! ( run_stage(
31
- vec![ & format!(
32
- "{} run" ,
33
- env:: var( "PERSEUS_CARGO_PATH" ) . unwrap_or_else( |_| "cargo" . to_string( ) )
34
- ) ] ,
35
- & target,
36
- format!(
37
- "{} {} Generating your app" ,
38
- style( format!( "[1/{}]" , num_steps) ) . bold( ) . dim( ) ,
39
- GENERATING
40
- )
41
- ) ?) ;
42
- // WASM building
43
- handle_exit_code ! ( run_stage(
44
- vec![ & format!(
45
- "{} build --target web" ,
46
- env:: var( "PERSEUS_WASM_PACK_PATH" ) . unwrap_or_else( |_| "wasm-pack" . to_string( ) )
47
- ) ] ,
48
- & target,
49
- format!(
50
- "{} {} Building your app to WASM" ,
51
- style( format!( "[2/{}]" , num_steps) ) . bold( ) . dim( ) ,
52
- BUILDING
53
- )
54
- ) ?) ;
24
+ /// Finalizes the build by renaming some directories.
25
+ pub fn finalize ( target : & Path ) -> Result < ( ) > {
55
26
// Move the `pkg/` directory into `dist/pkg/`
56
27
let pkg_dir = target. join ( "dist/pkg" ) ;
57
28
if pkg_dir. exists ( ) {
@@ -63,34 +34,98 @@ pub fn build_internal(dir: PathBuf, num_steps: u8) -> Result<i32> {
63
34
if let Err ( err) = fs:: rename ( target. join ( "pkg" ) , target. join ( "dist/pkg" ) ) {
64
35
bail ! ( ErrorKind :: MovePkgDirFailed ( err. to_string( ) ) ) ;
65
36
}
66
- // JS bundle generation
67
- handle_exit_code ! ( run_stage(
68
- vec![ & format!(
69
- "{} main.js --format iife --file dist/pkg/bundle.js" ,
70
- env:: var( "PERSEUS_ROLLUP_PATH" ) . unwrap_or_else( |_| "rollup" . to_string( ) )
71
- ) ] ,
72
- & target,
73
- format!(
74
- "{} {} Finalizing bundle" ,
75
- style( format!( "[3/{}]" , num_steps) ) . bold( ) . dim( ) ,
76
- FINALIZING
77
- )
78
- ) ?) ;
79
37
80
- Ok ( 0 )
38
+ Ok ( ( ) )
39
+ }
40
+
41
+ // This literally only exists to avoid type complexity warnings in the `build_internal`'s return type
42
+ type ThreadHandle = JoinHandle < Result < i32 > > ;
43
+
44
+ /// Actually builds the user's code, program arguments having been interpreted. This needs to know how many steps there are in total
45
+ /// because the serving logic also uses it. This also takes a `MultiProgress` to interact with so it can be used truly atomically.
46
+ /// This returns handles for waiting on the component threads so we can use it composably.
47
+ pub fn build_internal (
48
+ dir : PathBuf ,
49
+ spinners : & MultiProgress ,
50
+ num_steps : u8 ,
51
+ ) -> Result < ( ThreadHandle , ThreadHandle ) > {
52
+ let target = dir. join ( ".perseus" ) ;
53
+
54
+ // Static generation message
55
+ let sg_msg = format ! (
56
+ "{} {} Generating your app" ,
57
+ style( format!( "[1/{}]" , num_steps) ) . bold( ) . dim( ) ,
58
+ GENERATING
59
+ ) ;
60
+ // Wasm building message
61
+ let wb_msg = format ! (
62
+ "{} {} Building your app to Wasm" ,
63
+ style( format!( "[2/{}]" , num_steps) ) . bold( ) . dim( ) ,
64
+ BUILDING
65
+ ) ;
66
+
67
+ // We parallelize the first two spinners (static generation and Wasm building)
68
+ // We make sure to add them at the top (the server spinner may have already been instantiated)
69
+ let sg_spinner = spinners. insert ( 0 , ProgressBar :: new_spinner ( ) ) ;
70
+ let sg_spinner = cfg_spinner ( sg_spinner, & sg_msg) ;
71
+ let sg_target = target. clone ( ) ;
72
+ let wb_spinner = spinners. insert ( 1 , ProgressBar :: new_spinner ( ) ) ;
73
+ let wb_spinner = cfg_spinner ( wb_spinner, & wb_msg) ;
74
+ let wb_target = target. clone ( ) ;
75
+ let sg_thread = thread:: spawn ( move || {
76
+ handle_exit_code ! ( run_stage(
77
+ vec![ & format!(
78
+ "{} run" ,
79
+ env:: var( "PERSEUS_CARGO_PATH" ) . unwrap_or_else( |_| "cargo" . to_string( ) )
80
+ ) ] ,
81
+ & sg_target,
82
+ & sg_spinner,
83
+ & sg_msg
84
+ ) ?) ;
85
+
86
+ Ok ( 0 )
87
+ } ) ;
88
+ let wb_thread = thread:: spawn ( move || {
89
+ handle_exit_code ! ( run_stage(
90
+ vec![ & format!(
91
+ "{} build --target web" ,
92
+ env:: var( "PERSEUS_WASM_PACK_PATH" ) . unwrap_or_else( |_| "wasm-pack" . to_string( ) )
93
+ ) ] ,
94
+ & wb_target,
95
+ & wb_spinner,
96
+ & wb_msg
97
+ ) ?) ;
98
+
99
+ Ok ( 0 )
100
+ } ) ;
101
+
102
+ Ok ( ( sg_thread, wb_thread) )
81
103
}
82
104
83
105
/// Builds the subcrates to get a directory that we can serve. Returns an exit code.
84
- pub fn build ( dir : PathBuf , prog_args : & [ String ] ) -> Result < i32 > {
106
+ pub fn build ( dir : PathBuf , _prog_args : & [ String ] ) -> Result < i32 > {
107
+ let spinners = MultiProgress :: new ( ) ;
85
108
// TODO support watching files
86
- // If we should watch for file changes, do so
87
- let should_watch = prog_args. get ( 1 ) ;
88
- let dflt_watch_path = "." . to_string ( ) ;
89
- let _watch_path = prog_args. get ( 2 ) . unwrap_or ( & dflt_watch_path) ;
90
- if should_watch == Some ( & "-w" . to_string ( ) ) || should_watch == Some ( & "--watch" . to_string ( ) ) {
91
- todo ! ( "watching not yet supported, try a tool like 'entr'" ) ;
109
+
110
+ let ( sg_thread, wb_thread) = build_internal ( dir. clone ( ) , & spinners, 2 ) ?;
111
+ let sg_res = sg_thread
112
+ . join ( )
113
+ . map_err ( |_| ErrorKind :: ThreadWaitFailed ) ??;
114
+ if sg_res != 0 {
115
+ return Ok ( sg_res) ;
116
+ }
117
+ let wb_res = wb_thread
118
+ . join ( )
119
+ . map_err ( |_| ErrorKind :: ThreadWaitFailed ) ??;
120
+ if wb_res != 0 {
121
+ return Ok ( wb_res) ;
92
122
}
93
- let exit_code = build_internal ( dir. clone ( ) , 3 ) ?;
94
123
95
- Ok ( exit_code)
124
+ // This waits for all the threads and lets the spinners draw to the terminal
125
+ // spinners.join().map_err(|_| ErrorKind::ThreadWaitFailed)?;
126
+ // And now we can run the finalization stage
127
+ finalize ( & dir. join ( ".perseus" ) ) ?;
128
+
129
+ // We've handled errors in the component threads, so the exit code is now zero
130
+ Ok ( 0 )
96
131
}
0 commit comments