dev.rs 5.9 KB

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