Skip to content

Commit 1a9f77a

Browse files
committed
Perform Input Modification Checks At the End of the Build
Also deduplicate them while we're at it. We don't need to ask each job for its input set, we just need to check the same set of files we recorded mod times for in the first place. This saves a quadratic amount of disk I/O in the incremental and non-batch builds, which can be a huge wall time savings for large compiles.
1 parent 9a1b628 commit 1a9f77a

File tree

3 files changed

+16
-5
lines changed

3 files changed

+16
-5
lines changed

Sources/SwiftDriver/Utilities/VirtualPath.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ extension TSCBasic.FileSystem {
648648
try resolvingVirtualPath(path, apply: exists)
649649
}
650650

651-
func lastModificationTime(for file: VirtualPath) throws -> Date {
651+
public func lastModificationTime(for file: VirtualPath) throws -> Date {
652652
try resolvingVirtualPath(file) { path in
653653
#if os(macOS)
654654
var s = Darwin.stat()

Sources/SwiftDriverExecution/MultiJobExecutor.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,16 @@ public final class MultiJobExecutor {
281281

282282
context.reportSkippedJobs()
283283

284+
// Check for any inputs that were modified during the build. Report these
285+
// as errors so we don't e.g. reuse corrupted incremental build state.
286+
for (input, recordedModTime) in context.recordedInputModificationDates {
287+
guard try fileSystem.lastModificationTime(for: input.file) == recordedModTime else {
288+
let err = Job.InputError.inputUnexpectedlyModified(input)
289+
context.diagnosticsEngine.emit(err)
290+
throw err
291+
}
292+
}
293+
284294
// Throw the stub error the build didn't finish successfully.
285295
if !result.success {
286296
throw Diagnostics.fatalError
@@ -535,8 +545,6 @@ class ExecuteJobRule: LLBuildRule {
535545
let arguments: [String] = try resolver.resolveArgumentList(for: job,
536546
forceResponseFiles: context.forceResponseFiles)
537547

538-
try job.verifyInputsNotModified(since: context.recordedInputModificationDates, fileSystem: engine.fileSystem)
539-
540548
let process = try context.processType.launchProcess(
541549
arguments: arguments, env: env
542550
)

Tests/SwiftDriverTests/JobExecutorTests.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,12 @@ final class JobExecutorTests: XCTestCase {
323323
$0 <<< "let bar = 3"
324324
}
325325

326-
// FIXME: It's unfortunate we diagnose this twice, once for each job which uses the input.
327-
verifier.expect(.error("input file '\(other.description)' was modified during the build"))
328326
verifier.expect(.error("input file '\(other.description)' was modified during the build"))
327+
// There's a tool-specific linker error that usually happens here from
328+
// whatever job runs last - probably the linker.
329+
// It's no use testing for a particular error message, let's just make
330+
// sure we emit the diagnostic we need.
331+
verifier.permitUnexpected(.error)
329332
XCTAssertThrowsError(try driver.run(jobs: jobs))
330333
}
331334
}

0 commit comments

Comments
 (0)