123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- use cargo_mobile2::{
- android::{
- adb,
- config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig},
- device::Device,
- emulator,
- env::Env,
- target::Target,
- },
- config::app::{App, DEFAULT_ASSET_DIR},
- opts::{FilterLevel, NoiseLevel},
- os,
- util::prompt,
- };
- use clap::{Parser, Subcommand};
- use std::{
- env::set_var,
- fs::{create_dir, create_dir_all, write},
- process::exit,
- thread::sleep,
- time::Duration,
- };
- use sublime_fuzzy::best_match;
- use super::{
- ensure_init, get_app,
- init::{command as init_command, configure_cargo},
- log_finished, read_options, setup_dev_config, CliOptions, OptionsHandle, Target as MobileTarget,
- MIN_DEVICE_MATCH_SCORE,
- };
- use crate::{helpers::config::Config as TauriConfig, Result};
- mod android_studio_script;
- mod build;
- mod dev;
- mod open;
- pub(crate) mod project;
- #[derive(Parser)]
- #[clap(
- author,
- version,
- about = "Android commands",
- subcommand_required(true),
- arg_required_else_help(true)
- )]
- pub struct Cli {
- #[clap(subcommand)]
- command: Commands,
- }
- #[derive(Debug, Parser)]
- #[clap(about = "Initialize Android target in the project")]
- pub struct InitOptions {
- /// Skip prompting for values
- #[clap(long, env = "CI")]
- ci: bool,
- /// Skips installing rust toolchains via rustup
- #[clap(long)]
- skip_targets_install: bool,
- }
- #[derive(Subcommand)]
- enum Commands {
- Init(InitOptions),
- /// Open project in Android Studio
- Open,
- Dev(dev::Options),
- Build(build::Options),
- #[clap(hide(true))]
- AndroidStudioScript(android_studio_script::Options),
- }
- pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
- let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
- match cli.command {
- Commands::Init(options) => init_command(
- MobileTarget::Android,
- options.ci,
- false,
- options.skip_targets_install,
- )?,
- Commands::Open => open::command()?,
- Commands::Dev(options) => dev::command(options, noise_level)?,
- Commands::Build(options) => build::command(options, noise_level)?,
- Commands::AndroidStudioScript(options) => android_studio_script::command(options)?,
- }
- Ok(())
- }
- pub fn get_config(
- app: &App,
- config: &TauriConfig,
- features: Option<&Vec<String>>,
- cli_options: &CliOptions,
- ) -> (AndroidConfig, AndroidMetadata) {
- let mut android_options = cli_options.clone();
- if let Some(features) = features {
- android_options
- .features
- .get_or_insert(Vec::new())
- .extend_from_slice(features);
- }
- let raw = RawAndroidConfig {
- features: android_options.features.clone(),
- logcat_filter_specs: vec![
- "RustStdoutStderr".into(),
- format!(
- "*:{}",
- match cli_options.noise_level {
- NoiseLevel::Polite => FilterLevel::Info,
- NoiseLevel::LoudAndProud => FilterLevel::Debug,
- NoiseLevel::FranklyQuitePedantic => FilterLevel::Verbose,
- }
- .logcat()
- ),
- ],
- min_sdk_version: Some(config.bundle.android.min_sdk_version),
- ..Default::default()
- };
- let config = AndroidConfig::from_raw(app.clone(), Some(raw)).unwrap();
- let metadata = AndroidMetadata {
- supported: true,
- cargo_args: Some(android_options.args),
- features: android_options.features,
- ..Default::default()
- };
- set_var(
- "WRY_ANDROID_PACKAGE",
- format!("{}.{}", app.reverse_domain(), app.name_snake()),
- );
- set_var("WRY_ANDROID_LIBRARY", app.lib_name());
- set_var("TAURI_ANDROID_PROJECT_PATH", config.project_dir());
- let src_main_dir = config.project_dir().join("app/src/main").join(format!(
- "java/{}/{}",
- app.reverse_domain().replace('.', "/"),
- app.name_snake()
- ));
- if config.project_dir().exists() {
- if src_main_dir.exists() {
- let _ = create_dir(src_main_dir.join("generated"));
- } else {
- log::error!(
- "Project directory {} does not exist. Did you update the package name in `Cargo.toml` or the bundle identifier in `tauri.conf.json > identifier`? Save your changes, delete the `gen/android` folder and run `tauri android init` to recreate the Android project.",
- src_main_dir.display()
- );
- exit(1);
- }
- }
- set_var(
- "WRY_ANDROID_KOTLIN_FILES_OUT_DIR",
- src_main_dir.join("generated"),
- );
- (config, metadata)
- }
- fn env() -> Result<Env> {
- let env = super::env()?;
- cargo_mobile2::android::env::Env::from_env(env).map_err(Into::into)
- }
- fn delete_codegen_vars() {
- for (k, _) in std::env::vars() {
- if k.starts_with("WRY_") && (k.ends_with("CLASS_EXTENSION") || k.ends_with("CLASS_INIT")) {
- std::env::remove_var(k);
- }
- }
- }
- fn adb_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
- let device_list = adb::device_list(env)
- .map_err(|cause| anyhow::anyhow!("Failed to detect connected Android devices: {cause}"))?;
- if !device_list.is_empty() {
- let device = if let Some(t) = target {
- let (device, score) = device_list
- .into_iter()
- .rev()
- .map(|d| {
- let score = best_match(t, d.name()).map_or(0, |m| m.score());
- (d, score)
- })
- .max_by_key(|(_, score)| *score)
- // we already checked the list is not empty
- .unwrap();
- if score > MIN_DEVICE_MATCH_SCORE {
- device
- } else {
- anyhow::bail!("Could not find an Android device matching {t}")
- }
- } else if device_list.len() > 1 {
- let index = prompt::list(
- concat!("Detected ", "Android", " devices"),
- device_list.iter(),
- "device",
- None,
- "Device",
- )
- .map_err(|cause| anyhow::anyhow!("Failed to prompt for Android device: {cause}"))?;
- device_list.into_iter().nth(index).unwrap()
- } else {
- device_list.into_iter().next().unwrap()
- };
- log::info!(
- "Detected connected device: {} with target {:?}",
- device,
- device.target().triple,
- );
- Ok(device)
- } else {
- Err(anyhow::anyhow!("No connected Android devices detected"))
- }
- }
- fn emulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<emulator::Emulator> {
- let emulator_list = emulator::avd_list(env).unwrap_or_default();
- if !emulator_list.is_empty() {
- let emulator = if let Some(t) = target {
- let (device, score) = emulator_list
- .into_iter()
- .rev()
- .map(|d| {
- let score = best_match(t, d.name()).map_or(0, |m| m.score());
- (d, score)
- })
- .max_by_key(|(_, score)| *score)
- // we already checked the list is not empty
- .unwrap();
- if score > MIN_DEVICE_MATCH_SCORE {
- device
- } else {
- anyhow::bail!("Could not find an Android Emulator matching {t}")
- }
- } else if emulator_list.len() > 1 {
- let index = prompt::list(
- concat!("Detected ", "Android", " emulators"),
- emulator_list.iter(),
- "emulator",
- None,
- "Emulator",
- )
- .map_err(|cause| anyhow::anyhow!("Failed to prompt for Android Emulator device: {cause}"))?;
- emulator_list.into_iter().nth(index).unwrap()
- } else {
- emulator_list.into_iter().next().unwrap()
- };
- Ok(emulator)
- } else {
- Err(anyhow::anyhow!("No available Android Emulator detected"))
- }
- }
- fn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
- if let Ok(device) = adb_device_prompt(env, target) {
- Ok(device)
- } else {
- let emulator = emulator_prompt(env, target)?;
- log::info!("Starting emulator {}", emulator.name());
- emulator.start_detached(env)?;
- let mut tries = 0;
- loop {
- sleep(Duration::from_secs(2));
- if let Ok(device) = adb_device_prompt(env, Some(emulator.name())) {
- return Ok(device);
- }
- if tries >= 3 {
- log::info!("Waiting for emulator to start... (maybe the emulator is unathorized or offline, run `adb devices` to check)");
- } else {
- log::info!("Waiting for emulator to start...");
- }
- tries += 1;
- }
- }
- }
- fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
- device_prompt(env, None).map(|device| device.target()).ok()
- }
- fn open_and_wait(config: &AndroidConfig, env: &Env) -> ! {
- log::info!("Opening Android Studio");
- if let Err(e) = os::open_file_with("Android Studio", config.project_dir(), &env.base) {
- log::error!("{}", e);
- }
- loop {
- sleep(Duration::from_secs(24 * 60 * 60));
- }
- }
- fn inject_assets(config: &AndroidConfig, tauri_config: &TauriConfig) -> Result<()> {
- let asset_dir = config
- .project_dir()
- .join("app/src/main")
- .join(DEFAULT_ASSET_DIR);
- create_dir_all(&asset_dir)?;
- write(
- asset_dir.join("tauri.conf.json"),
- serde_json::to_string(&tauri_config)?,
- )?;
- Ok(())
- }
|