Skip to content

Commit 8503254

Browse files
[Caching] Fix incremental cache build with bridging header
When planning a caching build with bridging header, the consumer of the pre-compiled bridging header needs to know the cache key for the bridging header in order to construct the correct build command, which requires visibility to the job that creates PCH. Fix the incremental planning for bridging header users so those job can be correctly constructed even the bridging header job is skipped for the incremental build. rdar://118143215
1 parent 8395c0f commit 8503254

File tree

6 files changed

+91
-13
lines changed

6 files changed

+91
-13
lines changed

Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ extension IncrementalCompilationState.FirstWaveComputer {
8383
Dictionary(uniqueKeysWithValues:
8484
jobsInPhases.compileGroups.map { ($0.primaryInput, $0) })
8585
let buildRecord = self.moduleDependencyGraph.buildRecord
86+
let jobCreatingPch = jobsInPhases.beforeCompiles.first(where: {$0.kind == .generatePCH})
8687
guard !buildRecord.inputInfos.isEmpty else {
8788
func everythingIsMandatory()
8889
throws -> (initiallySkippedCompileGroups: [TypedVirtualPath: CompileJobGroup],
@@ -97,7 +98,8 @@ extension IncrementalCompilationState.FirstWaveComputer {
9798
jobsInPhases.beforeCompiles +
9899
batchJobFormer.formBatchedJobs(
99100
mandatoryCompileGroupsInOrder.flatMap {$0.allJobs()},
100-
showJobLifecycle: showJobLifecycle)
101+
showJobLifecycle: showJobLifecycle,
102+
jobCreatingPch: jobCreatingPch)
101103

102104
moduleDependencyGraph.setPhase(to: .buildingAfterEachCompilation)
103105
return (initiallySkippedCompileGroups: [:],
@@ -124,7 +126,8 @@ extension IncrementalCompilationState.FirstWaveComputer {
124126
let mandatoryBeforeCompilesJobs = try computeMandatoryBeforeCompilesJobs()
125127
let batchedCompilationJobs = try batchJobFormer.formBatchedJobs(
126128
mandatoryCompileGroupsInOrder.flatMap {$0.allJobs()},
127-
showJobLifecycle: showJobLifecycle)
129+
showJobLifecycle: showJobLifecycle,
130+
jobCreatingPch: jobCreatingPch)
128131

129132
// In the case where there are no compilation jobs to run on this build (no source-files were changed),
130133
// we can skip running `beforeCompiles` jobs if we also ensure that none of the `afterCompiles` jobs

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationProtectedState.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@ extension IncrementalCompilationState {
3434
/// fileprivate in order to control concurrency.
3535
fileprivate let moduleDependencyGraph: ModuleDependencyGraph
3636

37+
fileprivate var jobCreatingPch: Job?
3738
fileprivate let reporter: Reporter?
3839

3940
init(skippedCompileGroups: [TypedVirtualPath: CompileJobGroup],
4041
_ moduleDependencyGraph: ModuleDependencyGraph,
42+
_ jobCreatingPch: Job?,
4143
_ driver: inout Driver) {
4244
self.skippedCompileGroups = skippedCompileGroups
4345
self.moduleDependencyGraph = moduleDependencyGraph
4446
self.reporter = moduleDependencyGraph.info.reporter
47+
self.jobCreatingPch = jobCreatingPch
4548
self.driver = driver
4649
}
4750
}
@@ -59,9 +62,13 @@ extension IncrementalCompilationState.ProtectedState {
5962
job finishedJob: Job
6063
) throws -> [Job]? {
6164
mutationSafetyPrecondition()
65+
// If a new pch job is finished, update the pch creating job.
66+
if finishedJob.kind == .generatePCH {
67+
jobCreatingPch = finishedJob
68+
}
6269
// batch in here to protect the Driver from concurrent access
6370
return try collectUnbatchedJobsDiscoveredToBeNeededAfterFinishing(job: finishedJob)
64-
.map {try driver.formBatchedJobs($0, showJobLifecycle: driver.showJobLifecycle)}
71+
.map {try driver.formBatchedJobs($0, showJobLifecycle: driver.showJobLifecycle, jobCreatingPch: jobCreatingPch)}
6572
}
6673

6774
/// Remember a job (group) that is before a compile or a compile itself.

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState.swift

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public final class IncrementalCompilationState {
7777
self.protectedState = ProtectedState(
7878
skippedCompileGroups: firstWave.initiallySkippedCompileGroups,
7979
initialState.graph,
80+
jobsInPhases.allJobs.first(where: {$0.kind == .generatePCH}),
8081
&driver)
8182
self.mandatoryJobsInOrder = firstWave.mandatoryJobsInOrder
8283
self.jobsAfterCompiles = jobsInPhases.afterCompiles

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

+5-7
Original file line numberDiff line numberDiff line change
@@ -453,14 +453,12 @@ extension Driver {
453453
pchCompileJob: Job?) throws {
454454
guard let pchJob = pchCompileJob, isCachingEnabled else { return }
455455

456-
// The pch input file (the bridging header) is added as last inputs to the job.
457-
guard let inputFile = pchJob.inputs.last else { assertionFailure("no input files from pch job"); return }
458-
assert(inputFile.type == .objcHeader, "Expect objc header input type")
459-
let bridgingHeaderCacheKey = try computeOutputCacheKey(commandLine: pchJob.commandLine,
460-
input: inputFile)
461-
guard let key = bridgingHeaderCacheKey else { return }
456+
assert(pchJob.outputCacheKeys.count == 1, "Expect one and only one cache key from pch job")
457+
guard let bridgingHeaderCacheKey = pchJob.outputCacheKeys.first?.value else {
458+
throw Error.unsupportedConfigurationForCaching("pch job doesn't have an associated cache key")
459+
}
462460
commandLine.appendFlag("-bridging-header-pch-key")
463-
commandLine.appendFlag(key)
461+
commandLine.appendFlag(bridgingHeaderCacheKey)
464462
}
465463

466464
mutating func addFrontendSupplementaryOutputArguments(commandLine: inout [Job.ArgTemplate],

Sources/SwiftDriver/Jobs/Planning.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ extension Driver {
127127
// can be returned from `planBuild`.
128128
// But in that case, don't emit lifecycle messages.
129129
formBatchedJobs(jobsInPhases.allJobs,
130-
showJobLifecycle: showJobLifecycle && incrementalCompilationState == nil),
130+
showJobLifecycle: showJobLifecycle && incrementalCompilationState == nil,
131+
jobCreatingPch: jobsInPhases.allJobs.first(where: {$0.kind == .generatePCH})),
131132
incrementalCompilationState
132133
)
133134
}
@@ -836,7 +837,7 @@ extension Driver {
836837
///
837838
/// So, in order to avoid making jobs and rebatching, the code would have to just get outputs for each
838839
/// compilation. But `compileJob` intermixes the output computation with other stuff.
839-
mutating func formBatchedJobs(_ jobs: [Job], showJobLifecycle: Bool) throws -> [Job] {
840+
mutating func formBatchedJobs(_ jobs: [Job], showJobLifecycle: Bool, jobCreatingPch: Job?) throws -> [Job] {
840841
guard compilerMode.isBatchCompile else {
841842
// Don't even go through the logic so as to not print out confusing
842843
// "batched foobar" messages.
@@ -862,7 +863,6 @@ extension Driver {
862863
compileJobs.filter { $0.outputs.contains {$0.type == .moduleTrace} }
863864
.flatMap {$0.primaryInputs}
864865
)
865-
let jobCreatingPch: Job? = jobs.first(where: {$0.kind == .generatePCH})
866866

867867
let batchedCompileJobs = try inputsInOrder.compactMap { anInput -> Job? in
868868
let idx = partitions.assignment[anInput]!

Tests/SwiftDriverTests/CachingBuildTests.swift

+69
Original file line numberDiff line numberDiff line change
@@ -761,4 +761,73 @@ final class CachingBuildTests: XCTestCase {
761761
}
762762
}
763763
}
764+
765+
func testCacheIncrementalBuildPlan() throws {
766+
try withTemporaryDirectory { path in
767+
try localFileSystem.changeCurrentWorkingDirectory(to: path)
768+
let moduleCachePath = path.appending(component: "ModuleCache")
769+
let casPath = path.appending(component: "cas")
770+
try localFileSystem.createDirectory(moduleCachePath)
771+
let main = path.appending(component: "testCachingBuild.swift")
772+
let mainFileContent = "import C;import E;import G;"
773+
try localFileSystem.writeFileContents(main) {
774+
$0.send(mainFileContent)
775+
}
776+
let ofm = path.appending(component: "ofm.json")
777+
let inputPathsAndContents: [(AbsolutePath, String)] = [(main, mainFileContent)]
778+
OutputFileMapCreator.write(
779+
module: "Test", inputPaths: inputPathsAndContents.map {$0.0},
780+
derivedData: path, to: ofm, excludeMainEntry: false)
781+
782+
let cHeadersPath: AbsolutePath =
783+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
784+
.appending(component: "CHeaders")
785+
let swiftModuleInterfacesPath: AbsolutePath =
786+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
787+
.appending(component: "Swift")
788+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
789+
let bridgingHeaderpath: AbsolutePath =
790+
cHeadersPath.appending(component: "Bridging.h")
791+
var driver = try Driver(args: ["swiftc",
792+
"-I", cHeadersPath.nativePathString(escaped: true),
793+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
794+
"-explicit-module-build", "-v", "-Rcache-compile-job", "-incremental",
795+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
796+
"-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true),
797+
"-import-objc-header", bridgingHeaderpath.nativePathString(escaped: true),
798+
"-output-file-map", ofm.nativePathString(escaped: true),
799+
"-working-directory", path.nativePathString(escaped: true),
800+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
801+
env: ProcessEnv.vars)
802+
guard driver.isFeatureSupported(.cache_compile_job) else {
803+
throw XCTSkip("toolchain does not support caching.")
804+
}
805+
let jobs = try driver.planBuild()
806+
try driver.run(jobs: jobs)
807+
XCTAssertFalse(driver.diagnosticEngine.hasErrors)
808+
809+
let dependencyOracle = InterModuleDependencyOracle()
810+
let scanLibPath = try XCTUnwrap(driver.toolchain.lookupSwiftScanLib())
811+
guard try dependencyOracle
812+
.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
813+
swiftScanLibPath: scanLibPath) else {
814+
XCTFail("Dependency scanner library not found")
815+
return
816+
}
817+
818+
let cas = try dependencyOracle.createCAS(pluginPath: nil, onDiskPath: casPath, pluginOptions: [])
819+
try checkCASForResults(jobs: jobs, cas: cas, fs: driver.fileSystem)
820+
821+
// try replan the job and make sure some key command-line options are generated.
822+
let rebuildJobs = try driver.planBuild()
823+
for job in rebuildJobs {
824+
if job.kind == .compile || job.kind == .emitModule {
825+
XCTAssertTrue(job.commandLine.contains(.flag(String("-disable-implicit-swift-modules"))))
826+
XCTAssertTrue(job.commandLine.contains(.flag(String("-cache-compile-job"))))
827+
XCTAssertTrue(job.commandLine.contains(.flag(String("-cas-path"))))
828+
XCTAssertTrue(job.commandLine.contains(.flag(String("-bridging-header-pch-key"))))
829+
}
830+
}
831+
}
832+
}
764833
}

0 commit comments

Comments
 (0)