|
11 | 11 | use clap::{Parser, Subcommand, ValueEnum}; |
12 | 12 | use clap_complete::Shell; |
13 | 13 | use std::process::ExitCode; |
14 | | -use uad_core::adb::AdbBackend; |
| 14 | +use uad_core::adb::{ACommand, AdbBackend}; |
15 | 15 | use uad_core::uad_lists::PackageState; |
16 | 16 |
|
17 | 17 | mod commands; |
@@ -58,6 +58,10 @@ pub struct Cli { |
58 | 58 | )] |
59 | 59 | backend: AdbBackendArg, |
60 | 60 |
|
| 61 | + /// Stop the system ADB server before using the Builtin backend |
| 62 | + #[arg(long, global = true)] |
| 63 | + kill_adb_server: bool, |
| 64 | + |
61 | 65 | #[command(subcommand)] |
62 | 66 | command: Commands, |
63 | 67 | } |
@@ -204,8 +208,7 @@ async fn main() -> ExitCode { |
204 | 208 |
|
205 | 209 | fn run() -> Result<(), Box<dyn std::error::Error>> { |
206 | 210 | let cli = Cli::parse(); |
207 | | - let backend: AdbBackend = cli.backend.into(); |
208 | | - backend.set_current(); |
| 211 | + configure_adb(&cli)?; |
209 | 212 |
|
210 | 213 | match cli.command { |
211 | 214 | Commands::Devices => { |
@@ -289,3 +292,75 @@ fn run() -> Result<(), Box<dyn std::error::Error>> { |
289 | 292 |
|
290 | 293 | Ok(()) |
291 | 294 | } |
| 295 | + |
| 296 | +fn configure_adb(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> { |
| 297 | + let backend: AdbBackend = cli.backend.into(); |
| 298 | + #[cfg(feature = "builtin-adb")] |
| 299 | + let using_builtin = backend == AdbBackend::Builtin; |
| 300 | + #[cfg(not(feature = "builtin-adb"))] |
| 301 | + let using_builtin = false; |
| 302 | + |
| 303 | + if cli.kill_adb_server && !using_builtin { |
| 304 | + return Err("--kill-adb-server requires --backend builtin".into()); |
| 305 | + } |
| 306 | + if cli.kill_adb_server { |
| 307 | + ACommand::kill_system_server() |
| 308 | + .map_err(|err| format!("Failed to stop the system ADB server: {err}"))?; |
| 309 | + } |
| 310 | + |
| 311 | + backend.set_current(); |
| 312 | + |
| 313 | + if using_builtin && !cli.kill_adb_server && command_uses_device(&cli.command) { |
| 314 | + let devices = ACommand::new().devices()?; |
| 315 | + if devices.iter().any(|(_, status)| status == "busy") { |
| 316 | + return Err( |
| 317 | + "The system ADB server is using the USB device. Stop it with `adb kill-server` \ |
| 318 | + or rerun with `--kill-adb-server`." |
| 319 | + .into(), |
| 320 | + ); |
| 321 | + } |
| 322 | + } |
| 323 | + |
| 324 | + Ok(()) |
| 325 | +} |
| 326 | + |
| 327 | +fn command_uses_device(command: &Commands) -> bool { |
| 328 | + match command { |
| 329 | + Commands::Devices |
| 330 | + | Commands::List { .. } |
| 331 | + | Commands::Uninstall { .. } |
| 332 | + | Commands::Enable { .. } |
| 333 | + | Commands::Disable { .. } |
| 334 | + | Commands::Repl { .. } => true, |
| 335 | + Commands::Info { device, .. } => device.is_some(), |
| 336 | + Commands::Update | Commands::Adb | Commands::Completions { .. } => false, |
| 337 | + } |
| 338 | +} |
| 339 | + |
| 340 | +#[cfg(test)] |
| 341 | +mod tests { |
| 342 | + use super::*; |
| 343 | + |
| 344 | + #[cfg(feature = "builtin-adb")] |
| 345 | + #[test] |
| 346 | + fn builtin_server_shutdown_is_explicitly_opted_in() { |
| 347 | + let cli = Cli::try_parse_from([ |
| 348 | + "uad", |
| 349 | + "--backend", |
| 350 | + "builtin", |
| 351 | + "--kill-adb-server", |
| 352 | + "devices", |
| 353 | + ]) |
| 354 | + .expect("valid Builtin CLI arguments"); |
| 355 | + |
| 356 | + assert_eq!(cli.backend, AdbBackendArg::Builtin); |
| 357 | + assert!(cli.kill_adb_server); |
| 358 | + assert!(command_uses_device(&cli.command)); |
| 359 | + } |
| 360 | + |
| 361 | + #[test] |
| 362 | + fn non_device_commands_skip_usb_preflight() { |
| 363 | + assert!(!command_uses_device(&Commands::Update)); |
| 364 | + assert!(!command_uses_device(&Commands::Adb)); |
| 365 | + } |
| 366 | +} |
0 commit comments