@@ -15,6 +15,20 @@ module FronteggSPM
1515 SPM_TARGET_DEP_ID = '7A1B2C3D4E5F6000100140001'
1616 SPM_FRAMEWORK_BUILD_ID = '7A1B2C3D4E5F6000100140002'
1717
18+ # One XCSwiftPackageProductDependency + one PBXBuildFile per app/unit-test target that links
19+ # FronteggRN (Xcode uses a distinct product-dependency object per target). Index 0..N follow the
20+ # order targets appear in the app pbxproj; three slots cover app + up to two test bundles.
21+ APP_SPM_PRODUCT_DEP_IDS = %w[
22+ 6DD78663B3B3114CC0D8A6DA
23+ 6DD78663B3B3114CC0D8A6DB
24+ 6DD78663B3B3114CC0D8A6DC
25+ ] . freeze
26+ APP_SPM_FRAMEWORK_BUILD_IDS = %w[
27+ 7A1B2C3D4E5F6000100140003
28+ 7A1B2C3D4E5F6000100140004
29+ 7A1B2C3D4E5F6000100140005
30+ ] . freeze
31+
1832 module_function
1933
2034 def link_frontegg_rn_pod ( installer )
@@ -24,7 +38,7 @@ def link_frontegg_rn_pod(installer)
2438 patch_app_pods_xcconfigs ( installer . sandbox . root )
2539
2640 app_pbxproj = user_app_pbxproj_path ( installer )
27- dedupe_app_frontegg_spm_link ( app_pbxproj ) if app_pbxproj
41+ link_frontegg_spm_into_app ( app_pbxproj ) if app_pbxproj
2842 end
2943
3044 def user_app_pbxproj_path ( installer )
@@ -53,51 +67,110 @@ def patch_app_pods_xcconfigs(pods_root)
5367 end
5468 end
5569
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 )
70+ # The app's Swift files AND every target that links FronteggRN reference FronteggSwift symbols, so
71+ # each such binary must LINK the FronteggSwift SPM product. Under use_frameworks! :static FronteggRN
72+ # builds as a static framework and does NOT embed FronteggSwift's objects, so the link has to live
73+ # on those targets themselves — otherwise the final link fails with
74+ # "Undefined symbols for architecture arm64: ... FronteggSwift.*". The application target and any
75+ # unit-test bundle (which links FronteggRN via `inherit! :complete`) need it; UI-test bundles are
76+ # black-box and link neither, so they are intentionally excluded.
77+ def link_frontegg_spm_into_app ( app_pbxproj_path )
5978 return unless File . exist? ( app_pbxproj_path )
6079
6180 content = File . read ( app_pbxproj_path )
6281 content = content . sub ( %r{^// FRONTEGG_SPM_DEDUPED_MARKER\n } , '' )
6382
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- ''
83+ targets = frontegg_link_targets ( content )
84+ return if targets . empty?
85+
86+ content = ensure_app_package_reference ( content , targets . length )
87+ targets . each_with_index do |target , idx |
88+ product_dep_id = APP_SPM_PRODUCT_DEP_IDS [ idx ]
89+ build_id = APP_SPM_FRAMEWORK_BUILD_IDS [ idx ]
90+ next unless product_dep_id && build_id
91+
92+ content = add_target_product_dependency ( content , target [ :id ] , product_dep_id )
93+ content = add_target_framework_build_file ( content , target [ :frameworks_phase_id ] , build_id , product_dep_id )
94+ end
95+
96+ File . write ( app_pbxproj_path , content )
97+ end
98+
99+ # Native targets whose final binary links FronteggRN and therefore need FronteggSwift linked too:
100+ # the application and any unit-test bundle. Returns [{id:, frameworks_phase_id:}] in pbxproj order.
101+ def frontegg_link_targets ( content )
102+ targets = [ ]
103+ content . scan ( /\t \t [A-F0-9]+ \/ \* [^*]+ \* \/ = \{ \n \t \t \t isa = PBXNativeTarget;[\s \S ]*?\n \t \t \} ;/ ) do |block |
104+ next unless block . match? ( /productType = "com\. apple\. product-type\. (?:application|bundle\. unit-test)";/ )
105+ tid = block [ /\A \t \t ([A-F0-9]+) / , 1 ]
106+ phases = block [ /buildPhases = \( \n ([\s \S ]*?)\t \t \t \) ;/ , 1 ]
107+ next unless tid && phases
108+ fid = phases [ /([A-F0-9]+) \/ \* Frameworks \* \/ / , 1 ]
109+ targets << { id : tid , frameworks_phase_id : fid } if fid
110+ end
111+ targets
112+ end
113+
114+ # Project-level Swift package resolution: one XCRemoteSwiftPackageReference + one
115+ # XCSwiftPackageProductDependency per linking target.
116+ def ensure_app_package_reference ( content , product_dep_count )
117+ return content if content . include? ( 'XCRemoteSwiftPackageReference "frontegg-ios-swift.git"' )
118+
119+ content = content . sub (
120+ /(mainGroup = [A-F0-9]+;\n )(\t \t \t productRefGroup)/ ,
121+ "\\ 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"
67122 )
68123
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- )
124+ product_deps = ( 0 ...product_dep_count ) . map do |i |
125+ "\t \t #{ APP_SPM_PRODUCT_DEP_IDS [ i ] } /* #{ PRODUCT_NAME } */ = {\n " \
126+ "\t \t \t isa = XCSwiftPackageProductDependency;\n " \
127+ "\t \t \t package = #{ PACKAGE_REF_ID } /* XCRemoteSwiftPackageReference \" frontegg-ios-swift.git\" */;\n " \
128+ "\t \t \t productName = #{ PRODUCT_NAME } ;\n " \
129+ "\t \t };\n "
130+ end . join
131+
132+ package_sections =
133+ "\n /* Begin XCRemoteSwiftPackageReference section */\n " \
134+ "\t \t #{ PACKAGE_REF_ID } /* XCRemoteSwiftPackageReference \" frontegg-ios-swift.git\" */ = {\n " \
135+ "\t \t \t isa = XCRemoteSwiftPackageReference;\n " \
136+ "\t \t \t repositoryURL = \" #{ PACKAGE_URL } \" ;\n " \
137+ "\t \t \t requirement = {\n " \
138+ "\t \t \t \t kind = exactVersion;\n " \
139+ "\t \t \t \t version = #{ PACKAGE_VERSION } ;\n " \
140+ "\t \t \t };\n " \
141+ "\t \t };\n " \
142+ "/* End XCRemoteSwiftPackageReference section */\n \n " \
143+ "/* Begin XCSwiftPackageProductDependency section */\n " \
144+ "#{ product_deps } " \
145+ "/* End XCSwiftPackageProductDependency section */\n "
74146
75- package_sections = <<~PBX
147+ content . sub ( /\n \t \} ;\n \t rootObject = / , "#{ package_sections } \n \t };\n \t rootObject = " )
148+ end
76149
77- /* Begin XCRemoteSwiftPackageReference section */
78- \t \t #{ PACKAGE_REF_ID } /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */ = {
79- \t \t \t isa = XCRemoteSwiftPackageReference;
80- \t \t \t repositoryURL = "#{ PACKAGE_URL } ";
81- \t \t \t requirement = {
82- \t \t \t \t kind = exactVersion;
83- \t \t \t \t version = #{ PACKAGE_VERSION } ;
84- \t \t \t };
85- \t \t };
86- /* End XCRemoteSwiftPackageReference section */
150+ # Attach a FronteggSwift product dependency to a specific target so Xcode links it into that binary.
151+ def add_target_product_dependency ( content , target_id , product_dep_id )
152+ block_re = /#{ Regexp . escape ( target_id ) } \/ \* [^*]+ \* \/ = \{ \n \t \t \t isa = PBXNativeTarget;[\s \S ]*?\n \t \t \} ;/
153+ block = content [ block_re ]
154+ return content if block . nil? || block . include? ( 'packageProductDependencies = (' )
87155
88- /* Begin XCSwiftPackageProductDependency section */
89- \t \t #{ PRODUCT_DEP_ID } /* #{ PRODUCT_NAME } */ = {
90- \t \t \t isa = XCSwiftPackageProductDependency;
91- \t \t \t package = #{ PACKAGE_REF_ID } /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */;
92- \t \t \t productName = #{ PRODUCT_NAME } ;
93- \t \t };
94- /* End XCSwiftPackageProductDependency section */
95- PBX
156+ content . sub (
157+ /(#{ Regexp . escape ( target_id ) } \/ \* [^*]+ \* \/ = \{ \n \t \t \t isa = PBXNativeTarget;[\s \S ]*?\n )(\t \t \t productType = )/m ,
158+ "\\ 1\t \t \t packageProductDependencies = (\n \t \t \t \t #{ product_dep_id } /* #{ PRODUCT_NAME } */,\n \t \t \t );\n \\ 2"
159+ )
160+ end
96161
97- content . sub! ( /\n \t \} ;\n \t rootObject = / , "#{ package_sections } \n \t };\n \t rootObject = " )
98- end
162+ # Add a FronteggSwift build file to a specific Frameworks (link binary) build phase.
163+ def add_target_framework_build_file ( content , phase_id , build_id , product_dep_id )
164+ return content unless phase_id
165+ return content if content . include? ( "#{ build_id } /* #{ PRODUCT_NAME } in Frameworks */" )
99166
100- File . write ( app_pbxproj_path , content )
167+ build_file = "\t \t #{ build_id } /* #{ PRODUCT_NAME } in Frameworks */ = {isa = PBXBuildFile; productRef = #{ product_dep_id } /* #{ PRODUCT_NAME } */; };\n "
168+ content = content . sub ( '/* Begin PBXBuildFile section */' , "/* Begin PBXBuildFile section */\n #{ build_file } " )
169+
170+ content . sub (
171+ /(#{ Regexp . escape ( phase_id ) } \/ \* Frameworks \* \/ = \{ [\s \S ]*?files = \( \n )/m ,
172+ "\\ 1\t \t \t \t #{ build_id } /* #{ PRODUCT_NAME } in Frameworks */,\n "
173+ )
101174 end
102175
103176 def patch_frontegg_rn_xcconfigs ( pods_root )
0 commit comments