@@ -143,20 +143,18 @@ 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 {
151- // if we can't find the binary, it might be a static framework that SwiftPM
152- // did not copy into the .build directory. we don't need to pack it anyway.
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 {
153150 break
154151 }
155- // if the magic matches one of these it's a static archive; don't embed it.
156- // https://github.com/apple/llvm-project/blob/e716ff14c46490d2da6b240806c04e2beef01f40/llvm/include/llvm/Object/Archive.h#L33
157- // swiftlint:disable:previous line_length
158- if bytes != magic && bytes != thinMagic {
159- try await packFile ( srcName: " \( name) .framework " , dstName: " Frameworks/ \( name) .framework " , sign: true )
152+ if !isStaticBinary( at: src) {
153+ try await packFile (
154+ srcName: " \( resolvedName) .framework " ,
155+ dstName: " Frameworks/ \( resolvedName) .framework " ,
156+ sign: true
157+ )
160158 }
161159 case . library( let name) :
162160 try await packFile ( srcName: " lib \( name) .dylib " , dstName: " Frameworks/lib \( name) .dylib " , sign: true )
@@ -198,6 +196,67 @@ public struct Packer: Sendable {
198196 }
199197}
200198
199+ private func findFrameworkName( for binaryTargetName: String , in binDir: URL ) -> String ? {
200+ let fm = FileManager . default
201+ guard let contents = try ? fm. contentsOfDirectory ( atPath: binDir. path) else {
202+ return nil
203+ }
204+ for item in contents where item. hasSuffix ( " .framework " ) {
205+ let frameworkName = String ( item. dropLast ( " .framework " . count) )
206+ let binaryPath = binDir
207+ . appendingPathComponent ( item)
208+ . appendingPathComponent ( frameworkName)
209+ guard fm. fileExists ( atPath: binaryPath. path) ,
210+ let handle = try ? FileHandle ( forReadingFrom: binaryPath) ,
211+ let data = try ? handle. read ( upToCount: 256 ) else {
212+ continue
213+ }
214+ try ? handle. close ( )
215+ if data. range ( of: Data ( binaryTargetName. utf8) ) != nil {
216+ return frameworkName
217+ }
218+ if frameworkName. hasPrefix ( binaryTargetName) || binaryTargetName. hasPrefix ( frameworkName) {
219+ return frameworkName
220+ }
221+ }
222+ return nil
223+ }
224+
225+ private func isStaticBinary( at url: URL ) -> Bool {
226+ guard let handle = try ? FileHandle ( forReadingFrom: url) ,
227+ let bytes = try ? handle. read ( upToCount: 8 ) else {
228+ return false
229+ }
230+ defer { try ? handle. close ( ) }
231+
232+ let archMagic = Data ( " !<arch> \n " . utf8)
233+ let thinMagic = Data ( " !<thin> \n " . utf8)
234+
235+ if bytes. starts ( with: archMagic) || bytes. starts ( with: thinMagic) {
236+ return true
237+ }
238+
239+ guard bytes. count >= 4 else { return false }
240+
241+ let magic = bytes. prefix ( 4 ) . withUnsafeBytes { $0. load ( as: UInt32 . self) . bigEndian }
242+ let fatMagic : UInt32 = 0xCAFEBABE
243+ let fatMagic64 : UInt32 = 0xCAFEBABF
244+
245+ if magic == fatMagic || magic == fatMagic64 {
246+ let is64 = ( magic == fatMagic64)
247+ try ? handle. seek ( toOffset: 16 )
248+ guard let offsetData = try ? handle. read ( upToCount: is64 ? 8 : 4 ) else { return false }
249+ let sliceOffset : UInt64 = is64
250+ ? offsetData. withUnsafeBytes { $0. load ( as: UInt64 . self) . bigEndian }
251+ : UInt64 ( offsetData. withUnsafeBytes { $0. load ( as: UInt32 . self) . bigEndian } )
252+ try ? handle. seek ( toOffset: sliceOffset)
253+ guard let sliceMagic = try ? handle. read ( upToCount: 8 ) else { return false }
254+ return sliceMagic. starts ( with: archMagic) || sliceMagic. starts ( with: thinMagic)
255+ }
256+
257+ return false
258+ }
259+
201260extension Plan . Product {
202261 fileprivate var linkerSettings : String {
203262 switch self . type {
0 commit comments