@@ -36,7 +36,7 @@ use rand::distr::{Alphanumeric, SampleString};
3636use std:: {
3737 env:: { set_current_dir, var, var_os} ,
3838 fs,
39- path:: PathBuf ,
39+ path:: { Path , PathBuf } ,
4040} ;
4141
4242#[ derive( Debug , Clone , Parser ) ]
@@ -94,6 +94,12 @@ pub struct Options {
9494 /// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.
9595 #[ clap( long) ]
9696 pub ignore_version_mismatches : bool ,
97+ /// Skip code signing when bundling the app
98+ #[ clap( long) ]
99+ pub no_sign : bool ,
100+ /// Only archive the app, skip generating the IPA.
101+ #[ clap( long) ]
102+ pub archive_only : bool ,
97103 /// Target device of this build
98104 #[ clap( skip) ]
99105 pub target_device : Option < TargetDevice > ,
@@ -154,7 +160,7 @@ impl From<Options> for BuildOptions {
154160 ci : options. ci ,
155161 skip_stapling : false ,
156162 ignore_version_mismatches : options. ignore_version_mismatches ,
157- no_sign : false ,
163+ no_sign : options . no_sign ,
158164 }
159165 }
160166}
@@ -423,20 +429,26 @@ fn run_build(
423429 }
424430
425431 let credentials = auth_credentials_from_env ( ) ?;
426- let skip_signing = credentials. is_some ( ) ;
432+ let skip_signing = options . no_sign || credentials. is_some ( ) ;
427433
428- let mut build_config = BuildConfig :: new ( ) . allow_provisioning_updates ( ) ;
429- if let Some ( credentials) = & credentials {
430- build_config = build_config
431- . authentication_credentials ( credentials. clone ( ) )
432- . skip_codesign ( ) ;
433- }
434+ if !( options. archive_only || options. no_sign ) {
435+ let mut build_config = BuildConfig :: new ( ) . allow_provisioning_updates ( ) ;
436+ if let Some ( credentials) = & credentials {
437+ build_config = build_config. authentication_credentials ( credentials. clone ( ) ) ;
438+ }
439+ if skip_signing {
440+ build_config = build_config. skip_codesign ( ) ;
441+ }
434442
435- target
436- . build ( None , config, env, noise_level, profile, build_config)
437- . context ( "failed to build iOS app" ) ?;
443+ target
444+ . build ( None , config, env, noise_level, profile, build_config)
445+ . context ( "failed to build iOS app" ) ?;
446+ }
438447
439- let mut archive_config = ArchiveConfig :: new ( ) ;
448+ let mut archive_config = ArchiveConfig :: new ( ) . allow_provisioning_updates ( ) ;
449+ if let Some ( credentials) = & credentials {
450+ archive_config = archive_config. authentication_credentials ( credentials. clone ( ) ) ;
451+ }
440452 if skip_signing {
441453 archive_config = archive_config. skip_codesign ( ) ;
442454 }
@@ -452,6 +464,15 @@ fn run_build(
452464 )
453465 . context ( "failed to archive iOS app" ) ?;
454466
467+ if options. archive_only {
468+ out_files. push (
469+ config
470+ . archive_dir ( )
471+ . join ( format ! ( "{}.xcarchive" , config. scheme( ) ) ) ,
472+ ) ;
473+ return Ok ( ( ) ) ;
474+ }
475+
455476 let out_dir = config. export_dir ( ) . join ( target. arch ) ;
456477
457478 if target. sdk == "iphonesimulator" {
@@ -469,6 +490,23 @@ fn run_build(
469490 let path = out_dir. join ( app_path. file_name ( ) . unwrap ( ) ) ;
470491 fs:: rename ( & app_path, & path) . fs_context ( "failed to rename app" , app_path) ?;
471492 out_files. push ( path) ;
493+ } else if options. no_sign {
494+ fs:: create_dir_all ( & out_dir)
495+ . fs_context ( "failed to create Xcode output directory" , out_dir. clone ( ) ) ?;
496+
497+ let app_path = config
498+ . archive_dir ( )
499+ . join ( format ! ( "{}.xcarchive" , config. scheme( ) ) )
500+ . join ( "Products" )
501+ . join ( "Applications" )
502+ . join ( config. app ( ) . stylized_name ( ) )
503+ . with_extension ( "app" ) ;
504+
505+ let ipa_path = out_dir
506+ . join ( config. app ( ) . stylized_name ( ) )
507+ . with_extension ( "ipa" ) ;
508+ create_ipa ( & app_path, & ipa_path) ?;
509+ out_files. push ( ipa_path) ;
472510 } else {
473511 // if we skipped code signing, we do not have the entitlements applied to our exported IPA
474512 // we must force sign the app binary with a dummy certificate just to preserve the entitlements
@@ -545,6 +583,62 @@ fn run_build(
545583 Ok ( handle)
546584}
547585
586+ fn create_ipa ( app_path : & Path , ipa_path : & Path ) -> Result < ( ) > {
587+ let ipa_file =
588+ fs:: File :: create ( ipa_path) . fs_context ( "failed to create IPA file" , ipa_path. to_path_buf ( ) ) ?;
589+ let mut zip = zip:: ZipWriter :: new ( ipa_file) ;
590+ let options = zip:: write:: SimpleFileOptions :: default ( )
591+ . compression_method ( zip:: CompressionMethod :: Deflated )
592+ . unix_permissions ( 0o755 ) ;
593+
594+ zip
595+ . add_directory ( "Payload/" , options)
596+ . context ( "failed to add Payload directory to zip" ) ?;
597+
598+ let mut app_files = Vec :: new ( ) ;
599+ let mut stack = vec ! [ app_path. to_path_buf( ) ] ;
600+ while let Some ( path) = stack. pop ( ) {
601+ if path. is_dir ( ) {
602+ app_files. push ( path. clone ( ) ) ;
603+ for entry in fs:: read_dir ( & path) . fs_context ( "failed to read directory" , path. clone ( ) ) ? {
604+ stack. push (
605+ entry
606+ . fs_context ( "failed to read directory entry" , path. clone ( ) ) ?
607+ . path ( ) ,
608+ ) ;
609+ }
610+ } else {
611+ app_files. push ( path) ;
612+ }
613+ }
614+
615+ for file_path in app_files {
616+ let name = file_path. strip_prefix ( app_path. parent ( ) . unwrap ( ) ) . unwrap ( ) ;
617+ let mut name_str = name. to_string_lossy ( ) . to_string ( ) ;
618+ // zip expects forward slashes
619+ if std:: path:: MAIN_SEPARATOR == '\\' {
620+ name_str = name_str. replace ( '\\' , "/" ) ;
621+ }
622+ let mut name_in_zip = format ! ( "Payload/{name_str}" ) ;
623+
624+ if file_path. is_dir ( ) {
625+ name_in_zip. push ( '/' ) ;
626+ zip
627+ . add_directory ( name_in_zip, options)
628+ . context ( "failed to add directory to zip" ) ?;
629+ } else {
630+ zip
631+ . start_file ( name_in_zip, options)
632+ . context ( "failed to start file in zip" ) ?;
633+ let mut f = fs:: File :: open ( & file_path) . fs_context ( "failed to open file" , file_path) ?;
634+ std:: io:: copy ( & mut f, & mut zip) . context ( "failed to copy file to zip" ) ?;
635+ }
636+ }
637+
638+ zip. finish ( ) . context ( "failed to finish zip" ) ?;
639+ Ok ( ( ) )
640+ }
641+
548642fn auth_credentials_from_env ( ) -> Result < Option < cargo_mobile2:: apple:: AuthCredentials > > {
549643 match (
550644 var ( "APPLE_API_KEY" ) ,
0 commit comments