2024-03-01 11:29:01 +00:00
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2023-02-19 14:21:50 +00:00
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
2024-08-05 12:45:18 +00:00
use super ::{ ensure_init , env , get_app , get_config , read_options , MobileTarget } ;
2022-12-12 13:36:47 +00:00
use crate ::{
2025-10-02 09:58:26 +00:00
error ::{ Context , ErrorExt } ,
2026-01-17 15:52:42 +00:00
helpers ::config ::{ get_config as get_tauri_config , reload_config as reload_tauri_config } ,
2026-01-29 02:35:55 +00:00
interface ::{ AppInterface , Options as InterfaceOptions } ,
2024-08-28 15:11:50 +00:00
mobile ::ios ::LIB_OUTPUT_FILE_NAME ,
2025-10-02 09:58:26 +00:00
Error , Result ,
2022-12-12 13:36:47 +00:00
} ;
2022-08-23 00:59:17 +00:00
2025-08-27 21:05:24 +00:00
use cargo_mobile2 ::{ apple ::target ::Target , opts ::Profile , target ::TargetTrait } ;
2024-09-23 21:31:29 +00:00
use clap ::{ ArgAction , Parser } ;
2024-09-02 16:13:34 +00:00
use object ::{ Object , ObjectSymbol } ;
2022-08-23 00:59:17 +00:00
2023-05-26 16:45:20 +00:00
use std ::{
collections ::HashMap ,
2024-08-13 12:00:41 +00:00
env ::{ current_dir , set_current_dir , var , var_os } ,
2023-05-26 16:45:20 +00:00
ffi ::OsStr ,
2024-08-28 15:11:50 +00:00
fs ::read_to_string ,
2024-09-02 16:13:34 +00:00
io ::Read ,
2024-08-05 12:46:28 +00:00
path ::{ Path , PathBuf } ,
2023-05-26 16:45:20 +00:00
} ;
2022-08-23 00:59:17 +00:00
#[ derive(Debug, Parser) ]
pub struct Options {
/// Value of `PLATFORM_DISPLAY_NAME` env var
#[ clap(long) ]
platform : String ,
/// Value of `SDKROOT` env var
#[ clap(long) ]
sdk_root : PathBuf ,
2022-11-11 14:43:25 +00:00
/// Value of `FRAMEWORK_SEARCH_PATHS` env var
2024-09-23 21:31:29 +00:00
#[ clap(long, action = ArgAction::Append, num_args(0..)) ]
framework_search_paths : Vec < String > ,
2022-11-11 14:43:25 +00:00
/// Value of `GCC_PREPROCESSOR_DEFINITIONS` env var
2024-09-23 21:31:29 +00:00
#[ clap(long, action = ArgAction::Append, num_args(0..)) ]
gcc_preprocessor_definitions : Vec < String > ,
2022-11-11 14:43:25 +00:00
/// Value of `HEADER_SEARCH_PATHS` env var
2024-09-23 21:31:29 +00:00
#[ clap(long, action = ArgAction::Append, num_args(0..)) ]
header_search_paths : Vec < String > ,
2022-08-23 00:59:17 +00:00
/// 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 > ,
}
pub fn command ( options : Options ) -> 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
}
}
2025-08-28 21:02:46 +00:00
let process_path = std ::env ::current_exe ( ) . unwrap_or_default ( ) ;
// `xcode-script` is ran from the `gen/apple` folder when not using NPM/yarn/pnpm/deno.
2024-08-13 12:00:41 +00:00
// so we must change working directory to the src-tauri folder to resolve the tauri dir
2025-03-14 19:20:39 +00:00
// additionally, bun@<1.2 does not modify the current working directory, so it is also runs this script from `gen/apple`
// bun@>1.2 now actually moves the CWD to the package root so we shouldn't modify CWD in that case
// see https://bun.sh/blog/bun-v1.2#bun-run-uses-the-correct-directory
2025-08-28 21:02:46 +00:00
if ( var_os ( " npm_lifecycle_event " ) . is_none ( )
& & var_os ( " PNPM_PACKAGE_NAME " ) . is_none ( )
& & process_path . file_stem ( ) . unwrap_or_default ( ) ! = " deno " )
2025-03-14 19:20:39 +00:00
| | var ( " npm_config_user_agent " )
. is_ok_and ( | agent | agent . starts_with ( " bun/1.0 " ) | | agent . starts_with ( " bun/1.1 " ) )
2024-08-13 12:00:41 +00:00
{
2025-10-02 09:58:26 +00:00
set_current_dir (
current_dir ( )
. context ( " failed to resolve current directory " ) ?
. parent ( )
. unwrap ( )
. parent ( )
. unwrap ( ) ,
)
. unwrap ( ) ;
2023-03-16 12:27:40 +00:00
}
2022-11-11 14:43:25 +00:00
2026-01-17 15:52:42 +00:00
let dirs = crate ::helpers ::app_paths ::resolve_dirs ( ) ;
2024-08-11 12:44:15 +00:00
2022-08-23 00:59:17 +00:00
let profile = profile_from_configuration ( & options . configuration ) ;
let macos = macos_from_platform ( & options . platform ) ;
2026-01-17 15:52:42 +00:00
let mut tauri_config = get_tauri_config ( tauri_utils ::platform ::Target ::Ios , & [ ] , dirs . tauri ) ? ;
2026-01-29 02:39:00 +00:00
let cli_options = read_options ( & tauri_config ) ;
if ! cli_options . config . is_empty ( ) {
// reload config with merges from the ios dev|build script
reload_tauri_config (
& mut tauri_config ,
& cli_options
. config
. iter ( )
. map ( | conf | & conf . 0 )
. collect ::< Vec < _ > > ( ) ,
dirs . tauri ,
) ?
2025-06-28 20:16:36 +00:00
} ;
2022-08-23 00:59:17 +00:00
2026-01-29 02:39:00 +00:00
let ( config , metadata ) = get_config (
& get_app (
MobileTarget ::Ios ,
2026-01-17 15:52:42 +00:00
& tauri_config ,
2026-01-29 02:39:00 +00:00
& AppInterface ::new ( & tauri_config , None , dirs . tauri ) ? ,
2026-01-17 15:52:42 +00:00
dirs . tauri ,
2026-01-29 02:39:00 +00:00
) ,
& tauri_config ,
& [ ] ,
& cli_options ,
dirs . tauri ,
) ? ;
2024-08-05 12:45:18 +00:00
ensure_init (
& tauri_config ,
config . app ( ) ,
config . project_dir ( ) ,
MobileTarget ::Ios ,
2025-10-06 17:06:04 +00:00
std ::env ::var ( " CI " ) . is_ok ( ) ,
2024-08-05 12:45:18 +00:00
) ? ;
2023-05-26 16:45:20 +00:00
2025-03-14 15:04:58 +00:00
if ! cli_options . config . is_empty ( ) {
2026-01-17 15:52:42 +00:00
crate ::helpers ::config ::merge_config_with (
& mut tauri_config ,
2025-03-14 15:04:58 +00:00
& cli_options
. config
. iter ( )
. map ( | conf | & conf . 0 )
. collect ::< Vec < _ > > ( ) ,
) ? ;
2024-08-15 13:50:19 +00:00
}
2025-10-02 09:58:26 +00:00
let env = env ( )
. context ( " failed to load iOS environment " ) ?
. explicit_env_vars ( cli_options . vars ) ;
2023-05-26 16:45:20 +00:00
if ! options . sdk_root . is_dir ( ) {
2025-10-02 09:58:26 +00:00
crate ::error ::bail! (
2023-05-26 16:45:20 +00:00
" SDK root provided by Xcode was invalid. {} doesn't exist or isn't a directory " ,
options . sdk_root . display ( ) ,
2025-10-02 09:58:26 +00:00
) ;
2023-05-26 16:45:20 +00:00
}
let include_dir = options . sdk_root . join ( " usr/include " ) ;
if ! include_dir . is_dir ( ) {
2025-10-02 09:58:26 +00:00
crate ::error ::bail! (
2023-05-26 16:45:20 +00:00
" Include dir was invalid. {} doesn't exist or isn't a directory " ,
include_dir . display ( )
2025-10-02 09:58:26 +00:00
) ;
2023-05-26 16:45:20 +00:00
}
// Host flags that are used by build scripts
let macos_isysroot = {
let macos_sdk_root = options
. sdk_root
. join ( " ../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk " ) ;
if ! macos_sdk_root . is_dir ( ) {
2025-10-02 09:58:26 +00:00
crate ::error ::bail! ( " Invalid SDK root {} " , macos_sdk_root . display ( ) ) ;
2022-08-23 00:59:17 +00:00
}
2023-05-26 16:45:20 +00:00
format! ( " -isysroot {} " , macos_sdk_root . display ( ) )
} ;
2022-08-23 00:59:17 +00:00
2023-05-26 16:45:20 +00:00
let mut host_env = HashMap ::< & str , & OsStr > ::new ( ) ;
2022-11-11 14:43:25 +00:00
2023-05-26 16:45:20 +00:00
host_env . insert ( " RUST_BACKTRACE " , " 1 " . as_ref ( ) ) ;
2022-08-23 00:59:17 +00:00
2023-05-26 16:45:20 +00:00
host_env . insert ( " CFLAGS_x86_64_apple_darwin " , macos_isysroot . as_ref ( ) ) ;
host_env . insert ( " CXXFLAGS_x86_64_apple_darwin " , macos_isysroot . as_ref ( ) ) ;
2022-08-23 00:59:17 +00:00
2023-05-26 16:45:20 +00:00
host_env . insert (
" OBJC_INCLUDE_PATH_x86_64_apple_darwin " ,
include_dir . as_os_str ( ) ,
) ;
2022-11-11 14:43:25 +00:00
2024-09-23 21:31:29 +00:00
let framework_search_paths = options . framework_search_paths . join ( " " ) ;
host_env . insert ( " FRAMEWORK_SEARCH_PATHS " , framework_search_paths . as_ref ( ) ) ;
let gcc_preprocessor_definitions = options . gcc_preprocessor_definitions . join ( " " ) ;
2023-05-26 16:45:20 +00:00
host_env . insert (
" GCC_PREPROCESSOR_DEFINITIONS " ,
2024-09-23 21:31:29 +00:00
gcc_preprocessor_definitions . as_ref ( ) ,
2023-05-26 16:45:20 +00:00
) ;
2024-09-23 21:31:29 +00:00
let header_search_paths = options . header_search_paths . join ( " " ) ;
host_env . insert ( " HEADER_SEARCH_PATHS " , header_search_paths . as_ref ( ) ) ;
2022-11-11 14:43:25 +00:00
2023-05-26 16:45:20 +00:00
let macos_target = Target ::macos ( ) ;
2022-11-11 14:43:25 +00:00
2023-05-26 16:45:20 +00:00
let isysroot = format! ( " -isysroot {} " , options . sdk_root . display ( ) ) ;
2022-08-23 00:59:17 +00:00
2025-05-21 17:12:44 +00:00
let simulator =
options . platform = = " iOS Simulator " | | options . arches . contains ( & " Simulator " . to_string ( ) ) ;
2025-04-12 15:50:06 +00:00
let arches = if simulator {
// when compiling for the simulator, we don't need to build other targets
vec! [ if cfg! ( target_arch = " aarch64 " ) {
" arm64 "
} else {
" x86_64 "
}
. to_string ( ) ]
} else {
options . arches
} ;
2025-08-27 21:05:24 +00:00
let installed_targets =
crate ::interface ::rust ::installation ::installed_targets ( ) . unwrap_or_default ( ) ;
2025-04-12 15:50:06 +00:00
for arch in arches {
2023-05-26 16:45:20 +00:00
// Set target-specific flags
let ( env_triple , rust_triple ) = match arch . as_str ( ) {
2025-04-12 15:50:06 +00:00
" arm64 " if ! simulator = > ( " aarch64_apple_ios " , " aarch64-apple-ios " ) ,
" arm64 " if simulator = > ( " aarch64_apple_ios_sim " , " aarch64-apple-ios-sim " ) ,
2023-05-26 16:45:20 +00:00
" x86_64 " = > ( " x86_64_apple_ios " , " x86_64-apple-ios " ) ,
_ = > {
2025-10-02 09:58:26 +00:00
crate ::error ::bail! ( " Arch specified by Xcode was invalid. {arch} isn't a known arch " )
2022-12-28 18:02:07 +00:00
}
2023-05-26 16:45:20 +00:00
} ;
2023-04-13 21:26:32 +00:00
2026-01-17 15:52:42 +00:00
let interface = AppInterface ::new ( & tauri_config , Some ( rust_triple . into ( ) ) , dirs . tauri ) ? ;
2023-05-26 16:45:20 +00:00
2025-06-27 13:33:36 +00:00
let cflags = format! ( " CFLAGS_ {env_triple} " ) ;
let cxxflags = format! ( " CFLAGS_ {env_triple} " ) ;
let objc_include_path = format! ( " OBJC_INCLUDE_PATH_ {env_triple} " ) ;
2023-05-26 16:45:20 +00:00
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 ( ) ) ;
let target = if macos {
& macos_target
} else {
2025-04-12 15:50:06 +00:00
Target ::for_arch ( if arch = = " arm64 " & & simulator {
" arm64-sim "
} else {
& arch
} )
2025-10-02 09:58:26 +00:00
. with_context ( | | format! ( " Arch specified by Xcode was invalid. {arch} isn't a known arch " ) ) ?
2023-05-26 16:45:20 +00:00
} ;
2025-08-27 21:05:24 +00:00
if ! installed_targets . contains ( & rust_triple . into ( ) ) {
log ::info! ( " Installing target {} " , target . triple ( ) ) ;
2025-10-02 09:58:26 +00:00
target . install ( ) . map_err ( | error | Error ::CommandFailed {
command : " rustup target add " . to_string ( ) ,
error ,
} ) ? ;
2025-08-27 21:05:24 +00:00
}
2025-10-02 09:58:26 +00:00
target
. compile_lib (
& config ,
& metadata ,
cli_options . noise_level ,
true ,
profile ,
& env ,
target_env ,
)
. context ( " failed to compile iOS app " ) ? ;
2023-05-26 16:45:20 +00:00
2026-01-17 15:52:42 +00:00
let out_dir = interface . app_settings ( ) . out_dir (
& InterfaceOptions {
debug : matches ! ( profile , Profile ::Debug ) ,
target : Some ( rust_triple . into ( ) ) ,
.. Default ::default ( )
} ,
dirs . tauri ,
) ? ;
2023-05-26 16:45:20 +00:00
let lib_path = out_dir . join ( format! ( " lib {} .a " , config . app ( ) . lib_name ( ) ) ) ;
if ! lib_path . exists ( ) {
2025-10-02 09:58:26 +00:00
crate ::error ::bail! ( " Library not found at {}. Make sure your Cargo.toml file has a [lib] block with `crate-type = [ \" staticlib \" , \" cdylib \" , \" lib \" ]` " , lib_path . display ( ) ) ;
2022-08-23 00:59:17 +00:00
}
2023-05-26 16:45:20 +00:00
2024-09-02 16:13:34 +00:00
validate_lib ( & lib_path ) ? ;
2024-08-05 12:46:28 +00:00
2023-05-26 16:45:20 +00:00
let project_dir = config . project_dir ( ) ;
2023-11-14 01:35:43 +00:00
let externals_lib_dir = project_dir . join ( format! ( " Externals/ {arch} / {} " , profile . as_str ( ) ) ) ;
2025-10-02 09:58:26 +00:00
std ::fs ::create_dir_all ( & externals_lib_dir ) . fs_context (
" failed to create externals lib directory " ,
externals_lib_dir . clone ( ) ,
) ? ;
2024-08-28 15:11:50 +00:00
// backwards compatible lib output file name
let uses_new_lib_output_file_name = {
2025-10-02 09:58:26 +00:00
let pbxproj_path = project_dir
. join ( format! ( " {} .xcodeproj " , config . app ( ) . name ( ) ) )
. join ( " project.pbxproj " ) ;
let pbxproj_contents = read_to_string ( & pbxproj_path )
. fs_context ( " failed to read project.pbxproj file " , pbxproj_path ) ? ;
2024-08-28 15:11:50 +00:00
pbxproj_contents . contains ( LIB_OUTPUT_FILE_NAME )
} ;
let lib_output_file_name = if uses_new_lib_output_file_name {
LIB_OUTPUT_FILE_NAME . to_string ( )
} else {
format! ( " lib {} .a " , config . app ( ) . lib_name ( ) )
} ;
2025-10-02 09:58:26 +00:00
std ::fs ::copy ( & lib_path , externals_lib_dir . join ( lib_output_file_name ) ) . fs_context (
" failed to copy mobile lib file to Externals directory " ,
lib_path . to_path_buf ( ) ,
) ? ;
2023-05-26 16:45:20 +00:00
}
Ok ( ( ) )
2022-08-23 00:59:17 +00:00
}
2024-08-05 12:46:28 +00:00
fn validate_lib ( path : & Path ) -> Result < ( ) > {
2025-10-02 09:58:26 +00:00
let mut archive = ar ::Archive ::new (
std ::fs ::File ::open ( path ) . fs_context ( " failed to open mobile lib file " , path . to_path_buf ( ) ) ? ,
) ;
2024-09-02 16:13:34 +00:00
// Iterate over all entries in the archive:
while let Some ( entry ) = archive . next_entry ( ) {
let Ok ( mut entry ) = entry else {
continue ;
} ;
let mut obj_bytes = Vec ::new ( ) ;
2025-10-02 09:58:26 +00:00
entry
. read_to_end ( & mut obj_bytes )
. fs_context ( " failed to read mobile lib entry " , path . to_path_buf ( ) ) ? ;
2024-09-02 16:13:34 +00:00
2025-10-02 09:58:26 +00:00
let file = object ::File ::parse ( & * obj_bytes )
. map_err ( std ::io ::Error ::other )
. fs_context ( " failed to parse mobile lib entry " , path . to_path_buf ( ) ) ? ;
2024-09-02 16:13:34 +00:00
for symbol in file . symbols ( ) {
let Ok ( name ) = symbol . name ( ) else {
continue ;
} ;
if name . contains ( " start_app " ) {
return Ok ( ( ) ) ;
}
2024-08-05 12:46:28 +00:00
}
}
2024-09-02 16:13:34 +00:00
2025-10-02 09:58:26 +00:00
crate ::error ::bail! (
2024-09-02 16:13:34 +00:00
" Library from {} does not include required runtime symbols. This means you are likely missing the tauri::mobile_entry_point macro usage, see the documentation for more information: https://v2.tauri.app/start/migrate/from-tauri-1 " ,
path . display ( )
)
2024-08-05 12:46:28 +00:00
}