From b1946a2dbbf00d1b1d0c97fe43d7ae9a55a83a69 Mon Sep 17 00:00:00 2001 From: yfblock <321353225@qq.com> Date: Mon, 24 Aug 2026 18:32:35 +0800 Subject: [PATCH] fix(ostool): honor board_reset_cmd/board_power_off_cmd from uboot config UbootConfig and the #[serde(flatten)]-ed LocalUbootConfig both declared board_reset_cmd and board_power_off_cmd. Under serde flatten the outer fields take priority, so LocalBackend (which read the flattened copy) never observed the configured values and the reset/power-off hooks silently never ran. Mark the LocalUbootConfig copies #[serde(skip)] as legacy Rust-API compatibility fields, and pass the commands into LocalBackend from UbootConfig, falling back to the legacy fields for callers that still set them directly. --- README.en.md | 10 ++-- README.md | 10 ++-- ostool/src/run/uboot.rs | 100 +++++++++++++++++++++++++++++++++------- 3 files changed, 94 insertions(+), 26 deletions(-) diff --git a/README.en.md b/README.en.md index ae0b04e..79b9e9e 100644 --- a/README.en.md +++ b/README.en.md @@ -270,11 +270,6 @@ dtb_file = "tools/device_tree.dtb" # Kernel load address (optional) kernel_load_addr = "0x80080000" -# Network boot configuration (optional) -[net] -interface = "eth0" -board_ip = "192.168.1.100" - # Board reset command (optional) board_reset_cmd = "reset" @@ -283,6 +278,11 @@ board_power_off_cmd = "poweroff" # Failure boot regex patterns fail_regex = ["Boot failed", "Error loading kernel"] + +# Network boot configuration (optional) +[net] +interface = "eth0" +board_ip = "192.168.1.100" ``` ### Ordered shell initialization steps diff --git a/README.md b/README.md index fece119..49dbf67 100644 --- a/README.md +++ b/README.md @@ -267,11 +267,6 @@ dtb_file = "tools/device_tree.dtb" # 内核加载地址(可选) kernel_load_addr = "0x80080000" -# 网络启动配置(可选) -[net] -interface = "eth0" -board_ip = "192.168.1.100" - # 板子重置命令(可选) board_reset_cmd = "reset" @@ -280,6 +275,11 @@ board_power_off_cmd = "poweroff" # 失败启动的正则表达式 fail_regex = ["Boot failed", "Error loading kernel"] + +# 网络启动配置(可选) +[net] +interface = "eth0" +board_ip = "192.168.1.100" ``` ### 有序 Shell 初始化步骤 diff --git a/ostool/src/run/uboot.rs b/ostool/src/run/uboot.rs index 96696e1..4ab8648 100644 --- a/ostool/src/run/uboot.rs +++ b/ostool/src/run/uboot.rs @@ -160,11 +160,13 @@ pub struct LocalUbootConfig { pub baud_rate: Option, /// TFTP boot configuration pub net: Option, - /// Board reset command - /// shell command to reset the board + /// Legacy Rust API compatibility field. Use `UbootConfig::board_reset_cmd`. + #[serde(skip)] + #[schemars(skip)] pub board_reset_cmd: Option, - /// Board power off command - /// shell command to power off the board + /// Legacy Rust API compatibility field. Use `UbootConfig::board_power_off_cmd`. + #[serde(skip)] + #[schemars(skip)] pub board_power_off_cmd: Option, } @@ -451,7 +453,11 @@ pub(crate) async fn run_uboot_with_config( input: UbootRunInput, config: UbootConfig, ) -> anyhow::Result<()> { - let backend = LocalBackend::new(config.local.clone()); + let backend = LocalBackend::new( + config.local.clone(), + config.board_reset_cmd.clone(), + config.board_power_off_cmd.clone(), + ); let mut runner = Runner::new(input, config, backend); runner.run().await } @@ -591,6 +597,10 @@ trait RunnerBackend { struct LocalBackend { config: LocalUbootConfig, + /// Host-side reset command, taken from `UbootConfig::board_reset_cmd`. + reset_cmd: Option, + /// Host-side power-off command, taken from `UbootConfig::board_power_off_cmd`. + power_off_cmd: Option, baud_rate: Option, linux_system_tftp: Option, linux_tftp_staging: Vec, @@ -599,9 +609,17 @@ struct LocalBackend { } impl LocalBackend { - fn new(config: LocalUbootConfig) -> Self { + fn new( + config: LocalUbootConfig, + reset_cmd: Option, + power_off_cmd: Option, + ) -> Self { + let reset_cmd = reset_cmd.or_else(|| config.board_reset_cmd.clone()); + let power_off_cmd = power_off_cmd.or_else(|| config.board_power_off_cmd.clone()); Self { config, + reset_cmd, + power_off_cmd, baud_rate: None, linux_system_tftp: None, linux_tftp_staging: Vec::new(), @@ -746,7 +764,7 @@ impl RunnerBackend for LocalBackend { async fn after_console_open(&mut self, context: &ProcessContext) -> anyhow::Result<()> { println!("Waiting for board on power or reset..."); - if let Some(cmd) = self.config.board_reset_cmd.as_deref() + if let Some(cmd) = self.reset_cmd.as_deref() && !cmd.trim().is_empty() { crate::process::shell_run_cmd(context, cmd)?; @@ -805,7 +823,7 @@ impl RunnerBackend for LocalBackend { } } - if let Some(cmd) = self.config.board_power_off_cmd.as_deref() + if let Some(cmd) = self.power_off_cmd.as_deref() && !cmd.trim().is_empty() && let Err(err) = crate::process::shell_run_cmd(context, cmd) { @@ -1642,6 +1660,8 @@ mod tests { fn local_backend() -> LocalBackend { LocalBackend { config: LocalUbootConfig::default(), + reset_cmd: None, + power_off_cmd: None, baud_rate: None, linux_system_tftp: None, linux_tftp_staging: Vec::new(), @@ -2278,7 +2298,7 @@ mod tests { std::fs::write(prepared.target_dir().join("keep.txt"), b"keep").unwrap(); let marker = temp.path().join("powered-off"); let mut backend = local_backend(); - backend.config.board_power_off_cmd = Some(format!("touch {}", marker.display())); + backend.power_off_cmd = Some(format!("touch {}", marker.display())); backend.linux_tftp_staging.push(prepared); let context = make_invocation(temp.path()).process_context().unwrap(); @@ -2416,6 +2436,56 @@ interface = "eth0" assert_eq!(net.interface, "eth0"); } + #[test] + fn uboot_config_parses_board_commands_at_top_level() { + let config: UbootConfig = toml::from_str( + r#" +serial = "/dev/null" +baud_rate = "115200" +fail_regex = [] +board_reset_cmd = "reset-board" +board_power_off_cmd = "power-off-board" +"#, + ) + .unwrap(); + + assert_eq!(config.board_reset_cmd.as_deref(), Some("reset-board")); + assert_eq!( + config.board_power_off_cmd.as_deref(), + Some("power-off-board") + ); + assert_eq!(config.local.board_reset_cmd, None); + assert_eq!(config.local.board_power_off_cmd, None); + + let serialized = toml::to_string(&config).unwrap(); + assert_eq!(serialized.matches("board_reset_cmd").count(), 1); + assert_eq!(serialized.matches("board_power_off_cmd").count(), 1); + + let backend = LocalBackend::new( + config.local, + config.board_reset_cmd, + config.board_power_off_cmd, + ); + assert_eq!(backend.reset_cmd.as_deref(), Some("reset-board")); + assert_eq!(backend.power_off_cmd.as_deref(), Some("power-off-board")); + } + + #[test] + fn local_backend_keeps_legacy_command_fields_usable() { + let backend = LocalBackend::new( + LocalUbootConfig { + board_reset_cmd: Some("legacy-reset".into()), + board_power_off_cmd: Some("legacy-power-off".into()), + ..Default::default() + }, + None, + None, + ); + + assert_eq!(backend.reset_cmd.as_deref(), Some("legacy-reset")); + assert_eq!(backend.power_off_cmd.as_deref(), Some("legacy-power-off")); + } + #[test] fn uboot_config_replaces_string_fields() { let tmp = tempfile::tempdir().unwrap(); @@ -2460,6 +2530,8 @@ interface = "eth0" kernel_load_addr: Some("${workspaceFolder}".into()), fit_load_addr: Some("${package}".into()), bootm_addr: Some("${workspace}".into()), + board_reset_cmd: Some("${workspace}".into()), + board_power_off_cmd: Some("${package}".into()), fail_regex: vec!["${package}".into()], uboot_cmd: Some(vec!["setenv boot ${workspace}".into()]), shell_check_steps: vec![ShellCheckStep { @@ -2471,8 +2543,6 @@ interface = "eth0" local: LocalUbootConfig { serial: Some("${workspace}/tty".into()), baud_rate: Some("${env:OSTOOL_UBOOT_TEST_ENV}".into()), - board_reset_cmd: Some("${workspace}".into()), - board_power_off_cmd: Some("${package}".into()), net: Some(Net { interface: "${env:OSTOOL_UBOOT_TEST_ENV}".into(), board_ip: Some("${workspace}".into()), @@ -2480,6 +2550,7 @@ interface = "eth0" netmask: Some("${workspaceFolder}".into()), tftp_dir: Some("${package}/tftp".into()), }), + ..Default::default() }, ..Default::default() }; @@ -2501,12 +2572,9 @@ interface = "eth0" assert_eq!(config.kernel_load_addr.as_deref(), Some(expected.as_str())); assert_eq!(config.fit_load_addr.as_deref(), Some(expected.as_str())); assert_eq!(config.bootm_addr.as_deref(), Some(expected.as_str())); + assert_eq!(config.board_reset_cmd.as_deref(), Some(expected.as_str())); assert_eq!( - config.local.board_reset_cmd.as_deref(), - Some(expected.as_str()) - ); - assert_eq!( - config.local.board_power_off_cmd.as_deref(), + config.board_power_off_cmd.as_deref(), Some(expected.as_str()) ); assert_eq!(config.fail_regex, vec![expected.clone()]);