@@ -6,14 +6,16 @@ use std::{
6
6
} ;
7
7
8
8
use cargo_metadata:: Message ;
9
- use clap:: { App , Arg , SubCommand } ;
9
+ use clap:: { App , Arg , ArgMatches , SubCommand } ;
10
10
use error:: Error ;
11
- use espflash:: { Chip , Config , Flasher , PartitionTable } ;
11
+ use espflash:: { Chip , Config , FirmwareImage , Flasher , PartitionTable } ;
12
12
use miette:: { IntoDiagnostic , Result , WrapErr } ;
13
13
use monitor:: monitor;
14
14
use package_metadata:: CargoEspFlashMeta ;
15
15
use serial:: { BaudRate , FlowControl , SerialPort } ;
16
16
17
+ use crate :: cargo_config:: CargoConfig ;
18
+ use crate :: error:: NoTargetError ;
17
19
use crate :: { cargo_config:: parse_cargo_config, error:: UnsupportedTargetError } ;
18
20
19
21
mod cargo_config;
@@ -25,6 +27,27 @@ mod package_metadata;
25
27
fn main ( ) -> Result < ( ) > {
26
28
miette:: set_panic_hook ( ) ;
27
29
30
+ let build_args = [
31
+ Arg :: with_name ( "release" )
32
+ . long ( "release" )
33
+ . help ( "Build the application using the release profile" ) ,
34
+ Arg :: with_name ( "example" )
35
+ . long ( "example" )
36
+ . takes_value ( true )
37
+ . value_name ( "EXAMPLE" )
38
+ . help ( "Example to build and flash" ) ,
39
+ Arg :: with_name ( "features" )
40
+ . long ( "features" )
41
+ . use_delimiter ( true )
42
+ . takes_value ( true )
43
+ . value_name ( "FEATURES" )
44
+ . help ( "Comma delimited list of build features" ) ,
45
+ ] ;
46
+ let connect_args = [ Arg :: with_name ( "serial" )
47
+ . takes_value ( true )
48
+ . value_name ( "SERIAL" )
49
+ . help ( "Serial port connected to target device" ) ] ;
50
+
28
51
let mut app = App :: new ( env ! ( "CARGO_PKG_NAME" ) )
29
52
. bin_name ( "cargo" )
30
53
. subcommand (
@@ -34,40 +57,21 @@ fn main() -> Result<()> {
34
57
. arg (
35
58
Arg :: with_name ( "board_info" )
36
59
. long ( "board-info" )
37
- . help ( "Display the connected board's information" ) ,
60
+ . help ( "Display the connected board's information (deprecated, use the `board-info` subcommand instead) " ) ,
38
61
)
62
+ . args ( & build_args)
39
63
. arg (
40
64
Arg :: with_name ( "ram" )
41
65
. long ( "ram" )
42
66
. help ( "Load the application to RAM instead of Flash" ) ,
43
67
)
44
- . arg (
45
- Arg :: with_name ( "release" )
46
- . long ( "release" )
47
- . help ( "Build the application using the release profile" ) ,
48
- )
49
68
. arg (
50
69
Arg :: with_name ( "bootloader" )
51
70
. long ( "bootloader" )
52
71
. takes_value ( true )
53
72
. value_name ( "PATH" )
54
73
. help ( "Path to a binary (.bin) bootloader file" ) ,
55
74
)
56
- . arg (
57
- Arg :: with_name ( "example" )
58
- . long ( "example" )
59
- . takes_value ( true )
60
- . value_name ( "EXAMPLE" )
61
- . help ( "Example to build and flash" ) ,
62
- )
63
- . arg (
64
- Arg :: with_name ( "features" )
65
- . long ( "features" )
66
- . use_delimiter ( true )
67
- . takes_value ( true )
68
- . value_name ( "FEATURES" )
69
- . help ( "Comma delimited list of build features" ) ,
70
- )
71
75
. arg (
72
76
Arg :: with_name ( "partition_table" )
73
77
. long ( "partition-table" )
@@ -82,16 +86,30 @@ fn main() -> Result<()> {
82
86
. value_name ( "SPEED" )
83
87
. help ( "Baud rate at which to flash target device" ) ,
84
88
)
85
- . arg (
86
- Arg :: with_name ( "serial" )
87
- . takes_value ( true )
88
- . value_name ( "SERIAL" )
89
- . help ( "Serial port connected to target device" ) ,
90
- )
89
+ . args ( & connect_args)
91
90
. arg (
92
91
Arg :: with_name ( "monitor" )
93
92
. long ( "monitor" )
94
93
. help ( "Open a serial monitor after flashing" ) ,
94
+ )
95
+ . subcommand (
96
+ SubCommand :: with_name ( "save-image" )
97
+ . version ( env ! ( "CARGO_PKG_VERSION" ) )
98
+ . about ( "Save the image to disk instead of flashing to device" )
99
+ . arg (
100
+ Arg :: with_name ( "file" )
101
+ . takes_value ( true )
102
+ . required ( true )
103
+ . value_name ( "FILE" )
104
+ . help ( "File name to save the generated image to" ) ,
105
+ )
106
+ . args ( & build_args) ,
107
+ )
108
+ . subcommand (
109
+ SubCommand :: with_name ( "board-info" )
110
+ . version ( env ! ( "CARGO_PKG_VERSION" ) )
111
+ . about ( "Display the connected board's information" )
112
+ . args ( & connect_args) ,
95
113
) ,
96
114
) ;
97
115
@@ -106,18 +124,30 @@ fn main() -> Result<()> {
106
124
107
125
let config = Config :: load ( ) ;
108
126
let metadata = CargoEspFlashMeta :: load ( "Cargo.toml" ) ?;
127
+ let cargo_config = parse_cargo_config ( "." ) ?;
128
+
129
+ match matches. subcommand ( ) {
130
+ ( "board-info" , Some ( matches) ) => board_info ( matches, config, metadata, cargo_config) ,
131
+ ( "save-image" , Some ( matches) ) => save_image ( matches, config, metadata, cargo_config) ,
132
+ _ => flash ( matches, config, metadata, cargo_config) ,
133
+ }
134
+ }
109
135
136
+ fn get_serial_port ( matches : & ArgMatches , config : & Config ) -> Result < String , Error > {
110
137
// The serial port must be specified, either as a command-line argument or in
111
138
// the cargo configuration file. In the case that both have been provided the
112
139
// command-line argument will take precedence.
113
- let port = if let Some ( serial) = matches. value_of ( "serial" ) {
114
- serial. to_string ( )
115
- } else if let Some ( serial) = config. connection . serial {
116
- serial
140
+ if let Some ( serial) = matches. value_of ( "serial" ) {
141
+ Ok ( serial. to_string ( ) )
142
+ } else if let Some ( serial) = & config. connection . serial {
143
+ Ok ( serial. into ( ) )
117
144
} else {
118
- app. print_help ( ) . into_diagnostic ( ) ?;
119
- exit ( 0 ) ;
120
- } ;
145
+ Err ( Error :: NoSerial )
146
+ }
147
+ }
148
+
149
+ fn connect ( matches : & ArgMatches , config : & Config ) -> Result < Flasher > {
150
+ let port = get_serial_port ( matches, config) ?;
121
151
122
152
// Attempt to open the serial port and set its initial baud rate.
123
153
println ! ( "Serial port: {}" , port) ;
@@ -144,19 +174,29 @@ fn main() -> Result<()> {
144
174
// Connect the Flasher to the target device and print the board information
145
175
// upon connection. If the '--board-info' flag has been provided, we have
146
176
// nothing left to do so exit early.
147
- let mut flasher = Flasher :: connect ( serial, speed) ?;
177
+ Ok ( Flasher :: connect ( serial, speed) ?)
178
+ }
179
+
180
+ fn flash (
181
+ matches : & ArgMatches ,
182
+ config : Config ,
183
+ metadata : CargoEspFlashMeta ,
184
+ cargo_config : CargoConfig ,
185
+ ) -> Result < ( ) > {
186
+ // Connect the Flasher to the target device and print the board information
187
+ // upon connection. If the '--board-info' flag has been provided, we have
188
+ // nothing left to do so exit early.
189
+ let mut flasher = connect ( matches, & config) ?;
148
190
flasher. board_info ( ) ?;
149
191
150
192
if matches. is_present ( "board_info" ) {
151
193
return Ok ( ( ) ) ;
152
194
}
153
195
154
- let release = matches. is_present ( "release" ) ;
155
- let example = matches. value_of ( "example" ) ;
156
- let features = matches. value_of ( "features" ) ;
196
+ let build_options = BuildOptions :: from_args ( matches) ;
157
197
158
- let path =
159
- build ( release , example , features , flasher . chip ( ) ) . wrap_err ( "Failed to build project" ) ?;
198
+ let path = build ( build_options , & cargo_config , Some ( flasher . chip ( ) ) )
199
+ . wrap_err ( "Failed to build project" ) ?;
160
200
161
201
// If the '--bootloader' option is provided, load the binary file at the
162
202
// specified path.
@@ -205,38 +245,56 @@ fn main() -> Result<()> {
205
245
Ok ( ( ) )
206
246
}
207
247
208
- fn build (
248
+ struct BuildOptions < ' a > {
209
249
release : bool ,
210
- example : Option < & str > ,
211
- features : Option < & str > ,
212
- chip : Chip ,
250
+ example : Option < & ' a str > ,
251
+ features : Option < & ' a str > ,
252
+ }
253
+
254
+ impl < ' a > BuildOptions < ' a > {
255
+ pub fn from_args ( args : & ' a ArgMatches ) -> Self {
256
+ BuildOptions {
257
+ release : args. is_present ( "release" ) ,
258
+ example : args. value_of ( "example" ) ,
259
+ features : args. value_of ( "features" ) ,
260
+ }
261
+ }
262
+ }
263
+
264
+ fn build (
265
+ build_options : BuildOptions ,
266
+ cargo_config : & CargoConfig ,
267
+ chip : Option < Chip > ,
213
268
) -> Result < PathBuf > {
214
269
// The 'build-std' unstable cargo feature is required to enable
215
270
// cross-compilation. If it has not been set then we cannot build the
216
271
// application.
217
- let cargo_config = parse_cargo_config ( "." ) ?;
218
272
if !cargo_config. has_build_std ( ) {
219
273
return Err ( Error :: NoBuildStd . into ( ) ) ;
220
274
} ;
221
275
222
- let target = cargo_config. target ( ) . ok_or ( Error :: NoTarget { chip } ) ?;
223
- if !chip. supports_target ( target) {
224
- return Err ( Error :: UnsupportedTarget ( UnsupportedTargetError :: new ( target, chip) ) . into ( ) ) ;
276
+ let target = cargo_config
277
+ . target ( )
278
+ . ok_or_else ( || NoTargetError :: new ( chip) ) ?;
279
+ if let Some ( chip) = chip {
280
+ if !chip. supports_target ( target) {
281
+ return Err ( Error :: UnsupportedTarget ( UnsupportedTargetError :: new ( target, chip) ) . into ( ) ) ;
282
+ }
225
283
}
226
284
227
285
// Build the list of arguments to pass to 'cargo build'.
228
286
let mut args = vec ! [ ] ;
229
287
230
- if release {
288
+ if build_options . release {
231
289
args. push ( "--release" ) ;
232
290
}
233
291
234
- if let Some ( example) = example {
292
+ if let Some ( example) = build_options . example {
235
293
args. push ( "--example" ) ;
236
294
args. push ( example) ;
237
295
}
238
296
239
- if let Some ( features) = features {
297
+ if let Some ( features) = build_options . features {
240
298
args. push ( "--features" ) ;
241
299
args. push ( features) ;
242
300
}
@@ -281,7 +339,7 @@ fn build(
281
339
}
282
340
283
341
// Check if the command succeeded, otherwise return an error. Any error messages
284
- // occuring during the build are shown above, when the compiler messages are
342
+ // occurring during the build are shown above, when the compiler messages are
285
343
// rendered.
286
344
if !output. status . success ( ) {
287
345
exit_with_process_status ( output. status ) ;
@@ -295,6 +353,52 @@ fn build(
295
353
Ok ( artifact_path)
296
354
}
297
355
356
+ fn save_image (
357
+ matches : & ArgMatches ,
358
+ _config : Config ,
359
+ _metadata : CargoEspFlashMeta ,
360
+ cargo_config : CargoConfig ,
361
+ ) -> Result < ( ) > {
362
+ let target = cargo_config
363
+ . target ( )
364
+ . ok_or_else ( || NoTargetError :: new ( None ) ) ?;
365
+ let chip = Chip :: from_target ( target) . ok_or_else ( || Error :: UnknownTarget ( target. into ( ) ) ) ?;
366
+ let build_options = BuildOptions :: from_args ( matches) ;
367
+
368
+ let path = build ( build_options, & cargo_config, Some ( chip) ) ?;
369
+ let elf_data = fs:: read ( path) . into_diagnostic ( ) ?;
370
+
371
+ let image = FirmwareImage :: from_data ( & elf_data) ?;
372
+
373
+ let flash_image = chip. get_flash_image ( & image, None , None , None ) ?;
374
+ let parts: Vec < _ > = flash_image. ota_segments ( ) . collect ( ) ;
375
+
376
+ let out_path = matches. value_of ( "file" ) . unwrap ( ) ;
377
+
378
+ match parts. as_slice ( ) {
379
+ [ single] => fs:: write ( out_path, & single. data ) . into_diagnostic ( ) ?,
380
+ parts => {
381
+ for part in parts {
382
+ let part_path = format ! ( "{:#x}_{}" , part. addr, out_path) ;
383
+ fs:: write ( part_path, & part. data ) . into_diagnostic ( ) ?
384
+ }
385
+ }
386
+ }
387
+
388
+ Ok ( ( ) )
389
+ }
390
+
391
+ fn board_info (
392
+ matches : & ArgMatches ,
393
+ config : Config ,
394
+ _metadata : CargoEspFlashMeta ,
395
+ _cargo_config : CargoConfig ,
396
+ ) -> Result < ( ) > {
397
+ let mut flasher = connect ( matches, & config) ?;
398
+ flasher. board_info ( ) ?;
399
+ Ok ( ( ) )
400
+ }
401
+
298
402
#[ cfg( unix) ]
299
403
fn exit_with_process_status ( status : ExitStatus ) -> ! {
300
404
use std:: os:: unix:: process:: ExitStatusExt ;
0 commit comments