dev.rs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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. /// Force prompting for an IP to use to connect to the dev server on mobile.
  51. #[clap(long)]
  52. pub force_ip_prompt: bool,
  53. }
  54. impl From<Options> for DevOptions {
  55. fn from(options: Options) -> Self {
  56. Self {
  57. runner: None,
  58. target: None,
  59. features: options.features,
  60. exit_on_panic: options.exit_on_panic,
  61. config: options.config,
  62. release_mode: options.release_mode,
  63. args: Vec::new(),
  64. no_watch: options.no_watch,
  65. no_dev_server: options.no_dev_server,
  66. force_ip_prompt: options.force_ip_prompt,
  67. }
  68. }
  69. }
  70. pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
  71. if var_os(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME).is_none() {
  72. if let Ok(teams) = find_development_teams() {
  73. let index = match teams.len() {
  74. 0 => None,
  75. 1 => Some(0),
  76. _ => {
  77. let index = Select::with_theme(&ColorfulTheme::default())
  78. .items(
  79. &teams
  80. .iter()
  81. .map(|t| format!("{} (ID: {})", t.name, t.id))
  82. .collect::<Vec<String>>(),
  83. )
  84. .default(0)
  85. .interact()?;
  86. Some(index)
  87. }
  88. };
  89. if let Some(index) = index {
  90. let team = teams.get(index).unwrap();
  91. log::info!(
  92. "Using development team `{}`. To make this permanent, set the `{}` environment variable to `{}`",
  93. team.name,
  94. APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME,
  95. team.id
  96. );
  97. set_var(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME, &team.id);
  98. }
  99. }
  100. }
  101. with_config(
  102. Some(Default::default()),
  103. |app, config, _metadata, _cli_options| {
  104. ensure_init(config.project_dir(), MobileTarget::Ios)?;
  105. run_dev(options, app, config, noise_level).map_err(Into::into)
  106. },
  107. )
  108. }
  109. fn run_dev(
  110. mut options: Options,
  111. app: &App,
  112. config: &AppleConfig,
  113. noise_level: NoiseLevel,
  114. ) -> Result<()> {
  115. setup_dev_config(&mut options.config, options.force_ip_prompt)?;
  116. let env = env()?;
  117. let device = if options.open {
  118. None
  119. } else {
  120. match device_prompt(&env, options.device.as_deref()) {
  121. Ok(d) => Some(d),
  122. Err(e) => {
  123. log::error!("{e}");
  124. None
  125. }
  126. }
  127. };
  128. let mut dev_options: DevOptions = options.clone().into();
  129. dev_options.target = Some(
  130. device
  131. .as_ref()
  132. .map(|d| d.target().triple.to_string())
  133. .unwrap_or_else(|| "aarch64-apple-ios".into()),
  134. );
  135. let mut interface = crate::dev::setup(&mut dev_options, true)?;
  136. let app_settings = interface.app_settings();
  137. let bin_path = app_settings.app_binary_path(&InterfaceOptions {
  138. debug: !dev_options.release_mode,
  139. target: dev_options.target.clone(),
  140. ..Default::default()
  141. })?;
  142. let out_dir = bin_path.parent().unwrap();
  143. let _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?;
  144. configure_cargo(app, None)?;
  145. let open = options.open;
  146. let exit_on_panic = options.exit_on_panic;
  147. let no_watch = options.no_watch;
  148. interface.mobile_dev(
  149. MobileOptions {
  150. debug: true,
  151. features: options.features,
  152. args: Vec::new(),
  153. config: options.config,
  154. no_watch: options.no_watch,
  155. },
  156. |options| {
  157. let cli_options = CliOptions {
  158. features: options.features.clone(),
  159. args: options.args.clone(),
  160. noise_level,
  161. vars: Default::default(),
  162. };
  163. let _handle = write_options(
  164. &get_config(options.config.as_deref())?
  165. .lock()
  166. .unwrap()
  167. .as_ref()
  168. .unwrap()
  169. .tauri
  170. .bundle
  171. .identifier,
  172. cli_options,
  173. )?;
  174. if open {
  175. open_and_wait(config, &env)
  176. } else if let Some(device) = &device {
  177. match run(device, options, config, &env) {
  178. Ok(c) => {
  179. crate::dev::wait_dev_process(c.clone(), move |status, reason| {
  180. crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
  181. });
  182. Ok(Box::new(c) as Box<dyn DevProcess>)
  183. }
  184. Err(e) => {
  185. crate::dev::kill_before_dev_process();
  186. Err(e.into())
  187. }
  188. }
  189. } else {
  190. open_and_wait(config, &env)
  191. }
  192. },
  193. )
  194. }
  195. #[derive(Debug, thiserror::Error)]
  196. enum RunError {
  197. #[error("{0}")]
  198. RunFailed(String),
  199. }
  200. fn run(
  201. device: &Device<'_>,
  202. options: MobileOptions,
  203. config: &AppleConfig,
  204. env: &Env,
  205. ) -> Result<DevChild, RunError> {
  206. let profile = if options.debug {
  207. Profile::Debug
  208. } else {
  209. Profile::Release
  210. };
  211. device
  212. .run(
  213. config,
  214. env,
  215. NoiseLevel::FranklyQuitePedantic,
  216. false, // do not quit on app exit
  217. profile,
  218. )
  219. .map(DevChild::new)
  220. .map_err(|e| RunError::RunFailed(e.to_string()))
  221. }