12
12
import TSCBasic
13
13
import Foundation
14
14
import SwiftOptions
15
- @ _spi ( Testing ) public class IncrementalCompilationState {
15
+ public class IncrementalCompilationState {
16
16
17
17
/// The oracle for deciding what depends on what. Applies to this whole module.
18
- public let moduleDependencyGraph : ModuleDependencyGraph
18
+ private let moduleDependencyGraph : ModuleDependencyGraph
19
19
20
20
/// If non-null outputs information for `-driver-show-incremental` for input path
21
21
public let reportIncrementalDecision : ( ( String , TypedVirtualPath ? ) -> Void ) ?
22
22
23
+ /// All of the compilation job (groups) known to be required, preserving planning order
24
+ public private ( set) var primaryJobsInOrder = [ Job] ( )
25
+
26
+ /// All the compilation job (groups) known to be required, which have not finished yet.
27
+ /// (Changes as jobs complete.)
28
+ private var unfinishedPrimaryJobs = Set < Job > ( )
29
+
23
30
/// Inputs that must be compiled, and swiftDeps processed.
24
31
/// When empty, the compile phase is done.
25
32
private var pendingInputs = Set < TypedVirtualPath > ( )
@@ -33,19 +40,15 @@ import SwiftOptions
33
40
/// treated as a unit.
34
41
private var skippedCompileGroups = [ TypedVirtualPath: [ Job] ] ( )
35
42
36
- /// Accumulates jobs to be run through compilation
37
- public var preOrCompileJobs = SynchronizedQueue < [ Job ] ? > ( )
38
-
39
43
/// Jobs to run after the last compile
40
- /// Nonnil means planning has informed me
41
- internal private( set) var postCompileJobs : [ Job ] ? = nil
44
+ public private( set) var tertiaryJobs = [ Job] ( )
42
45
43
46
/// A check for reentrancy.
44
47
private var amHandlingJobCompletion = false
45
48
46
49
// MARK: - Creating IncrementalCompilationState if possible
47
50
/// Return nil if not compiling incrementally
48
- public init ? (
51
+ @ _spi ( Testing ) public init ? (
49
52
buildRecordInfo: BuildRecordInfo ? ,
50
53
compilerMode: CompilerMode ,
51
54
diagnosticEngine: DiagnosticsEngine ,
@@ -358,41 +361,42 @@ extension IncrementalCompilationState {
358
361
// MARK: - Scheduling
359
362
extension IncrementalCompilationState {
360
363
/// Decide if a job can be skipped, and register accordingly
361
- func addPreOrCompileJobGroups( _ groups: [ [ Job ] ] ) {
362
- var wereAnyJobsScheduled = false
364
+ func addPrimaryOrSkippedJobGroups( _ groups: [ [ Job ] ] ) {
363
365
for group in groups {
364
366
if let firstJob = group. first, isSkipped ( firstJob) {
365
367
recordSkippedGroup ( group)
366
368
}
367
369
else {
368
- schedule ( group: group)
369
- wereAnyJobsScheduled = true
370
+ schedulePrimary ( group: group)
370
371
}
371
372
}
372
- if !wereAnyJobsScheduled {
373
- finishedWithCompilations ( )
373
+ }
374
+
375
+ /// Remember that `group` (a compilation and possibly bitcode generation)
376
+ /// must definitely be executed.
377
+ func schedulePrimary( group: [ Job ] ) {
378
+ if let report = reportIncrementalDecision {
379
+ for job in group {
380
+ report ( " Queuing \( job. descriptionForLifecycle) " , nil )
381
+ }
374
382
}
383
+ primaryJobsInOrder. append ( contentsOf: group)
384
+ unfinishedPrimaryJobs. formUnion ( group)
385
+ let primaryCompilationInputs = group
386
+ . flatMap { $0. kind == . compile ? $0. primaryInputs : [ ] }
387
+ pendingInputs. formUnion ( primaryCompilationInputs)
375
388
}
376
389
390
+ /// Decide if this job does not need to run, unless some yet-to-be-discovered dependency changes.
377
391
func isSkipped( _ job: Job ) -> Bool {
378
392
guard job. kind == . compile else {
379
393
return false
380
394
}
381
- func isInputSkipped( _ p: TypedVirtualPath ) -> Bool {
382
- skippedCompilationInputs. contains ( p)
383
- }
384
- guard let jobCanBeSkipped = job. primaryInputs. first. map ( isInputSkipped)
385
- else {
386
- return false
387
- }
388
- // Should only be one primary here, but check anyway
389
- assert (
390
- job. primaryInputs. dropFirst ( ) . allSatisfy {
391
- isInputSkipped ( $0) == jobCanBeSkipped}
392
- )
393
- return jobCanBeSkipped
395
+ assert ( job. primaryInputs. count <= 1 , " Jobs should not be batched here. " )
396
+ return job. primaryInputs. first. map ( skippedCompilationInputs. contains) ?? false
394
397
}
395
398
399
+ /// Remember that this job-group will be skipped (but may be needed later)
396
400
func recordSkippedGroup( _ group: [ Job ] ) {
397
401
let job = group. first!
398
402
for input in job. primaryInputs {
@@ -402,26 +406,9 @@ extension IncrementalCompilationState {
402
406
}
403
407
}
404
408
405
- func schedule( group: [ Job ] ) {
406
- schedule ( preOrCompileJobs: group)
407
- }
408
- /// Put job in queue for execution
409
- func schedule( preOrCompileJobs jobs: [ Job ] ) {
410
- if let report = reportIncrementalDecision {
411
- for job in jobs {
412
- report ( " Queuing \( job. descriptionForLifecycle) " , nil )
413
- }
414
- }
415
- let primaryCompilationInputs = jobs
416
- . flatMap { $0. kind == . compile ? $0. primaryInputs : [ ] }
417
- pendingInputs. formUnion ( primaryCompilationInputs)
418
- preOrCompileJobs. enqueue ( jobs)
419
- }
420
-
421
- /// Remember a job that runs after all compile jobs
422
- func addPostCompileJobs( _ jobs: [ Job ] ) {
423
- assert ( postCompileJobs == nil , " Should only be called once " )
424
- postCompileJobs = jobs
409
+ /// Remember a job that runs after all compile jobs, e.g., ld
410
+ func addTertiaryJobs( _ jobs: [ Job ] ) {
411
+ self . tertiaryJobs = jobs
425
412
for job in jobs {
426
413
if let report = reportIncrementalDecision {
427
414
for input in job. primaryInputs {
@@ -431,31 +418,46 @@ extension IncrementalCompilationState {
431
418
}
432
419
}
433
420
434
- /// Update the incremental build state when a job finishes:
435
- /// Read it's swiftDeps files and queue up any required discovered jobs.
436
- func jobFinished( job finishedJob: Job , result: ProcessResult ) {
421
+ /// `job` just finished. Update state, and return the skipped compile job (groups) that are now known to be needed.
422
+ /// If no more compiles are needed, return nil.
423
+ /// Careful: job may not be primary.
424
+ public func getSecondaryJobsAfterFinishing( job finishedJob: Job , result: ProcessResult ) -> [ Job ] ? {
437
425
defer {
438
426
amHandlingJobCompletion = false
439
427
}
440
428
assert ( !amHandlingJobCompletion, " was reentered, need to synchronize " )
441
429
amHandlingJobCompletion = true
442
430
431
+ unfinishedPrimaryJobs. remove ( finishedJob)
432
+ if finishedJob. kind == . compile {
433
+ finishedJob. primaryInputs. forEach {
434
+ if pendingInputs. remove ( $0) == nil {
435
+ fatalError ( " should have been pending " )
436
+ }
437
+ }
438
+ }
439
+
440
+ // Find and deal with inputs that how need to be compiled
443
441
let discoveredInputs = collectInputsDiscovered ( from: finishedJob)
442
+ assert ( Set ( discoveredInputs) . isDisjoint ( with: finishedJob. primaryInputs) ,
443
+ " Primaries should not overlap secondaries. " )
444
+ skippedCompilationInputs. subtract ( discoveredInputs)
445
+ pendingInputs. formUnion ( discoveredInputs)
446
+
444
447
if let report = reportIncrementalDecision {
445
448
for input in discoveredInputs {
446
449
report ( " Queuing because of dependencies discovered later: " , input)
447
450
}
448
451
}
449
- schedule ( compilationInputs: discoveredInputs)
450
- finishedJob. primaryInputs. forEach { pendingInputs. remove ( $0) }
451
- if pendingInputs. isEmpty {
452
- finishedWithCompilations ( )
452
+ if pendingInputs. isEmpty && unfinishedPrimaryJobs. isEmpty {
453
+ // no more compilations are possible
454
+ return nil
453
455
}
456
+ return getSecondaryJobsFor ( discoveredCompilationInputs: discoveredInputs)
454
457
}
455
458
456
- private func collectInputsDiscovered(
457
- from job: Job
458
- ) -> [ TypedVirtualPath ] {
459
+ /// After `job` finished find out which inputs must compiled that were not known to need compilation before
460
+ private func collectInputsDiscovered( from job: Job ) -> [ TypedVirtualPath ] {
459
461
Array (
460
462
Set (
461
463
job. primaryInputs. flatMap {
@@ -467,11 +469,15 @@ extension IncrementalCompilationState {
467
469
. sorted { $0. file. name < $1. file. name}
468
470
}
469
471
470
- private func schedule( compilationInputs inputs: [ TypedVirtualPath ] ) {
471
- let jobs = inputs. flatMap { input -> [ Job ] in
472
+ /// Find the jobs that now must be run that were not originally known to be needed.
473
+ private func getSecondaryJobsFor(
474
+ discoveredCompilationInputs inputs: [ TypedVirtualPath ]
475
+ ) -> [ Job ] {
476
+ inputs. flatMap { input -> [ Job ] in
472
477
if let group = skippedCompileGroups. removeValue ( forKey: input) {
473
478
let primaryInputs = group. first!. primaryInputs
474
- skippedCompilationInputs. subtract ( primaryInputs)
479
+ assert ( primaryInputs. count == 1 )
480
+ assert ( primaryInputs [ 0 ] == input)
475
481
reportIncrementalDecision ? ( " Scheduling discovered " , input)
476
482
return group
477
483
}
@@ -480,11 +486,5 @@ extension IncrementalCompilationState {
480
486
return [ ]
481
487
}
482
488
}
483
- schedule ( preOrCompileJobs: jobs)
484
- }
485
-
486
- func finishedWithCompilations( ) {
487
- preOrCompileJobs. enqueue ( nil )
488
489
}
489
490
}
490
-
0 commit comments