diff --git a/.changes/export-methods-for-ios-building.md b/.changes/export-methods-for-ios-building.md new file mode 100644 index 000000000000..57dc0b01e59d --- /dev/null +++ b/.changes/export-methods-for-ios-building.md @@ -0,0 +1,6 @@ +--- +@tauri-apps/cli: patch:bug +tauri-cli: patch:bug +--- + +Use correct export methods for iOS building on Xcode < 15.4 diff --git a/crates/tauri-cli/src/info/ios.rs b/crates/tauri-cli/src/info/ios.rs index d9250ed6b7d3..b500bac37594 100644 --- a/crates/tauri-cli/src/info/ios.rs +++ b/crates/tauri-cli/src/info/ios.rs @@ -2,26 +2,36 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use crate::mobile::ios::get_xcode_version; + use super::SectionItem; use colored::Colorize; pub fn items() -> Vec { - vec![SectionItem::new().action(|| { - let teams = cargo_mobile2::apple::teams::find_development_teams().unwrap_or_default(); + vec![ + SectionItem::new().action(|| { + let teams = cargo_mobile2::apple::teams::find_development_teams().unwrap_or_default(); - if teams.is_empty() { - "Developer Teams: None".red().to_string().into() - } else { - format!( - "Developer Teams: {}", - teams - .iter() - .map(|t| format!("{} (ID: {})", t.name, t.id)) - .collect::>() - .join(", ") - ) - .into() - } - })] + if teams.is_empty() { + "Developer Teams: None".red().to_string().into() + } else { + format!( + "Developer Teams: {}", + teams + .iter() + .map(|t| format!("{} (ID: {})", t.name, t.id)) + .collect::>() + .join(", ") + ) + .into() + } + }), + SectionItem::new().action(|| { + let xcode_version = get_xcode_version() + .map(|v| v.to_string()) + .unwrap_or_else(|_| "Unknown".to_string()); + format!("Xcode Version: {}", xcode_version).into() + }), + ] } diff --git a/crates/tauri-cli/src/mobile/ios/build.rs b/crates/tauri-cli/src/mobile/ios/build.rs index 7766b2d00a2d..1d86d67cf41b 100644 --- a/crates/tauri-cli/src/mobile/ios/build.rs +++ b/crates/tauri-cli/src/mobile/ios/build.rs @@ -3,9 +3,9 @@ // SPDX-License-Identifier: MIT use super::{ - detect_target_ok, ensure_init, env, get_app, get_config, inject_resources, load_pbxproj, - log_finished, merge_plist, open_and_wait, project_config, synchronize_project_config, - MobileTarget, OptionsHandle, + detect_target_ok, ensure_init, env, get_app, get_config, get_xcode_version, inject_resources, + load_pbxproj, log_finished, merge_plist, open_and_wait, project_config, + synchronize_project_config, MobileTarget, OptionsHandle, }; use crate::{ build::Options as BuildOptions, @@ -38,6 +38,8 @@ use std::{ path::PathBuf, }; +const DEPRECATED_EXPORT_METHODS_XCODE_VERSION: semver::Version = semver::Version::new(15, 4, 0); + #[derive(Debug, Clone, Parser)] #[clap( about = "Build your app in release mode for iOS and generate IPAs", @@ -80,7 +82,12 @@ pub struct Options { pub ci: bool, /// Describes how Xcode should export the archive. /// - /// Use this to create a package ready for the App Store (app-store-connect option) or TestFlight (release-testing option). + /// Use this to create a package ready for distribution. + /// for more information, see https://developer.apple.com/documentation/xcode/distributing-your-app-for-beta-testing-and-releases + /// + /// - `app-store-connect`: Distribute using TestFlight or through the App Store. + /// - `release-testing`: Distribute to a limited number of devices you register in App Store Connect. + /// - `debugging`: Package the application for local testing. #[clap(long, value_enum)] pub export_method: Option, } @@ -110,7 +117,7 @@ impl std::str::FromStr for ExportMethod { "app-store-connect" => Ok(Self::AppStoreConnect), "release-testing" => Ok(Self::ReleaseTesting), "debugging" => Ok(Self::Debugging), - _ => Err("unknown ios target"), + _ => Err("unknown ios export method"), } } } @@ -203,7 +210,24 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { let mut export_options_plist = plist::Dictionary::new(); if let Some(method) = options.export_method { - export_options_plist.insert("method".to_string(), method.to_string().into()); + let xcode_version = get_xcode_version().unwrap_or_else(|_| + // by default we'll use the deprecated method + semver::Version::new(14, 0, 0)); + + if xcode_version < DEPRECATED_EXPORT_METHODS_XCODE_VERSION { + // use the method names that were deprecated in Xcode 15.4 + export_options_plist.insert( + "method".to_string(), + match method { + ExportMethod::AppStoreConnect => "app-store".into(), + ExportMethod::ReleaseTesting => "ad-hoc".into(), + ExportMethod::Debugging => "development".into(), + }, + ); + } else { + // use the default, new method names + export_options_plist.insert("method".to_string(), method.to_string().into()); + } } let (keychain, provisioning_profile) = super::signing_from_env()?; diff --git a/crates/tauri-cli/src/mobile/ios/mod.rs b/crates/tauri-cli/src/mobile/ios/mod.rs index 8bdd88337df7..5b78b07052a6 100644 --- a/crates/tauri-cli/src/mobile/ios/mod.rs +++ b/crates/tauri-cli/src/mobile/ios/mod.rs @@ -39,6 +39,7 @@ use std::{ env::{set_var, var_os}, fs::create_dir_all, path::PathBuf, + process::Command, str::FromStr, thread::sleep, time::Duration, @@ -650,3 +651,24 @@ pub fn synchronize_project_config( Ok(()) } + +pub fn get_xcode_version() -> Result { + let output = Command::new("xcodebuild").arg("-version").output()?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.starts_with("Xcode ") { + if let Some(version) = line.split_whitespace().nth(1) { + let mut tokens = version.split('.').collect::>(); + if tokens.len() == 2 { + tokens.push("0"); + } + return Ok(semver::Version::parse(&tokens.join("."))?); + } + } + } + } + + anyhow::bail!("failed to run xcodebuild -version") +}