@@ -143,20 +143,23 @@ public struct Packer: Sendable {
143143 switch command {
144144 case . bundle( let package , let target) :
145145 try await packFile ( srcName: " \( package ) _ \( target) .bundle " )
146- case . binaryTarget( let name) :
147- let src = URL ( fileURLWithPath: " \( name) .framework/ \( name) " , relativeTo: binDir)
148- let magic = Data ( " !<arch> \n " . utf8)
149- let thinMagic = Data ( " !<thin> \n " . utf8)
150- guard let bytes = try ? FileHandle ( forReadingFrom: src) . read ( upToCount: magic. count) else {
146+ case . binaryTarget( let name, let frameworkName) :
147+ let resolvedName = frameworkName ?? findFrameworkName ( for: name, in: binDir) ?? name
148+ let src = URL ( fileURLWithPath: " \( resolvedName) .framework/ \( resolvedName) " , relativeTo: binDir)
149+ guard FileManager . default. fileExists ( atPath: src. path) else {
151150 // if we can't find the binary, it might be a static framework that SwiftPM
152151 // did not copy into the .build directory. we don't need to pack it anyway.
153152 break
154153 }
155- // if the magic matches one of these it's a static archive; don't embed it.
154+ // if the binary is a static archive, don't embed it.
156155 // https://github.com/apple/llvm-project/blob/e716ff14c46490d2da6b240806c04e2beef01f40/llvm/include/llvm/Object/Archive.h#L33
157156 // swiftlint:disable:previous line_length
158- if bytes != magic && bytes != thinMagic {
159- try await packFile ( srcName: " \( name) .framework " , dstName: " Frameworks/ \( name) .framework " , sign: true )
157+ if !isStaticBinary( at: src) {
158+ try await packFile (
159+ srcName: " \( resolvedName) .framework " ,
160+ dstName: " Frameworks/ \( resolvedName) .framework " ,
161+ sign: true
162+ )
160163 }
161164 case . library( let name) :
162165 try await packFile ( srcName: " lib \( name) .dylib " , dstName: " Frameworks/lib \( name) .dylib " , sign: true )
@@ -198,6 +201,67 @@ public struct Packer: Sendable {
198201 }
199202}
200203
204+ private func findFrameworkName( for binaryTargetName: String , in binDir: URL ) -> String ? {
205+ let fm = FileManager . default
206+ guard let contents = try ? fm. contentsOfDirectory ( atPath: binDir. path) else {
207+ return nil
208+ }
209+ for item in contents where item. hasSuffix ( " .framework " ) {
210+ let frameworkName = String ( item. dropLast ( " .framework " . count) )
211+ let binaryPath = binDir
212+ . appendingPathComponent ( item)
213+ . appendingPathComponent ( frameworkName)
214+ guard fm. fileExists ( atPath: binaryPath. path) ,
215+ let handle = try ? FileHandle ( forReadingFrom: binaryPath) ,
216+ let data = try ? handle. read ( upToCount: 256 ) else {
217+ continue
218+ }
219+ try ? handle. close ( )
220+ if data. range ( of: Data ( binaryTargetName. utf8) ) != nil {
221+ return frameworkName
222+ }
223+ if frameworkName. hasPrefix ( binaryTargetName) || binaryTargetName. hasPrefix ( frameworkName) {
224+ return frameworkName
225+ }
226+ }
227+ return nil
228+ }
229+
230+ private func isStaticBinary( at url: URL ) -> Bool {
231+ guard let handle = try ? FileHandle ( forReadingFrom: url) ,
232+ let bytes = try ? handle. read ( upToCount: 8 ) else {
233+ return false
234+ }
235+ defer { try ? handle. close ( ) }
236+
237+ let archMagic = Data ( " !<arch> \n " . utf8)
238+ let thinMagic = Data ( " !<thin> \n " . utf8)
239+
240+ if bytes. starts ( with: archMagic) || bytes. starts ( with: thinMagic) {
241+ return true
242+ }
243+
244+ guard bytes. count >= 4 else { return false }
245+
246+ let magic = bytes. prefix ( 4 ) . withUnsafeBytes { $0. load ( as: UInt32 . self) . bigEndian }
247+ let fatMagic : UInt32 = 0xCAFEBABE
248+ let fatMagic64 : UInt32 = 0xCAFEBABF
249+
250+ if magic == fatMagic || magic == fatMagic64 {
251+ let is64 = ( magic == fatMagic64)
252+ try ? handle. seek ( toOffset: 16 )
253+ guard let offsetData = try ? handle. read ( upToCount: is64 ? 8 : 4 ) else { return false }
254+ let sliceOffset : UInt64 = is64
255+ ? offsetData. withUnsafeBytes { $0. load ( as: UInt64 . self) . bigEndian }
256+ : UInt64 ( offsetData. withUnsafeBytes { $0. load ( as: UInt32 . self) . bigEndian } )
257+ try ? handle. seek ( toOffset: sliceOffset)
258+ guard let sliceMagic = try ? handle. read ( upToCount: 8 ) else { return false }
259+ return sliceMagic. starts ( with: archMagic) || sliceMagic. starts ( with: thinMagic)
260+ }
261+
262+ return false
263+ }
264+
201265extension Plan . Product {
202266 fileprivate var linkerSettings : String {
203267 switch self . type {
0 commit comments