From cbc266259f9e54967663a534bc79c38101f5be33 Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Mon, 22 Jun 2026 14:58:49 +0200 Subject: [PATCH 1/3] feat: infer single bin flash target --- docs/bootload.md | 11 ++++-- src/main.rs | 87 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/docs/bootload.md b/docs/bootload.md index 692f78e..94d67b7 100644 --- a/docs/bootload.md +++ b/docs/bootload.md @@ -111,15 +111,22 @@ in the release or ZIP file. Since the bin files do not contain where they should be flashed you can either set the target on the commandline or leave it blank to select. -The following command will promt you to select what target to flash the firmware +The following command will prompt you to select what target to flash the firmware bin to: ```text cfcli bootload flash --bin firmware.bin ``` +If you want to flash one binary to one target without an interactive prompt, +select that target with `--targets`: + +```text +cfcli bootload flash --bin lighthouse.bin --targets bcLighthouse4-fw +``` + It's also possible to set the target directly and also to flash multiple bins. The command -below will promt you for where you want to flash ```color-led-firmware.bin``` and will +below will prompt you for where you want to flash ```color-led-firmware.bin``` and will then flash it to the selected target as well as flashing the ```nrf51-firmware.bin``` to the nRF51. diff --git a/src/main.rs b/src/main.rs index 15f2f27..7a32f9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,71 @@ use modules::settings; include!("cli.rs"); +fn single_explicit_target_for_bare_bin( + bin: &Option>>, + targets: &Option>, +) -> Option { + let bin_map = bin.as_ref()?; + if bin_map.len() != 1 { + return None; + } + + let (_bin_path, selected_target) = bin_map.iter().next()?; + if selected_target.is_some() { + return None; + } + + let target_arg = match targets { + Some(Some(target_arg)) => target_arg, + _ => return None, + }; + + let mut target_names = target_arg + .split(',') + .map(str::trim) + .filter(|target| !target.is_empty()); + let target = target_names.next()?; + if target_names.next().is_some() { + return None; + } + + Some(target.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn single_bare_bin_uses_single_explicit_target() { + let mut bin = HashMap::new(); + bin.insert("lighthouse.bin".to_string(), None); + let targets = Some(Some("bcLighthouse4-fw".to_string())); + + assert_eq!( + single_explicit_target_for_bare_bin(&Some(bin), &targets), + Some("bcLighthouse4-fw".to_string()) + ); + } + + #[test] + fn single_bare_bin_does_not_use_multiple_explicit_targets() { + let mut bin = HashMap::new(); + bin.insert("lighthouse.bin".to_string(), None); + let targets = Some(Some("bcLighthouse4-fw,stm32-fw".to_string())); + + assert_eq!(single_explicit_target_for_bare_bin(&Some(bin), &targets), None); + } + + #[test] + fn keyed_bin_does_not_get_rewritten_from_targets() { + let mut bin = HashMap::new(); + bin.insert("bcLighthouse4-fw".to_string(), Some("lighthouse.bin".to_string())); + let targets = Some(Some("bcLighthouse4-fw".to_string())); + + assert_eq!(single_explicit_target_for_bare_bin(&Some(bin), &targets), None); + } +} impl MemoryTypeArg { /// Convert to the `crazyflie_lib` memory type. Defined here (not on the @@ -1367,19 +1432,25 @@ async fn run() -> Result<()> { // until we reach the deck flashing stage. let bin_with_selections = { let mut result = HashMap::new(); + let target_from_single_bare_bin = + single_explicit_target_for_bare_bin(¶ms.bin, ¶ms.targets); if let Some(bin_map) = ¶ms.bin { for (key, value_opt) in bin_map.iter() { let (k,v) = match (key, value_opt) { (k, Some(v)) => (k.clone(), v.clone()), (k, None) => { - require_arg(non_interactive, "--bin target=file")?; - let selected_target = Select::new( - &format!("Select target for [{}]:", k), - bootloader::get_hardcoded_list_of_targets() - ) - .prompt() - .map_err(|_| anyhow::anyhow!("No binary selected"))?; - (selected_target.to_string(), k.to_string()) + if let Some(selected_target) = &target_from_single_bare_bin { + (selected_target.clone(), k.to_string()) + } else { + require_arg(non_interactive, "--bin target=file")?; + let selected_target = Select::new( + &format!("Select target for [{}]:", k), + bootloader::get_hardcoded_list_of_targets() + ) + .prompt() + .map_err(|_| anyhow::anyhow!("No binary selected"))?; + (selected_target.to_string(), k.to_string()) + } } }; result.insert(k, v); From 25a5ab4e478bebd60961d31b1f14952985932506 Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Tue, 23 Jun 2026 11:20:24 +0200 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 7a32f9f..23f94ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1442,7 +1442,7 @@ async fn run() -> Result<()> { if let Some(selected_target) = &target_from_single_bare_bin { (selected_target.clone(), k.to_string()) } else { - require_arg(non_interactive, "--bin target=file")?; + require_arg(non_interactive, "--bin target=file (or: --targets for a single bare --bin)")?; let selected_target = Select::new( &format!("Select target for [{}]:", k), bootloader::get_hardcoded_list_of_targets() From fe44daa3876283c438e875777bdac192820b4567 Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Tue, 23 Jun 2026 16:23:30 +0200 Subject: [PATCH 3/3] Validate explicit bootload flash targets --- src/main.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main.rs b/src/main.rs index 23f94ed..0fc7415 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,6 +76,16 @@ fn single_explicit_target_for_bare_bin( Some(target.to_string()) } +fn unsupported_flash_targets(selected: &[String]) -> Vec { + let supported = bootloader::get_hardcoded_list_of_targets(); + + selected + .iter() + .filter(|target| !supported.contains(&target.as_str())) + .cloned() + .collect() +} + #[cfg(test)] mod tests { use super::*; @@ -109,6 +119,13 @@ mod tests { assert_eq!(single_explicit_target_for_bare_bin(&Some(bin), &targets), None); } + + #[test] + fn unsupported_flash_targets_reports_unknown_targets() { + let selected = vec!["stm32ohnooo-fw".to_string(), "stm32-fw".to_string()]; + + assert_eq!(unsupported_flash_targets(&selected), vec!["stm32ohnooo-fw".to_string()]); + } } impl MemoryTypeArg { @@ -1515,6 +1532,17 @@ async fn run() -> Result<()> { None => upgrade.get_target_and_types(), }; + if matches!(¶ms.targets, Some(Some(_))) { + let unsupported_targets = unsupported_flash_targets(&selected_target_and_types); + if !unsupported_targets.is_empty() { + bail!(CliError::InvalidValue(format!( + "unknown flash target(s): {}. Valid targets: {}", + unsupported_targets.join(", "), + bootloader::get_hardcoded_list_of_targets().join(", ") + ))); + } + } + upgrade.filter_targets(&selected_target_and_types); if upgrade.get_target_and_types().is_empty() {