feat(cli): add option to run on specific Android emulator/device (#5093)

This commit is contained in:
Lucas Fernandes Nogueira 2022-08-30 10:27:53 -03:00 committed by GitHub
parent e22d21beaf
commit 82e8751ae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 127 additions and 17 deletions

View File

@ -293,7 +293,7 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]] [[package]]
name = "cargo-mobile" name = "cargo-mobile"
version = "0.1.0" 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 = [ dependencies = [
"cocoa", "cocoa",
"colored 1.9.3", "colored 1.9.3",

View File

@ -7,6 +7,7 @@ use cargo_mobile::{
adb, adb,
config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig}, config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig},
device::Device, device::Device,
emulator,
env::Env, env::Env,
target::Target, target::Target,
}, },
@ -17,7 +18,11 @@ use cargo_mobile::{
util::prompt, util::prompt,
}; };
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::env::set_var; use std::{
env::set_var,
thread::{sleep, spawn},
time::Duration,
};
use super::{ use super::{
ensure_init, get_app, ensure_init, get_app,
@ -136,11 +141,21 @@ fn delete_codegen_vars() {
} }
} }
fn device_prompt<'a>(env: &'_ Env) -> Result<Device<'a>, PromptError<adb::device_list::Error>> { fn adb_device_prompt<'a>(
env: &'_ Env,
target: Option<&str>,
) -> Result<Device<'a>, PromptError<adb::device_list::Error>> {
let device_list = let device_list =
adb::device_list(env).map_err(|cause| PromptError::detection_failed("Android", cause))?; adb::device_list(env).map_err(|cause| PromptError::detection_failed("Android", cause))?;
if !device_list.is_empty() { if !device_list.is_empty() {
let index = if device_list.len() > 1 { let index = if device_list.len() > 1 {
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( prompt::list(
concat!("Detected ", "Android", " devices"), concat!("Detected ", "Android", " devices"),
device_list.iter(), device_list.iter(),
@ -149,6 +164,7 @@ fn device_prompt<'a>(env: &'_ Env) -> Result<Device<'a>, PromptError<adb::device
"Device", "Device",
) )
.map_err(|cause| PromptError::prompt_failed("Android", cause))? .map_err(|cause| PromptError::prompt_failed("Android", cause))?
}
} else { } else {
0 0
}; };
@ -164,8 +180,67 @@ fn device_prompt<'a>(env: &'_ Env) -> Result<Device<'a>, PromptError<adb::device
} }
} }
fn emulator_prompt(
env: &'_ Env,
target: Option<&str>,
) -> Result<emulator::Emulator, PromptError<adb::device_list::Error>> {
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<Device<'a>, PromptError<adb::device_list::Error>> {
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>> { 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) -> ! { fn open_and_wait(config: &AndroidConfig, env: &Env) -> ! {
@ -174,6 +249,6 @@ fn open_and_wait(config: &AndroidConfig, env: &Env) -> ! {
log::error!("{}", e); log::error!("{}", e);
} }
loop { loop {
std::thread::sleep(std::time::Duration::from_secs(24 * 60 * 60)); sleep(Duration::from_secs(24 * 60 * 60));
} }
} }

View File

@ -15,13 +15,18 @@ use cargo_mobile::{
adb, adb,
config::{Config as AndroidConfig, Metadata as AndroidMetadata}, config::{Config as AndroidConfig, Metadata as AndroidMetadata},
device::RunError as DeviceRunError, device::RunError as DeviceRunError,
emulator,
env::Env, env::Env,
}, },
config::app::App, config::app::App,
opts::{NoiseLevel, Profile}, opts::{NoiseLevel, Profile},
}; };
use std::env::set_var; use std::{
env::set_var,
thread::{sleep, spawn},
time::Duration,
};
const WEBVIEW_CLIENT_CLASS_EXTENSION: &str = " const WEBVIEW_CLIENT_CLASS_EXTENSION: &str = "
@android.annotation.SuppressLint(\"WebViewClientOnReceivedSslError\") @android.annotation.SuppressLint(\"WebViewClientOnReceivedSslError\")
@ -50,6 +55,8 @@ pub struct Options {
/// Open Android Studio instead of trying to run on a connected device /// Open Android Studio instead of trying to run on a connected device
#[clap(short, long)] #[clap(short, long)]
pub open: bool, pub open: bool,
/// Runs on the given device name
pub device: Option<String>,
} }
impl From<Options> for crate::dev::Options { impl From<Options> for crate::dev::Options {
@ -112,9 +119,29 @@ fn run_dev(
let env = env()?; let env = env()?;
init_dot_cargo(app, Some((&env, config)))?; 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 open = options.open;
let exit_on_panic = options.exit_on_panic; let exit_on_panic = options.exit_on_panic;
let no_watch = options.no_watch; let no_watch = options.no_watch;
let device = options.device;
interface.mobile_dev( interface.mobile_dev(
MobileOptions { MobileOptions {
debug: true, debug: true,
@ -135,7 +162,14 @@ fn run_dev(
if open { if open {
open_and_wait(config, &env) open_and_wait(config, &env)
} else { } else {
match run(options, config, &env, metadata, noise_level) { match run(
device.as_deref(),
options,
config,
&env,
metadata,
noise_level,
) {
Ok(c) => { Ok(c) => {
crate::dev::wait_dev_process(c.clone(), move |status, reason| { crate::dev::wait_dev_process(c.clone(), move |status, reason| {
crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch) crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
@ -162,6 +196,7 @@ enum RunError {
} }
fn run( fn run(
device: Option<&str>,
options: MobileOptions, options: MobileOptions,
config: &AndroidConfig, config: &AndroidConfig,
env: &Env, env: &Env,
@ -176,7 +211,7 @@ fn run(
let build_app_bundle = metadata.asset_packs().is_some(); let build_app_bundle = metadata.asset_packs().is_some();
device_prompt(env) device_prompt(env, device)
.map_err(RunError::FailedToPromptForDevice)? .map_err(RunError::FailedToPromptForDevice)?
.run( .run(
config, config,