dev.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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, device_prompt, ensure_init, env, open_and_wait, setup_dev_config, with_config,
  6. MobileTarget, APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME,
  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 dialoguer::{theme::ColorfulTheme, Select};
  17. use tauri_mobile::{
  18. apple::{config::Config as AppleConfig, device::Device, teams::find_development_teams},
  19. config::app::App,
  20. env::Env,
  21. opts::{NoiseLevel, Profile},
  22. };
  23. use std::env::{set_var, var_os};
  24. #[derive(Debug, Clone, Parser)]
  25. #[clap(about = "iOS dev")]
  26. pub struct Options {
  27. /// List of cargo features to activate
  28. #[clap(short, long, action = ArgAction::Append, num_args(0..))]
  29. pub features: Option<Vec<String>>,
  30. /// Exit on panic
  31. #[clap(short, long)]
  32. exit_on_panic: bool,
  33. /// JSON string or path to JSON file to merge with tauri.conf.json
  34. #[clap(short, long)]
  35. pub config: Option<String>,
  36. /// Run the code in release mode
  37. #[clap(long = "release")]
  38. pub release_mode: bool,
  39. /// Disable the file watcher
  40. #[clap(long)]
  41. pub no_watch: bool,
  42. /// Disable the dev server for static files.
  43. #[clap(long)]
  44. pub no_dev_server: bool,
  45. /// Open Xcode instead of trying to run on a connected device
  46. #[clap(short, long)]
  47. pub open: bool,
  48. /// Runs on the given device name
  49. pub device: Option<String>,
  50. }
  51. impl From<Options> for DevOptions {
  52. fn from(options: Options) -> Self {
  53. Self {
  54. runner: None,
  55. target: None,
  56. features: options.features,
  57. exit_on_panic: options.exit_on_panic,
  58. config: options.config,
  59. release_mode: options.release_mode,
  60. args: Vec::new(),
  61. no_watch: options.no_watch,
  62. no_dev_server: options.no_dev_server,
  63. }
  64. }
  65. }
  66. pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
  67. if var_os(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME).is_none() {
  68. if let Ok(teams) = find_development_teams() {
  69. let index = match teams.len() {
  70. 0 => None,
  71. 1 => Some(0),
  72. _ => {
  73. let index = Select::with_theme(&ColorfulTheme::default())
  74. .items(
  75. &teams
  76. .iter()
  77. .map(|t| format!("{} (ID: {})", t.name, t.id))
  78. .collect::<Vec<String>>(),
  79. )
  80. .default(0)
  81. .interact()?;
  82. Some(index)
  83. }
  84. };
  85. if let Some(index) = index {
  86. let team = teams.get(index).unwrap();
  87. log::info!(
  88. "Using development team `{}`. To make this permanent, set the `{}` environment variable to `{}`",
  89. team.name,
  90. APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME,
  91. team.id
  92. );
  93. set_var(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME, &team.id);
  94. }
  95. }
  96. }
  97. with_config(
  98. Some(Default::default()),
  99. |app, config, _metadata, _cli_options| {
  100. ensure_init(config.project_dir(), MobileTarget::Ios)?;
  101. run_dev(options, app, config, noise_level).map_err(Into::into)
  102. },
  103. )
  104. }
  105. fn run_dev(
  106. mut options: Options,
  107. app: &App,
  108. config: &AppleConfig,
  109. noise_level: NoiseLevel,
  110. ) -> Result<()> {
  111. setup_dev_config(&mut options.config)?;
  112. let env = env()?;
  113. let device = if options.open {
  114. None
  115. } else {
  116. match device_prompt(&env, options.device.as_deref()) {
  117. Ok(d) => Some(d),
  118. Err(e) => {
  119. log::error!("{e}");
  120. None
  121. }
  122. }
  123. };
  124. let mut dev_options: DevOptions = options.clone().into();
  125. dev_options.target = Some(
  126. device
  127. .as_ref()
  128. .map(|d| d.target().triple.to_string())
  129. .unwrap_or_else(|| "aarch64-apple-ios".into()),
  130. );
  131. let mut interface = crate::dev::setup(&mut dev_options, true)?;
  132. let app_settings = interface.app_settings();
  133. let bin_path = app_settings.app_binary_path(&InterfaceOptions {
  134. debug: !dev_options.release_mode,
  135. target: dev_options.target.clone(),
  136. ..Default::default()
  137. })?;
  138. let out_dir = bin_path.parent().unwrap();
  139. let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?;
  140. configure_cargo(app, None)?;
  141. let open = options.open;
  142. let exit_on_panic = options.exit_on_panic;
  143. let no_watch = options.no_watch;
  144. interface.mobile_dev(
  145. MobileOptions {
  146. debug: true,
  147. features: options.features,
  148. args: Vec::new(),
  149. config: options.config,
  150. no_watch: options.no_watch,
  151. },
  152. |options| {
  153. let cli_options = CliOptions {
  154. features: options.features.clone(),
  155. args: options.args.clone(),
  156. noise_level,
  157. vars: Default::default(),
  158. };
  159. let _handle = write_options(
  160. &get_config(options.config.as_deref())?
  161. .lock()
  162. .unwrap()
  163. .as_ref()
  164. .unwrap()
  165. .tauri
  166. .bundle
  167. .identifier,
  168. cli_options,
  169. )?;
  170. if open {
  171. open_and_wait(config, &env)
  172. } else if let Some(device) = &device {
  173. match run(device, options, config, &env) {
  174. Ok(c) => {
  175. crate::dev::wait_dev_process(c.clone(), move |status, reason| {
  176. crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
  177. });
  178. Ok(Box::new(c) as Box<dyn DevProcess>)
  179. }
  180. Err(e) => {
  181. crate::dev::kill_before_dev_process();
  182. Err(e.into())
  183. }
  184. }
  185. } else {
  186. open_and_wait(config, &env)
  187. }
  188. },
  189. )
  190. }
  191. #[derive(Debug, thiserror::Error)]
  192. enum RunError {
  193. #[error("{0}")]
  194. RunFailed(String),
  195. }
  196. fn run(
  197. device: &Device<'_>,
  198. options: MobileOptions,
  199. config: &AppleConfig,
  200. env: &Env,
  201. ) -> Result<DevChild, RunError> {
  202. let profile = if options.debug {
  203. Profile::Debug
  204. } else {
  205. Profile::Release
  206. };
  207. device
  208. .run(
  209. config,
  210. env,
  211. NoiseLevel::FranklyQuitePedantic,
  212. false, // do not quit on app exit
  213. profile,
  214. )
  215. .map(DevChild::new)
  216. .map_err(|e| RunError::RunFailed(e.to_string()))
  217. }