@@ -71,11 +71,20 @@ public actor SPMBuildSystem: BuildSystem {
7171 consumerTargetName: targetName
7272 )
7373
74+ // 6b. Discover binary XCFramework dependencies.
75+ // SPM copies pre-built .framework bundles (from binaryTarget / XCFramework
76+ // dependencies) directly into binPath. These aren't covered by the
77+ // .build/-directory scan above — they need -F (framework search path)
78+ // and -framework flags instead of -L/-l.
79+ let frameworkNames = collectFrameworks ( binPath: binPath)
80+
7481 // 7. Build compiler flags
7582 // -I <Modules> resolves dependency .swiftmodule files at compile time
7683 // -L <binPath> library search path for the archives created above
7784 // -l<Dep> per-dependency archive (lazy archive linking means only
7885 // object files actually referenced get pulled in)
86+ // -F <binPath> framework search path for binary XCFramework deps
87+ // -framework X link against a binary framework
7988 var flags : [ String ] = [
8089 " -I " , modulesDir. path,
8190 ]
@@ -85,6 +94,16 @@ public actor SPMBuildSystem: BuildSystem {
8594 flags += [ " -l \( dep) " ]
8695 }
8796 }
97+ if !frameworkNames. isEmpty {
98+ flags += [ " -F " , binPath. path]
99+ for fw in frameworkNames {
100+ flags += [ " -framework " , fw]
101+ }
102+ // Embed the framework search path as an rpath so dlopen can
103+ // find the framework at runtime (the dylib references it via
104+ // @rpath/Foo.framework/...).
105+ flags += [ " -Xlinker " , " -rpath " , " -Xlinker " , binPath. path]
106+ }
88107
89108 // Add C module include paths for targets with C shims
90109 let targetBuildDir = binPath. appendingPathComponent ( " \( targetName) .build " )
@@ -247,6 +266,34 @@ public actor SPMBuildSystem: BuildSystem {
247266 return libs
248267 }
249268
269+ /// Collect `.framework` bundles in binPath (binary XCFramework dependencies).
270+ /// Returns the framework names (e.g. ["Lottie"]) for use with `-framework`.
271+ private func collectFrameworks( binPath: URL ) -> [ String ] {
272+ let fm = FileManager . default
273+ guard
274+ let entries = try ? fm. contentsOfDirectory (
275+ at: binPath,
276+ includingPropertiesForKeys: [ . isDirectoryKey] ,
277+ options: [ . skipsHiddenFiles]
278+ )
279+ else {
280+ return [ ]
281+ }
282+
283+ var frameworks : [ String ] = [ ]
284+ for entry in entries {
285+ let name = entry. lastPathComponent
286+ guard name. hasSuffix ( " .framework " ) else { continue }
287+ var isDir : ObjCBool = false
288+ guard fm. fileExists ( atPath: entry. path, isDirectory: & isDir) , isDir. boolValue else {
289+ continue
290+ }
291+ let frameworkName = String ( name. dropLast ( " .framework " . count) )
292+ frameworks. append ( frameworkName)
293+ }
294+ return frameworks
295+ }
296+
250297 /// Recursively collect `.o` files under a target's build directory, including
251298 /// files named `Foo.swift.o` that swift build emits for Swift sources.
252299 private func collectObjectFiles( in directory: URL ) -> [ URL ] {
0 commit comments