Skip to content
This repository was archived by the owner on Apr 11, 2024. It is now read-only.

Commit a529ad7

Browse files
authored
Merge pull request #23 from unsignedapps/debug-symbols
Include debugging symbols in the merged XCFrameworks
2 parents 2118f4a + b4ed606 commit a529ad7

File tree

5 files changed

+211
-32
lines changed

5 files changed

+211
-32
lines changed

Sources/CreateXCFramework/Command+Options.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ extension Command {
2828
@Flag(inversion: .prefixedNo, help: "Whether to clean before we build")
2929
var clean = true
3030

31+
@Flag(inversion: .prefixedNo, help: "Whether to include debug symbols in the built XCFramework")
32+
var debugSymbols = true
33+
3134
@Flag(help: "Prints the available products and targets")
3235
var listProducts = false
3336

Sources/CreateXCFramework/Command.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ struct Command: ParsableCommand {
8080
}
8181

8282
// all of our targets for each platform, then group the resulting .frameworks by target
83-
var frameworkFiles: [String: [Foundation.URL]] = [:]
83+
var frameworkFiles: [String: [XcodeBuilder.BuildResult]] = [:]
8484

8585
for sdk in sdks {
8686
try builder.build(targets: productNames, sdk: sdk)
@@ -97,7 +97,7 @@ struct Command: ParsableCommand {
9797
// then we merge the resulting frameworks
9898
try frameworkFiles
9999
.forEach { pair in
100-
xcframeworkFiles.append((pair.key, try builder.merge(target: pair.key, frameworks: pair.value)))
100+
xcframeworkFiles.append((pair.key, try builder.merge(target: pair.key, buildResults: pair.value)))
101101
}
102102

103103
// zip it up if thats what they want

Sources/CreateXCFramework/Platforms.swift

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,37 +35,78 @@ enum TargetPlatform: String, ExpressibleByArgument, CaseIterable {
3535
struct SDK {
3636
let destination: String
3737
let archiveName: String
38+
let releaseFolder: String
3839
let buildSettings: [String: String]?
3940
}
4041

4142
var sdks: [SDK] {
4243
switch self {
4344
case .ios:
4445
return [
45-
SDK(destination: "generic/platform=iOS", archiveName: "iphoneos.xcarchive", buildSettings: nil),
46-
SDK(destination: "generic/platform=iOS Simulator", archiveName: "iphonesimulator.xcarchive", buildSettings: nil)
46+
SDK (
47+
destination: "generic/platform=iOS",
48+
archiveName: "iphoneos.xcarchive",
49+
releaseFolder: "Release-iphoneos",
50+
buildSettings: nil
51+
),
52+
SDK (
53+
destination: "generic/platform=iOS Simulator",
54+
archiveName: "iphonesimulator.xcarchive",
55+
releaseFolder: "Release-iphonesimulator",
56+
buildSettings: nil
57+
)
4758
]
4859

4960
case .macos:
5061
return [
51-
SDK(destination: "platform=macOS", archiveName: "macos.xcarchive", buildSettings: nil)
62+
SDK (
63+
destination: "platform=macOS",
64+
archiveName: "macos.xcarchive",
65+
releaseFolder: "Release",
66+
buildSettings: nil
67+
)
5268
]
5369

5470
case .maccatalyst:
5571
return [
56-
SDK(destination: "platform=macOS,variant=Mac Catalyst", archiveName: "maccatalyst.xcarchive", buildSettings: [ "SUPPORTS_MACCATALYST": "YES" ])
72+
SDK (
73+
destination: "platform=macOS,variant=Mac Catalyst",
74+
archiveName: "maccatalyst.xcarchive",
75+
releaseFolder: "Release-maccatalyst",
76+
buildSettings: [ "SUPPORTS_MACCATALYST": "YES" ]
77+
)
5778
]
5879

5980
case .tvos:
6081
return [
61-
SDK(destination: "generic/platform=tvOS", archiveName: "appletvos.xcarchive", buildSettings: nil),
62-
SDK(destination: "generic/platform=tvOS Simulator", archiveName: "appletvsimulator.xcarchive", buildSettings: nil)
82+
SDK (
83+
destination: "generic/platform=tvOS",
84+
archiveName: "appletvos.xcarchive",
85+
releaseFolder: "Release-appletvos",
86+
buildSettings: nil
87+
),
88+
SDK (
89+
destination: "generic/platform=tvOS Simulator",
90+
archiveName: "appletvsimulator.xcarchive",
91+
releaseFolder: "Release-appletvsimulator",
92+
buildSettings: nil
93+
)
6394
]
6495

6596
case .watchos:
6697
return [
67-
SDK(destination: "generic/platform=watchOS", archiveName: "watchos.xcarchive", buildSettings: nil),
68-
SDK(destination: "generic/platform=watchOS Simulator", archiveName: "watchsimulator.xcarchive", buildSettings: nil)
98+
SDK (
99+
destination: "generic/platform=watchOS",
100+
archiveName: "watchos.xcarchive",
101+
releaseFolder: "Release-watchos",
102+
buildSettings: nil
103+
),
104+
SDK (
105+
destination: "generic/platform=watchOS Simulator",
106+
archiveName: "watchsimulator.xcarchive",
107+
releaseFolder: "Release-watchsimulator",
108+
buildSettings: nil
109+
)
69110
]
70111
}
71112
}

Sources/CreateXCFramework/XcodeBuilder.swift

Lines changed: 155 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,23 @@ struct XcodeBuilder {
5757
switch result.exitStatus {
5858
case let .terminated(code: code):
5959
if code != 0 {
60-
throw Error.nonZeroExit(code)
60+
throw Error.nonZeroExit("xcodebuild", code)
6161
}
6262
case let .signalled(signal: signal):
63-
throw Error.signalExit(signal)
63+
throw Error.signalExit("xcodebuild", signal)
6464
}
6565
}
6666

6767

6868
// MARK: - Build
6969

70-
func build (targets: [String], sdk: TargetPlatform.SDK) throws -> [String: Foundation.URL] {
70+
struct BuildResult {
71+
let target: String
72+
let frameworkPath: Foundation.URL
73+
let debugSymbolsPath: Foundation.URL
74+
}
75+
76+
func build (targets: [String], sdk: TargetPlatform.SDK) throws -> [String: BuildResult] {
7177
for target in targets {
7278
let process = TSCBasic.Process (
7379
arguments: try self.buildCommand(target: target, sdk: sdk),
@@ -80,16 +86,20 @@ struct XcodeBuilder {
8086
switch result.exitStatus {
8187
case let .terminated(code: code):
8288
if code != 0 {
83-
throw Error.nonZeroExit(code)
89+
throw Error.nonZeroExit("xcodebuild", code)
8490
}
8591
case let .signalled(signal: signal):
86-
throw Error.signalExit(signal)
92+
throw Error.signalExit("xcodebuild", signal)
8793
}
8894
}
8995

9096
return targets
91-
.reduce(into: [String: Foundation.URL]()) { dict, name in
92-
dict[name] = self.frameworkPath(target: name, sdk: sdk)
97+
.reduce(into: [String: BuildResult]()) { dict, name in
98+
dict[name] = BuildResult (
99+
target: name,
100+
frameworkPath: self.frameworkPath(target: name, sdk: sdk),
101+
debugSymbolsPath: self.debugSymbolsPath(target: name, sdk: sdk)
102+
)
93103
}
94104
}
95105

@@ -131,17 +141,103 @@ struct XcodeBuilder {
131141
.absoluteURL
132142
}
133143

144+
// MARK: - Debug Symbols
145+
146+
private func debugSymbolsPath (target: String, sdk: TargetPlatform.SDK) -> Foundation.URL {
147+
return self.buildDirectory
148+
.appendingPathComponent(sdk.releaseFolder)
149+
}
150+
151+
private func dSYMPath (target: String, path: Foundation.URL) -> Foundation.URL {
152+
return path
153+
.appendingPathComponent("\(self.productName(target: target)).framework.dSYM")
154+
}
155+
156+
private func dwarfPath (target: String, path: Foundation.URL) -> Foundation.URL {
157+
return path
158+
.appendingPathComponent("Contents/Resources/DWARF")
159+
.appendingPathComponent(self.productName(target: target))
160+
}
161+
162+
private func debugSymbolFiles (target: String, path: Foundation.URL) throws -> [Foundation.URL] {
163+
164+
// if there is no dSYM directory there is no point continuing
165+
let dsym = self.dSYMPath(target: target, path: path)
166+
guard FileManager.default.fileExists(atPath: dsym.absoluteURL.path) else {
167+
return []
168+
}
169+
170+
var files = [
171+
dsym
172+
]
173+
174+
// if we have a dwarf file we can inspect that to get the slice UUIDs
175+
let dwarf = self.dwarfPath(target: target, path: dsym)
176+
guard FileManager.default.fileExists(atPath: dwarf.absoluteURL.path) else {
177+
return files
178+
}
179+
180+
// get the UUID of the slices in the DWARF
181+
let identifiers = try self.binarySliceIdentifiers(file: dwarf)
182+
183+
// They should be bcsymbolmap files in the debug dir
184+
for identifier in identifiers {
185+
let file = "\(identifier.uuidString.uppercased()).bcsymbolmap"
186+
files.append(path.appendingPathComponent(file))
187+
}
188+
189+
return files
190+
}
191+
192+
private func binarySliceIdentifiers (file: Foundation.URL) throws -> [UUID] {
193+
let command = [
194+
"xcrun",
195+
"dwarfdump",
196+
"--uuid",
197+
file.absoluteURL.path
198+
]
199+
200+
let process = TSCBasic.Process (
201+
arguments: command,
202+
outputRedirection: .collect
203+
)
204+
205+
try process.launch()
206+
let result = try process.waitUntilExit()
207+
208+
switch result.exitStatus {
209+
case let .terminated(code: code):
210+
if code != 0 {
211+
throw Error.nonZeroExit("dwarfdump", code)
212+
}
213+
214+
case let .signalled(signal: signal):
215+
throw Error.signalExit("dwarfdump", signal)
216+
}
217+
218+
switch result.output {
219+
case let .success(output):
220+
guard let string = String(bytes: output, encoding: .utf8) else {
221+
return []
222+
}
223+
return try string.sliceIdentifiers()
224+
225+
case let .failure(error):
226+
throw Error.errorThrown("dwarfdump", error)
227+
}
228+
}
229+
134230

135231
// MARK: - Merging
136232

137-
func merge (target: String, frameworks: [Foundation.URL]) throws -> Foundation.URL {
233+
func merge (target: String, buildResults: [BuildResult]) throws -> Foundation.URL {
138234
let outputPath = self.xcframeworkPath(target: target)
139235

140236
// try to remove it if its already there, otherwise we're going to get errors
141237
try? FileManager.default.removeItem(at: outputPath)
142238

143239
let process = TSCBasic.Process (
144-
arguments: self.mergeCommand(outputPath: outputPath, frameworks: frameworks),
240+
arguments: try self.mergeCommand(outputPath: outputPath, buildResults: buildResults),
145241
outputRedirection: .none
146242
)
147243

@@ -151,27 +247,42 @@ struct XcodeBuilder {
151247
switch result.exitStatus {
152248
case let .terminated(code: code):
153249
if code != 0 {
154-
throw Error.nonZeroExit(code)
250+
throw Error.nonZeroExit("xcodebuild", code)
155251
}
156252
case let .signalled(signal: signal):
157-
throw Error.signalExit(signal)
253+
throw Error.signalExit("xcodebuild", signal)
158254
}
159255

160256
return outputPath
161257
}
162258

163-
private func mergeCommand (outputPath: Foundation.URL, frameworks: [Foundation.URL]) -> [String] {
259+
private func mergeCommand (outputPath: Foundation.URL, buildResults: [BuildResult]) throws -> [String] {
164260
var command: [String] = [
165261
"xcrun",
166262
"xcodebuild",
167263
"-create-xcframework"
168264
]
169265

170-
// add our frameworks
171-
command += frameworks.flatMap { [ "-framework", $0.path ] }
266+
// add our frameworks and any debugging symbols
267+
command += try buildResults.flatMap { result -> [String] in
268+
var args = [ "-framework", result.frameworkPath.absoluteURL.path ]
269+
270+
if self.package.options.debugSymbols {
271+
let symbolFiles = try self.debugSymbolFiles(target: result.target, path: result.debugSymbolsPath)
272+
for file in symbolFiles {
273+
if FileManager.default.fileExists(atPath: file.absoluteURL.path) {
274+
args += [ "-debug-symbols", file.absoluteURL.path ]
275+
}
276+
}
277+
278+
}
279+
280+
return args
281+
}
172282

173283
// and the output
174284
command += [ "-output", outputPath.path ]
285+
175286
return command
176287
}
177288

@@ -193,15 +304,18 @@ struct XcodeBuilder {
193304
// MARK: - Errors
194305

195306
enum Error: Swift.Error, LocalizedError {
196-
case nonZeroExit(Int32)
197-
case signalExit(Int32)
307+
case nonZeroExit(String, Int32)
308+
case signalExit(String, Int32)
309+
case errorThrown(String, Swift.Error)
198310

199311
var errorDescription: String? {
200312
switch self {
201-
case let .nonZeroExit(code):
202-
return "xcodebuild exited with a non-zero code: \(code)"
203-
case let .signalExit(signal):
204-
return "xcodebuild exited due to signal: \(signal)"
313+
case let .nonZeroExit(command, code):
314+
return "\(command) exited with a non-zero code: \(code)"
315+
case let .signalExit(command, signal):
316+
return "\(command) exited due to signal: \(signal)"
317+
case let .errorThrown(command, error):
318+
return "\(command) returned unexpected error: \(error)"
205319
}
206320
}
207321
}
@@ -218,3 +332,24 @@ extension BuildConfiguration {
218332
}
219333
}
220334
}
335+
336+
private extension String {
337+
func sliceIdentifiers() throws -> [UUID] {
338+
let regex = try NSRegularExpression(pattern: #"^UUID: ([a-zA-Z0-9\-]+)"#, options: .anchorsMatchLines)
339+
let matches = regex.matches(in: self, options: [], range: NSRange(location: 0, length: self.count))
340+
341+
guard matches.isEmpty == false else {
342+
return []
343+
}
344+
345+
return matches
346+
.compactMap { match in
347+
let nsrange = match.range(at: 1)
348+
guard let range = Range(nsrange, in: self) else {
349+
return nil
350+
}
351+
return String(self[range])
352+
}
353+
.compactMap(UUID.init(uuidString:))
354+
}
355+
}

Sources/CreateXCFramework/Zipper.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ struct Zipper {
4141
switch result.exitStatus {
4242
case let .terminated(code: code):
4343
if code != 0 {
44-
throw XcodeBuilder.Error.nonZeroExit(code)
44+
throw XcodeBuilder.Error.nonZeroExit("ditto", code)
4545
}
4646
case let .signalled(signal: signal):
47-
throw XcodeBuilder.Error.signalExit(signal)
47+
throw XcodeBuilder.Error.signalExit("ditto", signal)
4848
}
4949

5050
return zipURL

0 commit comments

Comments
 (0)