dev.rs 5.5 KB

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