Skip to content

Commit 34c9d80

Browse files
committed
Fix Sentry package bugs: name mismatch and FAT static detection
Fixes #130 - Add frameworkName parameter to binaryTarget resource to handle xcframework name mismatches (e.g., Sentry-Dynamic.xcframework containing Sentry.framework) - Add findFrameworkName() to scan binDir for matching frameworks when the expected name doesn't exist - Add isStaticBinary() with FAT Mach-O detection to correctly identify FAT binaries (0xCAFEBABE/0xCAFEBABF) containing static archive slices, preventing incorrect embedding
1 parent f12a84e commit 34c9d80

2 files changed

Lines changed: 73 additions & 14 deletions

File tree

Sources/PackLib/Packer.swift

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
201260
extension Plan.Product {
202261
fileprivate var linkerSettings: String {
203262
switch self.type {

Sources/PackLib/Planner.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public struct Planner: Sendable {
137137
}
138138
guard visited.insert(targetName).inserted else { continue }
139139
if target.moduleType == "BinaryTarget" {
140-
resources.append(.binaryTarget(name: targetName))
140+
resources.append(.binaryTarget(name: targetName, frameworkName: nil))
141141
}
142142
if target.resources?.isEmpty == false {
143143
resources.append(.bundle(package: targetPackage.name, target: targetName))
@@ -299,7 +299,7 @@ public struct Plan: Sendable {
299299

300300
public enum Resource: Codable, Sendable, Hashable {
301301
case bundle(package: String, target: String)
302-
case binaryTarget(name: String)
302+
case binaryTarget(name: String, frameworkName: String?)
303303
case library(name: String)
304304
case root(source: String)
305305
}

0 commit comments

Comments
 (0)