@@ -14,6 +14,7 @@ module FronteggSPM
1414 FRONTEGG_RN_FRAMEWORKS_PHASE_ID = '46EB2E000076A0'
1515 SPM_TARGET_DEP_ID = '7A1B2C3D4E5F6000100140001'
1616 SPM_FRAMEWORK_BUILD_ID = '7A1B2C3D4E5F6000100140002'
17+ APP_SPM_FRAMEWORK_BUILD_ID = '7A1B2C3D4E5F6000100140003'
1718
1819 module_function
1920
@@ -24,7 +25,7 @@ def link_frontegg_rn_pod(installer)
2425 patch_app_pods_xcconfigs ( installer . sandbox . root )
2526
2627 app_pbxproj = user_app_pbxproj_path ( installer )
27- dedupe_app_frontegg_spm_link ( app_pbxproj ) if app_pbxproj
28+ link_frontegg_spm_into_app ( app_pbxproj ) if app_pbxproj
2829 end
2930
3031 def user_app_pbxproj_path ( installer )
@@ -53,26 +54,34 @@ def patch_app_pods_xcconfigs(pods_root)
5354 end
5455 end
5556
56- # App Swift files import FronteggSwift but must not link it (FronteggRN already does).
57- # Keep package resolution on the app project; drop target-level package products only.
58- def dedupe_app_frontegg_spm_link ( app_pbxproj_path )
57+ # The app's Swift files (and the FronteggRN pod) reference FronteggSwift symbols, so the
58+ # application binary must LINK the FronteggSwift SPM product. Under use_frameworks! :static,
59+ # FronteggRN builds as a static framework and does NOT embed FronteggSwift's objects, so the
60+ # link has to live on the application target itself — otherwise the final link fails with
61+ # "Undefined symbols for architecture arm64: ... FronteggSwift.*".
62+ def link_frontegg_spm_into_app ( app_pbxproj_path )
5963 return unless File . exist? ( app_pbxproj_path )
6064
6165 content = File . read ( app_pbxproj_path )
6266 content = content . sub ( %r{^// FRONTEGG_SPM_DEDUPED_MARKER\n } , '' )
6367
64- content = content . gsub (
65- /\n \t \t \t packageProductDependencies = \( \n \t \t \t \t #{ PRODUCT_DEP_ID } \/ \* #{ PRODUCT_NAME } \* \/ ,\n \t \t \t \) ;/ ,
66- ''
67- )
68+ content = ensure_app_package_reference ( content )
69+ content = add_app_target_product_dependency ( content )
70+ content = add_app_target_framework_build_file ( content )
6871
69- unless content . include? ( 'XCRemoteSwiftPackageReference "frontegg-ios-swift.git"' )
70- content = content . sub (
71- /(mainGroup = [A-F0-9]+;\n )(\t \t \t productRefGroup)/ ,
72- "\\ 1\t \t \t packageReferences = (\n \t \t \t \t #{ PACKAGE_REF_ID } /* XCRemoteSwiftPackageReference \" frontegg-ios-swift.git\" */,\n \t \t \t );\n \t \t \t \\ 2"
73- )
72+ File . write ( app_pbxproj_path , content )
73+ end
74+
75+ # Project-level Swift package resolution (XCRemoteSwiftPackageReference + product dependency).
76+ def ensure_app_package_reference ( content )
77+ return content if content . include? ( 'XCRemoteSwiftPackageReference "frontegg-ios-swift.git"' )
7478
75- package_sections = <<~PBX
79+ content = content . sub (
80+ /(mainGroup = [A-F0-9]+;\n )(\t \t \t productRefGroup)/ ,
81+ "\\ 1\t \t \t packageReferences = (\n \t \t \t \t #{ PACKAGE_REF_ID } /* XCRemoteSwiftPackageReference \" frontegg-ios-swift.git\" */,\n \t \t \t );\n \t \t \t \\ 2"
82+ )
83+
84+ package_sections = <<~PBX
7685
7786 /* Begin XCRemoteSwiftPackageReference section */
7887 \t \t #{ PACKAGE_REF_ID } /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */ = {
@@ -92,12 +101,49 @@ def dedupe_app_frontegg_spm_link(app_pbxproj_path)
92101 \t \t \t productName = #{ PRODUCT_NAME } ;
93102 \t \t };
94103 /* End XCSwiftPackageProductDependency section */
95- PBX
104+ PBX
96105
97- content . sub! ( /\n \t \} ;\n \t rootObject = / , "#{ package_sections } \n \t };\n \t rootObject = " )
98- end
106+ content . sub ( /\n \t \} ;\n \t rootObject = / , "#{ package_sections } \n \t };\n \t rootObject = " )
107+ end
99108
100- File . write ( app_pbxproj_path , content )
109+ # Attach the FronteggSwift product to the application target so Xcode links it into the app binary.
110+ def add_app_target_product_dependency ( content )
111+ return content if content . match? (
112+ /packageProductDependencies = \( \n \t \t \t \t #{ PRODUCT_DEP_ID } \/ \* #{ PRODUCT_NAME } \* \/ ,\n \t \t \t \) ;\n \t \t \t productType = "com\. apple\. product-type\. application";/
113+ )
114+
115+ content . sub (
116+ /(\n \t \t \t productReference = [A-F0-9]+ \/ \* [^*]+\. app \* \/ ;\n )(\t \t \t productType = "com\. apple\. product-type\. application";)/ ,
117+ "\\ 1\t \t \t packageProductDependencies = (\n \t \t \t \t #{ PRODUCT_DEP_ID } /* #{ PRODUCT_NAME } */,\n \t \t \t );\n \\ 2"
118+ )
119+ end
120+
121+ # Add FronteggSwift to the application target's Frameworks (link binary) build phase.
122+ def add_app_target_framework_build_file ( content )
123+ return content if content . include? ( "#{ APP_SPM_FRAMEWORK_BUILD_ID } /* #{ PRODUCT_NAME } in Frameworks */" )
124+
125+ phase_id = application_target_frameworks_phase_id ( content )
126+ return content unless phase_id
127+
128+ build_file = "\t \t #{ APP_SPM_FRAMEWORK_BUILD_ID } /* #{ PRODUCT_NAME } in Frameworks */ = {isa = PBXBuildFile; productRef = #{ PRODUCT_DEP_ID } /* #{ PRODUCT_NAME } */; };\n "
129+ content = content . sub ( '/* Begin PBXBuildFile section */' , "/* Begin PBXBuildFile section */\n #{ build_file } " )
130+
131+ content . sub (
132+ /(#{ Regexp . escape ( phase_id ) } \/ \* Frameworks \* \/ = \{ [\s \S ]*?files = \( \n )/m ,
133+ "\\ 1\t \t \t \t #{ APP_SPM_FRAMEWORK_BUILD_ID } /* #{ PRODUCT_NAME } in Frameworks */,\n "
134+ )
135+ end
136+
137+ # The id of the Frameworks (link binary) build phase that belongs to the application target.
138+ def application_target_frameworks_phase_id ( content )
139+ content . scan ( /\t \t [A-F0-9]+ \/ \* [^*]+ \* \/ = \{ \n \t \t \t isa = PBXNativeTarget;[\s \S ]*?\n \t \t \} ;/ ) do |block |
140+ next unless block . include? ( 'productType = "com.apple.product-type.application";' )
141+ phases = block [ /buildPhases = \( \n ([\s \S ]*?)\t \t \t \) ;/ , 1 ]
142+ next unless phases
143+ fid = phases [ /([A-F0-9]+) \/ \* Frameworks \* \/ / , 1 ]
144+ return fid if fid
145+ end
146+ nil
101147 end
102148
103149 def patch_frontegg_rn_xcconfigs ( pods_root )
0 commit comments