@@ -34,6 +34,7 @@ actor BuildGraphService {
3434
3535 private let projectName : String
3636 private var snapshotCache : BuildGraphSnapshot ?
37+ private var warmupTasksByScheme : [ String : Task < Void , Error > ]
3738
3839 init (
3940 xcodebuild: any XcodeBuildClient ,
@@ -44,6 +45,7 @@ actor BuildGraphService {
4445 self . logger = logger
4546 self . configProvider = configProvider
4647 projectName = URL ( filePath: FileManager . default. currentDirectoryPath) . lastPathComponent
48+ warmupTasksByScheme = [ : ]
4749 }
4850
4951 func snapshot( decoder: JSONDecoder ) async throws -> BuildGraphSnapshot {
@@ -57,8 +59,34 @@ actor BuildGraphService {
5759 }
5860
5961 func refresh( decoder: JSONDecoder , checkCache: Bool ) async throws -> BuildGraphRefreshResult {
62+ let refreshStart = Date ( )
63+ var checkpointStart = refreshStart
64+ var refreshSummary = " failed "
65+
66+ func durationMs( since start: Date ) -> Int {
67+ Int ( Date ( ) . timeIntervalSince ( start) * 1_000 )
68+ }
69+
70+ func logCheckpoint( _ name: String ) {
71+ let now = Date ( )
72+ let stepMs = Int ( now. timeIntervalSince ( checkpointStart) * 1_000 )
73+ let totalMs = Int ( now. timeIntervalSince ( refreshStart) * 1_000 )
74+ logger. debug (
75+ " build graph refresh checkpoint \( name) (stepMs: \( stepMs) , totalMs: \( totalMs) ) "
76+ )
77+ checkpointStart = now
78+ }
79+
80+ logger. debug ( " build graph refresh started (checkCache: \( checkCache) ) " )
81+ defer {
82+ logger. debug (
83+ " build graph refresh finished (status: \( refreshSummary) , totalMs: \( durationMs ( since: refreshStart) ) ) "
84+ )
85+ }
86+
6087 let config = try configProvider. load ( decoder: decoder)
6188 let schemes = try resolveSchemes ( config: config, checkCache: checkCache)
89+ logCheckpoint ( " config+schemes (schemes: \( schemes. count) ) " )
6290
6391 var targets : [ BuildGraphTarget ] = [ ]
6492 var filesByTargetURI : [ String : [ String ] ] = [ : ]
@@ -125,6 +153,7 @@ actor BuildGraphService {
125153 }
126154 }
127155 }
156+ logCheckpoint ( " build targets+options (targets: \( targets. count) , filesByTarget: \( filesByTargetURI. count) ) " )
128157
129158 let targetsByFilePath = targetsByFile. mapValues { Array ( $0) . sorted ( ) }
130159
@@ -136,12 +165,14 @@ actor BuildGraphService {
136165 }
137166 }
138167 }
168+ logCheckpoint ( " flatten options by file (files: \( optionsByFilePath. count) ) " )
139169
140170 let indexStorePath = preferredIndexStorePath (
141171 candidates: indexStoreCandidates,
142172 schemes: schemes,
143173 checkCache: checkCache
144174 )
175+ logCheckpoint ( " resolve index store path " )
145176
146177 let snapshot = BuildGraphSnapshot (
147178 targets: targets. sorted ( by: { $0. uri < $1. uri } ) ,
@@ -157,6 +188,7 @@ actor BuildGraphService {
157188
158189 let changedTargetURIs = changedTargetURIs ( previous: previous, current: snapshot)
159190 let changedOptionsByFilePath = changedOptionsByFilePath ( previous: previous, current: snapshot)
191+ logCheckpoint ( " compute snapshot diff (changedTargets: \( changedTargetURIs. count) , changedFiles: \( changedOptionsByFilePath. count) ) " )
160192
161193 logger. trace (
162194 """
@@ -166,6 +198,13 @@ actor BuildGraphService {
166198 """
167199 )
168200
201+ refreshSummary =
202+ """
203+ success, checkCache: \( checkCache) , schemes: \( schemes. count) , targets: \( snapshot. targets. count) , \
204+ filesByTarget: \( snapshot. filesByTargetURI. count) , optionsByFile: \( snapshot. optionsByFilePath. count) , \
205+ changedTargets: \( changedTargetURIs. count) , changedFiles: \( changedOptionsByFilePath. count)
206+ """
207+
169208 return BuildGraphRefreshResult (
170209 snapshot: snapshot,
171210 changedTargetURIs: changedTargetURIs,
@@ -176,6 +215,27 @@ actor BuildGraphService {
176215 func invalidate( ) {
177216 snapshotCache = nil
178217 }
218+
219+ func warmupBuild( forScheme scheme: String ) async throws {
220+ if let task = warmupTasksByScheme [ scheme] {
221+ try await task. value
222+ return
223+ }
224+
225+ let task = Task < Void , Error > { [ xcodebuild] in
226+ try xcodebuild. warmupBuild ( forScheme: scheme)
227+ }
228+ warmupTasksByScheme [ scheme] = task
229+
230+ do {
231+ try await task. value
232+ } catch {
233+ warmupTasksByScheme [ scheme] = nil
234+ throw error
235+ }
236+
237+ warmupTasksByScheme [ scheme] = nil
238+ }
179239}
180240
181241extension BuildGraphService {
@@ -198,22 +258,8 @@ extension BuildGraphService {
198258 }
199259
200260 private func settingsForIndex( forScheme scheme: String , checkCache: Bool ) throws -> XcodeBuild . SettingsForIndex {
201- do {
202- logger. trace ( " invoking xcodebuild.settingsForIndex(forScheme: \( scheme) , checkCache: \( checkCache) ) " )
203- let settings = try xcodebuild. settingsForIndex ( forScheme: scheme, checkCache: checkCache)
204- if settings. isEmpty, checkCache {
205- logger. trace ( " invoking xcodebuild.settingsForIndex(forScheme: \( scheme) , checkCache: false) because cached settings are empty " )
206- return try xcodebuild. settingsForIndex ( forScheme: scheme, checkCache: false )
207- }
208- return settings
209- } catch {
210- guard checkCache else {
211- throw error
212- }
213-
214- logger. trace ( " invoking xcodebuild.settingsForIndex(forScheme: \( scheme) , checkCache: false) after failure with cached result " )
215- return try xcodebuild. settingsForIndex ( forScheme: scheme, checkCache: false )
216- }
261+ logger. trace ( " invoking xcodebuild.settingsForIndex(forScheme: \( scheme) , checkCache: \( checkCache) ) " )
262+ return try xcodebuild. settingsForIndex ( forScheme: scheme, checkCache: checkCache)
217263 }
218264
219265 private func makeTargetURI( scheme: String , target: String ? ) -> String {
@@ -283,9 +329,22 @@ extension BuildGraphService {
283329 }
284330
285331 private func sanitizedCompilerArguments( from settings: XcodeBuild . FileSettings ) -> [ String ] {
286- var arguments = settings. swiftASTCommandArguments ?? [ ]
287- arguments. append ( contentsOf: settings. clangASTCommandArguments ?? [ ] )
288- arguments. append ( contentsOf: settings. clangPCHCommandArguments ?? [ ] )
332+ var arguments = stripCompilerExecutable (
333+ from: settings. swiftASTCommandArguments ?? [ ] ,
334+ expectedCompilers: [ " swiftc " , " swift-frontend " ]
335+ )
336+ arguments. append (
337+ contentsOf: stripCompilerExecutable (
338+ from: settings. clangASTCommandArguments ?? [ ] ,
339+ expectedCompilers: [ " clang " , " clang++ " , " cc " , " c++ " ]
340+ )
341+ )
342+ arguments. append (
343+ contentsOf: stripCompilerExecutable (
344+ from: settings. clangPCHCommandArguments ?? [ ] ,
345+ expectedCompilers: [ " clang " , " clang++ " , " cc " , " c++ " ]
346+ )
347+ )
289348
290349 arguments = arguments. filter { $0 != " -use-frontend-parseable-output " }
291350 for (index, argument) in arguments. enumerated ( ) . reversed ( ) {
@@ -300,6 +359,33 @@ extension BuildGraphService {
300359 return removeMissingSDK ( arguments: arguments)
301360 }
302361
362+ private func stripCompilerExecutable( from arguments: [ String ] , expectedCompilers: [ String ] ) -> [ String ] {
363+ let commands = Set ( expectedCompilers. map { $0. lowercased ( ) } )
364+ guard arguments. isEmpty == false else {
365+ return arguments
366+ }
367+
368+ if arguments. count > 1 , isCommand ( arguments [ 0 ] , oneOf: [ " xcrun " ] ) , isCommand ( arguments [ 1 ] , oneOf: commands) {
369+ return Array ( arguments. dropFirst ( 2 ) )
370+ }
371+
372+ if isCommand ( arguments [ 0 ] , oneOf: commands) {
373+ return Array ( arguments. dropFirst ( ) )
374+ }
375+
376+ return arguments
377+ }
378+
379+ private func isCommand( _ argument: String , oneOf commands: Set < String > ) -> Bool {
380+ let lowercased = argument. lowercased ( )
381+ if commands. contains ( lowercased) {
382+ return true
383+ }
384+
385+ let basename = URL ( filePath: argument) . lastPathComponent. lowercased ( )
386+ return commands. contains ( basename)
387+ }
388+
303389 private func removeMissingSDK( arguments: [ String ] ) -> [ String ] {
304390 var sanitized = arguments
305391 for (index, argument) in arguments. enumerated ( ) . reversed ( ) {
@@ -317,6 +403,7 @@ extension BuildGraphService {
317403 return sanitized
318404 }
319405
406+
320407 private func workingDirectory( arguments: [ String ] ) -> String ? {
321408 for (index, argument) in arguments. enumerated ( ) {
322409 if argument == " -working-directory " , index + 1 < arguments. count {
0 commit comments