Skip to content
Open
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
38 changes: 35 additions & 3 deletions Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,41 @@ extension IncrementalCompilationState.FirstWaveComputer {
return false
}

// Ensure that no output is older than any of the inputs
let oldestOutputModTime: TimePoint = try emitModuleJob.outputs.map { try fileSystem.lastModificationTime(for: $0.file) }.min() ?? .distantPast
return try emitModuleJob.inputs.swiftSourceFiles.allSatisfy({ try fileSystem.lastModificationTime(for: $0.typedFile.file) < oldestOutputModTime })
// If all the modules are older than the outputs, we can skip
let oldestOutputModTime = try emitModuleJob.outputs.map { try fileSystem.lastModificationTime(for: $0.file) }.min() ?? .distantPast
let areModulesOlderThanOutput = try emitModuleJob.inputs.swiftSourceFiles.allSatisfy({ try fileSystem.lastModificationTime(for: $0.typedFile.file) < oldestOutputModTime })
guard !areModulesOlderThanOutput else {
return true
}
// If we are not using hashes, we cannot skip
guard useHashes else {
return false
}
let inputs = emitModuleJob.inputs
for input in inputs {
guard let currentDate = buildRecordInfo.compilationInputModificationDates[input] else {
reporter?.report("Missing file metadata for: \(input)")
return false
}

guard let currentHash = currentDate.hash else {
reporter?.report("Missing file hash data for: \(input)")
return false
}

let inputInfos = buildRecord.inputInfos
guard let inputInfo = inputInfos[input.file] else {
reporter?.report("Missing incremental info for: \(input)")
return false
}

if currentHash != inputInfo.hash {
reporter?.report("Changed hash for: \(input)")
return false
}
}
return true

}

/// Figure out which compilation inputs are *not* mandatory at the start
Expand Down
29 changes: 29 additions & 0 deletions Tests/SwiftDriverTests/IncrementalCompilationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,35 @@ extension IncrementalCompilationTests {
XCTAssertFalse(mandatoryJobInputs.contains("other.swift"))
}

// Source file timestamps updated but contents are the same, with file-hashing emit-module job should be skipped
func testNullBuildNoEmitModuleWithHashing() throws {
let extraArguments = ["-experimental-emit-module-separately", "-emit-module", "-enable-incremental-file-hashing"]
try buildInitialState(extraArguments: extraArguments)
touch("main")
touch("other")
touch(try AbsolutePath(validating: explicitSwiftDependenciesPath.appending(component: "E.swiftinterface").pathString))
let driver = try doABuildWithoutExpectations(arguments: commonArgs + extraArguments + (try XCTUnwrap(Driver.sdkArgumentsForTesting())))
let mandatoryJobs = try XCTUnwrap(driver.incrementalCompilationState?.mandatoryJobsInOrder)
let mandatoryJobInputs = mandatoryJobs.flatMap { $0.inputs }.map { $0.file.basename }
XCTAssertFalse(mandatoryJobs.contains { $0.kind == .emitModule }, "emit-module should be skipped when using hashes and content unchanged")
XCTAssertFalse(mandatoryJobInputs.contains("main.swift"))
XCTAssertFalse(mandatoryJobInputs.contains("other.swift"))
}

// Source file updated, emit-module job should not be skipped regardless of file-hashing
func testEmitModuleWithHashingWhenContentChanges() throws {
let extraArguments = ["-experimental-emit-module-separately", "-emit-module", "-enable-incremental-file-hashing"]
try buildInitialState(extraArguments: extraArguments)
replace(contentsOf: "main", with: "let foo = 2")
let driver = try doABuildWithoutExpectations(arguments: commonArgs + extraArguments + (try XCTUnwrap(Driver.sdkArgumentsForTesting())))
let mandatoryJobs = try XCTUnwrap(driver.incrementalCompilationState?.mandatoryJobsInOrder)
let mandatoryJobInputs = mandatoryJobs.flatMap { $0.inputs }.map { $0.file.basename }
XCTAssertTrue(mandatoryJobs.contains { $0.kind == .emitModule }, "emit-module should run when using hashes and content has changed")
XCTAssertTrue(mandatoryJobInputs.contains("main.swift"))
}



// External deps timestamp updated but contents are the same, and file-hashing is explicitly disabled
func testExplicitIncrementalBuildExternalDepsWithoutHashing() throws {
replace(contentsOf: "other", with: "import E;let bar = foo")
Expand Down