From f6abf30552b6677fc3aa2d4c0793d7aeb67e7775 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Mon, 18 Mar 2024 09:32:42 -0400 Subject: [PATCH 01/20] ravedude: add temporary file config support --- ravedude/Cargo.lock | 129 ++++++++++++++++++++++++++++++++++++++--- ravedude/Cargo.toml | 2 + ravedude/src/board.rs | 56 ++++++++++++++++-- ravedude/src/config.rs | 73 +++++++++++++++++++++++ ravedude/src/main.rs | 3 +- 5 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 ravedude/src/config.rs diff --git a/ravedude/Cargo.lock b/ravedude/Cargo.lock index 1e46957410..bae2a4b99c 100644 --- a/ravedude/Cargo.lock +++ b/ravedude/Cargo.lock @@ -122,6 +122,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "getrandom" version = "0.2.2" @@ -152,9 +158,15 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn", + "syn 1.0.64", ] +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "heck" version = "0.3.2" @@ -173,6 +185,16 @@ dependencies = [ "libc", ] +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -285,7 +307,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.64", "version_check", ] @@ -308,18 +330,18 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -372,9 +394,11 @@ dependencies = [ "colored", "ctrlc", "git-version", + "serde", "serialport", "structopt", "tempfile", + "toml", ] [[package]] @@ -412,6 +436,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serialport" version = "4.0.0" @@ -450,7 +503,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.64", ] [[package]] @@ -464,6 +517,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" version = "3.2.0" @@ -487,6 +551,46 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "toml" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -544,3 +648,12 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +dependencies = [ + "memchr", +] diff --git a/ravedude/Cargo.toml b/ravedude/Cargo.toml index b3b133ba99..7afb205c2d 100644 --- a/ravedude/Cargo.toml +++ b/ravedude/Cargo.toml @@ -17,6 +17,8 @@ serialport = "4.0.0" anyhow = "1.0.38" git-version = "0.3.4" ctrlc = "3.2.1" +serde = { version = "1.0.197", features = ["serde_derive"] } +toml = "0.8.11" [dependencies.structopt] version = "0.3.21" diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index 5bb3991327..595ffa3562 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -1,4 +1,6 @@ -use crate::avrdude; +use std::num::NonZeroU32; + +use crate::{avrdude, config}; pub trait Board { fn display_name(&self) -> &str; @@ -7,8 +9,8 @@ pub trait Board { fn guess_port(&self) -> Option>; } -pub fn get_board(board: &str) -> Option> { - Some(match board { +pub fn get_board(board: &str) -> anyhow::Result> { + Ok(match board { "uno" => Box::new(ArduinoUno), "nano" => Box::new(ArduinoNano), "nano-new" => Box::new(ArduinoNanoNew), @@ -24,7 +26,9 @@ pub fn get_board(board: &str) -> Option> { "trinket" => Box::new(Trinket), "nano168" => Box::new(Nano168), "duemilanove" => Box::new(ArduinoDuemilanove), - _ => return None, + // TODO: figure out custom board integration into ravedude. like if the Ravedude.toml path should be configurable etc + "custom" => Box::new(CustomBoard::from_file()?), + _ => return Err(anyhow::anyhow!("Invalid board: {board}")), }) } @@ -463,3 +467,47 @@ impl Board for ArduinoDuemilanove { Some(Err(anyhow::anyhow!("Not able to guess port"))) } } + +struct CustomBoard(config::BoardConfig); + +impl CustomBoard { + fn from_file() -> anyhow::Result { + use std::fs; + + let file_contents = fs::read_to_string("Ravedude.toml") + .map_err(|_| anyhow::anyhow!("couldn't find Ravedude.toml in project"))?; + let config = toml::from_str(&file_contents)?; + + Ok(Self(config)) + } +} + +impl Board for CustomBoard { + fn display_name(&self) -> &str { + &self.0.name + } + + fn needs_reset(&self) -> Option<&str> { + self.0.reset_message.as_deref() + } + + fn avrdude_options(&self) -> avrdude::AvrdudeOptions { + let avrdude_config = &self.0.avrdude; + avrdude::AvrdudeOptions { + programmer: &avrdude_config.programmer, + partno: &avrdude_config.partno, + baudrate: avrdude_config.baudrate.map(NonZeroU32::get), + do_chip_erase: avrdude_config.do_chip_erase, + } + } + + fn guess_port(&self) -> Option> { + // TODO: figure out when to return `None` + match self.0.usb_info.as_ref().and_then(|i| i.port_ids.as_ref()) { + None => Some(Err(anyhow::anyhow!("Not able to guess port"))), + Some(ports) => Some(find_port_from_vid_pid_list( + &ports.iter().map(|id| (id.pid, id.vid)).collect::>(), + )), + } + } +} diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs new file mode 100644 index 0000000000..9d9fb22ea9 --- /dev/null +++ b/ravedude/src/config.rs @@ -0,0 +1,73 @@ +use serde::{Deserialize, Serialize}; +use std::num::NonZeroU32; + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct BoardConfig { + pub name: String, + #[serde( + serialize_with = "serialize_reset_message", + deserialize_with = "deserialize_reset_message", + rename = "reset" + )] + pub reset_message: Option, + pub avrdude: BoardAvrdudeOptions, + pub usb_info: Option, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +struct ResetOptions<'a> { + automatic: bool, + #[serde(default)] + message: Option<&'a str>, +} + +fn serialize_reset_message(val: &Option, serializer: S) -> Result +where + S: serde::Serializer, +{ + let reset_options = ResetOptions { + automatic: val.is_none(), + message: val.as_deref(), + }; + + reset_options.serialize(serializer) +} + +fn deserialize_reset_message<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let reset_options = ResetOptions::deserialize(deserializer)?; + + if reset_options.automatic && reset_options.message.is_some() { + return Err(serde::de::Error::custom( + "cannot have automatic reset with a message for non-automatic reset", + )); + } + if !reset_options.automatic && reset_options.message.is_none() { + return Err(serde::de::Error::custom( + "non-automatic reset option must have a message with it", + )); + } + + Ok(reset_options.message.map(ToOwned::to_owned)) +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct BoardAvrdudeOptions { + pub programmer: String, + pub partno: String, + pub baudrate: Option, + pub do_chip_erase: bool, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct BoardUSBInfo { + pub port_ids: Option>, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct BoardPortID { + pub vid: u16, + pub pid: u16, +} diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index d91ec780f9..bdaac0c6ef 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -7,6 +7,7 @@ use std::time::Duration; mod avrdude; mod board; +mod config; mod console; mod ui; @@ -95,7 +96,7 @@ fn ravedude() -> anyhow::Result<()> { let args: Args = structopt::StructOpt::from_args(); avrdude::Avrdude::require_min_ver(MIN_VERSION_AVRDUDE)?; - let board = board::get_board(&args.board).expect("board not found"); + let board = board::get_board(&args.board)?; task_message!("Board", "{}", board.display_name()); From cc4e6fc199dd230652ec9ec74e40cd054ee6245d Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Mon, 18 Mar 2024 09:39:01 -0400 Subject: [PATCH 02/20] ravedude: fix incorrect substitution in get_board --- ravedude/src/board.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index 595ffa3562..9c8069bf05 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -28,7 +28,7 @@ pub fn get_board(board: &str) -> anyhow::Result> { "duemilanove" => Box::new(ArduinoDuemilanove), // TODO: figure out custom board integration into ravedude. like if the Ravedude.toml path should be configurable etc "custom" => Box::new(CustomBoard::from_file()?), - _ => return Err(anyhow::anyhow!("Invalid board: {board}")), + _ => return Err(anyhow::anyhow!("Invalid board: {}", board)), }) } From 08fefee315a9532efc5f6659ce4f946348d1f125 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Mon, 25 Mar 2024 10:52:15 -0400 Subject: [PATCH 03/20] ravedude: move board configs to a toml file, implement --dump-config, make everything take the new structs --- ravedude/Cargo.lock | 7 + ravedude/Cargo.toml | 1 + ravedude/src/avrdude/mod.rs | 14 +- ravedude/src/board.rs | 523 ++---------------------------------- ravedude/src/boards.toml | 240 +++++++++++++++++ ravedude/src/config.rs | 43 ++- ravedude/src/main.rs | 46 +++- 7 files changed, 342 insertions(+), 532 deletions(-) create mode 100644 ravedude/src/boards.toml diff --git a/ravedude/Cargo.lock b/ravedude/Cargo.lock index bae2a4b99c..4131ee0561 100644 --- a/ravedude/Cargo.lock +++ b/ravedude/Cargo.lock @@ -122,6 +122,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + [[package]] name = "equivalent" version = "1.0.1" @@ -393,6 +399,7 @@ dependencies = [ "anyhow", "colored", "ctrlc", + "either", "git-version", "serde", "serialport", diff --git a/ravedude/Cargo.toml b/ravedude/Cargo.toml index 7afb205c2d..3ee61459d1 100644 --- a/ravedude/Cargo.toml +++ b/ravedude/Cargo.toml @@ -19,6 +19,7 @@ git-version = "0.3.4" ctrlc = "3.2.1" serde = { version = "1.0.197", features = ["serde_derive"] } toml = "0.8.11" +either = "1.10.0" [dependencies.structopt] version = "0.3.21" diff --git a/ravedude/src/avrdude/mod.rs b/ravedude/src/avrdude/mod.rs index ff913da94b..24d2957266 100644 --- a/ravedude/src/avrdude/mod.rs +++ b/ravedude/src/avrdude/mod.rs @@ -4,13 +4,7 @@ use std::process; use std::io::Write; -#[derive(Debug)] -pub struct AvrdudeOptions<'a> { - pub programmer: &'a str, - pub partno: &'a str, - pub baudrate: Option, - pub do_chip_erase: bool, -} +use crate::config::BoardAvrdudeOptions; #[derive(Debug)] pub struct Avrdude { @@ -50,7 +44,7 @@ impl Avrdude { } pub fn run( - options: &AvrdudeOptions, + options: &BoardAvrdudeOptions, port: Option>, bin: &path::Path, debug: bool, @@ -79,9 +73,9 @@ impl Avrdude { let mut command = command .arg("-c") - .arg(options.programmer) + .arg(&options.programmer) .arg("-p") - .arg(options.partno); + .arg(&options.partno); if let Some(port) = port { command = command.arg("-P").arg(port.as_ref()); diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index 9c8069bf05..19341f9784 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -1,513 +1,30 @@ -use std::num::NonZeroU32; +use std::collections::HashMap; -use crate::{avrdude, config}; +use crate::config; -pub trait Board { - fn display_name(&self) -> &str; - fn needs_reset(&self) -> Option<&str>; - fn avrdude_options(&self) -> avrdude::AvrdudeOptions; - fn guess_port(&self) -> Option>; -} +pub fn get_board(board_name: Option<&str>) -> anyhow::Result { + match board_name { + Some(board_name) => { + let mut all_boards: HashMap = + toml::from_str(include_str!("boards.toml")) + .expect("boards.toml in ravedude source is invalid"); -pub fn get_board(board: &str) -> anyhow::Result> { - Ok(match board { - "uno" => Box::new(ArduinoUno), - "nano" => Box::new(ArduinoNano), - "nano-new" => Box::new(ArduinoNanoNew), - "leonardo" => Box::new(ArduinoLeonardo), - "micro" => Box::new(ArduinoMicro), - "mega2560" => Box::new(ArduinoMega2560), - "mega1280" => Box::new(ArduinoMega1280), - "diecimila" => Box::new(ArduinoDiecimila), - "promicro" => Box::new(SparkFunProMicro), - "promini-3v3" => Box::new(SparkFunProMini3V), - "promini-5v" => Box::new(SparkFunProMini5V), - "trinket-pro" => Box::new(TrinketPro), - "trinket" => Box::new(Trinket), - "nano168" => Box::new(Nano168), - "duemilanove" => Box::new(ArduinoDuemilanove), - // TODO: figure out custom board integration into ravedude. like if the Ravedude.toml path should be configurable etc - "custom" => Box::new(CustomBoard::from_file()?), - _ => return Err(anyhow::anyhow!("Invalid board: {}", board)), - }) -} + all_boards.remove(board_name).ok_or_else(|| { + let mut msg = format!("invalid board: {board_name}\n"); -// ---------------------------------------------------------------------------- + msg.push_str("valid boards:"); -fn find_port_from_vid_pid_list(list: &[(u16, u16)]) -> anyhow::Result { - for serialport::SerialPortInfo { - port_name, - port_type, - } in serialport::available_ports().unwrap() - { - if let serialport::SerialPortType::UsbPort(usb_info) = port_type { - for (vid, pid) in list.iter() { - if usb_info.vid == *vid && usb_info.pid == *pid { - return Ok(port_name.into()); + for board in all_boards.keys() { + msg.push('\n'); + msg.push_str(&board); } - } - } - } - Err(anyhow::anyhow!("Serial port not found.")) -} - -// ---------------------------------------------------------------------------- - -struct ArduinoUno; - -impl Board for ArduinoUno { - fn display_name(&self) -> &str { - "Arduino Uno" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega328p", - baudrate: None, - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(find_port_from_vid_pid_list(&[ - (0x2341, 0x0043), - (0x2341, 0x0001), - (0x2A03, 0x0043), - (0x2341, 0x0243), - ])) - } -} - -struct ArduinoMicro; - -impl Board for ArduinoMicro { - fn display_name(&self) -> &str { - "Arduino Micro" - } - - fn needs_reset(&self) -> Option<&str> { - Some("Reset the board by pressing the reset button once.") - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "avr109", - partno: "atmega32u4", - baudrate: Some(115200), - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(find_port_from_vid_pid_list(&[ - (0x2341, 0x0037), - (0x2341, 0x8037), - (0x2A03, 0x0037), - (0x2A03, 0x8037), - (0x2341, 0x0237), - (0x2341, 0x8237), - ])) - } -} - -struct ArduinoNano; - -impl Board for ArduinoNano { - fn display_name(&self) -> &str { - "Arduino Nano" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega328p", - baudrate: Some(57600), - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(Err(anyhow::anyhow!("Not able to guess port"))) - } -} - -struct ArduinoNanoNew; - -impl Board for ArduinoNanoNew { - fn display_name(&self) -> &str { - "Arduino Nano (New Bootloader)" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega328p", - baudrate: Some(115200), - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(Err(anyhow::anyhow!("Not able to guess port"))) - } -} - -struct ArduinoLeonardo; - -impl Board for ArduinoLeonardo { - fn display_name(&self) -> &str { - "Arduino Leonardo" - } - - fn needs_reset(&self) -> Option<&str> { - let a = self.guess_port(); - match a { - Some(Ok(name)) => match serialport::new(name.to_str().unwrap(), 1200).open() { - Ok(_) => { - std::thread::sleep(core::time::Duration::from_secs(1)); - None - } - Err(_) => Some("Reset the board by pressing the reset button once."), - }, - _ => Some("Reset the board by pressing the reset button once."), - } - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "avr109", - partno: "atmega32u4", - baudrate: None, - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(find_port_from_vid_pid_list(&[ - (0x2341, 0x0036), - (0x2341, 0x8036), - (0x2A03, 0x0036), - (0x2A03, 0x8036), - ])) - } -} - -struct ArduinoMega1280; - -impl Board for ArduinoMega1280 { - fn display_name(&self) -> &str { - "Arduino Mega 1280" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega1280", - baudrate: Some(57600), - do_chip_erase: false, - } - } - - fn guess_port(&self) -> Option> { - // This board uses a generic serial interface id 0403:6001 which is too common for auto detection. - Some(Err(anyhow::anyhow!("Unable to guess port."))) - } -} - -struct ArduinoMega2560; - -impl Board for ArduinoMega2560 { - fn display_name(&self) -> &str { - "Arduino Mega 2560" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "wiring", - partno: "atmega2560", - baudrate: Some(115200), - do_chip_erase: false, - } - } - - fn guess_port(&self) -> Option> { - Some(find_port_from_vid_pid_list(&[ - (0x2341, 0x0010), - (0x2341, 0x0042), - (0x2A03, 0x0010), - (0x2A03, 0x0042), - (0x2341, 0x0210), - (0x2341, 0x0242), - ])) - } -} - -struct ArduinoDiecimila; - -impl Board for ArduinoDiecimila { - fn display_name(&self) -> &str { - "Arduino Diecimila" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega168", - baudrate: Some(19200), - do_chip_erase: false, - } - } - - fn guess_port(&self) -> Option> { - Some(Err(anyhow::anyhow!("Not able to guess port"))) - } -} - -struct SparkFunProMicro; - -impl Board for SparkFunProMicro { - fn display_name(&self) -> &str { - "SparkFun Pro Micro" - } - - fn needs_reset(&self) -> Option<&str> { - Some("Reset the board by quickly pressing the reset button **twice**.") - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "avr109", - partno: "atmega32u4", - baudrate: None, - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(find_port_from_vid_pid_list(&[ - (0x1B4F, 0x9205), //5V - (0x1B4F, 0x9206), //5V - (0x1B4F, 0x9203), //3.3V - (0x1B4F, 0x9204), //3.3V - ])) - } -} - -struct SparkFunProMini3V; - -impl Board for SparkFunProMini3V { - fn display_name(&self) -> &str { - "SparkFun Pro Mini 3.3V (8MHz)" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega328p", - baudrate: Some(57600), - do_chip_erase: true, + anyhow::anyhow!(msg) + }) } - } - - fn guess_port(&self) -> Option> { - Some(Err(anyhow::anyhow!("Not able to guess port"))) - } -} - -struct SparkFunProMini5V; - -impl Board for SparkFunProMini5V { - fn display_name(&self) -> &str { - "SparkFun Pro Mini 5V (16MHz)" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega328p", - baudrate: Some(57600), - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(Err(anyhow::anyhow!("Not able to guess port"))) - } -} - -struct TrinketPro; - -impl Board for TrinketPro { - fn display_name(&self) -> &str { - "Trinket Pro" - } - - fn needs_reset(&self) -> Option<&str> { - Some("Reset the board by pressing the reset button once.") - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "usbtiny", - partno: "atmega328p", - baudrate: None, - do_chip_erase: false, - } - } - - fn guess_port(&self) -> Option> { - None // The TrinketPro does not have USB-to-Serial. - } -} - -struct Trinket; - -impl Board for Trinket { - fn display_name(&self) -> &str { - "Trinket" - } - - fn needs_reset(&self) -> Option<&str> { - Some("Reset the board by pressing the reset button once.") - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "usbtiny", - partno: "attiny85", - baudrate: None, - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - None // The Trinket does not have USB-to-Serial. - } -} - -struct Nano168; - -impl Board for Nano168 { - fn display_name(&self) -> &str { - "Nano Clone (ATmega168)" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega168", - baudrate: Some(19200), - do_chip_erase: false, - } - } - - fn guess_port(&self) -> Option> { - Some(Err(anyhow::anyhow!("Not able to guess port"))) - } -} - -struct ArduinoDuemilanove; - -impl Board for ArduinoDuemilanove { - fn display_name(&self) -> &str { - "Arduino Duemilanove" - } - - fn needs_reset(&self) -> Option<&str> { - None - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - avrdude::AvrdudeOptions { - programmer: "arduino", - partno: "atmega328p", - baudrate: Some(57600), - do_chip_erase: true, - } - } - - fn guess_port(&self) -> Option> { - Some(Err(anyhow::anyhow!("Not able to guess port"))) - } -} - -struct CustomBoard(config::BoardConfig); - -impl CustomBoard { - fn from_file() -> anyhow::Result { - use std::fs; - - let file_contents = fs::read_to_string("Ravedude.toml") - .map_err(|_| anyhow::anyhow!("couldn't find Ravedude.toml in project"))?; - let config = toml::from_str(&file_contents)?; - - Ok(Self(config)) - } -} - -impl Board for CustomBoard { - fn display_name(&self) -> &str { - &self.0.name - } - - fn needs_reset(&self) -> Option<&str> { - self.0.reset_message.as_deref() - } - - fn avrdude_options(&self) -> avrdude::AvrdudeOptions { - let avrdude_config = &self.0.avrdude; - avrdude::AvrdudeOptions { - programmer: &avrdude_config.programmer, - partno: &avrdude_config.partno, - baudrate: avrdude_config.baudrate.map(NonZeroU32::get), - do_chip_erase: avrdude_config.do_chip_erase, - } - } - - fn guess_port(&self) -> Option> { - // TODO: figure out when to return `None` - match self.0.usb_info.as_ref().and_then(|i| i.port_ids.as_ref()) { - None => Some(Err(anyhow::anyhow!("Not able to guess port"))), - Some(ports) => Some(find_port_from_vid_pid_list( - &ports.iter().map(|id| (id.pid, id.vid)).collect::>(), - )), + None => { + let file_contents = std::fs::read_to_string("Ravedude.toml") + .map_err(|_| anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))?; + Ok(toml::from_str(&file_contents)?) } } } diff --git a/ravedude/src/boards.toml b/ravedude/src/boards.toml new file mode 100644 index 0000000000..6940a89c82 --- /dev/null +++ b/ravedude/src/boards.toml @@ -0,0 +1,240 @@ +[uno] + name = "Arduino Uno" + + [uno.reset] + automatic = true + + [uno.avrdude] + programmer = "arduino" + partno = "atmega328p" + do-chip-erase = true + + [uno.usb-info] + port-ids = [ + { vid = 0x2341, pid = 0x0043 }, + { vid = 0x2341, pid = 0x0001 }, + { vid = 0x2A03, pid = 0x0043 }, + { vid = 0x2341, pid = 0x0243 }, + ] + +[nano] + name = "Arduino Nano" + + [nano.reset] + automatic = true + + [nano.avrdude] + programmer = "arduino" + partno = "atmega328p" + baudrate = 57600 + do-chip-erase = true + + [nano.usb-info] + error = "Not able to guess port" + +[nano-new] + name = "Arduino Nano (New Bootloader)" + + [nano-new.reset] + automatic = true + + [nano-new.avrdude] + programmer = "arduino" + partno = "atmega328p" + baudrate = 115200 + do-chip-erase = true + + [nano-new.usb-info] + error = "Not able to guess port" + +[leonardo] + name = "Arduino Leonardo" + + [leonardo.reset] + automatic = false + message = "Reset the board by pressing the reset button once." + + [leonardo.avrdude] + programmer = "avr109" + partno = "atmega32u4" + do-chip-erase = true + + [leonardo.usb-info] + port-ids = [ + { vid = 0x2341, pid = 0x0036 }, + { vid = 0x2341, pid = 0x8036 }, + { vid = 0x2A03, pid = 0x0036 }, + { vid = 0x2A03, pid = 0x8036 }, + ] + +[micro] + name = "Arduino Micro" + + [micro.reset] + automatic = false + message = "Reset the board by pressing the reset button once." + + [micro.avrdude] + programmer = "avr109" + partno = "atmega32u4" + baudrate = 115200 + do-chip-erase = true + + [micro.usb-info] + port-ids = [ + { vid = 0x2341, pid = 0x0037 }, + { vid = 0x2341, pid = 0x8037 }, + { vid = 0x2A03, pid = 0x0037 }, + { vid = 0x2A03, pid = 0x8037 }, + { vid = 0x2341, pid = 0x0237 }, + { vid = 0x2341, pid = 0x8237 }, + ] + +[mega2560] + name = "Arduino Mega 2560" + + [mega2560.reset] + automatic = true + + [mega2560.avrdude] + programmer = "wiring" + partno = "atmega2560" + baudrate = 115200 + do-chip-erase = false + + [mega2560.usb-info] + port-ids = [ + { vid = 0x2341, pid = 0x0010 }, + { vid = 0x2341, pid = 0x0042 }, + { vid = 0x2A03, pid = 0x0010 }, + { vid = 0x2A03, pid = 0x0042 }, + { vid = 0x2341, pid = 0x0210 }, + { vid = 0x2341, pid = 0x0242 }, + ] + +[mega1280] + name = "Arduino Mega 1280" + + [mega1280.reset] + automatic = true + + [mega1280.avrdude] + programmer = "wiring" + partno = "atmega1280" + baudrate = 57600 + do-chip-erase = false + + [mega1280.usb-info] + # This board uses a generic serial interface id 0403:6001 which is too common for auto detection. + error = "Not able to guess port" + +[diecimila] + name = "Arduino Diecimila" + + [diecimila.reset] + automatic = true + + [diecimila.avrdude] + programmer = "arduino" + partno = "atmega168" + baudrate = 19200 + do-chip-erase = false + + [diecimila.usb-info] + # No IDs known. + error = "Not able to guess port" + +[promicro] + name = "SparkFun Pro Micro" + + [promicro.reset] + automatic = false + message = "Reset the board by quickly pressing the reset button **twice**." + + [promicro.avrdude] + programmer = "avr109" + partno = "atmega32u4" + do-chip-erase = true + + [promicro.usb-info] + port-ids = [ + { vid = 0x1B4F, pid = 0x9205 }, # 5V + { vid = 0x1B4F, pid = 0x9206 }, # 5V + { vid = 0x1B4F, pid = 0x9203 }, # 3.3V + { vid = 0x1B4F, pid = 0x9204 }, # 3.3V + ] + +[promini-5v] + name = "SparkFun Pro Mini 5V (16MHz)" + + [promini-5v.reset] + automatic = true + + [promini-5v.avrdude] + programmer = "arduino" + partno = "atmega328p" + baudrate = 57600 + do-chip-erase = true + + [promini-5v.usb-info] + error = "Not able to guess port" + +[trinket-pro] + name = "Trinket Pro" + + [trinket-pro.reset] + automatic = false + message = "Reset the board by pressing the reset button once." + + [trinket-pro.avrdude] + programmer = "usbtiny" + partno = "atmega328p" + do-chip-erase = false + + # The Trinket Pro does not have USB-Serial, thus no port is known or needed. + +[trinket] + name = "Trinket" + + [trinket.reset] + automatic = false + message = "Reset the board by pressing the reset button once." + + [trinket.avrdude] + programmer = "usbtiny" + partno = "atmega328p" + do-chip-erase = true + + # The Trinket does not have USB-Serial, thus no port is known or needed. + +[nano168] + name = "Nano Clone (ATmega168)" + + [nano168.reset] + automatic = true + + [nano168.avrdude] + programmer = "arduino" + partno = "atmega168" + baudrate = 19200 + do-chip-erase = false + + [nano168.usb-info] + # No IDs here because the Nano 168 uses a generic USB-Serial chip. + error = "Not able to guess port" + +[duemilanove] + name = "Arduino Duemilanove" + + [duemilanove.reset] + automatic = true + + [duemilanove.avrdude] + programmer = "arduino" + partno = "atmega328p" + baudrate = 57600 + do-chip-erase = true + + [duemilanove.usb-info] + # No IDs here because the Nano 168 uses a generic USB-Serial chip. + error = "Not able to guess port" diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 9d9fb22ea9..6eaf8245cf 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use std::num::NonZeroU32; #[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] pub struct BoardConfig { pub name: String, #[serde( @@ -15,10 +16,9 @@ pub struct BoardConfig { } #[derive(serde::Serialize, serde::Deserialize, Debug)] -struct ResetOptions<'a> { +struct ResetOptions { automatic: bool, - #[serde(default)] - message: Option<&'a str>, + message: Option, } fn serialize_reset_message(val: &Option, serializer: S) -> Result @@ -27,7 +27,7 @@ where { let reset_options = ResetOptions { automatic: val.is_none(), - message: val.as_deref(), + message: val.clone(), }; reset_options.serialize(serializer) @@ -50,10 +50,11 @@ where )); } - Ok(reset_options.message.map(ToOwned::to_owned)) + Ok(reset_options.message) } #[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] pub struct BoardAvrdudeOptions { pub programmer: String, pub partno: String, @@ -62,8 +63,11 @@ pub struct BoardAvrdudeOptions { } #[derive(serde::Serialize, serde::Deserialize, Debug)] -pub struct BoardUSBInfo { - pub port_ids: Option>, +#[serde(rename_all = "kebab-case")] +pub enum BoardUSBInfo { + PortIds(Vec), + #[serde(rename = "error")] + Error(String), } #[derive(serde::Serialize, serde::Deserialize, Debug)] @@ -71,3 +75,28 @@ pub struct BoardPortID { pub vid: u16, pub pid: u16, } + +impl BoardConfig { + pub fn guess_port(&self) -> Option> { + match &self.usb_info { + Some(BoardUSBInfo::Error(err)) => Some(Err(anyhow::anyhow!(err.clone()))), + Some(BoardUSBInfo::PortIds(ports)) => { + for serialport::SerialPortInfo { + port_name, + port_type, + } in serialport::available_ports().unwrap() + { + if let serialport::SerialPortType::UsbPort(usb_info) = port_type { + for &BoardPortID { vid, pid } in ports { + if usb_info.vid == vid && usb_info.pid == pid { + return Some(Ok(port_name.into())); + } + } + } + } + Some(Err(anyhow::anyhow!("Serial port not found."))) + } + None => None, + } + } +} diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index bdaac0c6ef..c0cd8c0cbf 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -29,6 +29,14 @@ const MIN_VERSION_AVRDUDE: (u8, u8) = (6, 3); fallback = "unknown" ))] struct Args { + /// Utility flag for dumping a config of a named board to TOML. + /// ```sh + /// # Create a new Ravedude.toml with the default Arduino UNO Config. + /// ravedude --dump-config uno > Ravedude.toml + /// ``` + #[structopt(long = "dump-config")] + dump_config: bool, + /// After successfully flashing the program, open a serial console to see output sent by the /// board and possibly interact with it. #[structopt(short = "c", long = "open-console")] @@ -71,9 +79,9 @@ struct Args { /// * trinket-pro /// * trinket /// * nano168 - /// * duemilanove + /// * duemilanovepriori #[structopt(name = "BOARD", verbatim_doc_comment)] - board: String, + board: Option, /// The binary to be flashed. /// @@ -93,12 +101,22 @@ fn main() { } fn ravedude() -> anyhow::Result<()> { - let args: Args = structopt::StructOpt::from_args(); + let mut args: Args = structopt::StructOpt::from_args(); + if args.dump_config { + return dump_config(args.board.as_deref()); + } + + // Due to the ordering of the arguments, board is prioritized before bin. + // There doesn't seem to be an way to change the argument priority like this. + if args.board.is_some() && args.bin.is_none() { + args.bin = Some(std::path::PathBuf::from(args.board.take().unwrap())); + } + avrdude::Avrdude::require_min_ver(MIN_VERSION_AVRDUDE)?; - let board = board::get_board(&args.board)?; + let board = board::get_board(args.board.as_deref())?; - task_message!("Board", "{}", board.display_name()); + task_message!("Board", "{}", &board.name); if let Some(wait_time) = args.reset_delay { if wait_time > 0 { @@ -109,7 +127,7 @@ fn ravedude() -> anyhow::Result<()> { println!("Assuming board has been reset"); } } else { - if let Some(msg) = board.needs_reset() { + if let Some(ref msg) = board.reset_message { warning!("this board cannot reset itself."); eprintln!(""); eprintln!(" {}", msg); @@ -143,12 +161,8 @@ fn ravedude() -> anyhow::Result<()> { task_message!("Programming", "{}", bin.display(),); } - let mut avrdude = avrdude::Avrdude::run( - &board.avrdude_options(), - port.as_ref(), - bin, - args.debug_avrdude, - )?; + let mut avrdude = + avrdude::Avrdude::run(&board.avrdude, port.as_ref(), bin, args.debug_avrdude)?; avrdude.wait()?; task_message!("Programmed", "{}", bin.display()); @@ -178,3 +192,11 @@ fn ravedude() -> anyhow::Result<()> { Ok(()) } + +fn dump_config(board_name: Option<&str>) -> anyhow::Result<()> { + let board = board::get_board(board_name)?; + + println!("{}", toml::to_string_pretty(&board)?); + + Ok(()) +} From f5f9f83c3817e33970ab64ea2f6d334244183821 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Mon, 25 Mar 2024 11:05:00 -0400 Subject: [PATCH 04/20] ravedude: add options for command-line args in Ravedude.toml --- ravedude/src/config.rs | 63 ++++++++++++++++++++++++++++++++++++++++++ ravedude/src/main.rs | 4 ++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 6eaf8245cf..30fb6602dc 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use std::num::NonZeroU32; +use crate::warning; + #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct BoardConfig { @@ -13,6 +15,9 @@ pub struct BoardConfig { pub reset_message: Option, pub avrdude: BoardAvrdudeOptions, pub usb_info: Option, + + #[serde(flatten)] + pub overrides: BoardOverrides, } #[derive(serde::Serialize, serde::Deserialize, Debug)] @@ -100,3 +105,61 @@ impl BoardConfig { } } } + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct BoardOverrides { + open_console: Option, + serial_baudrate: Option, + port: Option, + reset_delay: Option, +} + +impl BoardOverrides { + pub fn apply_overrides(&mut self, args: &mut crate::Args) { + // command line args take priority over Ravedude.toml + if let Some(open_console) = self.open_console { + if args.open_console { + warning!( + "Overriding console with {} (was {} in Ravedude.toml)", + args.open_console, + open_console, + ); + } else { + args.open_console = open_console; + } + } + if let Some(serial_baudrate) = self.serial_baudrate { + if let Some(args_baudrate) = args.baudrate { + warning!( + "Overriding baudrate with {} (was {} in Ravedude.toml)", + args_baudrate, + serial_baudrate + ); + } else { + args.baudrate = Some(serial_baudrate.get()); + } + } + if let Some(port) = self.port.take() { + if let Some(ref args_port) = args.port { + warning!( + "Overriding port with {} (was {} in Ravedude.toml)", + port.to_str().unwrap(), + args_port.to_str().unwrap() + ); + } else { + args.port = Some(port); + } + } + if let Some(reset_delay) = self.reset_delay { + if let Some(args_reset_delay) = args.reset_delay { + warning!( + "Overriding reset delay with {} (was {} in Ravedude.toml)", + args_reset_delay, + reset_delay + ); + } else { + args.reset_delay = Some(reset_delay); + } + } + } +} diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index c0cd8c0cbf..4204ee38d7 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -114,7 +114,9 @@ fn ravedude() -> anyhow::Result<()> { avrdude::Avrdude::require_min_ver(MIN_VERSION_AVRDUDE)?; - let board = board::get_board(args.board.as_deref())?; + let mut board = board::get_board(args.board.as_deref())?; + + board.overrides.apply_overrides(&mut args); task_message!("Board", "{}", &board.name); From a29c093a034dce8061ed26ae8d041141bd964c28 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Mon, 25 Mar 2024 11:09:36 -0400 Subject: [PATCH 05/20] ravedude: that should be kebab case --- ravedude/src/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 30fb6602dc..42a667dedb 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -107,6 +107,7 @@ impl BoardConfig { } #[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] pub struct BoardOverrides { open_console: Option, serial_baudrate: Option, From b80cfb44ca478bd93e7b8d0c5a5697e6c2054dc1 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sat, 6 Apr 2024 23:02:20 -0400 Subject: [PATCH 06/20] ravedude: add board inheritance, change baudrate syntax, add test for boards.toml --- ravedude/src/avrdude/mod.rs | 28 ++++++++++-- ravedude/src/board.rs | 52 ++++++++++++++++++++-- ravedude/src/boards.toml | 5 +++ ravedude/src/config.rs | 89 +++++++++++++++++++++++++++++++------ ravedude/src/main.rs | 29 +++++++++--- 5 files changed, 177 insertions(+), 26 deletions(-) diff --git a/ravedude/src/avrdude/mod.rs b/ravedude/src/avrdude/mod.rs index 24d2957266..f8303bdf08 100644 --- a/ravedude/src/avrdude/mod.rs +++ b/ravedude/src/avrdude/mod.rs @@ -73,16 +73,32 @@ impl Avrdude { let mut command = command .arg("-c") - .arg(&options.programmer) + .arg(options.programmer.as_ref().ok_or_else(|| { + anyhow::anyhow!( + "Base board doesn't have a programmer. This is a bug, please report it!" + ) + })?) .arg("-p") - .arg(&options.partno); + .arg(options.partno.as_ref().ok_or_else(|| { + anyhow::anyhow!( + "Base board doesn't have a part number. This is a bug, please report it!" + ) + })?); if let Some(port) = port { command = command.arg("-P").arg(port.as_ref()); } if let Some(baudrate) = options.baudrate { - command = command.arg("-b").arg(baudrate.to_string()); + command = command.arg("-b").arg( + baudrate + .ok_or_else(|| { + anyhow::anyhow!( + "Base board doesn't have a baudrate. This is a bug, please report it!" + ) + })? + .to_string(), + ); } // TODO: Check that `bin` does not contain : @@ -90,7 +106,11 @@ impl Avrdude { flash_instruction.push(bin); flash_instruction.push(":e"); - if options.do_chip_erase { + if options.do_chip_erase.ok_or_else(|| { + anyhow::anyhow!( + "Base board doesn't specify whether to erase the chip. This is a bug, please report it!" + ) + })? { command = command.arg("-e"); } diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index 19341f9784..877ea89a81 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -2,12 +2,26 @@ use std::collections::HashMap; use crate::config; +fn get_all_boards() -> anyhow::Result> { + toml::from_str(include_str!("boards.toml")).map_err(|err| { + if cfg!(test) { + anyhow::anyhow!( + "boards.toml in ravedude source is invalid.\n{}", + err.message() + ) + } else { + anyhow::anyhow!( + "boards.toml in ravedude source is invalid. This is a bug, please report it!\n{}", + err.message() + ) + } + }) +} + pub fn get_board(board_name: Option<&str>) -> anyhow::Result { match board_name { Some(board_name) => { - let mut all_boards: HashMap = - toml::from_str(include_str!("boards.toml")) - .expect("boards.toml in ravedude source is invalid"); + let mut all_boards = get_all_boards()?; all_boards.remove(board_name).ok_or_else(|| { let mut msg = format!("invalid board: {board_name}\n"); @@ -24,7 +38,37 @@ pub fn get_board(board_name: Option<&str>) -> anyhow::Result { let file_contents = std::fs::read_to_string("Ravedude.toml") .map_err(|_| anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))?; - Ok(toml::from_str(&file_contents)?) + + let mut board: config::BoardConfig = toml::from_str(&file_contents)?; + if let Some(inherit) = board.inherit.as_deref() { + let base_board = get_board(Some(inherit))?; + board = board.merge(base_board); + } + Ok(board) } } } + +#[cfg(test)] +mod tests { + use super::get_all_boards; + + #[test] + fn validate_board_list() -> anyhow::Result<()> { + let all_boards = get_all_boards()?; + + for board in all_boards.values() { + assert!(board.name.is_some()); + assert!(board.inherit.is_none()); + assert!(board.reset_message.is_some()); + assert!(board.avrdude.is_some()); + let avrdude = board.avrdude.as_ref().unwrap(); + assert!(avrdude.programmer.is_some()); + assert!(avrdude.partno.is_some()); + assert!(avrdude.baudrate.is_some()); + assert!(avrdude.do_chip_erase.is_some()); + } + + Ok(()) + } +} diff --git a/ravedude/src/boards.toml b/ravedude/src/boards.toml index 6940a89c82..6e6c13f43a 100644 --- a/ravedude/src/boards.toml +++ b/ravedude/src/boards.toml @@ -7,6 +7,7 @@ [uno.avrdude] programmer = "arduino" partno = "atmega328p" + baudrate = -1 do-chip-erase = true [uno.usb-info] @@ -57,6 +58,7 @@ [leonardo.avrdude] programmer = "avr109" partno = "atmega32u4" + baudrate = -1 do-chip-erase = true [leonardo.usb-info] @@ -154,6 +156,7 @@ [promicro.avrdude] programmer = "avr109" partno = "atmega32u4" + baudrate = -1 do-chip-erase = true [promicro.usb-info] @@ -189,6 +192,7 @@ [trinket-pro.avrdude] programmer = "usbtiny" partno = "atmega328p" + baudrate = -1 do-chip-erase = false # The Trinket Pro does not have USB-Serial, thus no port is known or needed. @@ -203,6 +207,7 @@ [trinket.avrdude] programmer = "usbtiny" partno = "atmega328p" + baudrate = -1 do-chip-erase = true # The Trinket does not have USB-Serial, thus no port is known or needed. diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 42a667dedb..2d3915ae5b 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -6,43 +6,67 @@ use crate::warning; #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct BoardConfig { - pub name: String, + pub name: Option, + pub inherit: Option, #[serde( serialize_with = "serialize_reset_message", deserialize_with = "deserialize_reset_message", rename = "reset" )] - pub reset_message: Option, - pub avrdude: BoardAvrdudeOptions, + pub reset_message: Option>, + pub avrdude: Option, pub usb_info: Option, #[serde(flatten)] pub overrides: BoardOverrides, } +impl BoardConfig { + pub fn merge(self, base: BoardConfig) -> Self { + Self { + name: self.name.or(base.name), + // inherit is used to decide what BoardConfig to inherit and isn't used anywhere else + inherit: None, + reset_message: self.reset_message.or(base.reset_message), + avrdude: self + .avrdude + .and_then(|avrdude| base.avrdude.map(|base_avrdude| avrdude.merge(base_avrdude))), + usb_info: self.usb_info.or(base.usb_info), + + // overrides aren't related to the board + overrides: self.overrides, + } + } +} + #[derive(serde::Serialize, serde::Deserialize, Debug)] struct ResetOptions { automatic: bool, message: Option, } -fn serialize_reset_message(val: &Option, serializer: S) -> Result +fn serialize_reset_message( + val: &Option>, + serializer: S, +) -> Result where S: serde::Serializer, { - let reset_options = ResetOptions { + let reset_options = val.as_ref().map(|val| ResetOptions { automatic: val.is_none(), message: val.clone(), - }; + }); reset_options.serialize(serializer) } -fn deserialize_reset_message<'de, D>(deserializer: D) -> Result, D::Error> +fn deserialize_reset_message<'de, D>(deserializer: D) -> Result>, D::Error> where D: serde::Deserializer<'de>, { - let reset_options = ResetOptions::deserialize(deserializer)?; + let Some(reset_options) = Option::::deserialize(deserializer)? else { + return Ok(None); + }; if reset_options.automatic && reset_options.message.is_some() { return Err(serde::de::Error::custom( @@ -55,16 +79,55 @@ where )); } - Ok(reset_options.message) + Ok(Some(reset_options.message)) } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct BoardAvrdudeOptions { - pub programmer: String, - pub partno: String, - pub baudrate: Option, - pub do_chip_erase: bool, + pub programmer: Option, + pub partno: Option, + #[serde( + serialize_with = "serialize_baudrate", + deserialize_with = "deserialize_baudrate" + )] + pub baudrate: Option>, + pub do_chip_erase: Option, +} +impl BoardAvrdudeOptions { + pub fn merge(self, base: Self) -> Self { + Self { + programmer: self.programmer.or(base.programmer), + partno: self.partno.or(base.partno), + baudrate: self.baudrate.or(base.baudrate), + do_chip_erase: self.do_chip_erase.or(base.do_chip_erase), + } + } +} +fn serialize_baudrate(val: &Option>, serializer: S) -> Result +where + S: serde::Serializer, +{ + let baudrate = val.as_ref().map(|val| val.map_or(-1, |x| x.get() as i32)); + + baudrate.serialize(serializer) +} + +fn deserialize_baudrate<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let Some(baudrate) = Option::::deserialize(deserializer)? else { + return Ok(None); + }; + + match NonZeroU32::new(baudrate as _) { + None if baudrate == -1 => Ok(Some(None)), + Some(nonzero_baudrate) => Ok(Some(Some(nonzero_baudrate))), + _ => Err(serde::de::Error::custom(format!( + "invalid baudrate: {baudrate}" + ))), + } } #[derive(serde::Serialize, serde::Deserialize, Debug)] diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 4204ee38d7..a49de49e40 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -118,7 +118,13 @@ fn ravedude() -> anyhow::Result<()> { board.overrides.apply_overrides(&mut args); - task_message!("Board", "{}", &board.name); + task_message!( + "Board", + "{}", + &board.name.as_deref().ok_or_else(|| anyhow::anyhow!( + "Base board doesn't have a name. This is a bug, please report it!" + ))? + ); if let Some(wait_time) = args.reset_delay { if wait_time > 0 { @@ -132,7 +138,12 @@ fn ravedude() -> anyhow::Result<()> { if let Some(ref msg) = board.reset_message { warning!("this board cannot reset itself."); eprintln!(""); - eprintln!(" {}", msg); + eprintln!( + " {}", + msg.as_ref().ok_or_else(|| anyhow::anyhow!( + "Base board doesn't have a reset message. This is a bug, please report it!" + ))? + ); eprintln!(""); eprint!("Once reset, press ENTER here: "); std::io::stdin().read_line(&mut String::new())?; @@ -163,8 +174,16 @@ fn ravedude() -> anyhow::Result<()> { task_message!("Programming", "{}", bin.display(),); } - let mut avrdude = - avrdude::Avrdude::run(&board.avrdude, port.as_ref(), bin, args.debug_avrdude)?; + let mut avrdude = avrdude::Avrdude::run( + &board.avrdude.ok_or_else(|| { + anyhow::anyhow!( + "Base board doesn't have avrdude options. This is a bug, please report it!" + ) + })?, + port.as_ref(), + bin, + args.debug_avrdude, + )?; avrdude.wait()?; task_message!("Programmed", "{}", bin.display()); @@ -198,7 +217,7 @@ fn ravedude() -> anyhow::Result<()> { fn dump_config(board_name: Option<&str>) -> anyhow::Result<()> { let board = board::get_board(board_name)?; - println!("{}", toml::to_string_pretty(&board)?); + println!("{}", toml::to_string(&board)?); Ok(()) } From f1251f1872038acfdaf2b1c61a811fcda25301ff Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sat, 6 Apr 2024 23:23:17 -0400 Subject: [PATCH 07/20] ravedude: remove warnings for command-line overrides + fix some command-line args descriptions --- ravedude/src/config.rs | 42 ++++-------------------------------------- ravedude/src/main.rs | 8 +++----- 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 2d3915ae5b..caba00be60 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -1,8 +1,6 @@ use serde::{Deserialize, Serialize}; use std::num::NonZeroU32; -use crate::warning; - #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct BoardConfig { @@ -182,48 +180,16 @@ impl BoardOverrides { pub fn apply_overrides(&mut self, args: &mut crate::Args) { // command line args take priority over Ravedude.toml if let Some(open_console) = self.open_console { - if args.open_console { - warning!( - "Overriding console with {} (was {} in Ravedude.toml)", - args.open_console, - open_console, - ); - } else { - args.open_console = open_console; - } + args.open_console = open_console; } if let Some(serial_baudrate) = self.serial_baudrate { - if let Some(args_baudrate) = args.baudrate { - warning!( - "Overriding baudrate with {} (was {} in Ravedude.toml)", - args_baudrate, - serial_baudrate - ); - } else { - args.baudrate = Some(serial_baudrate.get()); - } + args.baudrate = Some(serial_baudrate.get()); } if let Some(port) = self.port.take() { - if let Some(ref args_port) = args.port { - warning!( - "Overriding port with {} (was {} in Ravedude.toml)", - port.to_str().unwrap(), - args_port.to_str().unwrap() - ); - } else { - args.port = Some(port); - } + args.port = Some(port); } if let Some(reset_delay) = self.reset_delay { - if let Some(args_reset_delay) = args.reset_delay { - warning!( - "Overriding reset delay with {} (was {} in Ravedude.toml)", - args_reset_delay, - reset_delay - ); - } else { - args.reset_delay = Some(reset_delay); - } + args.reset_delay = Some(reset_delay); } } } diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index a49de49e40..afaaeb6a0b 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -30,10 +30,6 @@ const MIN_VERSION_AVRDUDE: (u8, u8) = (6, 3); ))] struct Args { /// Utility flag for dumping a config of a named board to TOML. - /// ```sh - /// # Create a new Ravedude.toml with the default Arduino UNO Config. - /// ravedude --dump-config uno > Ravedude.toml - /// ``` #[structopt(long = "dump-config")] dump_config: bool, @@ -46,7 +42,7 @@ struct Args { #[structopt(short = "b", long = "baudrate")] baudrate: Option, - /// Overwrite which port to use. By default ravedude will try to find a connected board by + /// Overwrite which port to use. By default ravedude will try to find a connected board by /// itself. #[structopt(short = "P", long = "port", parse(from_os_str), env = "RAVEDUDE_PORT")] port: Option, @@ -108,6 +104,8 @@ fn ravedude() -> anyhow::Result<()> { // Due to the ordering of the arguments, board is prioritized before bin. // There doesn't seem to be an way to change the argument priority like this. + // + // TODO: this is currently a breaking change (for users that use `board` but not `bin`), how do we handle this? if args.board.is_some() && args.bin.is_none() { args.bin = Some(std::path::PathBuf::from(args.board.take().unwrap())); } From d5ff4fc3e7e1a6e0c30cab318b6453de893ca689 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sat, 6 Apr 2024 23:56:38 -0400 Subject: [PATCH 08/20] ravedude: make BoardConfig not say that nonexistent options are bugs because they are not, in fact, bugs --- ravedude/src/avrdude/mod.rs | 34 +++++++--------- ravedude/src/board.rs | 2 +- ravedude/src/config.rs | 77 ++++++++----------------------------- ravedude/src/main.rs | 32 +++++++-------- 4 files changed, 47 insertions(+), 98 deletions(-) diff --git a/ravedude/src/avrdude/mod.rs b/ravedude/src/avrdude/mod.rs index f8303bdf08..c71c3902d9 100644 --- a/ravedude/src/avrdude/mod.rs +++ b/ravedude/src/avrdude/mod.rs @@ -73,32 +73,26 @@ impl Avrdude { let mut command = command .arg("-c") - .arg(options.programmer.as_ref().ok_or_else(|| { - anyhow::anyhow!( - "Base board doesn't have a programmer. This is a bug, please report it!" - ) - })?) + .arg( + options + .programmer + .as_ref() + .ok_or_else(|| anyhow::anyhow!("board has no programmer"))?, + ) .arg("-p") - .arg(options.partno.as_ref().ok_or_else(|| { - anyhow::anyhow!( - "Base board doesn't have a part number. This is a bug, please report it!" - ) - })?); + .arg( + options + .partno + .as_ref() + .ok_or_else(|| anyhow::anyhow!("board has no part number"))?, + ); if let Some(port) = port { command = command.arg("-P").arg(port.as_ref()); } - if let Some(baudrate) = options.baudrate { - command = command.arg("-b").arg( - baudrate - .ok_or_else(|| { - anyhow::anyhow!( - "Base board doesn't have a baudrate. This is a bug, please report it!" - ) - })? - .to_string(), - ); + if let Some(baudrate) = options.baudrate.flatten() { + command = command.arg("-b").arg(baudrate.to_string()); } // TODO: Check that `bin` does not contain : diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index 877ea89a81..8a671a5b4a 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -60,7 +60,7 @@ mod tests { for board in all_boards.values() { assert!(board.name.is_some()); assert!(board.inherit.is_none()); - assert!(board.reset_message.is_some()); + assert!(board.reset.is_some()); assert!(board.avrdude.is_some()); let avrdude = board.avrdude.as_ref().unwrap(); assert!(avrdude.programmer.is_some()); diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index caba00be60..78da67c56d 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -6,12 +6,7 @@ use std::num::NonZeroU32; pub struct BoardConfig { pub name: Option, pub inherit: Option, - #[serde( - serialize_with = "serialize_reset_message", - deserialize_with = "deserialize_reset_message", - rename = "reset" - )] - pub reset_message: Option>, + pub reset: Option, pub avrdude: Option, pub usb_info: Option, @@ -25,10 +20,11 @@ impl BoardConfig { name: self.name.or(base.name), // inherit is used to decide what BoardConfig to inherit and isn't used anywhere else inherit: None, - reset_message: self.reset_message.or(base.reset_message), - avrdude: self - .avrdude - .and_then(|avrdude| base.avrdude.map(|base_avrdude| avrdude.merge(base_avrdude))), + reset: self.reset.or(base.reset), + avrdude: match self.avrdude { + Some(avrdude) => base.avrdude.map(|base_avrdude| avrdude.merge(base_avrdude)), + None => base.avrdude, + }, usb_info: self.usb_info.or(base.usb_info), // overrides aren't related to the board @@ -38,46 +34,9 @@ impl BoardConfig { } #[derive(serde::Serialize, serde::Deserialize, Debug)] -struct ResetOptions { - automatic: bool, - message: Option, -} - -fn serialize_reset_message( - val: &Option>, - serializer: S, -) -> Result -where - S: serde::Serializer, -{ - let reset_options = val.as_ref().map(|val| ResetOptions { - automatic: val.is_none(), - message: val.clone(), - }); - - reset_options.serialize(serializer) -} - -fn deserialize_reset_message<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: serde::Deserializer<'de>, -{ - let Some(reset_options) = Option::::deserialize(deserializer)? else { - return Ok(None); - }; - - if reset_options.automatic && reset_options.message.is_some() { - return Err(serde::de::Error::custom( - "cannot have automatic reset with a message for non-automatic reset", - )); - } - if !reset_options.automatic && reset_options.message.is_none() { - return Err(serde::de::Error::custom( - "non-automatic reset option must have a message with it", - )); - } - - Ok(Some(reset_options.message)) +pub struct ResetOptions { + pub automatic: bool, + pub message: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] @@ -115,17 +74,13 @@ fn deserialize_baudrate<'de, D>(deserializer: D) -> Result, { - let Some(baudrate) = Option::::deserialize(deserializer)? else { - return Ok(None); - }; - - match NonZeroU32::new(baudrate as _) { - None if baudrate == -1 => Ok(Some(None)), - Some(nonzero_baudrate) => Ok(Some(Some(nonzero_baudrate))), - _ => Err(serde::de::Error::custom(format!( - "invalid baudrate: {baudrate}" - ))), - } + Ok(match Option::::deserialize(deserializer)? { + None => None, + Some(-1) => Some(None), + Some(baudrate) => Some(Some(NonZeroU32::new(baudrate as _).ok_or_else(|| { + serde::de::Error::custom(format!("invalid baudrate: {baudrate}")) + })?)), + }) } #[derive(serde::Serialize, serde::Deserialize, Debug)] diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index afaaeb6a0b..0eb41c891d 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -114,14 +114,17 @@ fn ravedude() -> anyhow::Result<()> { let mut board = board::get_board(args.board.as_deref())?; + let board_avrdude_options = board + .avrdude + .take() + .ok_or_else(|| anyhow::anyhow!("board has no avrdude options"))?; + board.overrides.apply_overrides(&mut args); task_message!( "Board", "{}", - &board.name.as_deref().ok_or_else(|| anyhow::anyhow!( - "Base board doesn't have a name. This is a bug, please report it!" - ))? + &board.name.as_deref().unwrap_or("Unnamed Board") ); if let Some(wait_time) = args.reset_delay { @@ -133,15 +136,16 @@ fn ravedude() -> anyhow::Result<()> { println!("Assuming board has been reset"); } } else { - if let Some(ref msg) = board.reset_message { + if let Some(config::ResetOptions { + automatic: false, + message, + }) = board.reset.as_ref() + { warning!("this board cannot reset itself."); - eprintln!(""); - eprintln!( - " {}", - msg.as_ref().ok_or_else(|| anyhow::anyhow!( - "Base board doesn't have a reset message. This is a bug, please report it!" - ))? - ); + if let Some(msg) = message.as_deref() { + eprintln!(""); + eprintln!(" {msg}"); + } eprintln!(""); eprint!("Once reset, press ENTER here: "); std::io::stdin().read_line(&mut String::new())?; @@ -173,11 +177,7 @@ fn ravedude() -> anyhow::Result<()> { } let mut avrdude = avrdude::Avrdude::run( - &board.avrdude.ok_or_else(|| { - anyhow::anyhow!( - "Base board doesn't have avrdude options. This is a bug, please report it!" - ) - })?, + &board_avrdude_options, port.as_ref(), bin, args.debug_avrdude, From e8f3fd1ec0801f4577c1d30838e7ce96293c3646 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sun, 7 Apr 2024 12:55:49 -0400 Subject: [PATCH 09/20] ravedude: change config format, tweak error messages, add warning for passing board as argument --- ravedude/src/board.rs | 41 +++++++++------- ravedude/src/config.rs | 109 ++++++++++++++++++++++++++--------------- ravedude/src/main.rs | 54 +++++++++++--------- 3 files changed, 124 insertions(+), 80 deletions(-) diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index 8a671a5b4a..dcabf6c490 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::config; -fn get_all_boards() -> anyhow::Result> { +fn get_all_boards() -> anyhow::Result> { toml::from_str(include_str!("boards.toml")).map_err(|err| { if cfg!(test) { anyhow::anyhow!( @@ -18,35 +18,40 @@ fn get_all_boards() -> anyhow::Result> { }) } -pub fn get_board(board_name: Option<&str>) -> anyhow::Result { - match board_name { +pub fn get_board(board_name: Option<&str>) -> anyhow::Result { + Ok(match board_name { Some(board_name) => { let mut all_boards = get_all_boards()?; - all_boards.remove(board_name).ok_or_else(|| { - let mut msg = format!("invalid board: {board_name}\n"); + config::RavedudeConfig { + board_config: all_boards.remove(board_name).ok_or_else(|| { + let mut msg = format!("invalid board: {board_name}\n"); - msg.push_str("valid boards:"); + msg.push_str("valid boards:"); - for board in all_boards.keys() { - msg.push('\n'); - msg.push_str(&board); - } - anyhow::anyhow!(msg) - }) + for board in all_boards.keys() { + msg.push('\n'); + msg.push_str(&board); + } + anyhow::anyhow!(msg) + })?, + ..Default::default() + } } None => { let file_contents = std::fs::read_to_string("Ravedude.toml") .map_err(|_| anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))?; - let mut board: config::BoardConfig = toml::from_str(&file_contents)?; - if let Some(inherit) = board.inherit.as_deref() { - let base_board = get_board(Some(inherit))?; - board = board.merge(base_board); + let mut board: config::RavedudeConfig = toml::from_str(&file_contents) + .map_err(|err| anyhow::anyhow!("invalid Ravedude.toml:\n{}", err))?; + + if let Some(inherit) = board.board_config.inherit.as_deref() { + let base_board = get_board(Some(inherit))?.board_config; + board.board_config = board.board_config.merge(base_board); } - Ok(board) + board } - } + }) } #[cfg(test)] diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 78da67c56d..c0eabcf5d1 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -1,24 +1,85 @@ use serde::{Deserialize, Serialize}; use std::num::NonZeroU32; -#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[derive(serde::Serialize, serde::Deserialize, Debug, Default)] +#[serde(rename_all = "kebab-case")] +pub struct RavedudeConfig { + #[serde(rename = "general")] + pub general_options: RavedudeGeneralOptions, + + #[serde(rename = "board")] + pub board_config: BoardOptions, +} + +impl RavedudeConfig { + pub fn from_args(args: &crate::Args) -> anyhow::Result { + Ok(Self { + general_options: RavedudeGeneralOptions { + open_console: args.open_console.then_some(true), + serial_baudrate: match args.baudrate { + Some(serial_baudrate) => Some( + NonZeroU32::new(serial_baudrate) + .ok_or_else(|| anyhow::anyhow!("baudrate must not be 0"))?, + ), + None => None, + }, + port: args.port.clone(), + reset_delay: args.reset_delay, + }, + board_config: BoardOptions { + inherit: args.board.clone(), + ..Default::default() + }, + }) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Default)] #[serde(rename_all = "kebab-case")] -pub struct BoardConfig { +pub struct RavedudeGeneralOptions { + pub open_console: Option, + pub serial_baudrate: Option, + pub port: Option, + pub reset_delay: Option, +} + +impl RavedudeGeneralOptions { + pub fn apply_overrides(&mut self, args: &crate::Args) -> anyhow::Result<()> { + // command line args take priority over Ravedude.toml + if args.open_console { + self.open_console = Some(true); + } + if let Some(serial_baudrate) = args.baudrate { + self.serial_baudrate = Some( + NonZeroU32::new(serial_baudrate) + .ok_or_else(|| anyhow::anyhow!("baudrate must not be 0"))?, + ); + } + if let Some(port) = args.port.clone() { + self.port = Some(port); + } + if let Some(reset_delay) = args.reset_delay { + self.reset_delay = Some(reset_delay); + } + Ok(()) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Default)] +#[serde(rename_all = "kebab-case")] +pub struct BoardOptions { pub name: Option, pub inherit: Option, pub reset: Option, pub avrdude: Option, pub usb_info: Option, - - #[serde(flatten)] - pub overrides: BoardOverrides, } -impl BoardConfig { - pub fn merge(self, base: BoardConfig) -> Self { +impl BoardOptions { + pub fn merge(self, base: BoardOptions) -> Self { Self { name: self.name.or(base.name), - // inherit is used to decide what BoardConfig to inherit and isn't used anywhere else + // inherit is used to decide what BoardGeneralOptions to inherit and isn't used anywhere else inherit: None, reset: self.reset.or(base.reset), avrdude: match self.avrdude { @@ -26,9 +87,6 @@ impl BoardConfig { None => base.avrdude, }, usb_info: self.usb_info.or(base.usb_info), - - // overrides aren't related to the board - overrides: self.overrides, } } } @@ -97,7 +155,7 @@ pub struct BoardPortID { pub pid: u16, } -impl BoardConfig { +impl BoardOptions { pub fn guess_port(&self) -> Option> { match &self.usb_info { Some(BoardUSBInfo::Error(err)) => Some(Err(anyhow::anyhow!(err.clone()))), @@ -121,30 +179,3 @@ impl BoardConfig { } } } - -#[derive(serde::Serialize, serde::Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct BoardOverrides { - open_console: Option, - serial_baudrate: Option, - port: Option, - reset_delay: Option, -} - -impl BoardOverrides { - pub fn apply_overrides(&mut self, args: &mut crate::Args) { - // command line args take priority over Ravedude.toml - if let Some(open_console) = self.open_console { - args.open_console = open_console; - } - if let Some(serial_baudrate) = self.serial_baudrate { - args.baudrate = Some(serial_baudrate.get()); - } - if let Some(port) = self.port.take() { - args.port = Some(port); - } - if let Some(reset_delay) = self.reset_delay { - args.reset_delay = Some(reset_delay); - } - } -} diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 0eb41c891d..8bf653b901 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -99,28 +99,44 @@ fn main() { fn ravedude() -> anyhow::Result<()> { let mut args: Args = structopt::StructOpt::from_args(); if args.dump_config { - return dump_config(args.board.as_deref()); + println!( + "{}", + toml::to_string(&board::get_board(args.board.as_deref())?)? + ); + return Ok(()); } - // Due to the ordering of the arguments, board is prioritized before bin. - // There doesn't seem to be an way to change the argument priority like this. - // - // TODO: this is currently a breaking change (for users that use `board` but not `bin`), how do we handle this? - if args.board.is_some() && args.bin.is_none() { - args.bin = Some(std::path::PathBuf::from(args.board.take().unwrap())); + let ravedude_config_exists = std::path::Path::new("Ravedude.toml").try_exists()?; + + if ravedude_config_exists { + if let Some(board) = args.board.take() { + if args.bin.is_none() { + // The board arg is taken before the binary, so rearrange the args when Ravedude.toml exists + args.bin = Some(std::path::PathBuf::from(board)); + } else { + anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `inherit = \"{}\"` under [board] in Ravedude.toml", board) + } + } + } else { + warning!( + "Passing the board as command-line argument is deprecated, use Ravedude.toml instead:\n\n# Ravedude.toml\n{}", + toml::to_string(&config::RavedudeConfig::from_args(&args)?)? + ); } avrdude::Avrdude::require_min_ver(MIN_VERSION_AVRDUDE)?; - let mut board = board::get_board(args.board.as_deref())?; + let mut ravedude_config = board::get_board(args.board.as_deref())?; + + ravedude_config.general_options.apply_overrides(&mut args)?; + + let mut board = ravedude_config.board_config; let board_avrdude_options = board .avrdude .take() .ok_or_else(|| anyhow::anyhow!("board has no avrdude options"))?; - board.overrides.apply_overrides(&mut args); - task_message!( "Board", "{}", @@ -143,21 +159,21 @@ fn ravedude() -> anyhow::Result<()> { { warning!("this board cannot reset itself."); if let Some(msg) = message.as_deref() { - eprintln!(""); + eprintln!(); eprintln!(" {msg}"); } - eprintln!(""); + eprintln!(); eprint!("Once reset, press ENTER here: "); std::io::stdin().read_line(&mut String::new())?; } } - let port = match args.port { + let port = match ravedude_config.general_options.port { Some(port) => Ok(Some(port)), None => match board.guess_port() { Some(Ok(port)) => Ok(Some(port)), p @ Some(Err(_)) => p.transpose().context( - "no matching serial port found, use -P or set RAVEDUDE_PORT in your environment", + "no matching serial port found, use -P, add a serial-port entry under [general] in Ravedude.toml, or set RAVEDUDE_PORT in your environment", ), None => Ok(None), }, @@ -193,7 +209,7 @@ fn ravedude() -> anyhow::Result<()> { ); } - if args.open_console { + if ravedude_config.general_options.open_console == Some(true) { let baudrate = args .baudrate .context("-b/--baudrate is needed for the serial console")?; @@ -211,11 +227,3 @@ fn ravedude() -> anyhow::Result<()> { Ok(()) } - -fn dump_config(board_name: Option<&str>) -> anyhow::Result<()> { - let board = board::get_board(board_name)?; - - println!("{}", toml::to_string(&board)?); - - Ok(()) -} From 425eca48afbbaa046612ffe55281ad7c339f72ff Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sun, 7 Apr 2024 21:12:40 -0400 Subject: [PATCH 10/20] ravedude: add direct board name option in [general] for Ravedude.toml + associated error messages --- ravedude/src/board.rs | 22 ++++++++++---- ravedude/src/config.rs | 68 +++++++++++++++++++++--------------------- ravedude/src/main.rs | 6 ++-- 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index dcabf6c490..ab6779d1f7 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::config; -fn get_all_boards() -> anyhow::Result> { +fn get_all_boards() -> anyhow::Result> { toml::from_str(include_str!("boards.toml")).map_err(|err| { if cfg!(test) { anyhow::anyhow!( @@ -24,7 +24,7 @@ pub fn get_board(board_name: Option<&str>) -> anyhow::Result) -> anyhow::Result) -> anyhow::Result, +} + +fn serialize_baudrate(val: &Option>, serializer: S) -> Result +where + S: serde::Serializer, +{ + let baudrate = val.as_ref().map(|val| val.map_or(-1, |x| x.get() as i32)); + + baudrate.serialize(serializer) +} + +fn deserialize_baudrate<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + Ok(match Option::::deserialize(deserializer)? { + None => None, + Some(-1) => Some(None), + Some(baudrate) => Some(Some(NonZeroU32::new(baudrate as _).ok_or_else(|| { + serde::de::Error::custom(format!("invalid baudrate: {baudrate}")) + })?)), + }) } impl RavedudeConfig { pub fn from_args(args: &crate::Args) -> anyhow::Result { Ok(Self { - general_options: RavedudeGeneralOptions { + general_options: RavedudeGeneralConfig { open_console: args.open_console.then_some(true), serial_baudrate: match args.baudrate { Some(serial_baudrate) => Some( @@ -25,25 +47,24 @@ impl RavedudeConfig { }, port: args.port.clone(), reset_delay: args.reset_delay, + board: args.board.clone(), }, - board_config: BoardOptions { - inherit: args.board.clone(), - ..Default::default() - }, + board_config: Default::default(), }) } } #[derive(serde::Serialize, serde::Deserialize, Debug, Default)] #[serde(rename_all = "kebab-case")] -pub struct RavedudeGeneralOptions { +pub struct RavedudeGeneralConfig { pub open_console: Option, pub serial_baudrate: Option, pub port: Option, pub reset_delay: Option, + pub board: Option, } -impl RavedudeGeneralOptions { +impl RavedudeGeneralConfig { pub fn apply_overrides(&mut self, args: &crate::Args) -> anyhow::Result<()> { // command line args take priority over Ravedude.toml if args.open_console { @@ -67,7 +88,7 @@ impl RavedudeGeneralOptions { #[derive(serde::Serialize, serde::Deserialize, Debug, Default)] #[serde(rename_all = "kebab-case")] -pub struct BoardOptions { +pub struct BoardConfig { pub name: Option, pub inherit: Option, pub reset: Option, @@ -75,8 +96,8 @@ pub struct BoardOptions { pub usb_info: Option, } -impl BoardOptions { - pub fn merge(self, base: BoardOptions) -> Self { +impl BoardConfig { + pub fn merge(self, base: BoardConfig) -> Self { Self { name: self.name.or(base.name), // inherit is used to decide what BoardGeneralOptions to inherit and isn't used anywhere else @@ -119,27 +140,6 @@ impl BoardAvrdudeOptions { } } } -fn serialize_baudrate(val: &Option>, serializer: S) -> Result -where - S: serde::Serializer, -{ - let baudrate = val.as_ref().map(|val| val.map_or(-1, |x| x.get() as i32)); - - baudrate.serialize(serializer) -} - -fn deserialize_baudrate<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: serde::Deserializer<'de>, -{ - Ok(match Option::::deserialize(deserializer)? { - None => None, - Some(-1) => Some(None), - Some(baudrate) => Some(Some(NonZeroU32::new(baudrate as _).ok_or_else(|| { - serde::de::Error::custom(format!("invalid baudrate: {baudrate}")) - })?)), - }) -} #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename_all = "kebab-case")] @@ -155,7 +155,7 @@ pub struct BoardPortID { pub pid: u16, } -impl BoardOptions { +impl BoardConfig { pub fn guess_port(&self) -> Option> { match &self.usb_info { Some(BoardUSBInfo::Error(err)) => Some(Err(anyhow::anyhow!(err.clone()))), diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 8bf653b901..89193f5e19 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -114,7 +114,7 @@ fn ravedude() -> anyhow::Result<()> { // The board arg is taken before the binary, so rearrange the args when Ravedude.toml exists args.bin = Some(std::path::PathBuf::from(board)); } else { - anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `inherit = \"{}\"` under [board] in Ravedude.toml", board) + anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `board = \"{}\"` under [general] in Ravedude.toml", board) } } } else { @@ -130,7 +130,9 @@ fn ravedude() -> anyhow::Result<()> { ravedude_config.general_options.apply_overrides(&mut args)?; - let mut board = ravedude_config.board_config; + let Some(mut board) = ravedude_config.board_config else { + anyhow::bail!("no named board given and no board options provided"); + }; let board_avrdude_options = board .avrdude From da0e8904229969266995953ddccfaeac04ecb56b Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sun, 7 Apr 2024 22:20:22 -0400 Subject: [PATCH 11/20] ravedude: fix chip erase error message --- ravedude/src/avrdude/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ravedude/src/avrdude/mod.rs b/ravedude/src/avrdude/mod.rs index c71c3902d9..65224e4ba8 100644 --- a/ravedude/src/avrdude/mod.rs +++ b/ravedude/src/avrdude/mod.rs @@ -100,11 +100,10 @@ impl Avrdude { flash_instruction.push(bin); flash_instruction.push(":e"); - if options.do_chip_erase.ok_or_else(|| { - anyhow::anyhow!( - "Base board doesn't specify whether to erase the chip. This is a bug, please report it!" - ) - })? { + if options + .do_chip_erase + .ok_or_else(|| anyhow::anyhow!("board doesn't specify whether to erase the chip"))? + { command = command.arg("-e"); } From 2defa5e0b0c21598e4f76f6928f545d9c94d306f Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Wed, 17 Apr 2024 17:28:54 -0400 Subject: [PATCH 12/20] ravedude: fix serial-baudrate not working in Ravedude.toml --- ravedude/src/main.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 89193f5e19..b31bec56c0 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -212,9 +212,14 @@ fn ravedude() -> anyhow::Result<()> { } if ravedude_config.general_options.open_console == Some(true) { - let baudrate = args - .baudrate - .context("-b/--baudrate is needed for the serial console")?; + let baudrate = ravedude_config + .general_options + .serial_baudrate + .context(if ravedude_config_exists { + "`serial-baudrate` under [general] in Ravedude.toml is needed for the serial console" + }else{ + "-b/--baudrate is needed for the serial console" + })?; let port = port.context("console can only be opened for devices with USB-to-Serial")?; @@ -222,7 +227,7 @@ fn ravedude() -> anyhow::Result<()> { task_message!("", "{}", "CTRL+C to exit.".dimmed()); // Empty line for visual consistency eprintln!(); - console::open(&port, baudrate)?; + console::open(&port, baudrate.get())?; } else if args.bin.is_none() && port.is_some() { warning!("you probably meant to add -c/--open-console?"); } From f958dd23a63ac3f888fbfc98ea8641aebc43513b Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Fri, 10 May 2024 11:28:06 -0400 Subject: [PATCH 13/20] ravedude: make ravedude search in parent dirs for manifest, also fix deprecation warning showing up when it's not supposed to --- ravedude/src/board.rs | 67 +++++++++++++++++++++---------------------- ravedude/src/main.rs | 43 ++++++++++++++++++--------- 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index ab6779d1f7..e50366c123 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, path::Path}; use crate::config; @@ -18,49 +18,48 @@ fn get_all_boards() -> anyhow::Result> { }) } -pub fn get_board(board_name: Option<&str>) -> anyhow::Result { - Ok(match board_name { - Some(board_name) => { - let mut all_boards = get_all_boards()?; +pub fn get_board_from_name(board_name: &str) -> anyhow::Result { + let mut all_boards = get_all_boards()?; - config::RavedudeConfig { - board_config: Some(all_boards.remove(board_name).ok_or_else(|| { - let mut msg = format!("invalid board: {board_name}\n"); + Ok(config::RavedudeConfig { + board_config: Some(all_boards.remove(board_name).ok_or_else(|| { + let mut msg = format!("invalid board: {board_name}\n"); - msg.push_str("valid boards:"); + msg.push_str("valid boards:"); - for board in all_boards.keys() { - msg.push('\n'); - msg.push_str(&board); - } - anyhow::anyhow!(msg) - })?), - ..Default::default() + for board in all_boards.keys() { + msg.push('\n'); + msg.push_str(&board); } - } - None => { - let file_contents = std::fs::read_to_string("Ravedude.toml") - .map_err(|_| anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))?; + anyhow::anyhow!(msg) + })?), + ..Default::default() + }) +} - let mut board: config::RavedudeConfig = toml::from_str(&file_contents) - .map_err(|err| anyhow::anyhow!("invalid Ravedude.toml:\n{}", err))?; +pub fn get_board_from_manifest(manifest_path: &Path) -> anyhow::Result { + Ok({ + let file_contents = std::fs::read_to_string(manifest_path) + .map_err(|err| anyhow::anyhow!("Ravedude.toml read error:\n{}", err))?; - if let Some(board_config) = board.board_config.as_ref() { - if let Some(board_name) = board.general_options.board.as_deref() { - anyhow::bail!( + let mut board: config::RavedudeConfig = toml::from_str(&file_contents) + .map_err(|err| anyhow::anyhow!("invalid Ravedude.toml:\n{}", err))?; + + if let Some(board_config) = board.board_config.as_ref() { + if let Some(board_name) = board.general_options.board.as_deref() { + anyhow::bail!( "can't both have board in [general] and [board] section; set inherit = \"{}\" under [board] to inherit its options", board_name ) - } - if let Some(inherit) = board_config.inherit.as_deref() { - let base_board = get_board(Some(inherit))?.board_config.unwrap(); - board.board_config = Some(board.board_config.take().unwrap().merge(base_board)); - } - } else if let Some(board_name) = board.general_options.board.as_deref() { - let base_board = get_board(Some(board_name))?.board_config.unwrap(); - board.board_config = Some(base_board); } - board + if let Some(inherit) = board_config.inherit.as_deref() { + let base_board = get_board_from_name(inherit)?.board_config.unwrap(); + board.board_config = Some(board.board_config.take().unwrap().merge(base_board)); + } + } else if let Some(board_name) = board.general_options.board.as_deref() { + let base_board = get_board_from_name(board_name)?.board_config.unwrap(); + board.board_config = Some(base_board); } + board }) } diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index b31bec56c0..bce4db679f 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -2,6 +2,7 @@ use anyhow::Context as _; use colored::Colorize as _; use structopt::clap::AppSettings; +use std::path::Path; use std::thread; use std::time::Duration; @@ -75,7 +76,7 @@ struct Args { /// * trinket-pro /// * trinket /// * nano168 - /// * duemilanovepriori + /// * duemilanove #[structopt(name = "BOARD", verbatim_doc_comment)] board: Option, @@ -98,17 +99,23 @@ fn main() { fn ravedude() -> anyhow::Result<()> { let mut args: Args = structopt::StructOpt::from_args(); - if args.dump_config { - println!( - "{}", - toml::to_string(&board::get_board(args.board.as_deref())?)? - ); - return Ok(()); - } - let ravedude_config_exists = std::path::Path::new("Ravedude.toml").try_exists()?; + let manifest_path = 'manifest_path: { + // By default Cargo scans the current dir and all of its parents for Cargo.toml, + // so we mirror its behavior here. + let current_dir = std::env::current_dir()?; + + for dir_to_test in current_dir.ancestors() { + let path_to_test = dir_to_test.join(Path::new("Ravedude.toml")); + if path_to_test.exists() { + break 'manifest_path Some(path_to_test); + } + } + + None + }; - if ravedude_config_exists { + if manifest_path.is_some() { if let Some(board) = args.board.take() { if args.bin.is_none() { // The board arg is taken before the binary, so rearrange the args when Ravedude.toml exists @@ -117,16 +124,24 @@ fn ravedude() -> anyhow::Result<()> { anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `board = \"{}\"` under [general] in Ravedude.toml", board) } } - } else { + } else if args.board.is_some() { warning!( "Passing the board as command-line argument is deprecated, use Ravedude.toml instead:\n\n# Ravedude.toml\n{}", toml::to_string(&config::RavedudeConfig::from_args(&args)?)? ); } - avrdude::Avrdude::require_min_ver(MIN_VERSION_AVRDUDE)?; + let mut ravedude_config = match manifest_path.as_deref() { + Some(path) => board::get_board_from_manifest(path)?, + None => board::get_board_from_name(args.board.as_deref().ok_or_else(||anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))?)? + }; - let mut ravedude_config = board::get_board(args.board.as_deref())?; + if args.dump_config { + println!("{}", toml::to_string(&ravedude_config)?); + return Ok(()); + } + + avrdude::Avrdude::require_min_ver(MIN_VERSION_AVRDUDE)?; ravedude_config.general_options.apply_overrides(&mut args)?; @@ -215,7 +230,7 @@ fn ravedude() -> anyhow::Result<()> { let baudrate = ravedude_config .general_options .serial_baudrate - .context(if ravedude_config_exists { + .context(if manifest_path.is_some() { "`serial-baudrate` under [general] in Ravedude.toml is needed for the serial console" }else{ "-b/--baudrate is needed for the serial console" From a150c82d28c59971a25f869ba5c4355cc6ecb90e Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Fri, 10 May 2024 13:55:46 -0400 Subject: [PATCH 14/20] ravedude: change board arg to OsString, add error messages to asserts in board.rs, add test to ci --- .github/workflows/ci.yml | 3 +++ ravedude/src/board.rs | 47 ++++++++++++++++++++++++++++++---------- ravedude/src/config.rs | 9 +++++++- ravedude/src/main.rs | 14 +++++++++--- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4596a452e3..507335a20c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,3 +133,6 @@ jobs: - name: Check ravedude run: | cargo check --manifest-path ravedude/Cargo.toml + - name: Test ravedude + run: | + cargo test --manifest-path ravedude/Cargo.toml diff --git a/ravedude/src/board.rs b/ravedude/src/board.rs index e50366c123..7b7ab55e40 100644 --- a/ravedude/src/board.rs +++ b/ravedude/src/board.rs @@ -48,8 +48,9 @@ pub fn get_board_from_manifest(manifest_path: &Path) -> anyhow::Result anyhow::Result<()> { let all_boards = get_all_boards()?; - for board in all_boards.values() { - assert!(board.name.is_some()); - assert!(board.inherit.is_none()); - assert!(board.reset.is_some()); - assert!(board.avrdude.is_some()); + for (name, board) in all_boards.iter() { + assert!( + board.name.is_some(), + "Board {name:?} doesn't have a `name` key" + ); + assert!( + board.inherit.is_none(), + "Board {name:?} has illegal `inherit` key" + ); + assert!( + board.reset.is_some(), + "Board {name:?} doesn't have a `reset` key" + ); + assert!( + board.avrdude.is_some(), + "Board {name:?} doesn't have an `avrdude` key" + ); let avrdude = board.avrdude.as_ref().unwrap(); - assert!(avrdude.programmer.is_some()); - assert!(avrdude.partno.is_some()); - assert!(avrdude.baudrate.is_some()); - assert!(avrdude.do_chip_erase.is_some()); + assert!( + avrdude.programmer.is_some(), + "Board {name:?}'s avrdude options doesn't have a `programmer` key" + ); + assert!( + avrdude.partno.is_some(), + "Board {name:?}'s avrdude options doesn't have a `partno` key" + ); + assert!( + avrdude.baudrate.is_some(), + "Board {name:?}'s avrdude options doesn't have a `baudrate` key" + ); + assert!( + avrdude.do_chip_erase.is_some(), + "Board {name:?}'s avrdude options doesn't have a `do_chip_erase` key" + ); } Ok(()) diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 1af6765002..b5ffdb45b7 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -47,7 +47,14 @@ impl RavedudeConfig { }, port: args.port.clone(), reset_delay: args.reset_delay, - board: args.board.clone(), + board: match args.board.clone() { + Some(board_osstring) => Some( + board_osstring + .into_string() + .map_err(|_| anyhow::anyhow!("board is not valid utf-8"))?, + ), + None => None, + }, }, board_config: Default::default(), }) diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index bce4db679f..fee52975cf 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -2,6 +2,7 @@ use anyhow::Context as _; use colored::Colorize as _; use structopt::clap::AppSettings; +use std::ffi::OsString; use std::path::Path; use std::thread; use std::time::Duration; @@ -78,7 +79,7 @@ struct Args { /// * nano168 /// * duemilanove #[structopt(name = "BOARD", verbatim_doc_comment)] - board: Option, + board: Option, /// The binary to be flashed. /// @@ -121,7 +122,7 @@ fn ravedude() -> anyhow::Result<()> { // The board arg is taken before the binary, so rearrange the args when Ravedude.toml exists args.bin = Some(std::path::PathBuf::from(board)); } else { - anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `board = \"{}\"` under [general] in Ravedude.toml", board) + anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `board = {:?}` under [general] in Ravedude.toml", board) } } } else if args.board.is_some() { @@ -133,7 +134,14 @@ fn ravedude() -> anyhow::Result<()> { let mut ravedude_config = match manifest_path.as_deref() { Some(path) => board::get_board_from_manifest(path)?, - None => board::get_board_from_name(args.board.as_deref().ok_or_else(||anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))?)? + None => board::get_board_from_name( + args + .board + .as_deref() + .ok_or_else(|| anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))? + .to_str() + .ok_or_else(|| anyhow::anyhow!("board name isn't valid UTF-8"))? + )? }; if args.dump_config { From a4e22130ae3cadf55e434628ed2d2bbfd1f14121 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sat, 18 May 2024 10:59:49 -0400 Subject: [PATCH 15/20] ravedude: remove error message field from ResetOptions & change open_console to not an Option --- ravedude/src/boards.toml | 15 +++++---------- ravedude/src/config.rs | 9 ++++----- ravedude/src/main.rs | 12 ++---------- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/ravedude/src/boards.toml b/ravedude/src/boards.toml index 6e6c13f43a..7609a18683 100644 --- a/ravedude/src/boards.toml +++ b/ravedude/src/boards.toml @@ -53,8 +53,7 @@ [leonardo.reset] automatic = false - message = "Reset the board by pressing the reset button once." - + [leonardo.avrdude] programmer = "avr109" partno = "atmega32u4" @@ -74,8 +73,7 @@ [micro.reset] automatic = false - message = "Reset the board by pressing the reset button once." - + [micro.avrdude] programmer = "avr109" partno = "atmega32u4" @@ -151,8 +149,7 @@ [promicro.reset] automatic = false - message = "Reset the board by quickly pressing the reset button **twice**." - + [promicro.avrdude] programmer = "avr109" partno = "atmega32u4" @@ -187,8 +184,7 @@ [trinket-pro.reset] automatic = false - message = "Reset the board by pressing the reset button once." - + [trinket-pro.avrdude] programmer = "usbtiny" partno = "atmega328p" @@ -202,8 +198,7 @@ [trinket.reset] automatic = false - message = "Reset the board by pressing the reset button once." - + [trinket.avrdude] programmer = "usbtiny" partno = "atmega328p" diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index b5ffdb45b7..3d7a809f73 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -37,7 +37,7 @@ impl RavedudeConfig { pub fn from_args(args: &crate::Args) -> anyhow::Result { Ok(Self { general_options: RavedudeGeneralConfig { - open_console: args.open_console.then_some(true), + open_console: args.open_console, serial_baudrate: match args.baudrate { Some(serial_baudrate) => Some( NonZeroU32::new(serial_baudrate) @@ -64,7 +64,7 @@ impl RavedudeConfig { #[derive(serde::Serialize, serde::Deserialize, Debug, Default)] #[serde(rename_all = "kebab-case")] pub struct RavedudeGeneralConfig { - pub open_console: Option, + pub open_console: bool, pub serial_baudrate: Option, pub port: Option, pub reset_delay: Option, @@ -75,7 +75,7 @@ impl RavedudeGeneralConfig { pub fn apply_overrides(&mut self, args: &crate::Args) -> anyhow::Result<()> { // command line args take priority over Ravedude.toml if args.open_console { - self.open_console = Some(true); + self.open_console = true; } if let Some(serial_baudrate) = args.baudrate { self.serial_baudrate = Some( @@ -107,7 +107,7 @@ impl BoardConfig { pub fn merge(self, base: BoardConfig) -> Self { Self { name: self.name.or(base.name), - // inherit is used to decide what BoardGeneralOptions to inherit and isn't used anywhere else + // inherit is used to decide what BoardConfig to inherit and isn't used anywhere else inherit: None, reset: self.reset.or(base.reset), avrdude: match self.avrdude { @@ -122,7 +122,6 @@ impl BoardConfig { #[derive(serde::Serialize, serde::Deserialize, Debug)] pub struct ResetOptions { pub automatic: bool, - pub message: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index fee52975cf..f461a8aa30 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -177,16 +177,8 @@ fn ravedude() -> anyhow::Result<()> { println!("Assuming board has been reset"); } } else { - if let Some(config::ResetOptions { - automatic: false, - message, - }) = board.reset.as_ref() - { + if matches!(board.reset, Some(config::ResetOptions { automatic: false })) { warning!("this board cannot reset itself."); - if let Some(msg) = message.as_deref() { - eprintln!(); - eprintln!(" {msg}"); - } eprintln!(); eprint!("Once reset, press ENTER here: "); std::io::stdin().read_line(&mut String::new())?; @@ -234,7 +226,7 @@ fn ravedude() -> anyhow::Result<()> { ); } - if ravedude_config.general_options.open_console == Some(true) { + if ravedude_config.general_options.open_console { let baudrate = ravedude_config .general_options .serial_baudrate From 34d2a9d58c720ef3ec9c9af9ca6e6493068164bc Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sat, 18 May 2024 11:13:03 -0400 Subject: [PATCH 16/20] ravedude: fix open_console serialization & tweak error messages --- ravedude/src/config.rs | 1 + ravedude/src/main.rs | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 3d7a809f73..94a795ba74 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -64,6 +64,7 @@ impl RavedudeConfig { #[derive(serde::Serialize, serde::Deserialize, Debug, Default)] #[serde(rename_all = "kebab-case")] pub struct RavedudeGeneralConfig { + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub open_console: bool, pub serial_baudrate: Option, pub port: Option, diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index f461a8aa30..8111915029 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -125,9 +125,12 @@ fn ravedude() -> anyhow::Result<()> { anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `board = {:?}` under [general] in Ravedude.toml", board) } } - } else if args.board.is_some() { + } else if args.board.is_some() && !args.dump_config { warning!( - "Passing the board as command-line argument is deprecated, use Ravedude.toml instead:\n\n# Ravedude.toml\n{}", + "Passing the board as command-line argument is deprecated, use Ravedude.toml instead:" + ); + eprintln!( + "\n# Ravedude.toml\n{}", toml::to_string(&config::RavedudeConfig::from_args(&args)?)? ); } @@ -144,6 +147,8 @@ fn ravedude() -> anyhow::Result<()> { )? }; + ravedude_config.general_options.apply_overrides(&mut args)?; + if args.dump_config { println!("{}", toml::to_string(&ravedude_config)?); return Ok(()); @@ -151,8 +156,6 @@ fn ravedude() -> anyhow::Result<()> { avrdude::Avrdude::require_min_ver(MIN_VERSION_AVRDUDE)?; - ravedude_config.general_options.apply_overrides(&mut args)?; - let Some(mut board) = ravedude_config.board_config else { anyhow::bail!("no named board given and no board options provided"); }; From 53746932766bfa80953c775be5767b2de6710dd7 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sat, 18 May 2024 11:39:29 -0400 Subject: [PATCH 17/20] ravedude: make board reset prompt only display when a binary is given --- ravedude/src/main.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 8111915029..1f082b36a5 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -171,23 +171,6 @@ fn ravedude() -> anyhow::Result<()> { &board.name.as_deref().unwrap_or("Unnamed Board") ); - if let Some(wait_time) = args.reset_delay { - if wait_time > 0 { - println!("Waiting {} ms before proceeding", wait_time); - let wait_time = Duration::from_millis(wait_time); - thread::sleep(wait_time); - } else { - println!("Assuming board has been reset"); - } - } else { - if matches!(board.reset, Some(config::ResetOptions { automatic: false })) { - warning!("this board cannot reset itself."); - eprintln!(); - eprint!("Once reset, press ENTER here: "); - std::io::stdin().read_line(&mut String::new())?; - } - } - let port = match ravedude_config.general_options.port { Some(port) => Ok(Some(port)), None => match board.guess_port() { @@ -200,6 +183,21 @@ fn ravedude() -> anyhow::Result<()> { }?; if let Some(bin) = args.bin.as_ref() { + if let Some(wait_time) = args.reset_delay { + if wait_time > 0 { + println!("Waiting {} ms before proceeding", wait_time); + let wait_time = Duration::from_millis(wait_time); + thread::sleep(wait_time); + } else { + println!("Assuming board has been reset"); + } + } else if matches!(board.reset, Some(config::ResetOptions { automatic: false })) { + warning!("this board cannot reset itself."); + eprintln!(); + eprint!("Once reset, press ENTER here: "); + std::io::stdin().read_line(&mut String::new())?; + } + if let Some(port) = port.as_ref() { task_message!( "Programming", From a680ede00b66dc7ffad9a01e03b56206bce5761e Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Sat, 18 May 2024 12:07:19 -0400 Subject: [PATCH 18/20] ravedude: add comments for possibly confusing code --- ravedude/src/config.rs | 2 ++ ravedude/src/main.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index 94a795ba74..d83403ac50 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -134,6 +134,8 @@ pub struct BoardAvrdudeOptions { serialize_with = "serialize_baudrate", deserialize_with = "deserialize_baudrate" )] + // Inner option to represent whether the baudrate exists, outer option to allow for overriding. + // Option pub baudrate: Option>, pub do_chip_erase: Option, } diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 1f082b36a5..e6f7d3de35 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -79,6 +79,8 @@ struct Args { /// * nano168 /// * duemilanove #[structopt(name = "BOARD", verbatim_doc_comment)] + // When Ravedude.toml exists, the binary is placed where the board should be. This is an OsString to not lose + // informaton when we have to take the board as the binary. board: Option, /// The binary to be flashed. From 410a676efed34f7a7afaa0ec7642247e7ea5ac50 Mon Sep 17 00:00:00 2001 From: Colin Cai Date: Wed, 10 Jul 2024 14:49:05 -0400 Subject: [PATCH 19/20] ravedude: make legacy configuration deprecated & make ravedude respect CARGO_MANIFEST_DIR --- ravedude/src/config.rs | 13 +---- ravedude/src/main.rs | 128 ++++++++++++++++++++--------------------- 2 files changed, 64 insertions(+), 77 deletions(-) diff --git a/ravedude/src/config.rs b/ravedude/src/config.rs index d83403ac50..8e688f22ff 100644 --- a/ravedude/src/config.rs +++ b/ravedude/src/config.rs @@ -47,14 +47,7 @@ impl RavedudeConfig { }, port: args.port.clone(), reset_delay: args.reset_delay, - board: match args.board.clone() { - Some(board_osstring) => Some( - board_osstring - .into_string() - .map_err(|_| anyhow::anyhow!("board is not valid utf-8"))?, - ), - None => None, - }, + board: args.legacy_board_name().clone(), }, board_config: Default::default(), }) @@ -73,8 +66,8 @@ pub struct RavedudeGeneralConfig { } impl RavedudeGeneralConfig { - pub fn apply_overrides(&mut self, args: &crate::Args) -> anyhow::Result<()> { - // command line args take priority over Ravedude.toml + /// Apply command-line overrides to this configuration. Command-line arguments take priority over Ravedude.toml + pub fn apply_overrides_from(&mut self, args: &crate::Args) -> anyhow::Result<()> { if args.open_console { self.open_console = true; } diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index e6f7d3de35..781d475f64 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -2,7 +2,6 @@ use anyhow::Context as _; use colored::Colorize as _; use structopt::clap::AppSettings; -use std::ffi::OsString; use std::path::Path; use std::thread; use std::time::Duration; @@ -59,35 +58,30 @@ struct Args { #[structopt(long = "debug-avrdude")] debug_avrdude: bool, - /// Which board to interact with. - /// - /// Must be one of the known board identifiers: - /// - /// * uno - /// * nano - /// * nano-new - /// * leonardo - /// * micro - /// * mega2560 - /// * mega1280 - /// * diecimila - /// * promicro - /// * promini-3v3 - /// * promini-5v - /// * trinket-pro - /// * trinket - /// * nano168 - /// * duemilanove - #[structopt(name = "BOARD", verbatim_doc_comment)] - // When Ravedude.toml exists, the binary is placed where the board should be. This is an OsString to not lose - // informaton when we have to take the board as the binary. - board: Option, - + #[structopt(name = "BINARY", parse(from_os_str))] /// The binary to be flashed. /// /// If no binary is given, flashing will be skipped. - #[structopt(name = "BINARY", parse(from_os_str))] + // (Note: this is where the board is stored in legacy configurations.) bin: Option, + + /// Deprecated binary for old configurations of ravedude without `Ravedude.toml`. + /// Should not be used in newer configurations. + #[structopt(name = "LEGACY BINARY", parse(from_os_str))] + bin_legacy: Option, +} +impl Args { + /// Get the board name for legacy configurations. + /// `None` if the configuration isn't a legacy configuration or the board name doesn't exist. + fn legacy_board_name(&self) -> Option { + if self.bin_legacy.is_none() { + None + } else { + self.bin + .as_deref() + .and_then(|board| board.to_str().map(String::from)) + } + } } fn main() { @@ -100,56 +94,56 @@ fn main() { } } -fn ravedude() -> anyhow::Result<()> { - let mut args: Args = structopt::StructOpt::from_args(); +/// Finds the location of a `Ravedude.toml` or `None` if not found. +fn find_manifest() -> anyhow::Result> { + if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { + let path = Path::new(&manifest_dir).join("Ravedude.toml"); + return Ok(path.exists().then_some(path)); + } - let manifest_path = 'manifest_path: { - // By default Cargo scans the current dir and all of its parents for Cargo.toml, - // so we mirror its behavior here. - let current_dir = std::env::current_dir()?; + // If `CARGO_MANIFEST_DIR` isn't set, Cargo scans the current dir and all of its parents for Cargo.toml + // so we mirror its behavior here. + let current_dir = std::env::current_dir()?; - for dir_to_test in current_dir.ancestors() { - let path_to_test = dir_to_test.join(Path::new("Ravedude.toml")); - if path_to_test.exists() { - break 'manifest_path Some(path_to_test); - } + for dir_to_test in current_dir.ancestors() { + let path_to_test = dir_to_test.join("Ravedude.toml"); + if path_to_test.exists() { + return Ok(Some(path_to_test)); } + } - None - }; + Ok(None) +} - if manifest_path.is_some() { - if let Some(board) = args.board.take() { - if args.bin.is_none() { - // The board arg is taken before the binary, so rearrange the args when Ravedude.toml exists - args.bin = Some(std::path::PathBuf::from(board)); - } else { - anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `board = {:?}` under [general] in Ravedude.toml", board) - } +fn ravedude() -> anyhow::Result<()> { + let args: Args = structopt::StructOpt::from_args(); + + let manifest_path = find_manifest()?; + + let mut ravedude_config = match (manifest_path.as_deref(), args.legacy_board_name()) { + (Some(_), Some(board_name)) => { + anyhow::bail!("can't pass board as command-line argument when Ravedude.toml is present; set `board = {:?}` under [general] in Ravedude.toml", board_name); } - } else if args.board.is_some() && !args.dump_config { - warning!( - "Passing the board as command-line argument is deprecated, use Ravedude.toml instead:" - ); - eprintln!( - "\n# Ravedude.toml\n{}", - toml::to_string(&config::RavedudeConfig::from_args(&args)?)? - ); - } + (Some(path), None) => board::get_board_from_manifest(path)?, + (None, Some(board_name)) => { + warning!( + "Passing the board as command-line argument is deprecated; create a Ravedude.toml in the project root instead:" + ); + eprintln!( + "\n# Ravedude.toml\n{}", + toml::to_string(&config::RavedudeConfig::from_args(&args)?)? + ); - let mut ravedude_config = match manifest_path.as_deref() { - Some(path) => board::get_board_from_manifest(path)?, - None => board::get_board_from_name( - args - .board - .as_deref() - .ok_or_else(|| anyhow::anyhow!("no board given and couldn't find Ravedude.toml in project, either pass a board as an argument or make a Ravedude.toml."))? - .to_str() - .ok_or_else(|| anyhow::anyhow!("board name isn't valid UTF-8"))? - )? + board::get_board_from_name(&board_name)? + } + (None, None) => { + anyhow::bail!("couldn't find Ravedude.toml in project"); + } }; - ravedude_config.general_options.apply_overrides(&mut args)?; + ravedude_config + .general_options + .apply_overrides_from(&args)?; if args.dump_config { println!("{}", toml::to_string(&ravedude_config)?); From e775420d4c8b0355243de8e86b974082b3cd0ccd Mon Sep 17 00:00:00 2001 From: Rahix Date: Sat, 14 Sep 2024 11:15:30 +0200 Subject: [PATCH 20/20] ravedude: Fix backwards compatibility with legacy configuration Need to use `legacy_bin` as the binary in the legacy configuration mode. --- ravedude/src/main.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 781d475f64..d34b77e44c 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -82,6 +82,16 @@ impl Args { .and_then(|board| board.to_str().map(String::from)) } } + + /// Get the binary argument with fallback for the legacy menchanism. + /// + /// Returns `None` if no binary argument was passed. + fn bin_or_legacy_bin(&self) -> Option<&std::path::Path> { + self.bin_legacy + .as_ref() + .map(|p| p.as_path()) + .or(self.bin.as_ref().map(|p| p.as_path())) + } } fn main() { @@ -178,7 +188,7 @@ fn ravedude() -> anyhow::Result<()> { }, }?; - if let Some(bin) = args.bin.as_ref() { + if let Some(bin) = args.bin_or_legacy_bin() { if let Some(wait_time) = args.reset_delay { if wait_time > 0 { println!("Waiting {} ms before proceeding", wait_time);