Skip to content

Consign the YAML Build Record to the Legacy Incremental Build Path #1243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 52 additions & 13 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1424,30 +1424,69 @@ extension Driver {
// In case the write fails, don't crash the build.
// A mitigation to rdar://76359678.
// If the write fails, import incrementality is lost, but it is not a fatal error.
if let incrementalCompilationState = self.incrementalCompilationState {
guard
let buildRecordInfo = self.buildRecordInfo,
let absPath = buildRecordInfo.buildRecordPath.absolutePath
else {
return
}

let buildRecord = buildRecordInfo.buildRecord(
jobs, self.incrementalCompilationState?.blockingConcurrentMutationToProtectedState{
$0.skippedCompilationInputs
})

if
let incrementalCompilationState = self.incrementalCompilationState,
incrementalCompilationState.info.isCrossModuleIncrementalBuildEnabled
{
do {
try incrementalCompilationState.writeDependencyGraph(buildRecordInfo)
}
catch {
try incrementalCompilationState.writeDependencyGraph(to: buildRecordInfo.dependencyGraphPath, buildRecord)
} catch {
diagnosticEngine.emit(
.warning("next compile won't be incremental; could not write dependency graph: \(error.localizedDescription)"))
/// Ensure that a bogus dependency graph is not used next time.
buildRecordInfo?.removeBuildRecord()
return
/// Ensure that a bogus dependency graph is not used next time.
buildRecordInfo.removeBuildRecord()
buildRecordInfo.removeInterModuleDependencyGraph()
return
}
do {
try incrementalCompilationState.writeInterModuleDependencyGraph(buildRecordInfo)
}
catch {
} catch {
diagnosticEngine.emit(
.warning("next compile must run a full dependency scan; could not write inter-module dependency graph: \(error.localizedDescription)"))
buildRecordInfo?.removeInterModuleDependencyGraph()
buildRecordInfo.removeBuildRecord()
buildRecordInfo.removeInterModuleDependencyGraph()
return
}
} else {
// FIXME: This is all legacy code. Once the cross module incremental build
// becomes the default:
//
// 1) Delete this branch
// 2) Delete the parts of the incremental build that talk about anything
// derived from `buildRecordPath`
// 3) Delete the Yams dependency.

// Before writing to the dependencies file path, preserve any previous file
// that may have been there. No error handling -- this is just a nicety, it
// doesn't matter if it fails.
// Added for the sake of compatibility with the legacy driver.
try? fileSystem.move(
from: absPath, to: absPath.appending(component: absPath.basename + "~"))

guard let contents = buildRecord.encode(diagnosticEngine: diagnosticEngine) else {
diagnosticEngine.emit(.warning_could_not_write_build_record(absPath))
return
}

do {
try fileSystem.writeFileContents(absPath,
bytes: ByteString(encodingAsUTF8: contents))
} catch {
diagnosticEngine.emit(.warning_could_not_write_build_record(absPath))
}
}
buildRecordInfo?.writeBuildRecord(
jobs,
incrementalCompilationState?.blockingConcurrentMutationToProtectedState{$0.skippedCompilationInputs})
}

private func printBindings(_ job: Job) {
Expand Down
156 changes: 85 additions & 71 deletions Sources/SwiftDriver/IncrementalCompilation/BuildRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ import struct TSCBasic.Diagnostic
public struct BuildRecord {
public let swiftVersion: String
/// When testing, the argsHash may be missing from the build record
public let argsHash: String?
public let argsHash: String
/// Next compile, will compare an input mod time against the start time of the previous build
public let buildStartTime: TimePoint
/// Next compile, will compare an output mod time against the end time of the previous build
public let buildEndTime: TimePoint
/// The date is the modification time of the main input file the last time the driver ran
public let inputInfos: [VirtualPath: InputInfo]

public init(argsHash: String?,
public init(argsHash: String,
swiftVersion: String,
buildStartTime: TimePoint,
buildEndTime: TimePoint,
Expand All @@ -41,7 +41,7 @@ public struct BuildRecord {
self.inputInfos = inputInfos
}

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

// MARK: - Reading the old map and deciding whether to use it
public extension BuildRecord {
init?(contents: String, failedToReadOutOfDateMap: (String?) -> Void) {
enum Error: Swift.Error {
case malformedYAML
case invalidKey
case missingTimeStamp
case missingInputSequenceNode
case missingInputEntryNode
case missingPriorBuildState
case unexpectedKey(String)
case malformed(SectionName)

var reason: String {
switch self {
case .malformedYAML:
return ""
case .invalidKey:
return ""
case .missingTimeStamp:
return "could not read time value in build record"
case .missingInputSequenceNode:
return "no sequence node for input entry in build record"
case .missingInputEntryNode:
return "no input entry in build record"
case .missingPriorBuildState:
return "no previous build state in build record"
case .unexpectedKey(let key):
return "Unexpected key '\(key)'"
case .malformed(let section):
return "Malformed value for key '\(section.serializedName)'"
}
}
}
init(contents: String) throws {
guard let sections = try? Parser(yaml: contents, resolver: .basic, encoding: .utf8)
.singleRoot()?.mapping
else {
failedToReadOutOfDateMap(nil)
return nil
throw Error.malformedYAML
}
var argsHash: String?
var swiftVersion: String?
Expand All @@ -75,60 +105,42 @@ public extension BuildRecord {
var inputInfos: [VirtualPath: InputInfo]?
for (key, value) in sections {
guard let k = key.string else {
failedToReadOutOfDateMap(nil)
return nil
throw Error.invalidKey
}
switch k {
case SectionName.swiftVersion.serializedName:
// There's a test that uses "" for an illegal value
guard let s = value.string, s != "" else {
failedToReadOutOfDateMap("Malformed value for key '\(k)'")
return nil
break
}
swiftVersion = s
case SectionName.argsHash.serializedName:
guard let s = value.string, s != "" else {
failedToReadOutOfDateMap("no name node in build record")
return nil
break
}
argsHash = s
case SectionName.buildStartTime.serializedName,
SectionName.legacyBuildStartTime.serializedName:
guard let d = Self.decodeDate(value,
forInputInfo: false,
failedToReadOutOfDateMap)
else {
return nil
}
buildStartTime = d
buildStartTime = try Self.decodeDate(value, forInputInfo: false)
case SectionName.buildEndTime.serializedName:
guard let d = Self.decodeDate(value,
forInputInfo: false,
failedToReadOutOfDateMap)
else {
return nil
}
buildEndTime = d
buildEndTime = try Self.decodeDate(value, forInputInfo: false)
case SectionName.inputInfos.serializedName:
guard let ii = Self.decodeInputInfos(value, failedToReadOutOfDateMap) else {
return nil
}
inputInfos = ii
inputInfos = try Self.decodeInputInfos(value)
default:
failedToReadOutOfDateMap("Unexpected key '\(k)'")
return nil
throw Error.unexpectedKey(k)
}
}
// The legacy driver allows argHash to be absent to ease testing.
// Mimic the legacy driver for testing ease: If no `argsHash` section,
// record still matches.
guard let sv = swiftVersion else {
failedToReadOutOfDateMap("Malformed value for key '\(SectionName.swiftVersion.serializedName)'")
return nil
throw Error.malformed(.swiftVersion)
}
guard let iis = inputInfos else {
failedToReadOutOfDateMap("Malformed value for key '\(SectionName.inputInfos.serializedName)'")
return nil
throw Error.malformed(.inputInfos)
}
guard let argsHash = argsHash else {
throw Error.malformed(.argsHash)
}
self.init(argsHash: argsHash,
swiftVersion: sv,
Expand All @@ -139,55 +151,40 @@ public extension BuildRecord {

private static func decodeDate(
_ node: Yams.Node,
forInputInfo: Bool,
_ failedToReadOutOfDateMap: (String) -> Void
) -> TimePoint? {
forInputInfo: Bool
) throws -> TimePoint {
guard let vals = node.sequence else {
failedToReadOutOfDateMap(
forInputInfo
? "no sequence node for input entry in build record"
: "could not read time value in build record")
return nil
if forInputInfo {
throw Error.missingInputSequenceNode
} else {
throw Error.missingTimeStamp
}
}
guard vals.count == 2,
let secs = vals[0].int,
let ns = vals[1].int
else {
failedToReadOutOfDateMap("could not read time value in build record")
return nil
throw Error.missingTimeStamp
}
return TimePoint(seconds: UInt64(secs), nanoseconds: UInt32(ns))
}

private static func decodeInputInfos(
_ node: Yams.Node,
_ failedToReadOutOfDateMap: (String) -> Void
) -> [VirtualPath: InputInfo]? {
_ node: Yams.Node
) throws -> [VirtualPath: InputInfo] {
guard let map = node.mapping else {
failedToReadOutOfDateMap(
"Malformed value for key '\(SectionName.inputInfos.serializedName)'")
return nil
throw BuildRecord.Error.malformed(.inputInfos)
}
var infos = [VirtualPath: InputInfo]()
for (keyNode, valueNode) in map {
guard let pathString = keyNode.string,
let path = try? VirtualPath(path: pathString)
else {
failedToReadOutOfDateMap("no input entry in build record")
return nil
}
guard let previousModTime = decodeDate(valueNode,
forInputInfo: true,
failedToReadOutOfDateMap)
else {
return nil
}
guard let inputInfo = InputInfo(tag: valueNode.tag.description,
previousModTime: previousModTime,
failedToReadOutOfDateMap: failedToReadOutOfDateMap)
else {
return nil
throw BuildRecord.Error.missingInputEntryNode
}
let previousModTime = try decodeDate(valueNode, forInputInfo: true)
let inputInfo = try InputInfo(
tag: valueNode.tag.description, previousModTime: previousModTime)
infos[path] = inputInfo
}
return infos
Expand All @@ -202,7 +199,7 @@ extension BuildRecord {
skippedInputs: Set<TypedVirtualPath>?,
compilationInputModificationDates: [TypedVirtualPath: TimePoint],
actualSwiftVersion: String,
argsHash: String!,
argsHash: String,
timeBeforeFirstJob: TimePoint,
timeAfterLastJob: TimePoint
) {
Expand All @@ -227,9 +224,7 @@ extension BuildRecord {
}

/// Pass in `currentArgsHash` to ensure it is non-nil
/*@_spi(Testing)*/ public func encode(currentArgsHash: String,
diagnosticEngine: DiagnosticsEngine
) -> String? {
public func encode(diagnosticEngine: DiagnosticsEngine) -> String? {
let pathsAndInfos = inputInfos.map {
input, inputInfo -> (String, InputInfo) in
return (input.name, inputInfo)
Expand All @@ -241,7 +236,7 @@ extension BuildRecord {
)
let fieldNodes = [
(SectionName.swiftVersion, Yams.Node(swiftVersion, .implicit, .doubleQuoted)),
(SectionName.argsHash, Yams.Node(currentArgsHash, .implicit, .doubleQuoted)),
(SectionName.argsHash, Yams.Node(argsHash, .implicit, .doubleQuoted)),
(SectionName.buildStartTime, Self.encode(buildStartTime)),
(SectionName.buildEndTime, Self.encode(buildEndTime)),
(SectionName.inputInfos, inputInfosNode )
Expand Down Expand Up @@ -287,3 +282,22 @@ extension Diagnostic.Message {
.warning("next compile won't be incremental; could not write build record to \(path)")
}
}


// MARK: - reading
extension InputInfo {
fileprivate init(
tag: String,
previousModTime: TimePoint
) throws {
guard let status = Status(identifier: tag) else {
throw BuildRecord.Error.missingPriorBuildState
}
self.init(status: status, previousModTime: previousModTime)
}
}

// MARK: - writing
extension InputInfo {
fileprivate var tag: String { status.identifier }
}
Loading