dev.rs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright 2019-2023 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, device_prompt, ensure_init, env, open_and_wait,
  6. setup_dev_config, with_config, MobileTarget,
  7. };
  8. use crate::{
  9. dev::Options as DevOptions,
  10. helpers::{config::get as get_config, flock},
  11. interface::{AppSettings, Interface, MobileOptions, Options as InterfaceOptions},
  12. mobile::{write_options, CliOptions, DevChild, DevProcess},
  13. Result,
  14. };
  15. use clap::{ArgAction, Parser};
  16. use tauri_mobile::{
  17. android::{
  18. config::{Config as AndroidConfig, Metadata as AndroidMetadata},
  19. device::Device,
  20. env::Env,
  21. target::Target,
  22. },
  23. config::app::App,
  24. opts::{FilterLevel, NoiseLevel, Profile},
  25. target::TargetTrait,
  26. };
  27. use std::env::set_var;
  28. const WEBVIEW_CLIENT_CLASS_EXTENSION: &str = "
  29. @android.annotation.SuppressLint(\"WebViewClientOnReceivedSslError\")
  30. override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler, error: android.net.http.SslError) {
  31. handler.proceed()
  32. }
  33. ";
  34. const WEBVIEW_CLASS_INIT: &str =
  35. "this.settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW";
  36. #[derive(Debug, Clone, Parser)]
  37. #[clap(about = "Android dev")]
  38. pub struct Options {
  39. /// List of cargo features to activate
  40. #[clap(short, long, action = ArgAction::Append, num_args(0..))]
  41. pub features: Option<Vec<String>>,
  42. /// Exit on panic
  43. #[clap(short, long)]
  44. exit_on_panic: bool,
  45. /// JSON string or path to JSON file to merge with tauri.conf.json
  46. #[clap(short, long)]
  47. pub config: Option<String>,
  48. /// Disable the file watcher
  49. #[clap(long)]
  50. pub no_watch: bool,
  51. /// Disable the dev server for static files.
  52. #[clap(long)]
  53. pub no_dev_server: bool,
  54. /// Open Android Studio instead of trying to run on a connected device
  55. #[clap(short, long)]
  56. pub open: bool,
  57. /// Runs on the given device name
  58. pub device: Option<String>,
  59. }
  60. impl From<Options> for DevOptions {
  61. fn from(options: Options) -> Self {
  62. Self {
  63. runner: None,
  64. target: None,
  65. features: options.features,
  66. exit_on_panic: options.exit_on_panic,
  67. config: options.config,
  68. release_mode: false,
  69. args: Vec::new(),
  70. no_watch: options.no_watch,
  71. no_dev_server: options.no_dev_server,
  72. }
  73. }
  74. }
  75. pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
  76. delete_codegen_vars();
  77. with_config(
  78. Some(Default::default()),
  79. |app, config, metadata, _cli_options| {
  80. set_var(
  81. "WRY_RUSTWEBVIEWCLIENT_CLASS_EXTENSION",
  82. WEBVIEW_CLIENT_CLASS_EXTENSION,
  83. );
  84. set_var("WRY_RUSTWEBVIEW_CLASS_INIT", WEBVIEW_CLASS_INIT);
  85. ensure_init(config.project_dir(), MobileTarget::Android)?;
  86. run_dev(options, app, config, metadata, noise_level).map_err(Into::into)
  87. },
  88. )
  89. .map_err(Into::into)
  90. }
  91. fn run_dev(
  92. mut options: Options,
  93. app: &App,
  94. config: &AndroidConfig,
  95. metadata: &AndroidMetadata,
  96. noise_level: NoiseLevel,
  97. ) -> Result<()> {
  98. setup_dev_config(&mut options.config)?;
  99. let mut env = env()?;
  100. let device = if options.open {
  101. None
  102. } else {
  103. match device_prompt(&env, options.device.as_deref()) {
  104. Ok(d) => Some(d),
  105. Err(e) => {
  106. log::error!("{e}");
  107. None
  108. }
  109. }
  110. };
  111. let mut dev_options: DevOptions = options.clone().into();
  112. let target_triple = device
  113. .as_ref()
  114. .map(|d| d.target().triple.to_string())
  115. .unwrap_or_else(|| Target::all().values().next().unwrap().triple.into());
  116. dev_options.target = Some(target_triple.clone());
  117. let mut interface = crate::dev::setup(&mut dev_options, true)?;
  118. let interface_options = InterfaceOptions {
  119. debug: !dev_options.release_mode,
  120. target: dev_options.target.clone(),
  121. ..Default::default()
  122. };
  123. let app_settings = interface.app_settings();
  124. let bin_path = app_settings.app_binary_path(&interface_options)?;
  125. let out_dir = bin_path.parent().unwrap();
  126. let _lock = flock::open_rw(out_dir.join("lock").with_extension("android"), "Android")?;
  127. configure_cargo(app, Some((&mut env, config)))?;
  128. // run an initial build to initialize plugins
  129. let target = Target::all()
  130. .values()
  131. .find(|t| t.triple == target_triple)
  132. .unwrap_or_else(|| Target::all().values().next().unwrap());
  133. target.build(config, metadata, &env, noise_level, true, Profile::Debug)?;
  134. let open = options.open;
  135. let exit_on_panic = options.exit_on_panic;
  136. let no_watch = options.no_watch;
  137. interface.mobile_dev(
  138. MobileOptions {
  139. debug: true,
  140. features: options.features,
  141. args: Vec::new(),
  142. config: options.config,
  143. no_watch: options.no_watch,
  144. },
  145. |options| {
  146. let cli_options = CliOptions {
  147. features: options.features.clone(),
  148. args: options.args.clone(),
  149. noise_level,
  150. vars: Default::default(),
  151. };
  152. let _handle = write_options(
  153. &get_config(options.config.as_deref())?
  154. .lock()
  155. .unwrap()
  156. .as_ref()
  157. .unwrap()
  158. .tauri
  159. .bundle
  160. .identifier,
  161. cli_options,
  162. )?;
  163. if open {
  164. open_and_wait(config, &env)
  165. } else if let Some(device) = &device {
  166. match run(device, options, config, &env, metadata, noise_level) {
  167. Ok(c) => {
  168. crate::dev::wait_dev_process(c.clone(), move |status, reason| {
  169. crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
  170. });
  171. Ok(Box::new(c) as Box<dyn DevProcess>)
  172. }
  173. Err(e) => {
  174. crate::dev::kill_before_dev_process();
  175. Err(e.into())
  176. }
  177. }
  178. } else {
  179. open_and_wait(config, &env)
  180. }
  181. },
  182. )
  183. }
  184. #[derive(Debug, thiserror::Error)]
  185. enum RunError {
  186. #[error("{0}")]
  187. RunFailed(String),
  188. }
  189. fn run(
  190. device: &Device<'_>,
  191. options: MobileOptions,
  192. config: &AndroidConfig,
  193. env: &Env,
  194. metadata: &AndroidMetadata,
  195. noise_level: NoiseLevel,
  196. ) -> Result<DevChild, RunError> {
  197. let profile = if options.debug {
  198. Profile::Debug
  199. } else {
  200. Profile::Release
  201. };
  202. let build_app_bundle = metadata.asset_packs().is_some();
  203. device
  204. .run(
  205. config,
  206. env,
  207. noise_level,
  208. profile,
  209. Some(match noise_level {
  210. NoiseLevel::Polite => FilterLevel::Info,
  211. NoiseLevel::LoudAndProud => FilterLevel::Debug,
  212. NoiseLevel::FranklyQuitePedantic => FilterLevel::Verbose,
  213. }),
  214. build_app_bundle,
  215. false,
  216. ".MainActivity".into(),
  217. )
  218. .map(DevChild::new)
  219. .map_err(|e| RunError::RunFailed(e.to_string()))
  220. }