@@ -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+ }
0 commit comments