/
githubmirror
/
tauri
Обзор
Документация
Войти
/
githubmirror
/
tauri
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
crates/tauri-cli/src/mobile/android/android_studio_script.rs
328 строк
9 KB
Lucas Fernandes Nogueira
feat(cli): restart Android emulator if it is disconnected from adb (#14313)
30 апр 2026, 15:20
Не верифицирован
30 апр 2026, 15:20
764b913
Код
Авторство
О чём код?
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use super::{detect_target_ok, ensure_init, env, get_app, get_config, read_options, MobileTarget}; use crate::{ error::{Context, ErrorExt}, helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config}, interface::AppInterface, mobile::CliOptions, Error, Result, }; use clap::{ArgAction, Parser}; use cargo_mobile2::{ android::{adb, device::ConnectionStatus, target::Target}, opts::Profile, target::{call_for_targets_with_fallback, TargetTrait}, }; use std::path::Path; #[derive(Debug, Parser)] pub struct Options { /// Targets to build. #[clap( short, long = "target", action = ArgAction::Append, num_args(0..), default_value = Target::DEFAULT_KEY, value_parser(clap::builder::PossibleValuesParser::new(Target::name_list())) )] targets: Option<Vec<String>>, /// Builds with the release flag #[clap(short, long)] release: bool, } pub fn command(options: Options) -> Result<()> { let dirs = crate::helpers::app_paths::resolve_dirs(); let profile = if options.release { Profile::Release } else { Profile::Debug }; let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[], dirs.tauri)?; let cli_options = read_options(&tauri_config); if !cli_options.config.is_empty() { // reload config with merges from the android dev|build script reload_tauri_config( &mut tauri_config, &cli_options .config .iter() .map(|conf| &conf.0) .collect::<Vec<_>>(), dirs.tauri, )? }; let (config, metadata) = get_config( &get_app( MobileTarget::Android, &tauri_config, &AppInterface::new(&tauri_config, None, dirs.tauri)?, dirs.tauri, ), &tauri_config, &[], &cli_options, ); ensure_init( &tauri_config, config.app(), config.project_dir(), MobileTarget::Android, std::env::var("CI").is_ok(), )?; if !cli_options.config.is_empty() { crate::helpers::config::merge_config_with( &mut tauri_config, &cli_options .config .iter() .map(|conf| &conf.0) .collect::<Vec<_>>(), )?; } let env = env(std::env::var("CI").is_ok())?; if cli_options.dev { if let Some(url) = &tauri_config.build.dev_url { let localhost = match url.host() { Some(url::Host::Domain(d)) => d == "localhost", Some(url::Host::Ipv4(i)) => i == std::net::Ipv4Addr::LOCALHOST, _ => false, }; if localhost { if let Some(port) = url.port_or_known_default() { adb_forward_port(port, &env, &cli_options)?; } } } } let mut validated_lib = false; let installed_targets = crate::interface::rust::installation::installed_targets().unwrap_or_default(); call_for_targets_with_fallback( options.targets.unwrap_or_default().iter(), &detect_target_ok, &env, |target: &Target| { if !installed_targets.contains(&target.triple().into()) { log::info!("Installing target {}", target.triple()); target .install() .map_err(|error| Error::CommandFailed { command: "rustup target add".to_string(), error, }) .context("failed to install target")?; } target .build( &config, &metadata, &env, cli_options.noise_level, true, profile, ) .context("failed to build Android app")?; if !validated_lib { validated_lib = true; let lib_path = config .app() .target_dir(target.triple, profile) .join(config.so_name()); validate_lib(&lib_path).context("failed to validate library")?; } Ok(()) }, ) .map_err(|e| Error::GenericError(e.to_string()))? } fn validate_lib(path: &Path) -> Result<()> { let so_bytes = std::fs::read(path).fs_context("failed to read library", path.to_path_buf())?; let elf = elf::ElfBytes::<elf::endian::AnyEndian>::minimal_parse(&so_bytes) .context("failed to parse ELF")?; let (symbol_table, string_table) = elf .dynamic_symbol_table() .context("failed to read dynsym section")? .context("missing dynsym tables")?; let mut symbols = Vec::new(); for s in symbol_table.iter() { if let Ok(symbol) = string_table.get(s.st_name as usize) { symbols.push(symbol); } } if !symbols.contains(&"Java_app_tauri_plugin_PluginManager_handlePluginResponse") { crate::error::bail!( "Library from {} does not include required runtime symbols. This means you are likely missing the tauri::mobile_entry_point macro usage, see the documentation for more information: https://v2.tauri.app/start/migrate/from-tauri-1", path.display() ); } Ok(()) } fn adb_forward_port( port: u16, env: &cargo_mobile2::android::env::Env, cli_options: &CliOptions, ) -> Result<()> { let forward = format!("tcp:{port}"); log::info!("Forwarding port {port} with adb"); let mut devices = adb::device_list(env) .unwrap_or_default() .into_iter() .filter(|d| d.status() == ConnectionStatus::Connected) .collect::<Vec<_>>(); // if we could not detect any running device, let's wait a few seconds, it might be booting up if devices.is_empty() { log::warn!( "ADB device list is empty, waiting a few seconds to see if there's any booting device..." ); let max = 5; let mut count = 0; loop { std::thread::sleep(std::time::Duration::from_secs(1)); devices = adb::device_list(env) .unwrap_or_default() .into_iter() .filter(|d| d.status() == ConnectionStatus::Connected) .collect::<Vec<_>>(); if !devices.is_empty() { break; } count += 1; if count == max { break; } } } let target_device = if let Some(target_device) = &cli_options.target_device { Some((target_device.id.clone(), target_device.name.clone())) } else if devices.len() == 1 { let device = devices.first().unwrap(); Some((device.serial_no().to_string(), device.name().to_string())) } else if devices.len() > 1 { crate::error::bail!("Multiple Android devices are connected ({}), please disconnect devices you do not intend to use so Tauri can determine which to use", devices.iter().map(|d| d.name()).collect::<Vec<_>>().join(", ")); } else { // when building the app without running to a device, we might have an empty devices list None }; if let Some((target_device_serial_no, target_device_name)) = target_device { let mut already_forwarded = false; // clear port forwarding for all devices for device in &devices { let reverse_list_output = adb_reverse_list(env, device.serial_no()).map_err(|error| Error::CommandFailed { command: "adb reverse --list".to_string(), error, })?; // check if the device has the port forwarded if String::from_utf8_lossy(&reverse_list_output.stdout).contains(&forward) { // device matches our target, we can skip forwarding if device.serial_no() == target_device_serial_no { log::debug!( "device {} already has the forward for {}", device.name(), forward ); already_forwarded = true; } break; } } // if there's a known target, we should forward the port to it if already_forwarded { log::info!("{forward} already forwarded to {target_device_name}"); } else { loop { run_adb_reverse(env, &target_device_serial_no, &forward, &forward).map_err(|error| { Error::CommandFailed { command: format!("adb reverse {forward} {forward}"), error, } })?; let reverse_list_output = adb_reverse_list(env, &target_device_serial_no).map_err(|error| { Error::CommandFailed { command: "adb reverse --list".to_string(), error, } })?; // wait and retry until the port has actually been forwarded if String::from_utf8_lossy(&reverse_list_output.stdout).contains(&forward) { break; } else { log::warn!( "waiting for the port to be forwarded to {}...", target_device_name ); std::thread::sleep(std::time::Duration::from_secs(1)); } } } } else { log::warn!("no running devices detected with ADB; skipping port forwarding"); } Ok(()) } fn run_adb_reverse( env: &cargo_mobile2::android::env::Env, device_serial_no: &str, remote: &str, local: &str, ) -> std::io::Result<std::process::Output> { adb::adb(env, ["-s", device_serial_no, "reverse", remote, local]) .stdin_file(os_pipe::dup_stdin().unwrap()) .stdout_file(os_pipe::dup_stdout().unwrap()) .stderr_file(os_pipe::dup_stdout().unwrap()) .run() } fn adb_reverse_list( env: &cargo_mobile2::android::env::Env, device_serial_no: &str, ) -> std::io::Result<std::process::Output> { adb::adb(env, ["-s", device_serial_no, "reverse", "--list"]) .stdin_file(os_pipe::dup_stdin().unwrap()) .stdout_capture() .stderr_capture() .run() }