Skip to content

Commit 9ac758f

Browse files
fix(ios/spm): link FronteggSwift into every target that links FronteggRN
Under use_frameworks! :static, FronteggRN builds as a static framework and does not embed FronteggSwift's objects, so any binary that links FronteggRN — the app target AND the unit-test bundle (inherit! :complete) — linked with unresolved FronteggSwift.* symbols. frontegg_spm.rb previously *removed* the app-target SPM link assuming FronteggRN provides it. Instead link the FronteggSwift SPM product directly into each such target (one XCSwiftPackageProductDependency + a Frameworks build file per target), located generically by product type (application + bundle.unit-test); UI-test bundles link neither and are excluded. Idempotent; result is plutil-lint valid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 284ad35 commit 9ac758f

1 file changed

Lines changed: 107 additions & 34 deletions

File tree

ios/frontegg_spm.rb

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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\tpackageProductDependencies = \(\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\tisa = 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\tproductRefGroup)/,
121+
"\\1\t\t\tpackageReferences = (\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\tproductRefGroup)/,
72-
"\\1\t\t\tpackageReferences = (\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\tisa = XCSwiftPackageProductDependency;\n" \
127+
"\t\t\tpackage = #{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference \"frontegg-ios-swift.git\" */;\n" \
128+
"\t\t\tproductName = #{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\tisa = XCRemoteSwiftPackageReference;\n" \
136+
"\t\t\trepositoryURL = \"#{PACKAGE_URL}\";\n" \
137+
"\t\t\trequirement = {\n" \
138+
"\t\t\t\tkind = exactVersion;\n" \
139+
"\t\t\t\tversion = #{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\trootObject = /, "#{package_sections}\n\t};\n\trootObject = ")
148+
end
76149

77-
/* Begin XCRemoteSwiftPackageReference section */
78-
\t\t#{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */ = {
79-
\t\t\tisa = XCRemoteSwiftPackageReference;
80-
\t\t\trepositoryURL = "#{PACKAGE_URL}";
81-
\t\t\trequirement = {
82-
\t\t\t\tkind = exactVersion;
83-
\t\t\t\tversion = #{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\tisa = 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\tisa = XCSwiftPackageProductDependency;
91-
\t\t\tpackage = #{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */;
92-
\t\t\tproductName = #{PRODUCT_NAME};
93-
\t\t};
94-
/* End XCSwiftPackageProductDependency section */
95-
PBX
156+
content.sub(
157+
/(#{Regexp.escape(target_id)} \/\* [^*]+ \*\/ = \{\n\t\t\tisa = PBXNativeTarget;[\s\S]*?\n)(\t\t\tproductType = )/m,
158+
"\\1\t\t\tpackageProductDependencies = (\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\trootObject = /, "#{package_sections}\n\t};\n\trootObject = ")
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

Comments
 (0)