Skip to content

Commit bed7961

Browse files
authored
Merge pull request #1243 from apple/a-world-record-finish
Consign the YAML Build Record to the Legacy Incremental Build Path
2 parents cee8c66 + d7db503 commit bed7961

16 files changed

+621
-595
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,30 +1424,69 @@ extension Driver {
14241424
// In case the write fails, don't crash the build.
14251425
// A mitigation to rdar://76359678.
14261426
// If the write fails, import incrementality is lost, but it is not a fatal error.
1427-
if let incrementalCompilationState = self.incrementalCompilationState {
1427+
guard
1428+
let buildRecordInfo = self.buildRecordInfo,
1429+
let absPath = buildRecordInfo.buildRecordPath.absolutePath
1430+
else {
1431+
return
1432+
}
1433+
1434+
let buildRecord = buildRecordInfo.buildRecord(
1435+
jobs, self.incrementalCompilationState?.blockingConcurrentMutationToProtectedState{
1436+
$0.skippedCompilationInputs
1437+
})
1438+
1439+
if
1440+
let incrementalCompilationState = self.incrementalCompilationState,
1441+
incrementalCompilationState.info.isCrossModuleIncrementalBuildEnabled
1442+
{
14281443
do {
1429-
try incrementalCompilationState.writeDependencyGraph(buildRecordInfo)
1430-
}
1431-
catch {
1444+
try incrementalCompilationState.writeDependencyGraph(to: buildRecordInfo.dependencyGraphPath, buildRecord)
1445+
} catch {
14321446
diagnosticEngine.emit(
14331447
.warning("next compile won't be incremental; could not write dependency graph: \(error.localizedDescription)"))
1434-
/// Ensure that a bogus dependency graph is not used next time.
1435-
buildRecordInfo?.removeBuildRecord()
1436-
return
1448+
/// Ensure that a bogus dependency graph is not used next time.
1449+
buildRecordInfo.removeBuildRecord()
1450+
buildRecordInfo.removeInterModuleDependencyGraph()
1451+
return
14371452
}
14381453
do {
14391454
try incrementalCompilationState.writeInterModuleDependencyGraph(buildRecordInfo)
1440-
}
1441-
catch {
1455+
} catch {
14421456
diagnosticEngine.emit(
14431457
.warning("next compile must run a full dependency scan; could not write inter-module dependency graph: \(error.localizedDescription)"))
1444-
buildRecordInfo?.removeInterModuleDependencyGraph()
1458+
buildRecordInfo.removeBuildRecord()
1459+
buildRecordInfo.removeInterModuleDependencyGraph()
14451460
return
14461461
}
1462+
} else {
1463+
// FIXME: This is all legacy code. Once the cross module incremental build
1464+
// becomes the default:
1465+
//
1466+
// 1) Delete this branch
1467+
// 2) Delete the parts of the incremental build that talk about anything
1468+
// derived from `buildRecordPath`
1469+
// 3) Delete the Yams dependency.
1470+
1471+
// Before writing to the dependencies file path, preserve any previous file
1472+
// that may have been there. No error handling -- this is just a nicety, it
1473+
// doesn't matter if it fails.
1474+
// Added for the sake of compatibility with the legacy driver.
1475+
try? fileSystem.move(
1476+
from: absPath, to: absPath.appending(component: absPath.basename + "~"))
1477+
1478+
guard let contents = buildRecord.encode(diagnosticEngine: diagnosticEngine) else {
1479+
diagnosticEngine.emit(.warning_could_not_write_build_record(absPath))
1480+
return
1481+
}
1482+
1483+
do {
1484+
try fileSystem.writeFileContents(absPath,
1485+
bytes: ByteString(encodingAsUTF8: contents))
1486+
} catch {
1487+
diagnosticEngine.emit(.warning_could_not_write_build_record(absPath))
1488+
}
14471489
}
1448-
buildRecordInfo?.writeBuildRecord(
1449-
jobs,
1450-
incrementalCompilationState?.blockingConcurrentMutationToProtectedState{$0.skippedCompilationInputs})
14511490
}
14521491

14531492
private func printBindings(_ job: Job) {

Sources/SwiftDriver/IncrementalCompilation/BuildRecord.swift

Lines changed: 85 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ import struct TSCBasic.Diagnostic
2121
public struct BuildRecord {
2222
public let swiftVersion: String
2323
/// When testing, the argsHash may be missing from the build record
24-
public let argsHash: String?
24+
public let argsHash: String
2525
/// Next compile, will compare an input mod time against the start time of the previous build
2626
public let buildStartTime: TimePoint
2727
/// Next compile, will compare an output mod time against the end time of the previous build
2828
public let buildEndTime: TimePoint
2929
/// The date is the modification time of the main input file the last time the driver ran
3030
public let inputInfos: [VirtualPath: InputInfo]
3131

32-
public init(argsHash: String?,
32+
public init(argsHash: String,
3333
swiftVersion: String,
3434
buildStartTime: TimePoint,
3535
buildEndTime: TimePoint,
@@ -41,7 +41,7 @@ public struct BuildRecord {
4141
self.inputInfos = inputInfos
4242
}
4343

44-
private enum SectionName: String, CaseIterable {
44+
public enum SectionName: String, CaseIterable {
4545
case swiftVersion = "version"
4646
case argsHash = "options"
4747
// Implement this for a smoother transition
@@ -60,12 +60,42 @@ public struct BuildRecord {
6060

6161
// MARK: - Reading the old map and deciding whether to use it
6262
public extension BuildRecord {
63-
init?(contents: String, failedToReadOutOfDateMap: (String?) -> Void) {
63+
enum Error: Swift.Error {
64+
case malformedYAML
65+
case invalidKey
66+
case missingTimeStamp
67+
case missingInputSequenceNode
68+
case missingInputEntryNode
69+
case missingPriorBuildState
70+
case unexpectedKey(String)
71+
case malformed(SectionName)
72+
73+
var reason: String {
74+
switch self {
75+
case .malformedYAML:
76+
return ""
77+
case .invalidKey:
78+
return ""
79+
case .missingTimeStamp:
80+
return "could not read time value in build record"
81+
case .missingInputSequenceNode:
82+
return "no sequence node for input entry in build record"
83+
case .missingInputEntryNode:
84+
return "no input entry in build record"
85+
case .missingPriorBuildState:
86+
return "no previous build state in build record"
87+
case .unexpectedKey(let key):
88+
return "Unexpected key '\(key)'"
89+
case .malformed(let section):
90+
return "Malformed value for key '\(section.serializedName)'"
91+
}
92+
}
93+
}
94+
init(contents: String) throws {
6495
guard let sections = try? Parser(yaml: contents, resolver: .basic, encoding: .utf8)
6596
.singleRoot()?.mapping
6697
else {
67-
failedToReadOutOfDateMap(nil)
68-
return nil
98+
throw Error.malformedYAML
6999
}
70100
var argsHash: String?
71101
var swiftVersion: String?
@@ -75,60 +105,42 @@ public extension BuildRecord {
75105
var inputInfos: [VirtualPath: InputInfo]?
76106
for (key, value) in sections {
77107
guard let k = key.string else {
78-
failedToReadOutOfDateMap(nil)
79-
return nil
108+
throw Error.invalidKey
80109
}
81110
switch k {
82111
case SectionName.swiftVersion.serializedName:
83112
// There's a test that uses "" for an illegal value
84113
guard let s = value.string, s != "" else {
85-
failedToReadOutOfDateMap("Malformed value for key '\(k)'")
86-
return nil
114+
break
87115
}
88116
swiftVersion = s
89117
case SectionName.argsHash.serializedName:
90118
guard let s = value.string, s != "" else {
91-
failedToReadOutOfDateMap("no name node in build record")
92-
return nil
119+
break
93120
}
94121
argsHash = s
95122
case SectionName.buildStartTime.serializedName,
96123
SectionName.legacyBuildStartTime.serializedName:
97-
guard let d = Self.decodeDate(value,
98-
forInputInfo: false,
99-
failedToReadOutOfDateMap)
100-
else {
101-
return nil
102-
}
103-
buildStartTime = d
124+
buildStartTime = try Self.decodeDate(value, forInputInfo: false)
104125
case SectionName.buildEndTime.serializedName:
105-
guard let d = Self.decodeDate(value,
106-
forInputInfo: false,
107-
failedToReadOutOfDateMap)
108-
else {
109-
return nil
110-
}
111-
buildEndTime = d
126+
buildEndTime = try Self.decodeDate(value, forInputInfo: false)
112127
case SectionName.inputInfos.serializedName:
113-
guard let ii = Self.decodeInputInfos(value, failedToReadOutOfDateMap) else {
114-
return nil
115-
}
116-
inputInfos = ii
128+
inputInfos = try Self.decodeInputInfos(value)
117129
default:
118-
failedToReadOutOfDateMap("Unexpected key '\(k)'")
119-
return nil
130+
throw Error.unexpectedKey(k)
120131
}
121132
}
122133
// The legacy driver allows argHash to be absent to ease testing.
123134
// Mimic the legacy driver for testing ease: If no `argsHash` section,
124135
// record still matches.
125136
guard let sv = swiftVersion else {
126-
failedToReadOutOfDateMap("Malformed value for key '\(SectionName.swiftVersion.serializedName)'")
127-
return nil
137+
throw Error.malformed(.swiftVersion)
128138
}
129139
guard let iis = inputInfos else {
130-
failedToReadOutOfDateMap("Malformed value for key '\(SectionName.inputInfos.serializedName)'")
131-
return nil
140+
throw Error.malformed(.inputInfos)
141+
}
142+
guard let argsHash = argsHash else {
143+
throw Error.malformed(.argsHash)
132144
}
133145
self.init(argsHash: argsHash,
134146
swiftVersion: sv,
@@ -139,55 +151,40 @@ public extension BuildRecord {
139151

140152
private static func decodeDate(
141153
_ node: Yams.Node,
142-
forInputInfo: Bool,
143-
_ failedToReadOutOfDateMap: (String) -> Void
144-
) -> TimePoint? {
154+
forInputInfo: Bool
155+
) throws -> TimePoint {
145156
guard let vals = node.sequence else {
146-
failedToReadOutOfDateMap(
147-
forInputInfo
148-
? "no sequence node for input entry in build record"
149-
: "could not read time value in build record")
150-
return nil
157+
if forInputInfo {
158+
throw Error.missingInputSequenceNode
159+
} else {
160+
throw Error.missingTimeStamp
161+
}
151162
}
152163
guard vals.count == 2,
153164
let secs = vals[0].int,
154165
let ns = vals[1].int
155166
else {
156-
failedToReadOutOfDateMap("could not read time value in build record")
157-
return nil
167+
throw Error.missingTimeStamp
158168
}
159169
return TimePoint(seconds: UInt64(secs), nanoseconds: UInt32(ns))
160170
}
161171

162172
private static func decodeInputInfos(
163-
_ node: Yams.Node,
164-
_ failedToReadOutOfDateMap: (String) -> Void
165-
) -> [VirtualPath: InputInfo]? {
173+
_ node: Yams.Node
174+
) throws -> [VirtualPath: InputInfo] {
166175
guard let map = node.mapping else {
167-
failedToReadOutOfDateMap(
168-
"Malformed value for key '\(SectionName.inputInfos.serializedName)'")
169-
return nil
176+
throw BuildRecord.Error.malformed(.inputInfos)
170177
}
171178
var infos = [VirtualPath: InputInfo]()
172179
for (keyNode, valueNode) in map {
173180
guard let pathString = keyNode.string,
174181
let path = try? VirtualPath(path: pathString)
175182
else {
176-
failedToReadOutOfDateMap("no input entry in build record")
177-
return nil
178-
}
179-
guard let previousModTime = decodeDate(valueNode,
180-
forInputInfo: true,
181-
failedToReadOutOfDateMap)
182-
else {
183-
return nil
184-
}
185-
guard let inputInfo = InputInfo(tag: valueNode.tag.description,
186-
previousModTime: previousModTime,
187-
failedToReadOutOfDateMap: failedToReadOutOfDateMap)
188-
else {
189-
return nil
183+
throw BuildRecord.Error.missingInputEntryNode
190184
}
185+
let previousModTime = try decodeDate(valueNode, forInputInfo: true)
186+
let inputInfo = try InputInfo(
187+
tag: valueNode.tag.description, previousModTime: previousModTime)
191188
infos[path] = inputInfo
192189
}
193190
return infos
@@ -202,7 +199,7 @@ extension BuildRecord {
202199
skippedInputs: Set<TypedVirtualPath>?,
203200
compilationInputModificationDates: [TypedVirtualPath: TimePoint],
204201
actualSwiftVersion: String,
205-
argsHash: String!,
202+
argsHash: String,
206203
timeBeforeFirstJob: TimePoint,
207204
timeAfterLastJob: TimePoint
208205
) {
@@ -227,9 +224,7 @@ extension BuildRecord {
227224
}
228225

229226
/// Pass in `currentArgsHash` to ensure it is non-nil
230-
/*@_spi(Testing)*/ public func encode(currentArgsHash: String,
231-
diagnosticEngine: DiagnosticsEngine
232-
) -> String? {
227+
public func encode(diagnosticEngine: DiagnosticsEngine) -> String? {
233228
let pathsAndInfos = inputInfos.map {
234229
input, inputInfo -> (String, InputInfo) in
235230
return (input.name, inputInfo)
@@ -241,7 +236,7 @@ extension BuildRecord {
241236
)
242237
let fieldNodes = [
243238
(SectionName.swiftVersion, Yams.Node(swiftVersion, .implicit, .doubleQuoted)),
244-
(SectionName.argsHash, Yams.Node(currentArgsHash, .implicit, .doubleQuoted)),
239+
(SectionName.argsHash, Yams.Node(argsHash, .implicit, .doubleQuoted)),
245240
(SectionName.buildStartTime, Self.encode(buildStartTime)),
246241
(SectionName.buildEndTime, Self.encode(buildEndTime)),
247242
(SectionName.inputInfos, inputInfosNode )
@@ -287,3 +282,22 @@ extension Diagnostic.Message {
287282
.warning("next compile won't be incremental; could not write build record to \(path)")
288283
}
289284
}
285+
286+
287+
// MARK: - reading
288+
extension InputInfo {
289+
fileprivate init(
290+
tag: String,
291+
previousModTime: TimePoint
292+
) throws {
293+
guard let status = Status(identifier: tag) else {
294+
throw BuildRecord.Error.missingPriorBuildState
295+
}
296+
self.init(status: status, previousModTime: previousModTime)
297+
}
298+
}
299+
300+
// MARK: - writing
301+
extension InputInfo {
302+
fileprivate var tag: String { status.identifier }
303+
}

0 commit comments

Comments
 (0)