build.rs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use super::{
  5. configure_cargo, delete_codegen_vars, ensure_init, env, get_app, get_config, inject_assets,
  6. log_finished, open_and_wait, MobileTarget, OptionsHandle,
  7. };
  8. use crate::{
  9. build::Options as BuildOptions,
  10. helpers::{
  11. app_paths::tauri_dir,
  12. config::{get as get_tauri_config, ConfigHandle},
  13. flock,
  14. },
  15. interface::{AppInterface, AppSettings, Interface, Options as InterfaceOptions},
  16. mobile::{write_options, CliOptions},
  17. ConfigValue, Result,
  18. };
  19. use clap::{ArgAction, Parser};
  20. use anyhow::Context;
  21. use cargo_mobile2::{
  22. android::{aab, apk, config::Config as AndroidConfig, env::Env, target::Target},
  23. opts::{NoiseLevel, Profile},
  24. target::TargetTrait,
  25. };
  26. use std::env::set_current_dir;
  27. #[derive(Debug, Clone, Parser)]
  28. #[clap(
  29. about = "Build your app in release mode for Android and generate APKs and AABs",
  30. 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`."
  31. )]
  32. pub struct Options {
  33. /// Builds with the debug flag
  34. #[clap(short, long)]
  35. pub debug: bool,
  36. /// Which targets to build (all by default).
  37. #[clap(
  38. short,
  39. long = "target",
  40. action = ArgAction::Append,
  41. num_args(0..),
  42. value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
  43. )]
  44. pub targets: Option<Vec<String>>,
  45. /// List of cargo features to activate
  46. #[clap(short, long, action = ArgAction::Append, num_args(0..))]
  47. pub features: Option<Vec<String>>,
  48. /// JSON string or path to JSON file to merge with tauri.conf.json
  49. #[clap(short, long)]
  50. pub config: Option<ConfigValue>,
  51. /// Whether to split the APKs and AABs per ABIs.
  52. #[clap(long)]
  53. pub split_per_abi: bool,
  54. /// Build APKs.
  55. #[clap(long)]
  56. pub apk: bool,
  57. /// Build AABs.
  58. #[clap(long)]
  59. pub aab: bool,
  60. /// Open Android Studio
  61. #[clap(short, long)]
  62. pub open: bool,
  63. /// Skip prompting for values
  64. #[clap(long, env = "CI")]
  65. pub ci: bool,
  66. }
  67. impl From<Options> for BuildOptions {
  68. fn from(options: Options) -> Self {
  69. Self {
  70. runner: None,
  71. debug: options.debug,
  72. target: None,
  73. features: options.features,
  74. bundles: None,
  75. no_bundle: false,
  76. config: options.config,
  77. args: Vec::new(),
  78. ci: options.ci,
  79. }
  80. }
  81. }
  82. pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
  83. crate::helpers::app_paths::resolve();
  84. delete_codegen_vars();
  85. let mut build_options: BuildOptions = options.clone().into();
  86. let first_target = Target::all()
  87. .get(
  88. options
  89. .targets
  90. .as_ref()
  91. .and_then(|l| l.first().map(|t| t.as_str()))
  92. .unwrap_or(Target::DEFAULT_KEY),
  93. )
  94. .unwrap();
  95. build_options.target = Some(first_target.triple.into());
  96. let tauri_config = get_tauri_config(
  97. tauri_utils::platform::Target::Android,
  98. options.config.as_ref().map(|c| &c.0),
  99. )?;
  100. let (interface, app, config, metadata) = {
  101. let tauri_config_guard = tauri_config.lock().unwrap();
  102. let tauri_config_ = tauri_config_guard.as_ref().unwrap();
  103. let interface = AppInterface::new(tauri_config_, build_options.target.clone())?;
  104. interface.build_options(&mut Vec::new(), &mut build_options.features, true);
  105. let app = get_app(tauri_config_, &interface);
  106. let (config, metadata) = get_config(
  107. &app,
  108. tauri_config_,
  109. build_options.features.as_ref(),
  110. &Default::default(),
  111. );
  112. (interface, app, config, metadata)
  113. };
  114. let profile = if options.debug {
  115. Profile::Debug
  116. } else {
  117. Profile::Release
  118. };
  119. let tauri_path = tauri_dir();
  120. set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
  121. ensure_init(
  122. &tauri_config,
  123. config.app(),
  124. config.project_dir(),
  125. MobileTarget::Android,
  126. )?;
  127. let mut env = env()?;
  128. configure_cargo(&app, Some((&mut env, &config)))?;
  129. crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
  130. // run an initial build to initialize plugins
  131. first_target.build(&config, &metadata, &env, noise_level, true, profile)?;
  132. let open = options.open;
  133. let _handle = run_build(
  134. interface,
  135. options,
  136. build_options,
  137. tauri_config,
  138. profile,
  139. &config,
  140. &mut env,
  141. noise_level,
  142. )?;
  143. if open {
  144. open_and_wait(&config, &env);
  145. }
  146. Ok(())
  147. }
  148. #[allow(clippy::too_many_arguments)]
  149. fn run_build(
  150. interface: AppInterface,
  151. mut options: Options,
  152. build_options: BuildOptions,
  153. tauri_config: ConfigHandle,
  154. profile: Profile,
  155. config: &AndroidConfig,
  156. env: &mut Env,
  157. noise_level: NoiseLevel,
  158. ) -> Result<OptionsHandle> {
  159. if !(options.apk || options.aab) {
  160. // if the user didn't specify the format to build, we'll do both
  161. options.apk = true;
  162. options.aab = true;
  163. }
  164. let interface_options = InterfaceOptions {
  165. debug: build_options.debug,
  166. target: build_options.target.clone(),
  167. ..Default::default()
  168. };
  169. let app_settings = interface.app_settings();
  170. let bin_path = app_settings.app_binary_path(&interface_options)?;
  171. let out_dir = bin_path.parent().unwrap();
  172. let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?;
  173. let cli_options = CliOptions {
  174. dev: false,
  175. features: build_options.features.clone(),
  176. args: build_options.args.clone(),
  177. noise_level,
  178. vars: Default::default(),
  179. };
  180. let handle = write_options(
  181. &tauri_config.lock().unwrap().as_ref().unwrap().identifier,
  182. cli_options,
  183. )?;
  184. inject_assets(config, tauri_config.lock().unwrap().as_ref().unwrap())?;
  185. let apk_outputs = if options.apk {
  186. apk::build(
  187. config,
  188. env,
  189. noise_level,
  190. profile,
  191. get_targets_or_all(options.targets.clone().unwrap_or_default())?,
  192. options.split_per_abi,
  193. )?
  194. } else {
  195. Vec::new()
  196. };
  197. let aab_outputs = if options.aab {
  198. aab::build(
  199. config,
  200. env,
  201. noise_level,
  202. profile,
  203. get_targets_or_all(options.targets.unwrap_or_default())?,
  204. options.split_per_abi,
  205. )?
  206. } else {
  207. Vec::new()
  208. };
  209. log_finished(apk_outputs, "APK");
  210. log_finished(aab_outputs, "AAB");
  211. Ok(handle)
  212. }
  213. fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>> {
  214. if targets.is_empty() {
  215. Ok(Target::all().iter().map(|t| t.1).collect())
  216. } else {
  217. let mut outs = Vec::new();
  218. let possible_targets = Target::all()
  219. .keys()
  220. .map(|key| key.to_string())
  221. .collect::<Vec<String>>()
  222. .join(",");
  223. for t in targets {
  224. let target = Target::for_name(&t).ok_or_else(|| {
  225. anyhow::anyhow!(
  226. "Target {} is invalid; the possible targets are {}",
  227. t,
  228. possible_targets
  229. )
  230. })?;
  231. outs.push(target);
  232. }
  233. Ok(outs)
  234. }
  235. }