소스 검색

refactor: pull mobile config from tauri config instead of mobile.toml (#4948)

Lucas Fernandes Nogueira 3 년 전
부모
커밋
3f655d6280

+ 19 - 1
core/tauri-utils/src/config.rs

@@ -2085,6 +2085,9 @@ pub struct TauriConfig {
   /// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.
   #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
   pub macos_private_api: bool,
+  /// iOS configuration.
+  #[serde(rename = "iOS", default)]
+  pub ios: IosConfig,
 }
 
 impl TauriConfig {
@@ -2354,6 +2357,18 @@ fn default_dialog() -> bool {
   true
 }
 
+/// General configuration for the iOS target.
+#[skip_serializing_none]
+#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[cfg_attr(feature = "schema", derive(JsonSchema))]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct IosConfig {
+  /// The development team. This value is required for iOS development because code signing is enforced.
+  /// The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.
+  #[serde(alias = "development-team")]
+  pub development_team: Option<String>,
+}
+
 /// Defines the URL or assets to embed in the application.
 #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
 #[cfg_attr(feature = "schema", derive(JsonSchema))]
@@ -3443,6 +3458,7 @@ mod build {
       let system_tray = opt_lit(self.system_tray.as_ref());
       let allowlist = &self.allowlist;
       let macos_private_api = self.macos_private_api;
+      let ios = quote!(Default::default());
 
       literal_struct!(
         tokens,
@@ -3455,7 +3471,8 @@ mod build {
         security,
         system_tray,
         allowlist,
-        macos_private_api
+        macos_private_api,
+        ios
       );
     }
   }
@@ -3553,6 +3570,7 @@ mod test {
       allowlist: AllowlistConfig::default(),
       system_tray: None,
       macos_private_api: false,
+      ios: Default::default(),
     };
 
     // create a build config

+ 1 - 0
core/tauri/src/test/mod.rs

@@ -61,6 +61,7 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
         updater: Default::default(),
         system_tray: None,
         macos_private_api: false,
+        ios: Default::default(),
       },
       build: Default::default(),
       plugins: Default::default(),

+ 0 - 8
examples/api/src-tauri/mobile.toml

@@ -1,8 +0,0 @@
-[app]
-name = "api"
-stylized-name = "Tauri API"
-domain = "tauri.studio"
-template-pack = "tauri"
-
-[apple]
-development-team = "0"

+ 1 - 1
tooling/cli/Cargo.lock

@@ -272,7 +272,7 @@ dependencies = [
 [[package]]
 name = "cargo-mobile"
 version = "0.1.0"
-source = "git+https://github.com/tauri-apps/cargo-mobile?branch=feat/library#c1365b7e90d341d67cf40f4d2f5532e932631907"
+source = "git+https://github.com/tauri-apps/cargo-mobile?branch=dev#32de2201d4a607c2d4b910cc2a33d052fe852bd8"
 dependencies = [
  "bicycle",
  "bossy",

+ 2 - 2
tooling/cli/Cargo.toml

@@ -30,8 +30,8 @@ path = "src/main.rs"
 bossy = { git = "https://github.com/lucasfernog/bossy", branch = "fix/winapi-features" }
 
 [dependencies]
-#cargo-mobile = { path = "../../../cargo-mobile/", default-features = false }
-cargo-mobile = { git = "https://github.com/tauri-apps/cargo-mobile", branch = "feat/library", default-features = false }
+# cargo-mobile = { path = "../../../cargo-mobile/", default-features = false }
+cargo-mobile = { git = "https://github.com/tauri-apps/cargo-mobile", branch = "dev", default-features = false }
 bossy = "0.2"
 textwrap = { version = "0.11.0", features = ["term_size"] }
 thiserror = "1"

+ 24 - 0
tooling/cli/schema.json

@@ -144,6 +144,7 @@
             "wix": null
           }
         },
+        "iOS": {},
         "macOSPrivateApi": false,
         "pattern": {
           "use": "brownfield"
@@ -426,6 +427,15 @@
           "description": "MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.",
           "default": false,
           "type": "boolean"
+        },
+        "iOS": {
+          "description": "iOS configuration.",
+          "default": {},
+          "allOf": [
+            {
+              "$ref": "#/definitions/IosConfig"
+            }
+          ]
         }
       },
       "additionalProperties": false
@@ -2418,6 +2428,20 @@
       },
       "additionalProperties": false
     },
+    "IosConfig": {
+      "description": "General configuration for the iOS target.",
+      "type": "object",
+      "properties": {
+        "developmentTeam": {
+          "description": "The development team. This value is required for iOS development because code signing is enforced. The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.",
+          "type": [
+            "string",
+            "null"
+          ]
+        }
+      },
+      "additionalProperties": false
+    },
     "BuildConfig": {
       "description": "The Build configuration object.",
       "type": "object",

+ 119 - 24
tooling/cli/src/mobile/android.rs

@@ -3,34 +3,45 @@
 // SPDX-License-Identifier: MIT
 
 use cargo_mobile::{
-  android::config::{Config as AndroidConfig, Metadata as AndroidMetadata},
-  config::{metadata::Metadata, Config},
+  android::{
+    adb,
+    config::{Config as AndroidConfig, Metadata as AndroidMetadata},
+    device::Device,
+    env::{Env, Error as EnvError},
+    target::{BuildError, Target},
+  },
+  device::PromptError,
+  opts::{NoiseLevel, Profile},
   os,
-  util::cli::TextWrapper,
+  target::call_for_targets_with_fallback,
+  util::prompt,
 };
 use clap::{Parser, Subcommand};
 
 use super::{
-  ensure_init,
+  ensure_init, get_config, get_metadata,
   init::{command as init_command, Options as InitOptions},
-  Target,
+  Target as MobileTarget,
 };
+use crate::helpers::config::get as get_tauri_config;
 use crate::Result;
 
 pub(crate) mod project;
 
 #[derive(Debug, thiserror::Error)]
 enum Error {
+  #[error(transparent)]
+  EnvInitFailed(EnvError),
+  #[error("invalid tauri configuration: {0}")]
+  InvalidTauriConfig(String),
   #[error("{0}")]
   ProjectNotInitialized(String),
   #[error(transparent)]
-  ConfigFailed(cargo_mobile::config::LoadOrGenError),
-  #[error(transparent)]
-  MetadataFailed(cargo_mobile::config::metadata::Error),
-  #[error("Android is marked as unsupported in your configuration file")]
-  Unsupported,
-  #[error(transparent)]
   OpenFailed(os::OpenFileError),
+  #[error(transparent)]
+  BuildFailed(BuildError),
+  #[error("{0}")]
+  TargetInvalid(String),
 }
 
 #[derive(Parser)]
@@ -46,41 +57,125 @@ pub struct Cli {
   command: Commands,
 }
 
+#[derive(Debug, Parser)]
+pub struct BuildOptions {
+  /// Targets to build.
+  #[clap(
+    short,
+    long = "target",
+    multiple_occurrences(true),
+    multiple_values(true),
+    value_parser(clap::builder::PossibleValuesParser::new(["aarch64", "armv7", "i686", "x86_64"]))
+  )]
+  targets: Option<Vec<String>>,
+  /// Builds with the debug flag
+  #[clap(short, long)]
+  debug: bool,
+}
+
 #[derive(Subcommand)]
 enum Commands {
   Init(InitOptions),
+  /// Open project in Android Studio
   Open,
+  #[clap(hide(true))]
+  Build(BuildOptions),
 }
 
 pub fn command(cli: Cli) -> Result<()> {
   match cli.command {
-    Commands::Init(options) => init_command(options, Target::Android)?,
+    Commands::Init(options) => init_command(options, MobileTarget::Android)?,
     Commands::Open => open()?,
+    Commands::Build(options) => build(options)?,
   }
 
   Ok(())
 }
 
 fn with_config(
-  wrapper: &TextWrapper,
   f: impl FnOnce(&AndroidConfig, &AndroidMetadata) -> Result<(), Error>,
 ) -> Result<(), Error> {
-  let (config, _origin) =
-    Config::load_or_gen(".", true.into(), wrapper).map_err(Error::ConfigFailed)?;
-  let metadata = Metadata::load(config.app().root_dir()).map_err(Error::MetadataFailed)?;
-  if metadata.android().supported() {
-    f(config.android(), metadata.android())
-  } else {
-    Err(Error::Unsupported)
-  }
+  let tauri_config =
+    get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
+  let tauri_config_guard = tauri_config.lock().unwrap();
+  let tauri_config_ = tauri_config_guard.as_ref().unwrap();
+  let config = get_config(tauri_config_);
+  let metadata = get_metadata(tauri_config_);
+  f(config.android(), metadata.android())
 }
 
 fn open() -> Result<()> {
-  let wrapper = TextWrapper::with_splitter(textwrap::termwidth(), textwrap::NoHyphenation);
-  with_config(&wrapper, |config, _metadata| {
-    ensure_init(config.project_dir(), Target::Android)
+  with_config(|config, _metadata| {
+    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)
   })?;
   Ok(())
 }
+
+fn build(options: BuildOptions) -> Result<()> {
+  let profile = if options.debug {
+    Profile::Debug
+  } else {
+    Profile::Release
+  };
+
+  fn device_prompt<'a>(env: &'_ Env) -> Result<Device<'a>, PromptError<adb::device_list::Error>> {
+    let device_list =
+      adb::device_list(env).map_err(|cause| PromptError::detection_failed("Android", cause))?;
+    if !device_list.is_empty() {
+      let index = if device_list.len() > 1 {
+        prompt::list(
+          concat!("Detected ", "Android", " devices"),
+          device_list.iter(),
+          "device",
+          None,
+          "Device",
+        )
+        .map_err(|cause| PromptError::prompt_failed("Android", cause))?
+      } else {
+        0
+      };
+      let device = device_list.into_iter().nth(index).unwrap();
+      println!(
+        "Detected connected device: {} with target {:?}",
+        device,
+        device.target().triple,
+      );
+      Ok(device)
+    } else {
+      Err(PromptError::none_detected("Android"))
+    }
+  }
+
+  fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
+    device_prompt(env).map(|device| device.target()).ok()
+  }
+
+  with_config(|config, metadata| {
+    ensure_init(config.project_dir(), MobileTarget::Android)
+      .map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
+
+    let env = Env::new().map_err(Error::EnvInitFailed)?;
+
+    call_for_targets_with_fallback(
+      options.targets.unwrap_or_default().iter(),
+      &detect_target_ok,
+      &env,
+      |target: &Target| {
+        target
+          .build(
+            config,
+            metadata,
+            &env,
+            NoiseLevel::Polite,
+            true.into(),
+            profile,
+          )
+          .map_err(Error::BuildFailed)
+      },
+    )
+    .map_err(|e| Error::TargetInvalid(e.to_string()))?
+  })?;
+  Ok(())
+}

+ 5 - 1
tooling/cli/src/mobile/android/project.rs

@@ -71,7 +71,11 @@ pub fn gen(
   map.insert(
     "root-dir-rel",
     Path::new(&os::replace_path_separator(
-      util::relativize_path(config.app().root_dir(), config.project_dir()).into_os_string(),
+      util::relativize_path(
+        config.app().root_dir(),
+        config.project_dir().join(config.app().name()),
+      )
+      .into_os_string(),
     )),
   );
   map.insert("root-dir", config.app().root_dir());

+ 39 - 66
tooling/cli/src/mobile/init.rs

@@ -2,19 +2,13 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::Target;
-use crate::helpers::{app_paths::tauri_dir, template::JsonMap};
+use super::{get_config, get_metadata, Target};
+use crate::helpers::{app_paths::tauri_dir, config::get as get_tauri_config, template::JsonMap};
 use crate::Result;
 use cargo_mobile::{
   android,
-  config::{
-    self,
-    metadata::{self, Metadata},
-    Config,
-  },
-  dot_cargo,
-  init::{DOT_FIRST_INIT_CONTENTS, DOT_FIRST_INIT_FILE_NAME},
-  opts,
+  config::Config,
+  dot_cargo, opts,
   os::code_command,
   util::{
     self,
@@ -58,10 +52,8 @@ pub fn command(mut options: Options, target: Target) -> Result<()> {
 
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
-  #[error(transparent)]
-  ConfigLoadOrGen(config::LoadOrGenError),
-  #[error("failed to init first init file {path}: {cause}")]
-  DotFirstInitWrite { path: PathBuf, cause: io::Error },
+  #[error("invalid tauri configuration: {0}")]
+  InvalidTauriConfig(String),
   #[error("failed to create asset dir {asset_dir}: {cause}")]
   AssetDirCreation {
     asset_dir: PathBuf,
@@ -73,8 +65,6 @@ pub enum Error {
   DotCargoLoad(dot_cargo::LoadError),
   #[error(transparent)]
   HostTargetTripleDetection(util::HostTargetTripleError),
-  #[error(transparent)]
-  Metadata(metadata::Error),
   #[cfg(target_os = "macos")]
   #[error(transparent)]
   IosInit(super::ios::project::Error),
@@ -84,8 +74,6 @@ pub enum Error {
   AndroidInit(super::android::project::Error),
   #[error(transparent)]
   DotCargoWrite(dot_cargo::WriteError),
-  #[error("failed to delete first init file {path}: {cause}")]
-  DotFirstInitDelete { path: PathBuf, cause: io::Error },
   #[error(transparent)]
   OpenInEditor(util::OpenInEditorError),
 }
@@ -100,26 +88,13 @@ pub fn exec(
   cwd: impl AsRef<Path>,
 ) -> Result<Config, Error> {
   let cwd = cwd.as_ref();
-  let (config, config_origin) =
-    Config::load_or_gen(cwd, non_interactive, wrapper).map_err(Error::ConfigLoadOrGen)?;
-  let dot_first_init_path = config.app().root_dir().join(DOT_FIRST_INIT_FILE_NAME);
-  let dot_first_init_exists = {
-    let dot_first_init_exists = dot_first_init_path.exists();
-    if config_origin.freshly_minted() && !dot_first_init_exists {
-      // indicate first init is ongoing, so that if we error out and exit
-      // the next init will know to still use `WildWest` filtering
-      log::info!("creating first init dot file at {:?}", dot_first_init_path);
-      fs::write(&dot_first_init_path, DOT_FIRST_INIT_CONTENTS).map_err(|cause| {
-        Error::DotFirstInitWrite {
-          path: dot_first_init_path.clone(),
-          cause,
-        }
-      })?;
-      true
-    } else {
-      dot_first_init_exists
-    }
-  };
+  let tauri_config =
+    get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
+  let tauri_config_guard = tauri_config.lock().unwrap();
+  let tauri_config_ = tauri_config_guard.as_ref().unwrap();
+
+  let config = get_config(tauri_config_);
+  let metadata = get_metadata(tauri_config_);
 
   let asset_dir = config.app().asset_dir();
   if !asset_dir.is_dir() {
@@ -149,33 +124,24 @@ pub fn exec(
   dot_cargo
     .set_default_target(util::host_target_triple().map_err(Error::HostTargetTripleDetection)?);
 
-  let metadata = Metadata::load(config.app().root_dir()).map_err(Error::Metadata)?;
-
-  // Generate Xcode project
-  #[cfg(target_os = "macos")]
-  if target == Target::Ios && metadata.apple().supported() {
-    super::ios::project::gen(
-      config.apple(),
-      metadata.apple(),
-      handlebars(&config),
-      wrapper,
-      non_interactive,
-      skip_dev_tools,
-      reinstall_deps,
-    )
-    .map_err(Error::IosInit)?;
-  } else {
-    println!("Skipping iOS init, since it's marked as unsupported in your Cargo.toml metadata");
-  }
+  let (handlebars, mut map) = handlebars(&config);
+  // TODO: make this a relative path
+  map.insert(
+    "tauri-binary",
+    std::env::args_os()
+      .next()
+      .unwrap_or_else(|| std::ffi::OsString::from("cargo"))
+      .to_string_lossy(),
+  );
 
   // Generate Android Studio project
-  if target == Target::Android && metadata.android().supported() {
+  if target == Target::Android {
     match android::env::Env::new() {
       Ok(env) => super::android::project::gen(
         config.android(),
         metadata.android(),
         &env,
-        handlebars(&config),
+        (handlebars, map),
         wrapper,
         &mut dot_cargo,
       )
@@ -193,19 +159,26 @@ pub fn exec(
       }
     }
   } else {
-    println!("Skipping Android init, since it's marked as unsupported in your Cargo.toml metadata");
+    // Generate Xcode project
+    #[cfg(target_os = "macos")]
+    if target == Target::Ios {
+      super::ios::project::gen(
+        config.apple(),
+        metadata.apple(),
+        (handlebars, map),
+        wrapper,
+        non_interactive,
+        skip_dev_tools,
+        reinstall_deps,
+      )
+      .map_err(Error::IosInit)?;
+    }
   }
 
   dot_cargo
     .write(config.app())
     .map_err(Error::DotCargoWrite)?;
-  if dot_first_init_exists {
-    log::info!("deleting first init dot file at {:?}", dot_first_init_path);
-    fs::remove_file(&dot_first_init_path).map_err(|cause| Error::DotFirstInitDelete {
-      path: dot_first_init_path,
-      cause,
-    })?;
-  }
+
   Report::victory(
     "Project generated successfully!",
     "Make cool apps! 🌻 🐕 🎉",

+ 168 - 24
tooling/cli/src/mobile/ios.rs

@@ -2,35 +2,50 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+use crate::helpers::config::get as get_tauri_config;
 use cargo_mobile::{
-  apple::config::{Config as AppleConfig, Metadata as AppleMetadata},
-  config::{metadata::Metadata, Config},
-  os,
-  util::cli::TextWrapper,
+  apple::{
+    config::{Config as AppleConfig, Metadata as AppleMetadata},
+    target::{CompileLibError, Target},
+  },
+  env::{Env, Error as EnvError},
+  opts::{NoiseLevel, Profile},
+  os, util,
 };
 use clap::{Parser, Subcommand};
 
 use super::{
-  ensure_init,
+  ensure_init, get_config, get_metadata,
   init::{command as init_command, Options as InitOptions},
-  Target,
+  Target as MobileTarget,
 };
 use crate::Result;
+use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
 
 pub(crate) mod project;
 
 #[derive(Debug, thiserror::Error)]
 enum Error {
+  #[error(transparent)]
+  EnvInitFailed(EnvError),
+  #[error("invalid tauri configuration: {0}")]
+  InvalidTauriConfig(String),
   #[error("{0}")]
   ProjectNotInitialized(String),
   #[error(transparent)]
-  ConfigFailed(cargo_mobile::config::LoadOrGenError),
+  OpenFailed(os::OpenFileError),
   #[error(transparent)]
-  MetadataFailed(cargo_mobile::config::metadata::Error),
-  #[error("iOS is marked as unsupported in your configuration file")]
-  Unsupported,
+  NoHomeDir(util::NoHomeDir),
+  #[error("SDK root provided by Xcode was invalid. {sdk_root} doesn't exist or isn't a directory")]
+  SdkRootInvalid { sdk_root: PathBuf },
+  #[error("Include dir was invalid. {include_dir} doesn't exist or isn't a directory")]
+  IncludeDirInvalid { include_dir: PathBuf },
+  #[error("macOS SDK root was invalid. {macos_sdk_root} doesn't exist or isn't a directory")]
+  MacosSdkRootInvalid { macos_sdk_root: PathBuf },
+  #[error("Arch specified by Xcode was invalid. {arch} isn't a known arch")]
+  ArchInvalid { arch: String },
   #[error(transparent)]
-  OpenFailed(os::OpenFileError),
+  CompileLibFailed(CompileLibError),
 }
 
 #[derive(Parser)]
@@ -46,41 +61,170 @@ pub struct Cli {
   command: Commands,
 }
 
+#[derive(Debug, Parser)]
+pub struct XcodeScriptOptions {
+  /// Value of `PLATFORM_DISPLAY_NAME` env var
+  #[clap(long)]
+  platform: String,
+  /// Value of `SDKROOT` env var
+  #[clap(long)]
+  sdk_root: PathBuf,
+  /// Value of `CONFIGURATION` env var
+  #[clap(long)]
+  configuration: String,
+  /// Value of `FORCE_COLOR` env var
+  #[clap(long)]
+  force_color: bool,
+  /// Value of `ARCHS` env var
+  #[clap(index = 1, required = true)]
+  arches: Vec<String>,
+}
+
 #[derive(Subcommand)]
 enum Commands {
   Init(InitOptions),
   Open,
+  #[clap(hide(true))]
+  XcodeScript(XcodeScriptOptions),
 }
 
 pub fn command(cli: Cli) -> Result<()> {
   match cli.command {
-    Commands::Init(options) => init_command(options, Target::Ios)?,
+    Commands::Init(options) => init_command(options, MobileTarget::Ios)?,
     Commands::Open => open()?,
+    Commands::XcodeScript(options) => xcode_script(options)?,
   }
 
   Ok(())
 }
 
 fn with_config(
-  wrapper: &TextWrapper,
   f: impl FnOnce(&AppleConfig, &AppleMetadata) -> Result<(), Error>,
 ) -> Result<(), Error> {
-  let (config, _origin) =
-    Config::load_or_gen(".", true.into(), wrapper).map_err(Error::ConfigFailed)?;
-  let metadata = Metadata::load(config.app().root_dir()).map_err(Error::MetadataFailed)?;
-  if metadata.apple().supported() {
-    f(config.apple(), metadata.apple())
-  } else {
-    Err(Error::Unsupported)
-  }
+  let tauri_config =
+    get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
+  let tauri_config_guard = tauri_config.lock().unwrap();
+  let tauri_config_ = tauri_config_guard.as_ref().unwrap();
+  let config = get_config(tauri_config_);
+  let metadata = get_metadata(tauri_config_);
+  f(config.apple(), metadata.apple())
 }
 
 fn open() -> Result<()> {
-  let wrapper = TextWrapper::with_splitter(textwrap::termwidth(), textwrap::NoHyphenation);
-  with_config(&wrapper, |config, _metadata| {
-    ensure_init(config.project_dir(), Target::Ios)
+  with_config(|config, _metadata| {
+    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)
   })?;
   Ok(())
 }
+
+fn xcode_script(options: XcodeScriptOptions) -> Result<()> {
+  fn macos_from_platform(platform: &str) -> bool {
+    platform == "macOS"
+  }
+
+  fn profile_from_configuration(configuration: &str) -> Profile {
+    if configuration == "release" {
+      Profile::Release
+    } else {
+      Profile::Debug
+    }
+  }
+
+  let profile = profile_from_configuration(&options.configuration);
+  let macos = macos_from_platform(&options.platform);
+
+  with_config(|config, metadata| {
+    let env = Env::new().map_err(Error::EnvInitFailed)?;
+    // The `PATH` env var Xcode gives us is missing any additions
+    // made by the user's profile, so we'll manually add cargo's
+    // `PATH`.
+    let env = env.prepend_to_path(
+      util::home_dir()
+        .map_err(Error::NoHomeDir)?
+        .join(".cargo/bin"),
+    );
+
+    if !options.sdk_root.is_dir() {
+      return Err(Error::SdkRootInvalid {
+        sdk_root: options.sdk_root,
+      });
+    }
+    let include_dir = options.sdk_root.join("usr/include");
+    if !include_dir.is_dir() {
+      return Err(Error::IncludeDirInvalid { include_dir });
+    }
+
+    let mut host_env = HashMap::<&str, &OsStr>::new();
+
+    // Host flags that are used by build scripts
+    let (macos_isysroot, library_path) = {
+      let macos_sdk_root = options
+        .sdk_root
+        .join("../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk");
+      if !macos_sdk_root.is_dir() {
+        return Err(Error::MacosSdkRootInvalid { macos_sdk_root });
+      }
+      (
+        format!("-isysroot {}", macos_sdk_root.display()),
+        format!("{}/usr/lib", macos_sdk_root.display()),
+      )
+    };
+    host_env.insert("MAC_FLAGS", macos_isysroot.as_ref());
+    host_env.insert("CFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
+    host_env.insert("CXXFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
+
+    host_env.insert(
+      "OBJC_INCLUDE_PATH_x86_64_apple_darwin",
+      include_dir.as_os_str(),
+    );
+
+    host_env.insert("RUST_BACKTRACE", "1".as_ref());
+
+    let macos_target = Target::macos();
+
+    let isysroot = format!("-isysroot {}", options.sdk_root.display());
+
+    for arch in options.arches {
+      // Set target-specific flags
+      let triple = match arch.as_str() {
+        "arm64" => "aarch64_apple_ios",
+        "x86_64" => "x86_64_apple_ios",
+        _ => return Err(Error::ArchInvalid { arch }),
+      };
+      let cflags = format!("CFLAGS_{}", triple);
+      let cxxflags = format!("CFLAGS_{}", triple);
+      let objc_include_path = format!("OBJC_INCLUDE_PATH_{}", triple);
+      let mut target_env = host_env.clone();
+      target_env.insert(cflags.as_ref(), isysroot.as_ref());
+      target_env.insert(cxxflags.as_ref(), isysroot.as_ref());
+      target_env.insert(objc_include_path.as_ref(), include_dir.as_ref());
+      // Prevents linker errors in build scripts and proc macros:
+      // https://github.com/signalapp/libsignal-client/commit/02899cac643a14b2ced7c058cc15a836a2165b6d
+      target_env.insert("LIBRARY_PATH", library_path.as_ref());
+
+      let target = if macos {
+        &macos_target
+      } else {
+        Target::for_arch(&arch).ok_or_else(|| Error::ArchInvalid {
+          arch: arch.to_owned(),
+        })?
+      };
+      target
+        .compile_lib(
+          config,
+          metadata,
+          NoiseLevel::Polite,
+          true.into(),
+          profile,
+          &env,
+          target_env,
+        )
+        .map_err(Error::CompileLibFailed)?;
+    }
+    Ok(())
+  })?;
+
+  Ok(())
+}

+ 89 - 0
tooling/cli/src/mobile/mod.rs

@@ -2,7 +2,16 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+use crate::helpers::{app_paths::tauri_dir, config::Config as TauriConfig};
 use anyhow::{bail, Result};
+#[cfg(target_os = "macos")]
+use cargo_mobile::apple::config::{
+  Metadata as AppleMetadata, Platform as ApplePlatform, Raw as RawAppleConfig,
+};
+use cargo_mobile::{
+  android::config::{Metadata as AndroidMetadata, Raw as RawAndroidConfig},
+  config::{app::Raw as RawAppConfig, metadata::Metadata, Config, Raw},
+};
 use std::path::PathBuf;
 
 pub mod android;
@@ -35,6 +44,86 @@ impl Target {
   }
 }
 
+fn get_metadata(_config: &TauriConfig) -> Metadata {
+  Metadata {
+    #[cfg(target_os = "macos")]
+    apple: AppleMetadata {
+      supported: true,
+      ios: ApplePlatform {
+        features: None,
+        frameworks: None,
+        valid_archs: None,
+        vendor_frameworks: None,
+        vendor_sdks: None,
+        asset_catalogs: None,
+        pods: None,
+        additional_targets: None,
+        pre_build_scripts: None,
+        post_compile_scripts: None,
+        post_build_scripts: None,
+        command_line_arguments: None,
+      },
+      macos: Default::default(),
+    },
+    android: AndroidMetadata {
+      supported: true,
+      features: None,
+      app_sources: None,
+      app_plugins: None,
+      project_dependencies: None,
+      app_dependencies: None,
+      app_dependencies_platform: None,
+      asset_packs: None,
+    },
+  }
+}
+
+fn get_config(config: &TauriConfig) -> Config {
+  let mut s = config.tauri.bundle.identifier.rsplit('.');
+  let app_name = s.next().unwrap_or("app").to_string();
+  let mut domain = String::new();
+  for w in s {
+    domain.push_str(w);
+    domain.push('.');
+  }
+  domain.pop();
+  let raw = Raw {
+    app: RawAppConfig {
+      name: app_name,
+      stylized_name: config.package.product_name.clone(),
+      domain,
+      asset_dir: None,
+      template_pack: None,
+    },
+    #[cfg(target_os = "macos")]
+    apple: Some(RawAppleConfig {
+      development_team: std::env::var("APPLE_DEVELOPMENT_TEAM")
+        .ok()
+        .or_else(|| config.tauri.ios.development_team.clone())
+        .expect("you must set `tauri > iOS > developmentTeam` config value or the `APPLE_DEVELOPMENT_TEAM` environment variable"),
+      project_dir: None,
+      ios_no_default_features: None,
+      ios_features: None,
+      macos_no_default_features: None,
+      macos_features: None,
+      bundle_version: None,
+      bundle_version_short: None,
+      ios_version: None,
+      macos_version: None,
+      use_legacy_build_system: None,
+      plist_pairs: None,
+    }),
+    android: Some(RawAndroidConfig {
+      min_sdk_version: None,
+      vulkan_validation: None,
+      project_dir: None,
+      no_default_features: None,
+      features: None,
+    }),
+  };
+  Config::from_raw(tauri_dir(), raw).unwrap()
+}
+
 fn ensure_init(project_dir: PathBuf, target: Target) -> Result<()> {
   if !project_dir.exists() {
     bail!(

+ 3 - 3
tooling/cli/templates/mobile/android/buildSrc/src/main/kotlin/BuildTask.kt

@@ -37,8 +37,8 @@ open class BuildTask : DefaultTask() {
         }
         project.exec {
             workingDir(File(project.getProjectDir(), rootDirRel.getPath()))
-            executable("cargo")
-            args(listOf("android", "build"))
+            executable("{{ tauri-binary }}")
+            args(listOf("tauri", "android", "build"))
             if (project.logger.isEnabled(LogLevel.DEBUG)) {
                 args("-vv")
             } else if (project.logger.isEnabled(LogLevel.INFO)) {
@@ -47,7 +47,7 @@ open class BuildTask : DefaultTask() {
             if (release) {
                 args("--release")
             }
-            args("${target}")
+            args(listOf("--target", "${target}"))
         }.assertNormalExitValue()
     }
 }

+ 6 - 6
tooling/cli/templates/mobile/ios/project.yml

@@ -266,15 +266,15 @@ targets:
       ARCHS: [{{join ios-valid-archs}}]
       VALID_ARCHS: {{~#each ios-valid-archs}} {{this}} {{/each}}
     legacy:
-      toolPath: ${HOME}/.cargo/bin/cargo-apple
-      arguments: xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}
+      toolPath: {{ tauri-binary }}
+      arguments: ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}
       passSettings: false # prevents evil linker errors
-      workingDirectory: $(SRCROOT)/..
+      workingDirectory: $(SRCROOT)/../..
   lib_{{app.name}}_macOS:
     type: ""
     platform: macOS
     legacy:
-      toolPath: ${HOME}/.cargo/bin/cargo-apple
-      arguments: xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}
+      toolPath: {{ tauri-binary }}
+      arguments: ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}
       passSettings: false
-      workingDirectory: $(SRCROOT)/..
+      workingDirectory: $(SRCROOT)/../..