1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
//===---------- IncrementalCompilationActor.swift - Incremental -----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Dispatch
import SwiftOptions
import protocol TSCBasic.FileSystem
extension IncrementalCompilationState {
/// Encapsulates the data necessary to make incremental build scheduling decisions and protects it from concurrent access.
public struct ProtectedState {
/// Keyed by primary input. As required compilations are discovered after the first wave, these shrink.
///
/// This state is modified during the incremental build. All accesses must
/// be protected by the confinement queue.
fileprivate var skippedCompileGroups: [TypedVirtualPath: CompileJobGroup]
/// Sadly, has to be `var` for formBatchedJobs
///
/// After initialization, mutating accesses to the driver must be protected by
/// the confinement queue.
private var driver: Driver
/// The oracle for deciding what depends on what. Applies to this whole module.
/// fileprivate in order to control concurrency.
fileprivate let moduleDependencyGraph: ModuleDependencyGraph
fileprivate let jobCreatingPch: Job?
fileprivate let reporter: Reporter?
init(skippedCompileGroups: [TypedVirtualPath: CompileJobGroup],
_ moduleDependencyGraph: ModuleDependencyGraph,
_ jobCreatingPch: Job?,
_ driver: inout Driver) {
self.skippedCompileGroups = skippedCompileGroups
self.moduleDependencyGraph = moduleDependencyGraph
self.reporter = moduleDependencyGraph.info.reporter
self.jobCreatingPch = jobCreatingPch
self.driver = driver
}
}
}
extension IncrementalCompilationState.ProtectedState: IncrementalCompilationSynchronizer {
public var incrementalCompilationQueue: DispatchQueue {
moduleDependencyGraph.incrementalCompilationQueue
}
}
// MARK: - 2nd wave
extension IncrementalCompilationState.ProtectedState {
mutating func collectBatchedJobsDiscoveredToBeNeededAfterFinishing(
job finishedJob: Job
) throws -> [Job]? {
mutationSafetyPrecondition()
// batch in here to protect the Driver from concurrent access
return try collectUnbatchedJobsDiscoveredToBeNeededAfterFinishing(job: finishedJob)
.map {try driver.formBatchedJobs($0, showJobLifecycle: driver.showJobLifecycle, jobCreatingPch: jobCreatingPch)}
}
/// Remember a job (group) that is before a compile or a compile itself.
/// `job` just finished. Update state, and return the skipped compile job (groups) that are now known to be needed.
/// If no more compiles are needed, return nil.
/// Careful: job may not be primary.
fileprivate mutating func collectUnbatchedJobsDiscoveredToBeNeededAfterFinishing(
job finishedJob: Job) throws -> [Job]? {
mutationSafetyPrecondition()
// Find and deal with inputs that now need to be compiled
let invalidatedInputs = collectInputsInvalidatedByRunning(finishedJob)
assert(invalidatedInputs.isDisjoint(with: finishedJob.primarySwiftSourceFiles),
"Primaries should not overlap secondaries.")
if let reporter = self.reporter {
for input in invalidatedInputs {
reporter.report(
"Queuing because of dependencies discovered later:", input)
}
}
return try getUnbatchedJobs(for: invalidatedInputs)
}
/// After `job` finished find out which inputs must compiled that were not known to need compilation before
fileprivate mutating func collectInputsInvalidatedByRunning(_ job: Job)-> Set<SwiftSourceFile> {
mutationSafetyPrecondition()
guard job.kind == .compile else {
return Set<SwiftSourceFile>()
}
return job.primaryInputs.reduce(into: Set()) { invalidatedInputs, primaryInput in
if let primary = SwiftSourceFile(ifSource: primaryInput) {
invalidatedInputs.formUnion(collectInputsInvalidated(byCompiling: primary))
}
}
.subtracting(job.primarySwiftSourceFiles) // have already compiled these
}
// "Mutating" because it mutates the graph, which may be a struct someday
fileprivate mutating func collectInputsInvalidated(
byCompiling input: SwiftSourceFile
) -> TransitivelyInvalidatedSwiftSourceFileSet {
mutationSafetyPrecondition()
if let found = moduleDependencyGraph.collectInputsRequiringCompilation(byCompiling: input) {
return found
}
self.reporter?.report(
"Failed to read some dependencies source; compiling everything", input)
return TransitivelyInvalidatedSwiftSourceFileSet(skippedCompileGroups.keys.swiftSourceFiles)
}
/// Find the jobs that now must be run that were not originally known to be needed.
fileprivate mutating func getUnbatchedJobs(
for invalidatedInputs: Set<SwiftSourceFile>
) throws -> [Job] {
mutationSafetyPrecondition()
return invalidatedInputs.flatMap { input -> [Job] in
if let group = skippedCompileGroups.removeValue(forKey: input.typedFile) {
let primaryInputs = group.compileJob.primarySwiftSourceFiles
assert(primaryInputs.count == 1)
assert(primaryInputs[0] == input)
self.reporter?.report("Scheduling invalidated", input)
return group.allJobs()
}
else {
self.reporter?.report("Tried to schedule invalidated input again", input)
return []
}
}
}
}
// MARK: - After the build
extension IncrementalCompilationState.ProtectedState {
var skippedCompilationInputs: Set<TypedVirtualPath> {
accessSafetyPrecondition()
return Set(skippedCompileGroups.keys)
}
public var skippedJobs: [Job] {
accessSafetyPrecondition()
return skippedCompileGroups.values
.sorted {$0.primaryInput.file.name < $1.primaryInput.file.name}
.flatMap {$0.allJobs()}
}
func writeGraph(to path: VirtualPath,
on fs: FileSystem,
buildRecord: BuildRecord,
mockSerializedGraphVersion: Version? = nil
) throws {
accessSafetyPrecondition()
try moduleDependencyGraph.write(to: path, on: fs,
buildRecord: buildRecord,
mockSerializedGraphVersion: mockSerializedGraphVersion)
}
}
// MARK: - Testing - (must be here to access graph safely)
extension IncrementalCompilationState.ProtectedState {
/// Expose the protected ``ModuleDependencyGraph`` for testing
@_spi(Testing) public mutating func testWithModuleDependencyGraph(
_ fn: (ModuleDependencyGraph) throws -> Void
) rethrows {
mutationSafetyPrecondition()
try fn(moduleDependencyGraph)
}
}
|