Skip to content

Commit e4c1959

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 e4c1959

2 files changed

Lines changed: 74 additions & 10 deletions

File tree

Sources/PackLib/Packer.swift

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
201265
extension Plan.Product {
202266
fileprivate var linkerSettings: String {
203267
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)