Skip to content

Commit 3dfab50

Browse files
authored
Merge pull request swiftlang#1846 from swiftlang/owenv/ebm-verify-incremental-fix
Ensure we don't skip precompile jobs if a planned interface verification depends on recompiling modules
2 parents 2668804 + c72e321 commit 3dfab50

File tree

3 files changed

+82
-5
lines changed

3 files changed

+82
-5
lines changed

Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ extension IncrementalCompilationState.FirstWaveComputer {
139139
private func nonVerifyAfterCompileJobsDependOnBeforeCompileJobs() -> Bool {
140140
let beforeCompileJobOutputs = jobsInPhases.beforeCompiles.reduce(into: Set<TypedVirtualPath>(),
141141
{ (pathSet, job) in pathSet.formUnion(job.outputs) })
142-
let afterCompilesDependnigJobs = jobsInPhases.afterCompiles.filter {postCompileJob in postCompileJob.inputs.contains(where: beforeCompileJobOutputs.contains)}
143-
if afterCompilesDependnigJobs.isEmpty || afterCompilesDependnigJobs.allSatisfy({ $0.kind == .verifyModuleInterface }) {
142+
let afterCompilesDependingJobs = jobsInPhases.afterCompiles.filter {postCompileJob in postCompileJob.inputs.contains(where: beforeCompileJobOutputs.contains)}
143+
if afterCompilesDependingJobs.isEmpty {
144144
return false
145+
} else if afterCompilesDependingJobs.allSatisfy({ $0.kind == .verifyModuleInterface }) {
146+
return jobsInPhases.beforeCompiles.contains { $0.kind == .generatePCM || $0.kind == .compileModuleFromInterface }
145147
} else {
146148
return true
147149
}

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,12 @@ extension Driver {
199199
try commandLine.appendLast(.AssertConfig, from: &parsedOptions)
200200
try commandLine.appendLast(.autolinkForceLoad, from: &parsedOptions)
201201

202-
if let colorOption = parsedOptions.last(for: .colorDiagnostics, .noColorDiagnostics) {
203-
commandLine.appendFlag(colorOption.option)
204-
} else if shouldColorDiagnostics() {
202+
if parsedOptions.hasFlag(positive: .colorDiagnostics, negative: .noColorDiagnostics, default: shouldColorDiagnostics()) {
205203
commandLine.appendFlag(.colorDiagnostics)
204+
appendXccFlag("-fcolor-diagnostics")
205+
} else {
206+
commandLine.appendFlag(.noColorDiagnostics)
207+
appendXccFlag("-fno-color-diagnostics")
206208
}
207209
try commandLine.appendLast(.fixitAll, from: &parsedOptions)
208210
try commandLine.appendLast(.warnSwift3ObjcInferenceMinimal, .warnSwift3ObjcInferenceComplete, from: &parsedOptions)

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,79 @@ final class ExplicitModuleBuildTests: XCTestCase {
773773
}
774774
}
775775

776+
// This is a regression test for the following scenario:
777+
// 1. The user does a clean build of their project with explicit modules
778+
// 2. The user starts a subsequent build using changed flags/environment which do not affect incremental builds (e.g. color diagnostics)
779+
// 3. That flag/environment change does affect PCM hashes returned by the clang dependency scanner.
780+
// 4. The driver determines that modules need to rebuild, sources don't need to rebuild, and verification needs to re-run.
781+
// 5. The driver decides it's safe to skip the pre-compile jobs (the module builds) because the only post-compile jobs are for verification.
782+
// 6. Verification fails because the modules with the new PCM hashes are missing.
783+
// Ideally, this should never happen. If a flag doesn't invalidate Swift compilation, it shouldn;t be impacting module hashes either. But I
784+
// think we should be resilient to this by rebuilding the modules in this scenario.
785+
//
786+
// The below test is somewhat fragile in that it will be redundant if we canonicalize away -fcolor-diagnostics in clang, but it's useful to ensure
787+
// end to end behavior does not regress again.
788+
func testExplicitModuleBuildDoesNotSkipPrecompiledModulesWhenOnlyVerificationIsInvalidated() throws {
789+
try withTemporaryDirectory { path in
790+
try localFileSystem.changeCurrentWorkingDirectory(to: path)
791+
let moduleCachePath = path.appending(component: "ModuleCache")
792+
try localFileSystem.createDirectory(moduleCachePath)
793+
let main = path.appending(component: "testExplicitModuleBuildEndToEnd.swift")
794+
try localFileSystem.writeFileContents(main, bytes:
795+
"""
796+
import C;
797+
import E;
798+
import G;
799+
800+
func foo() {
801+
funcE()
802+
}
803+
"""
804+
)
805+
let outputFileMap = path.appending(component: "output-file-map.json")
806+
try localFileSystem.writeFileContents(outputFileMap, bytes: ByteString(encodingAsUTF8: """
807+
{
808+
"": {
809+
"swift-dependencies": "\(path.appending(component: "main.swiftdeps").nativePathString(escaped: true))"
810+
},
811+
"\(path.appending(component: "testExplicitModuleBuildEndToEnd.swift").nativePathString(escaped: true))": {
812+
"swift-dependencies": "\(path.appending(component: "testExplicitModuleBuildEndToEnd.swiftdeps").nativePathString(escaped: true))",
813+
"object": "\(path.appending(component: "testExplicitModuleBuildEndToEnd.o").nativePathString(escaped: true))"
814+
}
815+
}
816+
"""))
817+
818+
let cHeadersPath: AbsolutePath =
819+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
820+
.appending(component: "CHeaders")
821+
let swiftModuleInterfacesPath: AbsolutePath =
822+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
823+
.appending(component: "Swift")
824+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
825+
let invocationArguments = ["swiftc",
826+
"-incremental", "-c",
827+
"-emit-module",
828+
"-enable-library-evolution", "-emit-module-interface", "-driver-show-incremental", "-v",
829+
"-Xcc", "-Xclang", "-Xcc", "-fbuiltin-headers-in-system-modules",
830+
"-I", cHeadersPath.nativePathString(escaped: true),
831+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
832+
"-explicit-module-build",
833+
"-output-file-map", outputFileMap.nativePathString(escaped: true),
834+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
835+
"-working-directory", path.nativePathString(escaped: true),
836+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting
837+
var driver = try Driver(args: invocationArguments)
838+
let jobs = try driver.planBuild()
839+
try driver.run(jobs: jobs)
840+
XCTAssertFalse(driver.diagnosticEngine.hasErrors)
841+
842+
var incrementalDriver = try Driver(args: invocationArguments + ["-color-diagnostics"])
843+
let incrementalJobs = try incrementalDriver.planBuild()
844+
try incrementalDriver.run(jobs: incrementalJobs)
845+
XCTAssertFalse(incrementalDriver.diagnosticEngine.hasErrors)
846+
}
847+
}
848+
776849
/// Test generation of explicit module build jobs for dependency modules when the driver
777850
/// is invoked with -explicit-module-build, -verify-emitted-module-interface and -enable-library-evolution.
778851
func testExplicitModuleVerifyInterfaceJobs() throws {

0 commit comments

Comments
 (0)