@@ -6,14 +6,16 @@ use std::{
66} ;
77
88use cargo_metadata:: Message ;
9- use clap:: { App , Arg , SubCommand } ;
9+ use clap:: { App , Arg , ArgMatches , SubCommand } ;
1010use error:: Error ;
11- use espflash:: { Chip , Config , Flasher , PartitionTable } ;
11+ use espflash:: { Chip , Config , FirmwareImage , Flasher , PartitionTable } ;
1212use miette:: { IntoDiagnostic , Result , WrapErr } ;
1313use monitor:: monitor;
1414use package_metadata:: CargoEspFlashMeta ;
1515use serial:: { BaudRate , FlowControl , SerialPort } ;
1616
17+ use crate :: cargo_config:: CargoConfig ;
18+ use crate :: error:: NoTargetError ;
1719use crate :: { cargo_config:: parse_cargo_config, error:: UnsupportedTargetError } ;
1820
1921mod cargo_config;
@@ -25,6 +27,27 @@ mod package_metadata;
2527fn main ( ) -> Result < ( ) > {
2628 miette:: set_panic_hook ( ) ;
2729
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+
2851 let mut app = App :: new ( env ! ( "CARGO_PKG_NAME" ) )
2952 . bin_name ( "cargo" )
3053 . subcommand (
@@ -34,40 +57,21 @@ fn main() -> Result<()> {
3457 . arg (
3558 Arg :: with_name ( "board_info" )
3659 . 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) " ) ,
3861 )
62+ . args ( & build_args)
3963 . arg (
4064 Arg :: with_name ( "ram" )
4165 . long ( "ram" )
4266 . help ( "Load the application to RAM instead of Flash" ) ,
4367 )
44- . arg (
45- Arg :: with_name ( "release" )
46- . long ( "release" )
47- . help ( "Build the application using the release profile" ) ,
48- )
4968 . arg (
5069 Arg :: with_name ( "bootloader" )
5170 . long ( "bootloader" )
5271 . takes_value ( true )
5372 . value_name ( "PATH" )
5473 . help ( "Path to a binary (.bin) bootloader file" ) ,
5574 )
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- )
7175 . arg (
7276 Arg :: with_name ( "partition_table" )
7377 . long ( "partition-table" )
@@ -82,16 +86,30 @@ fn main() -> Result<()> {
8286 . value_name ( "SPEED" )
8387 . help ( "Baud rate at which to flash target device" ) ,
8488 )
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)
9190 . arg (
9291 Arg :: with_name ( "monitor" )
9392 . long ( "monitor" )
9493 . 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) ,
95113 ) ,
96114 ) ;
97115
@@ -106,18 +124,30 @@ fn main() -> Result<()> {
106124
107125 let config = Config :: load ( ) ;
108126 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+ }
109135
136+ fn get_serial_port ( matches : & ArgMatches , config : & Config ) -> Result < String , Error > {
110137 // The serial port must be specified, either as a command-line argument or in
111138 // the cargo configuration file. In the case that both have been provided the
112139 // 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 ( ) )
117144 } 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) ?;
121151
122152 // Attempt to open the serial port and set its initial baud rate.
123153 println ! ( "Serial port: {}" , port) ;
@@ -144,19 +174,29 @@ fn main() -> Result<()> {
144174 // Connect the Flasher to the target device and print the board information
145175 // upon connection. If the '--board-info' flag has been provided, we have
146176 // 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) ?;
148190 flasher. board_info ( ) ?;
149191
150192 if matches. is_present ( "board_info" ) {
151193 return Ok ( ( ) ) ;
152194 }
153195
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) ;
157197
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" ) ?;
160200
161201 // If the '--bootloader' option is provided, load the binary file at the
162202 // specified path.
@@ -205,38 +245,56 @@ fn main() -> Result<()> {
205245 Ok ( ( ) )
206246}
207247
208- fn build (
248+ struct BuildOptions < ' a > {
209249 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 > ,
213268) -> Result < PathBuf > {
214269 // The 'build-std' unstable cargo feature is required to enable
215270 // cross-compilation. If it has not been set then we cannot build the
216271 // application.
217- let cargo_config = parse_cargo_config ( "." ) ?;
218272 if !cargo_config. has_build_std ( ) {
219273 return Err ( Error :: NoBuildStd . into ( ) ) ;
220274 } ;
221275
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+ }
225283 }
226284
227285 // Build the list of arguments to pass to 'cargo build'.
228286 let mut args = vec ! [ ] ;
229287
230- if release {
288+ if build_options . release {
231289 args. push ( "--release" ) ;
232290 }
233291
234- if let Some ( example) = example {
292+ if let Some ( example) = build_options . example {
235293 args. push ( "--example" ) ;
236294 args. push ( example) ;
237295 }
238296
239- if let Some ( features) = features {
297+ if let Some ( features) = build_options . features {
240298 args. push ( "--features" ) ;
241299 args. push ( features) ;
242300 }
@@ -281,7 +339,7 @@ fn build(
281339 }
282340
283341 // 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
285343 // rendered.
286344 if !output. status . success ( ) {
287345 exit_with_process_status ( output. status ) ;
@@ -295,6 +353,52 @@ fn build(
295353 Ok ( artifact_path)
296354}
297355
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+
298402#[ cfg( unix) ]
299403fn exit_with_process_status ( status : ExitStatus ) -> ! {
300404 use std:: os:: unix:: process:: ExitStatusExt ;
0 commit comments