diff --git a/Sources/PackLib/Packer.swift b/Sources/PackLib/Packer.swift index 9712ead..5bfca7f 100644 --- a/Sources/PackLib/Packer.swift +++ b/Sources/PackLib/Packer.swift @@ -143,20 +143,23 @@ public struct Packer: Sendable { switch command { case .bundle(let package, let target): try await packFile(srcName: "\(package)_\(target).bundle") - case .binaryTarget(let name): - let src = URL(fileURLWithPath: "\(name).framework/\(name)", relativeTo: binDir) - let magic = Data("!\n".utf8) - let thinMagic = Data("!\n".utf8) - guard let bytes = try? FileHandle(forReadingFrom: src).read(upToCount: magic.count) else { + case .binaryTarget(let name, let frameworkName): + let resolvedName = frameworkName ?? findFrameworkName(for: name, in: binDir) ?? name + let src = URL(fileURLWithPath: "\(resolvedName).framework/\(resolvedName)", relativeTo: binDir) + guard FileManager.default.fileExists(atPath: src.path) else { // if we can't find the binary, it might be a static framework that SwiftPM // did not copy into the .build directory. we don't need to pack it anyway. break } - // if the magic matches one of these it's a static archive; don't embed it. + // if the binary is a static archive, don't embed it. // https://github.com/apple/llvm-project/blob/e716ff14c46490d2da6b240806c04e2beef01f40/llvm/include/llvm/Object/Archive.h#L33 // swiftlint:disable:previous line_length - if bytes != magic && bytes != thinMagic { - try await packFile(srcName: "\(name).framework", dstName: "Frameworks/\(name).framework", sign: true) + if !isStaticBinary(at: src) { + try await packFile( + srcName: "\(resolvedName).framework", + dstName: "Frameworks/\(resolvedName).framework", + sign: true + ) } case .library(let name): try await packFile(srcName: "lib\(name).dylib", dstName: "Frameworks/lib\(name).dylib", sign: true) @@ -198,6 +201,67 @@ public struct Packer: Sendable { } } +private func findFrameworkName(for binaryTargetName: String, in binDir: URL) -> String? { + let fm = FileManager.default + guard let contents = try? fm.contentsOfDirectory(atPath: binDir.path) else { + return nil + } + for item in contents where item.hasSuffix(".framework") { + let frameworkName = String(item.dropLast(".framework".count)) + let binaryPath = binDir + .appendingPathComponent(item) + .appendingPathComponent(frameworkName) + guard fm.fileExists(atPath: binaryPath.path), + let handle = try? FileHandle(forReadingFrom: binaryPath), + let data = try? handle.read(upToCount: 256) else { + continue + } + try? handle.close() + if data.range(of: Data(binaryTargetName.utf8)) != nil { + return frameworkName + } + if frameworkName.hasPrefix(binaryTargetName) || binaryTargetName.hasPrefix(frameworkName) { + return frameworkName + } + } + return nil +} + +private func isStaticBinary(at url: URL) -> Bool { + guard let handle = try? FileHandle(forReadingFrom: url), + let bytes = try? handle.read(upToCount: 8) else { + return false + } + defer { try? handle.close() } + + let archMagic = Data("!\n".utf8) + let thinMagic = Data("!\n".utf8) + + if bytes.starts(with: archMagic) || bytes.starts(with: thinMagic) { + return true + } + + guard bytes.count >= 4 else { return false } + + let magic = bytes.prefix(4).withUnsafeBytes { $0.load(as: UInt32.self).bigEndian } + let fatMagic: UInt32 = 0xCAFEBABE + let fatMagic64: UInt32 = 0xCAFEBABF + + if magic == fatMagic || magic == fatMagic64 { + let is64 = (magic == fatMagic64) + try? handle.seek(toOffset: 16) + guard let offsetData = try? handle.read(upToCount: is64 ? 8 : 4) else { return false } + let sliceOffset: UInt64 = is64 + ? offsetData.withUnsafeBytes { $0.load(as: UInt64.self).bigEndian } + : UInt64(offsetData.withUnsafeBytes { $0.load(as: UInt32.self).bigEndian }) + try? handle.seek(toOffset: sliceOffset) + guard let sliceMagic = try? handle.read(upToCount: 8) else { return false } + return sliceMagic.starts(with: archMagic) || sliceMagic.starts(with: thinMagic) + } + + return false +} + extension Plan.Product { fileprivate var linkerSettings: String { switch self.type { diff --git a/Sources/PackLib/Planner.swift b/Sources/PackLib/Planner.swift index b3f3e93..b4e33e4 100644 --- a/Sources/PackLib/Planner.swift +++ b/Sources/PackLib/Planner.swift @@ -137,7 +137,7 @@ public struct Planner: Sendable { } guard visited.insert(targetName).inserted else { continue } if target.moduleType == "BinaryTarget" { - resources.append(.binaryTarget(name: targetName)) + resources.append(.binaryTarget(name: targetName, frameworkName: nil)) } if target.resources?.isEmpty == false { resources.append(.bundle(package: targetPackage.name, target: targetName)) @@ -299,7 +299,7 @@ public struct Plan: Sendable { public enum Resource: Codable, Sendable, Hashable { case bundle(package: String, target: String) - case binaryTarget(name: String) + case binaryTarget(name: String, frameworkName: String?) case library(name: String) case root(source: String) }