diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 3fe7a070c..20e2d86e5 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -293,7 +293,7 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cargo-mobile" version = "0.1.0" -source = "git+https://github.com/tauri-apps/cargo-mobile?branch=dev#228f0eb83ccbc7bfeb0103d2f68b6664691a289d" +source = "git+https://github.com/tauri-apps/cargo-mobile?branch=dev#909edf9337ec0cb42c76e0a34af61e99b15ccfe0" dependencies = [ "cocoa", "colored 1.9.3", diff --git a/tooling/cli/Cargo.toml b/tooling/cli/Cargo.toml index f10c14a6c..a7c40d28c 100644 --- a/tooling/cli/Cargo.toml +++ b/tooling/cli/Cargo.toml @@ -28,7 +28,7 @@ name = "cargo-tauri" path = "src/main.rs" [dependencies] -# cargo-mobile = { path = "../../../cargo-mobile/", default-features = false } +#cargo-mobile = { path = "../../../cargo-mobile/", default-features = false } cargo-mobile = { git = "https://github.com/tauri-apps/cargo-mobile", branch = "dev", default-features = false } textwrap = { version = "0.11.0", features = ["term_size"] } interprocess = "1" diff --git a/tooling/cli/src/mobile/android.rs b/tooling/cli/src/mobile/android.rs index 6c1b17792..a65ee2995 100644 --- a/tooling/cli/src/mobile/android.rs +++ b/tooling/cli/src/mobile/android.rs @@ -7,6 +7,7 @@ use cargo_mobile::{ adb, config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig}, device::Device, + emulator, env::Env, target::Target, }, @@ -17,7 +18,11 @@ use cargo_mobile::{ util::prompt, }; use clap::{Parser, Subcommand}; -use std::env::set_var; +use std::{ + env::set_var, + thread::{sleep, spawn}, + time::Duration, +}; use super::{ ensure_init, get_app, @@ -136,19 +141,30 @@ fn delete_codegen_vars() { } } -fn device_prompt<'a>(env: &'_ Env) -> Result, PromptError> { +fn adb_device_prompt<'a>( + env: &'_ Env, + target: Option<&str>, +) -> Result, PromptError> { let device_list = adb::device_list(env).map_err(|cause| PromptError::detection_failed("Android", cause))?; if !device_list.is_empty() { let index = if device_list.len() > 1 { - prompt::list( - concat!("Detected ", "Android", " devices"), - device_list.iter(), - "device", - None, - "Device", - ) - .map_err(|cause| PromptError::prompt_failed("Android", cause))? + if let Some(t) = target { + let t = t.to_lowercase(); + device_list + .iter() + .position(|d| d.name().to_lowercase().starts_with(&t)) + .unwrap_or_default() + } else { + prompt::list( + concat!("Detected ", "Android", " devices"), + device_list.iter(), + "device", + None, + "Device", + ) + .map_err(|cause| PromptError::prompt_failed("Android", cause))? + } } else { 0 }; @@ -164,8 +180,67 @@ fn device_prompt<'a>(env: &'_ Env) -> Result, PromptError, +) -> Result> { + let emulator_list = emulator::avd_list(env).unwrap_or_default(); + if emulator_list.is_empty() { + Err(PromptError::none_detected("Android emulator")) + } else { + let index = if emulator_list.len() > 1 { + if let Some(t) = target { + let t = t.to_lowercase(); + emulator_list + .iter() + .position(|d| d.name().to_lowercase().starts_with(&t)) + .unwrap_or_default() + } else { + prompt::list( + concat!("Detected ", "Android", " emulators"), + emulator_list.iter(), + "emulator", + None, + "Emulator", + ) + .map_err(|cause| PromptError::prompt_failed("Android emulator", cause))? + } + } else { + 0 + }; + + Ok(emulator_list.iter().nth(index).cloned().unwrap()) + } +} + +fn device_prompt<'a>( + env: &'_ Env, + target: Option<&str>, +) -> Result, PromptError> { + if let Ok(device) = adb_device_prompt(env, target) { + Ok(device) + } else { + let emulator = emulator_prompt(env, target)?; + let handle = emulator.start(env).map_err(|e| { + PromptError::prompt_failed( + "Android emulator", + std::io::Error::new(std::io::ErrorKind::Other, e.to_string()), + ) + })?; + spawn(move || { + let _ = handle.wait(); + }); + loop { + sleep(Duration::from_secs(2)); + if let Ok(device) = adb_device_prompt(env, Some(emulator.name())) { + return Ok(device); + } + } + } +} + fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> { - device_prompt(env).map(|device| device.target()).ok() + device_prompt(env, None).map(|device| device.target()).ok() } fn open_and_wait(config: &AndroidConfig, env: &Env) -> ! { @@ -174,6 +249,6 @@ fn open_and_wait(config: &AndroidConfig, env: &Env) -> ! { log::error!("{}", e); } loop { - std::thread::sleep(std::time::Duration::from_secs(24 * 60 * 60)); + sleep(Duration::from_secs(24 * 60 * 60)); } } diff --git a/tooling/cli/src/mobile/android/dev.rs b/tooling/cli/src/mobile/android/dev.rs index d308886ef..52fb6a2c0 100644 --- a/tooling/cli/src/mobile/android/dev.rs +++ b/tooling/cli/src/mobile/android/dev.rs @@ -15,13 +15,18 @@ use cargo_mobile::{ adb, config::{Config as AndroidConfig, Metadata as AndroidMetadata}, device::RunError as DeviceRunError, + emulator, env::Env, }, config::app::App, opts::{NoiseLevel, Profile}, }; -use std::env::set_var; +use std::{ + env::set_var, + thread::{sleep, spawn}, + time::Duration, +}; const WEBVIEW_CLIENT_CLASS_EXTENSION: &str = " @android.annotation.SuppressLint(\"WebViewClientOnReceivedSslError\") @@ -50,6 +55,8 @@ pub struct Options { /// Open Android Studio instead of trying to run on a connected device #[clap(short, long)] pub open: bool, + /// Runs on the given device name + pub device: Option, } impl From for crate::dev::Options { @@ -112,9 +119,29 @@ fn run_dev( let env = env()?; init_dot_cargo(app, Some((&env, config)))?; + if let Some(device) = &options.device { + let emulators = emulator::avd_list(&env).unwrap_or_default(); + for emulator in emulators { + if emulator + .name() + .to_lowercase() + .starts_with(&device.to_lowercase()) + { + log::info!("Starting emulator {}", emulator.name()); + let handle = emulator.start(&env)?; + spawn(move || { + let _ = handle.wait(); + }); + sleep(Duration::from_secs(3)); + break; + } + } + } + let open = options.open; let exit_on_panic = options.exit_on_panic; let no_watch = options.no_watch; + let device = options.device; interface.mobile_dev( MobileOptions { debug: true, @@ -135,7 +162,14 @@ fn run_dev( if open { open_and_wait(config, &env) } else { - match run(options, config, &env, metadata, noise_level) { + match run( + device.as_deref(), + options, + config, + &env, + metadata, + noise_level, + ) { Ok(c) => { crate::dev::wait_dev_process(c.clone(), move |status, reason| { crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch) @@ -162,6 +196,7 @@ enum RunError { } fn run( + device: Option<&str>, options: MobileOptions, config: &AndroidConfig, env: &Env, @@ -176,7 +211,7 @@ fn run( let build_app_bundle = metadata.asset_packs().is_some(); - device_prompt(env) + device_prompt(env, device) .map_err(RunError::FailedToPromptForDevice)? .run( config,