Bläddra i källkod

feat(cli): improve DX on android dev and ios dev commands (#5059)

Lucas Fernandes Nogueira 3 år sedan
förälder
incheckning
53a3398beb

+ 1 - 1
tooling/cli/Cargo.lock

@@ -293,7 +293,7 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
 [[package]]
 name = "cargo-mobile"
 version = "0.1.0"
-source = "git+https://github.com/tauri-apps/cargo-mobile?branch=dev#5b866af17df4e2310e084efd3550ec31f62ff984"
+source = "git+https://github.com/tauri-apps/cargo-mobile?branch=dev#228f0eb83ccbc7bfeb0103d2f68b6664691a289d"
 dependencies = [
  "cocoa",
  "colored 1.9.3",

+ 23 - 3
tooling/cli/src/dev.rs

@@ -8,7 +8,7 @@ use crate::{
     command_env,
     config::{get as get_config, AppUrl, BeforeDevCommand, WindowUrl},
   },
-  interface::{AppInterface, ExitReason, Interface},
+  interface::{AppInterface, DevProcess, ExitReason, Interface},
   CommandExt, Result,
 };
 use clap::Parser;
@@ -78,7 +78,7 @@ fn command_internal(mut options: Options) -> Result<()> {
   let exit_on_panic = options.exit_on_panic;
   let no_watch = options.no_watch;
   interface.dev(options.into(), move |status, reason| {
-    on_dev_exit(status, reason, exit_on_panic, no_watch)
+    on_app_exit(status, reason, exit_on_panic, no_watch)
   })
 }
 
@@ -275,7 +275,27 @@ pub fn setup(options: &mut Options) -> Result<AppInterface> {
   Ok(interface)
 }
 
-fn on_dev_exit(status: ExitStatus, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {
+pub fn wait_dev_process<
+  C: DevProcess + Send + 'static,
+  F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static,
+>(
+  child: C,
+  on_exit: F,
+) {
+  std::thread::spawn(move || {
+    let status = child.wait().expect("failed to wait on app");
+    on_exit(
+      status,
+      if child.manually_killed_process() {
+        ExitReason::TriggeredKill
+      } else {
+        ExitReason::NormalExit
+      },
+    );
+  });
+}
+
+pub fn on_app_exit(status: ExitStatus, reason: ExitReason, exit_on_panic: bool, no_watch: bool) {
   if no_watch
     || (!matches!(reason, ExitReason::TriggeredKill)
       && (exit_on_panic || matches!(reason, ExitReason::NormalExit)))

+ 4 - 2
tooling/cli/src/interface/mod.rs

@@ -15,8 +15,10 @@ use tauri_bundler::bundle::{PackageType, Settings, SettingsBuilder};
 pub use rust::{manifest, MobileOptions, Options, Rust as AppInterface};
 
 pub trait DevProcess {
-  fn kill(&mut self) -> std::io::Result<()>;
-  fn try_wait(&mut self) -> std::io::Result<Option<ExitStatus>>;
+  fn kill(&self) -> std::io::Result<()>;
+  fn try_wait(&self) -> std::io::Result<Option<ExitStatus>>;
+  fn wait(&self) -> std::io::Result<ExitStatus>;
+  fn manually_killed_process(&self) -> bool;
 }
 
 pub trait AppSettings {

+ 24 - 14
tooling/cli/src/interface/rust/desktop.rs

@@ -23,7 +23,7 @@ pub struct DevChild {
 }
 
 impl DevProcess for DevChild {
-  fn kill(&mut self) -> std::io::Result<()> {
+  fn kill(&self) -> std::io::Result<()> {
     if let Some(child) = &*self.app_child.lock().unwrap() {
       child.kill()?;
     } else if let Some(child) = &self.build_child {
@@ -33,7 +33,7 @@ impl DevProcess for DevChild {
     Ok(())
   }
 
-  fn try_wait(&mut self) -> std::io::Result<Option<ExitStatus>> {
+  fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
     if let Some(child) = &*self.app_child.lock().unwrap() {
       child.try_wait()
     } else if let Some(child) = &self.build_child {
@@ -42,6 +42,20 @@ impl DevProcess for DevChild {
       unreachable!()
     }
   }
+
+  fn wait(&self) -> std::io::Result<ExitStatus> {
+    if let Some(child) = &*self.app_child.lock().unwrap() {
+      child.wait()
+    } else if let Some(child) = &self.build_child {
+      child.wait()
+    } else {
+      unreachable!()
+    }
+  }
+
+  fn manually_killed_process(&self) -> bool {
+    self.manually_killed_app.load(Ordering::Relaxed)
+  }
 }
 
 pub fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
@@ -73,18 +87,14 @@ pub fn run_dev<F: Fn(ExitStatus, ExitReason) + Send + Sync + 'static>(
         app.stderr(os_pipe::dup_stderr().unwrap());
         app.args(run_args);
         let app_child = Arc::new(SharedChild::spawn(&mut app).unwrap());
-        let app_child_t = app_child.clone();
-        std::thread::spawn(move || {
-          let status = app_child_t.wait().expect("failed to wait on app");
-          on_exit(
-            status,
-            if manually_killed_app_.load(Ordering::Relaxed) {
-              ExitReason::TriggeredKill
-            } else {
-              ExitReason::NormalExit
-            },
-          );
-        });
+        crate::dev::wait_dev_process(
+          DevChild {
+            manually_killed_app: manually_killed_app_,
+            build_child: None,
+            app_child: Arc::new(Mutex::new(Some(app_child.clone()))),
+          },
+          on_exit,
+        );
 
         app_child_.lock().unwrap().replace(app_child);
       } else {

+ 2 - 2
tooling/cli/src/mobile/android.rs

@@ -159,9 +159,9 @@ fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
   device_prompt(env).map(|device| device.target()).ok()
 }
 
-fn open_and_wait(config: &AndroidConfig) -> ! {
+fn open_and_wait(config: &AndroidConfig, env: &Env) -> ! {
   log::info!("Opening Android Studio");
-  if let Err(e) = os::open_file_with("Android Studio", config.project_dir()) {
+  if let Err(e) = os::open_file_with("Android Studio", config.project_dir(), &env.base) {
     log::error!("{}", e);
   }
   loop {

+ 5 - 5
tooling/cli/src/mobile/android/build.rs

@@ -82,11 +82,11 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
       init_dot_cargo(root_conf, Some(&env)).map_err(Error::InitDotCargo)?;
 
       let open = options.open;
-      run_build(options, config, env, noise_level)
+      run_build(options, config, &env, noise_level)
         .map_err(|e| Error::BuildFailed(format!("{:#}", e)))?;
 
       if open {
-        open_and_wait(config);
+        open_and_wait(config, &env);
       }
 
       Ok(())
@@ -98,7 +98,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
 fn run_build(
   mut options: Options,
   config: &AndroidConfig,
-  env: Env,
+  env: &Env,
   noise_level: NoiseLevel,
 ) -> Result<()> {
   let profile = if options.debug {
@@ -147,7 +147,7 @@ fn run_build(
   let apk_outputs = if options.apk {
     apk::build(
       config,
-      &env,
+      env,
       noise_level,
       profile,
       get_targets_or_all(Vec::new())?,
@@ -160,7 +160,7 @@ fn run_build(
   let aab_outputs = if options.aab {
     aab::build(
       config,
-      &env,
+      env,
       noise_level,
       profile,
       get_targets_or_all(Vec::new())?,

+ 22 - 12
tooling/cli/src/mobile/android/dev.rs

@@ -11,7 +11,10 @@ use crate::{
 use clap::Parser;
 
 use cargo_mobile::{
-  android::config::{Config as AndroidConfig, Metadata as AndroidMetadata},
+  android::{
+    config::{Config as AndroidConfig, Metadata as AndroidMetadata},
+    env::Env,
+  },
   config::Config,
   opts::{NoiseLevel, Profile},
 };
@@ -106,7 +109,12 @@ fn run_dev(
   let out_dir = bin_path.parent().unwrap();
   let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?;
 
+  let env = env()?;
+  init_dot_cargo(root_conf, Some(&env)).map_err(Error::InitDotCargo)?;
+
   let open = options.open;
+  let exit_on_panic = options.exit_on_panic;
+  let no_watch = options.no_watch;
   interface.mobile_dev(
     MobileOptions {
       debug: true,
@@ -125,13 +133,18 @@ fn run_dev(
       write_options(cli_options, &bundle_identifier, MobileTarget::Android)?;
 
       if open {
-        open_and_wait(config)
+        open_and_wait(config, &env)
       } else {
-        match run(options, root_conf, config, metadata, noise_level) {
-          Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess>),
+        match run(options, config, &env, metadata, noise_level) {
+          Ok(c) => {
+            crate::dev::wait_dev_process(c.clone(), move |status, reason| {
+              crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
+            });
+            Ok(Box::new(c) as Box<dyn DevProcess>)
+          }
           Err(Error::FailedToPromptForDevice(e)) => {
             log::error!("{}", e);
-            open_and_wait(config)
+            open_and_wait(config, &env)
           }
           Err(e) => Err(e.into()),
         }
@@ -142,8 +155,8 @@ fn run_dev(
 
 fn run(
   options: MobileOptions,
-  root_conf: &Config,
   config: &AndroidConfig,
+  env: &Env,
   metadata: &AndroidMetadata,
   noise_level: NoiseLevel,
 ) -> Result<DevChild, Error> {
@@ -155,14 +168,11 @@ fn run(
 
   let build_app_bundle = metadata.asset_packs().is_some();
 
-  let env = env()?;
-  init_dot_cargo(root_conf, Some(&env)).map_err(Error::InitDotCargo)?;
-
-  device_prompt(&env)
+  device_prompt(env)
     .map_err(Error::FailedToPromptForDevice)?
     .run(
       config,
-      &env,
+      env,
       noise_level,
       profile,
       None,
@@ -170,6 +180,6 @@ fn run(
       false,
       ".MainActivity".into(),
     )
-    .map(|c| DevChild(Some(c)))
+    .map(DevChild::new)
     .map_err(Error::RunFailed)
 }

+ 4 - 2
tooling/cli/src/mobile/android/open.rs

@@ -1,4 +1,4 @@
-use super::{ensure_init, with_config, Error, MobileTarget};
+use super::{ensure_init, env, with_config, Error, MobileTarget};
 use crate::Result;
 use cargo_mobile::os;
 
@@ -8,7 +8,9 @@ pub fn command() -> Result<()> {
     |_root_conf, config, _metadata, _cli_options| {
       ensure_init(config.project_dir(), MobileTarget::Android)
         .map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
-      os::open_file_with("Android Studio", config.project_dir()).map_err(Error::OpenFailed)
+      let env = env()?;
+      os::open_file_with("Android Studio", config.project_dir(), &env.base)
+        .map_err(Error::OpenFailed)
     },
   )
   .map_err(Into::into)

+ 2 - 2
tooling/cli/src/mobile/ios.rs

@@ -153,9 +153,9 @@ fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
   device_prompt(env).map(|device| device.target()).ok()
 }
 
-fn open_and_wait(config: &AppleConfig) -> ! {
+fn open_and_wait(config: &AppleConfig, env: &Env) -> ! {
   log::info!("Opening Xcode");
-  if let Err(e) = os::open_file_with("Xcode", config.project_dir()) {
+  if let Err(e) = os::open_file_with("Xcode", config.project_dir(), env) {
     log::error!("{}", e);
   }
   loop {

+ 7 - 7
tooling/cli/src/mobile/ios/build.rs

@@ -74,11 +74,11 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
       init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?;
 
       let open = options.open;
-      run_build(options, config, env, noise_level)
+      run_build(options, config, &env, noise_level)
         .map_err(|e| Error::BuildFailed(format!("{:#}", e)))?;
 
       if open {
-        open_and_wait(config);
+        open_and_wait(config, &env);
       }
 
       Ok(())
@@ -90,7 +90,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
 fn run_build(
   mut options: Options,
   config: &AppleConfig,
-  env: Env,
+  env: &Env,
   noise_level: NoiseLevel,
 ) -> Result<()> {
   let profile = if options.debug {
@@ -135,16 +135,16 @@ fn run_build(
   call_for_targets_with_fallback(
     options.targets.iter(),
     &detect_target_ok,
-    &env,
+    env,
     |target: &Target| {
       let mut app_version = config.bundle_version().clone();
       if let Some(build_number) = options.build_number {
         app_version.push_extra(build_number);
       }
 
-      target.build(config, &env, noise_level, profile)?;
-      target.archive(config, &env, noise_level, profile, Some(app_version))?;
-      target.export(config, &env, noise_level)?;
+      target.build(config, env, noise_level, profile)?;
+      target.archive(config, env, noise_level, profile, Some(app_version))?;
+      target.export(config, env, noise_level)?;
 
       if let Ok(ipa_path) = config.ipa_path() {
         let out_dir = config.export_dir().join(target.arch);

+ 21 - 10
tooling/cli/src/mobile/ios/dev.rs

@@ -12,6 +12,7 @@ use clap::Parser;
 use cargo_mobile::{
   apple::config::Config as AppleConfig,
   config::Config,
+  env::Env,
   opts::{NoiseLevel, Profile},
 };
 
@@ -91,7 +92,12 @@ fn run_dev(
   let out_dir = bin_path.parent().unwrap();
   let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?;
 
+  let env = env()?;
+  init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?;
+
   let open = options.open;
+  let exit_on_panic = options.exit_on_panic;
+  let no_watch = options.no_watch;
   interface.mobile_dev(
     MobileOptions {
       debug: true,
@@ -108,14 +114,20 @@ fn run_dev(
         vars: Default::default(),
       };
       write_options(cli_options, &bundle_identifier, MobileTarget::Ios)?;
+
       if open {
-        open_and_wait(config)
+        open_and_wait(config, &env)
       } else {
-        match run(options, root_conf, config, noise_level) {
-          Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess>),
+        match run(options, config, &env, noise_level) {
+          Ok(c) => {
+            crate::dev::wait_dev_process(c.clone(), move |status, reason| {
+              crate::dev::on_app_exit(status, reason, exit_on_panic, no_watch)
+            });
+            Ok(Box::new(c) as Box<dyn DevProcess>)
+          }
           Err(Error::FailedToPromptForDevice(e)) => {
             log::error!("{}", e);
-            open_and_wait(config)
+            open_and_wait(config, &env)
           }
           Err(e) => Err(e.into()),
         }
@@ -126,8 +138,8 @@ fn run_dev(
 
 fn run(
   options: MobileOptions,
-  root_conf: &Config,
   config: &AppleConfig,
+  env: &Env,
   noise_level: NoiseLevel,
 ) -> Result<DevChild, Error> {
   let profile = if options.debug {
@@ -136,12 +148,11 @@ fn run(
     Profile::Release
   };
 
-  let env = env()?;
-  init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?;
+  let non_interactive = true; // ios-deploy --noninteractive (quit when app crashes or exits)
 
-  device_prompt(&env)
+  device_prompt(env)
     .map_err(Error::FailedToPromptForDevice)?
-    .run(config, &env, noise_level, false, profile)
-    .map(|c| DevChild(Some(c)))
+    .run(config, env, noise_level, non_interactive, profile)
+    .map(DevChild::new)
     .map_err(Error::RunFailed)
 }

+ 3 - 2
tooling/cli/src/mobile/ios/open.rs

@@ -1,4 +1,4 @@
-use super::{ensure_init, with_config, Error, MobileTarget};
+use super::{ensure_init, env, with_config, Error, MobileTarget};
 use crate::Result;
 use cargo_mobile::os;
 
@@ -8,7 +8,8 @@ pub fn command() -> Result<()> {
     |_root_conf, config, _metadata, _cli_options| {
       ensure_init(config.project_dir(), MobileTarget::Ios)
         .map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
-      os::open_file_with("Xcode", config.project_dir()).map_err(Error::OpenFailed)
+      let env = env()?;
+      os::open_file_with("Xcode", config.project_dir(), &env).map_err(Error::OpenFailed)
     },
   )
   .map_err(Into::into)

+ 35 - 58
tooling/cli/src/mobile/mod.rs

@@ -20,6 +20,7 @@ use cargo_mobile::{
 };
 use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
 use serde::{Deserialize, Serialize};
+use shared_child::SharedChild;
 use std::{
   collections::HashMap,
   env::set_var,
@@ -28,6 +29,10 @@ use std::{
   io::{BufRead, BufReader, Write as _},
   path::PathBuf,
   process::ExitStatus,
+  sync::{
+    atomic::{AtomicBool, Ordering},
+    Arc,
+  },
 };
 
 #[cfg(not(windows))]
@@ -40,34 +45,38 @@ mod init;
 #[cfg(target_os = "macos")]
 pub mod ios;
 
-pub struct DevChild(Option<bossy::Handle>);
+#[derive(Clone)]
+pub struct DevChild {
+  child: Arc<SharedChild>,
+  manually_killed_process: Arc<AtomicBool>,
+}
 
-impl Drop for DevChild {
-  fn drop(&mut self) {
-    // consume the handle since we're not waiting on it
-    // just to prevent a log error
-    // note that this doesn't leak any memory
-    self.0.take().unwrap().leak();
+impl DevChild {
+  fn new(handle: bossy::Handle) -> Self {
+    Self {
+      child: Arc::new(SharedChild::new(handle.into()).unwrap()),
+      manually_killed_process: Default::default(),
+    }
   }
 }
 
 impl DevProcess for DevChild {
-  fn kill(&mut self) -> std::io::Result<()> {
-    self
-      .0
-      .as_mut()
-      .unwrap()
-      .kill()
-      .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "failed to kill"))
+  fn kill(&self) -> std::io::Result<()> {
+    self.child.kill()?;
+    self.manually_killed_process.store(true, Ordering::Relaxed);
+    Ok(())
+  }
+
+  fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
+    self.child.try_wait()
+  }
+
+  fn wait(&self) -> std::io::Result<ExitStatus> {
+    self.child.wait()
   }
 
-  fn try_wait(&mut self) -> std::io::Result<Option<ExitStatus>> {
-    self
-      .0
-      .as_mut()
-      .unwrap()
-      .try_wait()
-      .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "failed to wait"))
+  fn manually_killed_process(&self) -> bool {
+    self.manually_killed_process.load(Ordering::Relaxed)
   }
 }
 
@@ -243,25 +252,15 @@ fn get_config(config: &TauriConfig, cli_options: &CliOptions) -> (Config, Metada
         .ok()
         .or_else(|| config.tauri.ios.development_team.clone())
         .expect("you must set `tauri > iOS > developmentTeam` config value or the `TAURI_APPLE_DEVELOPMENT_TEAM` environment variable"),
-      project_dir: None,
-      ios_no_default_features: None,
+
       ios_features: ios_options.features.clone(),
-      macos_no_default_features: None,
-      macos_features: None,
       bundle_version: config.package.version.clone(),
       bundle_version_short: config.package.version.clone(),
-      ios_version: None,
-      macos_version: None,
-      use_legacy_build_system: None,
-      plist_pairs: None,
-      enable_bitcode: None,
+      ..Default::default()
     }),
     android: Some(RawAndroidConfig {
-      min_sdk_version: None,
-      vulkan_validation: None,
-      project_dir: None,
-      no_default_features: None,
       features: android_options.features.clone(),
+      ..Default::default()
     }),
   };
   let config = Config::from_raw(tauri_dir(), raw).unwrap();
@@ -271,39 +270,17 @@ fn get_config(config: &TauriConfig, cli_options: &CliOptions) -> (Config, Metada
     apple: AppleMetadata {
       supported: true,
       ios: ApplePlatform {
-        no_default_features: false,
         cargo_args: Some(ios_options.args),
         features: ios_options.features,
-        libraries: None,
-        frameworks: None,
-        valid_archs: None,
-        vendor_frameworks: None,
-        vendor_sdks: None,
-        asset_catalogs: None,
-        pods: None,
-        pod_options: None,
-        additional_targets: None,
-        pre_build_scripts: None,
-        post_compile_scripts: None,
-        post_build_scripts: None,
-        command_line_arguments: None,
+        ..Default::default()
       },
       macos: Default::default(),
     },
     android: AndroidMetadata {
       supported: true,
-      no_default_features: false,
       cargo_args: Some(android_options.args),
       features: android_options.features,
-      app_sources: None,
-      app_plugins: None,
-      project_dependencies: None,
-      app_dependencies: None,
-      app_dependencies_platform: None,
-      asset_packs: None,
-      app_activity_name: None,
-      app_permissions: None,
-      app_theme_parent: None,
+      ..Default::default()
     },
   };