/
githubmirror
/
tauri
Обзор
Документация
Войти
/
githubmirror
/
tauri
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
crates/tauri-cli/src/mobile/android/build.rs
342 строки
10 KB
Manmit Singh
feat(cli): add `--no-binary-patching` flag to build and bundle (#15619)
21 июл 2026, 08:12
Не верифицирован
21 июл 2026, 08:12
af465ea
Код
Авторство
О чём код?
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use super::{ configure_cargo, delete_codegen_vars, ensure_init, env, get_app, get_config, inject_resources, log_finished, open_and_wait, sync_debug_application_id_suffix, MobileTarget, OptionsHandle, }; use crate::{ build::Options as BuildOptions, error::Context, helpers::{ app_paths::Dirs, config::{get_config as get_tauri_config, ConfigMetadata}, flock, }, interface::{AppInterface, Options as InterfaceOptions}, mobile::{android::generate_tauri_properties, write_options, CliOptions, TargetDevice}, ConfigValue, Error, Result, }; use clap::{ArgAction, Parser}; use cargo_mobile2::{ android::{aab, apk, config::Config as AndroidConfig, env::Env, target::Target}, opts::{NoiseLevel, Profile}, target::TargetTrait, }; use std::env::set_current_dir; use std::path::Path; #[derive(Debug, Clone, Parser)] #[clap( about = "Build your app in release mode for Android and generate APKs and AABs", long_about = "Build your app in release mode for Android and generate APKs and AABs. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`." )] pub struct Options { /// Builds with the debug flag #[clap(short, long)] pub debug: bool, /// Which targets to build (all by default). #[clap( short, long = "target", action = ArgAction::Append, num_args(0..), value_parser(clap::builder::PossibleValuesParser::new(Target::name_list())) )] pub targets: Option<Vec<String>>, /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')] pub features: Vec<String>, /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file /// /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. /// /// Note that a platform-specific file is looked up and merged with the default file by default /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] pub config: Vec<ConfigValue>, /// Whether to split the APKs and AABs per ABIs. #[clap(long)] pub split_per_abi: bool, /// Build APKs. #[clap(long)] pub apk: bool, /// Build AABs. #[clap(long)] pub aab: bool, #[clap(skip)] pub skip_bundle: bool, /// Open Android Studio #[clap(short, long)] pub open: bool, /// Skip prompting for values #[clap(long, env = "CI")] pub ci: bool, /// Command line arguments passed to the runner. /// Use `--` to explicitly mark the start of the arguments. /// e.g. `tauri android build -- [runnerArgs]`. #[clap(last(true))] pub args: Vec<String>, /// Do not error out if a version mismatch is detected on a Tauri package. /// /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior. #[clap(long)] pub ignore_version_mismatches: bool, /// Target device of this build #[clap(skip)] pub target_device: Option<TargetDevice>, } impl From<Options> for BuildOptions { fn from(options: Options) -> Self { Self { runner: None, debug: options.debug, target: None, features: options.features, bundles: None, no_bundle: false, config: options.config, args: options.args, ci: options.ci, skip_stapling: false, ignore_version_mismatches: options.ignore_version_mismatches, no_sign: false, no_binary_patching: false, } } } pub struct BuiltApplication { pub config: AndroidConfig, pub interface: AppInterface, // prevent drop #[allow(dead_code)] options_handle: OptionsHandle, } pub fn command(options: Options, noise_level: NoiseLevel) -> Result<BuiltApplication> { let dirs = crate::helpers::app_paths::resolve_dirs(); let tauri_config = get_tauri_config( tauri_utils::platform::Target::Android, &options .config .iter() .map(|conf| &conf.0) .collect::<Vec<_>>(), dirs.tauri, )?; run(options, noise_level, &dirs, &tauri_config) } pub fn run( options: Options, noise_level: NoiseLevel, dirs: &Dirs, tauri_config: &ConfigMetadata, ) -> Result<BuiltApplication> { delete_codegen_vars(); let mut build_options: BuildOptions = options.clone().into(); let first_target = Target::all() .get( options .targets .as_ref() .and_then(|l| l.first().map(|t| t.as_str())) .unwrap_or(Target::DEFAULT_KEY), ) .unwrap(); build_options.target = Some(first_target.triple.into()); let interface = AppInterface::new(tauri_config, build_options.target.clone(), dirs.tauri)?; interface.build_options(&mut build_options.args, &mut build_options.features, true); let app = get_app(MobileTarget::Android, tauri_config, &interface, dirs.tauri); let (config, metadata) = get_config( &app, tauri_config, &build_options.features, &CliOptions { dev: false, features: build_options.features.clone(), args: build_options.args.clone(), noise_level, vars: Default::default(), config: build_options.config.clone(), target_device: None, }, ); let profile = if options.debug { Profile::Debug } else { Profile::Release }; set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?; ensure_init( tauri_config, config.app(), config.project_dir(), MobileTarget::Android, options.ci, )?; let mut env = env(options.ci)?; configure_cargo(&mut env, &config)?; generate_tauri_properties(&config, tauri_config, false)?; sync_debug_application_id_suffix(&config, tauri_config)?; crate::build::setup(&interface, &mut build_options, tauri_config, dirs, true)?; let installed_targets = crate::interface::rust::installation::installed_targets().unwrap_or_default(); if !installed_targets.contains(&first_target.triple().into()) { log::info!("Installing target {}", first_target.triple()); first_target .install() .map_err(|error| Error::CommandFailed { command: "rustup target add".to_string(), error, }) .context("failed to install target")?; } // run an initial build to initialize plugins first_target .build(&config, &metadata, &env, noise_level, true, profile) .context("failed to build Android app")?; let open = options.open; let options_handle = run_build( &interface, options, build_options, tauri_config, profile, &config, &mut env, noise_level, dirs.tauri, )?; if open { open_and_wait(&config, &env); } Ok(BuiltApplication { config, interface, options_handle, }) } #[allow(clippy::too_many_arguments)] fn run_build( interface: &AppInterface, mut options: Options, build_options: BuildOptions, tauri_config: &ConfigMetadata, profile: Profile, config: &AndroidConfig, env: &mut Env, noise_level: NoiseLevel, tauri_dir: &Path, ) -> Result<OptionsHandle> { if !(options.skip_bundle || options.apk || options.aab) { // if the user didn't specify the format to build, we'll do both options.apk = true; options.aab = true; } let interface_options = InterfaceOptions { debug: build_options.debug, target: build_options.target.clone(), args: build_options.args.clone(), ..Default::default() }; let app_settings = interface.app_settings(); let out_dir = app_settings.out_dir(&interface_options, tauri_dir)?; let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?; let cli_options = CliOptions { dev: false, features: build_options.features.clone(), args: build_options.args.clone(), noise_level, vars: Default::default(), config: build_options.config, target_device: options.target_device.clone(), }; let handle = write_options(tauri_config, cli_options)?; inject_resources(config, tauri_config)?; let apk_outputs = if options.apk { apk::build( config, env, noise_level, profile, get_targets_or_all(options.targets.clone().unwrap_or_default())?, options.split_per_abi, ) .context("failed to build APK")? } else { Vec::new() }; let aab_outputs = if options.aab { aab::build( config, env, noise_level, profile, get_targets_or_all(options.targets.unwrap_or_default())?, options.split_per_abi, ) .context("failed to build AAB")? } else { Vec::new() }; if !apk_outputs.is_empty() { log_finished(apk_outputs, "APK"); } if !aab_outputs.is_empty() { log_finished(aab_outputs, "AAB"); } Ok(handle) } fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>> { if targets.is_empty() { Ok(Target::all().iter().map(|t| t.1).collect()) } else { let mut outs = Vec::new(); let possible_targets = Target::all() .keys() .map(|key| key.to_string()) .collect::<Vec<String>>() .join(","); for t in targets { let target = Target::for_name(&t).with_context(|| { format!("Target {t} is invalid; the possible targets are {possible_targets}",) })?; outs.push(target); } Ok(outs) } }