Skip to content

Commit 20fd81e

Browse files
committed
external dependency hashing for incremental
1 parent 4e62341 commit 20fd81e

File tree

12 files changed

+256
-31
lines changed

12 files changed

+256
-31
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ add_library(SwiftDriver
114114
Utilities/DateAdditions.swift
115115
Utilities/Diagnostics.swift
116116
Utilities/FileList.swift
117+
Utilities/FileMetadata.swift
117118
Utilities/FileType.swift
118119
Utilities/PredictableRandomNumberGenerator.swift
119120
Utilities/RelativePathAdditions.swift

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,6 @@ extension Driver.ErrorDiagnostics: CustomStringConvertible {
4545
}
4646
}
4747

48-
public struct FileMetadata {
49-
public let mTime: TimePoint
50-
public let hash: String
51-
init(mTime: TimePoint, hash: String = "") {
52-
self.mTime = mTime
53-
self.hash = hash
54-
}
55-
}
56-
5748
/// The Swift driver.
5849
public struct Driver {
5950
public enum Error: Swift.Error, Equatable, DiagnosticData {
@@ -994,8 +985,8 @@ public struct Driver {
994985
self.recordedInputMetadata = .init(uniqueKeysWithValues:
995986
Set(inputFiles).compactMap { inputFile -> (TypedVirtualPath, FileMetadata)? in
996987
guard let modTime = try? fileSystem.lastModificationTime(for: inputFile.file) else { return nil }
997-
guard let data = try? fileSystem.readFileContents(inputFile.file) else { return nil }
998988
if incrementalFileHashes {
989+
guard let data = try? fileSystem.readFileContents(inputFile.file) else { return nil }
999990
let hash = SHA256().hash(data).hexadecimalRepresentation
1000991
return (inputFile, FileMetadata(mTime: modTime, hash: hash))
1001992
} else {

Sources/SwiftDriver/Execution/DriverExecutor.swift

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public protocol DriverExecutor {
2727
forceResponseFiles: Bool,
2828
recordedInputMetadata: [TypedVirtualPath: FileMetadata]) throws -> ProcessResult
2929

30+
func execute(job: Job,
31+
forceResponseFiles: Bool,
32+
recordedInputModificationDates: [TypedVirtualPath: TimePoint]) throws -> ProcessResult
33+
34+
3035
/// Execute multiple jobs, tracking job status using the provided execution delegate.
3136
/// Pass in the `IncrementalCompilationState` to allow for incremental compilation.
3237
/// Pass in the `InterModuleDependencyGraph` to allow for module dependency tracking.
@@ -37,6 +42,13 @@ public protocol DriverExecutor {
3742
recordedInputMetadata: [TypedVirtualPath: FileMetadata]
3843
) throws
3944

45+
func execute(workload: DriverExecutorWorkload,
46+
delegate: JobExecutionDelegate,
47+
numParallelJobs: Int,
48+
forceResponseFiles: Bool,
49+
recordedInputModificationDates: [TypedVirtualPath: TimePoint]
50+
) throws
51+
4052
/// Execute multiple jobs, tracking job status using the provided execution delegate.
4153
func execute(jobs: [Job],
4254
delegate: JobExecutionDelegate,
@@ -45,6 +57,13 @@ public protocol DriverExecutor {
4557
recordedInputMetadata: [TypedVirtualPath: FileMetadata]
4658
) throws
4759

60+
func execute(jobs: [Job],
61+
delegate: JobExecutionDelegate,
62+
numParallelJobs: Int,
63+
forceResponseFiles: Bool,
64+
recordedInputModificationDates: [TypedVirtualPath: TimePoint]
65+
) throws
66+
4867
/// Launch a process with the given command line and report the result.
4968
@discardableResult
5069
func checkNonZeroExit(args: String..., environment: [String: String]) throws -> String
@@ -53,6 +72,34 @@ public protocol DriverExecutor {
5372
func description(of job: Job, forceResponseFiles: Bool) throws -> String
5473
}
5574

75+
extension DriverExecutor {
76+
public func execute(job: Job,
77+
forceResponseFiles: Bool,
78+
recordedInputMetadata: [TypedVirtualPath: FileMetadata]) throws -> ProcessResult
79+
{
80+
return try execute(job: job, forceResponseFiles: forceResponseFiles, recordedInputModificationDates: recordedInputMetadata.mapValues { $0.mTime })
81+
}
82+
83+
84+
public func execute(workload: DriverExecutorWorkload,
85+
delegate: JobExecutionDelegate,
86+
numParallelJobs: Int,
87+
forceResponseFiles: Bool,
88+
recordedInputMetadata: [TypedVirtualPath: FileMetadata]
89+
) throws {
90+
try execute(workload: workload, delegate: delegate, numParallelJobs: numParallelJobs, forceResponseFiles: forceResponseFiles, recordedInputModificationDates: recordedInputMetadata.mapValues { $0.mTime })
91+
}
92+
93+
public func execute(jobs: [Job],
94+
delegate: JobExecutionDelegate,
95+
numParallelJobs: Int,
96+
forceResponseFiles: Bool,
97+
recordedInputMetadata: [TypedVirtualPath: FileMetadata]
98+
) throws {
99+
try execute(jobs: jobs, delegate: delegate, numParallelJobs: numParallelJobs, forceResponseFiles: forceResponseFiles, recordedInputModificationDates: recordedInputMetadata.mapValues { $0.mTime })
100+
}
101+
}
102+
56103
public struct DriverExecutorWorkload {
57104
public let continueBuildingAfterErrors: Bool
58105
public enum Kind {
@@ -121,14 +168,14 @@ extension DriverExecutor {
121168
delegate: JobExecutionDelegate,
122169
numParallelJobs: Int,
123170
forceResponseFiles: Bool,
124-
recordedInputMetadata: [TypedVirtualPath: FileMetadata]
171+
recordedInputModificationDates: [TypedVirtualPath: TimePoint]
125172
) throws {
126173
try execute(
127174
workload: .all(jobs),
128175
delegate: delegate,
129176
numParallelJobs: numParallelJobs,
130177
forceResponseFiles: forceResponseFiles,
131-
recordedInputMetadata: recordedInputMetadata)
178+
recordedInputModificationDates: recordedInputModificationDates)
132179
}
133180

134181
static func computeReturnCode(exitStatus: ProcessResult.ExitStatus) -> Int {

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ internal extension InterModuleDependencyGraph {
223223
reporter?.report("Unable to 'stat' \(inputPath.description)")
224224
return false
225225
}
226+
// SHA256 hashes for these files from the previous build are not
227+
// currently stored, so we can only check timestamps
226228
if inputModTime > outputModTime {
227229
reporter?.reportExplicitDependencyOutOfDate(moduleName,
228230
inputPath: inputPath.description)

Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ extension IncrementalCompilationState.FirstWaveComputer {
320320
let previousModTime = inputInfo?.previousModTime
321321
let previousHash = inputInfo?.hash
322322

323+
assert(metadata.hash != nil || !useHashes)
324+
323325
switch previousCompilationStatus {
324326
case .upToDate where metadata.mTime == previousModTime:
325327
reporter?.report("May skip current input:", input)

Sources/SwiftDriver/IncrementalCompilation/InputInfo.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ import struct TSCBasic.SHA256
2626
/// The last known modification time of this input.
2727
/*@_spi(Testing)*/ public let previousModTime: TimePoint
2828

29-
/*@_spi(Testing)*/ public let hash: String
29+
/*@_spi(Testing)*/ public let hash: String?
3030

31-
/*@_spi(Testing)*/ public init(status: Status, previousModTime: TimePoint, hash: String) {
31+
/*@_spi(Testing)*/ public init(status: Status, previousModTime: TimePoint, hash: String?) {
3232
self.status = status
3333
self.previousModTime = previousModTime
3434
self.hash = hash

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import SwiftOptions
1414

1515
import protocol TSCBasic.FileSystem
1616
import struct TSCBasic.ByteString
17+
import struct TSCBasic.SHA256
1718
import class Dispatch.DispatchQueue
1819
import struct Foundation.TimeInterval
1920

@@ -63,11 +64,12 @@ import struct Foundation.TimeInterval
6364
_ phase: Phase,
6465
_ internedStringTable: InternedStringTable,
6566
_ nodeFinder: NodeFinder,
66-
_ fingerprintedExternalDependencies: Set<FingerprintedExternalDependency>
67+
_ fingerprintedExternalDependencies: Set<FingerprintedExternalDependency>,
68+
_ externalDependencyFileHashes: Dictionary<ExternalDependency, String>
6769
) {
6870
self.buildRecord = buildRecord
6971
self.currencyCache = ExternalDependencyCurrencyCache(
70-
info.fileSystem, buildStartTime: buildRecord.buildStartTime)
72+
info.fileSystem, buildStartTime: buildRecord.buildStartTime, externalDependencyFileHashes: externalDependencyFileHashes, useFileHashes: info.useFileHashesInModuleDependencyGraph)
7173
self.info = info
7274
self.dotFileWriter = info.emitDependencyDotFileAfterEveryImport
7375
? DependencyGraphDotFileWriter(info)
@@ -88,22 +90,24 @@ import struct Foundation.TimeInterval
8890
"If updating from prior, should be supplying more ingredients")
8991
self.init(buildRecord, info, phase, InternedStringTable(info.incrementalCompilationQueue),
9092
NodeFinder(),
91-
Set())
93+
Set(), Dictionary())
9294
}
9395

9496
public static func createFromPrior(
9597
_ buildRecord: BuildRecord,
9698
_ info: IncrementalCompilationState.IncrementalDependencyAndInputSetup,
9799
_ internedStringTable: InternedStringTable,
98100
_ nodeFinder: NodeFinder,
99-
_ fingerprintedExternalDependencies: Set<FingerprintedExternalDependency>
101+
_ fingerprintedExternalDependencies: Set<FingerprintedExternalDependency>,
102+
_ externalDependencyFileHashes: Dictionary<ExternalDependency, String>
100103
) -> Self {
101104
self.init(buildRecord,
102105
info,
103106
.updatingFromAPrior,
104107
internedStringTable,
105108
nodeFinder,
106-
fingerprintedExternalDependencies)
109+
fingerprintedExternalDependencies,
110+
externalDependencyFileHashes)
107111
}
108112

109113
public static func createForBuildingFromSwiftDeps(
@@ -567,10 +571,24 @@ extension ModuleDependencyGraph {
567571
private let fileSystem: FileSystem
568572
private let buildStartTime: TimePoint
569573
private var currencyCache = [ExternalDependency: Bool]()
574+
public internal(set) var externalDependencyFileHashes: [ExternalDependency: String]
575+
private let useFileHashes: Bool
570576

571-
init(_ fileSystem: FileSystem, buildStartTime: TimePoint) {
577+
init(_ fileSystem: FileSystem, buildStartTime: TimePoint, externalDependencyFileHashes: Dictionary<ExternalDependency, String>, useFileHashes: Bool) {
572578
self.fileSystem = fileSystem
573579
self.buildStartTime = buildStartTime
580+
self.externalDependencyFileHashes = externalDependencyFileHashes
581+
self.useFileHashes = useFileHashes
582+
}
583+
584+
mutating func updateHash(_ externalDependency: ExternalDependency) {
585+
guard useFileHashes else {
586+
return
587+
}
588+
if let depFile = externalDependency.path,
589+
let data = try? fileSystem.readFileContents(depFile) {
590+
externalDependencyFileHashes[externalDependency] = SHA256().hash(data).hexadecimalRepresentation
591+
}
574592
}
575593

576594
mutating func beCurrent(_ externalDependency: ExternalDependency) {
@@ -581,11 +599,31 @@ extension ModuleDependencyGraph {
581599
if let cachedResult = self.currencyCache[externalDependency] {
582600
return cachedResult
583601
}
584-
let uncachedResult = isCurrentWRTFileSystem(externalDependency)
602+
var uncachedResult = isCurrentWRTFileSystem(externalDependency)
603+
if useFileHashes && !uncachedResult {
604+
uncachedResult = isCurrentWRTFileHash(externalDependency)
605+
}
585606
self.currencyCache[externalDependency] = uncachedResult
607+
if !uncachedResult {
608+
self.updateHash(externalDependency)
609+
}
586610
return uncachedResult
587611
}
588612

613+
mutating func ensureHash(_ externalDependency: ExternalDependency) {
614+
if (self.externalDependencyFileHashes[externalDependency] ?? "").isEmpty {
615+
self.updateHash(externalDependency)
616+
}
617+
}
618+
619+
private func isCurrentWRTFileHash(_ externalDependency: ExternalDependency) -> Bool {
620+
if let depFile = externalDependency.path,
621+
let data = try? fileSystem.readFileContents(depFile) {
622+
return self.externalDependencyFileHashes[externalDependency] == SHA256().hash(data).hexadecimalRepresentation
623+
}
624+
return false
625+
}
626+
589627
private func isCurrentWRTFileSystem(_ externalDependency: ExternalDependency) -> Bool {
590628
if let depFile = externalDependency.path,
591629
let fileModTime = try? self.fileSystem.lastModificationTime(for: depFile),
@@ -753,6 +791,7 @@ extension ModuleDependencyGraph {
753791
private var currentDefKey: DependencyKey? = nil
754792
private var nodeUses: [(DependencyKey, Int)] = []
755793
private var fingerprintedExternalDependencies = Set<FingerprintedExternalDependency>()
794+
private var externalDependencyFileHashes = Dictionary<ExternalDependency, String>()
756795

757796
/// Deserialized nodes, in order appearing in the priors file. If `nil`, the node is for a removed source file.
758797
///
@@ -789,7 +828,8 @@ extension ModuleDependencyGraph {
789828
info,
790829
internedStringTable,
791830
nodeFinder,
792-
fingerprintedExternalDependencies)
831+
fingerprintedExternalDependencies,
832+
externalDependencyFileHashes)
793833
for (dependencyKey, useID) in self.nodeUses {
794834
guard let use = self.potentiallyUsedNodes[useID] else {
795835
// Don't record uses of defs of removed files.
@@ -982,16 +1022,19 @@ extension ModuleDependencyGraph {
9821022
}
9831023
self.nodeUses.append( (key, Int(record.fields[0])) )
9841024
case .externalDepNode:
985-
guard record.fields.count == 2
1025+
guard record.fields.count == 2,
1026+
case .blob(let hashBlob) = record.payload
9861027
else {
9871028
throw malformedError
9881029
}
9891030
let path = try internedString(field: 0)
9901031
let fingerprint = try nonemptyInternedString(field: 1)
1032+
let hash = String(decoding: hashBlob, as: UTF8.self)
1033+
let externalDependency = ExternalDependency(fileName: path, internedStringTable)
1034+
9911035
fingerprintedExternalDependencies.insert(
992-
FingerprintedExternalDependency(
993-
ExternalDependency(fileName: path, internedStringTable),
994-
fingerprint))
1036+
FingerprintedExternalDependency(externalDependency, fingerprint))
1037+
externalDependencyFileHashes[externalDependency] = hash
9951038
case .identifierNode:
9961039
guard record.fields.count == 0,
9971040
case .blob(let identifierBlob) = record.payload
@@ -1170,7 +1213,7 @@ extension ModuleDependencyGraph {
11701213
$0.append(inputInfo.previousModTime)
11711214
$0.append(inputInfo.status.code)
11721215
$0.append(pathID)
1173-
}, blob: inputInfo.hash)
1216+
}, blob: inputInfo.hash ?? "")
11741217
}
11751218
}
11761219

@@ -1282,6 +1325,8 @@ extension ModuleDependencyGraph {
12821325
.vbr(chunkBitWidth: 13),
12831326
// fingerprint ID
12841327
.vbr(chunkBitWidth: 13),
1328+
// file hash
1329+
.blob,
12851330
])
12861331
self.abbreviate(.identifierNode, [
12871332
.literal(RecordID.identifierNode.rawValue),
@@ -1356,13 +1401,15 @@ extension ModuleDependencyGraph {
13561401
}
13571402
}
13581403
for fingerprintedExternalDependency in graph.fingerprintedExternalDependencies {
1359-
serializer.stream.writeRecord(serializer.abbreviations[.externalDepNode]!) {
1404+
graph.currencyCache.ensureHash(fingerprintedExternalDependency.externalDependency)
1405+
serializer.stream.writeRecord(serializer.abbreviations[.externalDepNode]!, {
13601406
$0.append(RecordID.externalDepNode)
13611407
$0.append(serializer.lookupIdentifierCode(
13621408
for: fingerprintedExternalDependency.externalDependency.fileName))
13631409
$0.append( serializer.lookupIdentifierCode(
13641410
for: fingerprintedExternalDependency.fingerprint))
1365-
}
1411+
},
1412+
blob: graph.currencyCache.externalDependencyFileHashes[fingerprintedExternalDependency.externalDependency] ?? "")
13661413
}
13671414
}
13681415
return ByteString(serializer.stream.data)

Sources/SwiftDriver/ToolingInterface/SimpleExecutor.swift

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,45 @@ import class TSCBasic.Process
4545
public func execute(workload: DriverExecutorWorkload, delegate: JobExecutionDelegate,
4646
numParallelJobs: Int, forceResponseFiles: Bool,
4747
recordedInputMetadata: [TypedVirtualPath : FileMetadata]) throws {
48-
fatalError("Unsupported operation on current executor")
48+
fatalError("Unsupported operation on current executor")
4949
}
5050

51+
public func execute(job: Job,
52+
forceResponseFiles: Bool,
53+
recordedInputModificationDates: [TypedVirtualPath : TimePoint]) throws -> ProcessResult {
54+
fatalError("Unsupported legacy operation on current executor")
55+
}
56+
57+
public func execute(workload: DriverExecutorWorkload, delegate: JobExecutionDelegate,
58+
numParallelJobs: Int, forceResponseFiles: Bool,
59+
recordedInputModificationDates: [TypedVirtualPath : TimePoint]) throws {
60+
fatalError("Unsupported operation on current executor")
61+
}
62+
63+
public func execute(jobs: [Job],
64+
delegate: JobExecutionDelegate,
65+
numParallelJobs: Int,
66+
forceResponseFiles: Bool,
67+
recordedInputModificationDates: [TypedVirtualPath: TimePoint]) throws {
68+
fatalError("Unsupported legacy operation on current executor")
69+
}
70+
71+
public func execute(
72+
jobs: [Job],
73+
delegate: JobExecutionDelegate,
74+
numParallelJobs: Int,
75+
forceResponseFiles: Bool,
76+
recordedInputMetadata: [TypedVirtualPath: FileMetadata]
77+
) throws {
78+
try execute(
79+
workload: .all(jobs),
80+
delegate: delegate,
81+
numParallelJobs: numParallelJobs,
82+
forceResponseFiles: forceResponseFiles,
83+
recordedInputMetadata: recordedInputMetadata)
84+
}
85+
86+
5187
public func checkNonZeroExit(args: String..., environment: [String : String]) throws -> String {
5288
try Process.checkNonZeroExit(arguments: args, environment: environment)
5389
}

0 commit comments

Comments
 (0)