Skip to content

Commit 77ffaa2

Browse files
committedJan 31, 2025·
[Caching] Invalidate built module if missing from CAS
For swift caching build, swift-frontend only reads from CAS for inputs for full build reproducibility. In this case, if the CAS is removed, we need to treat the modules as missing from CAS and rebuild them, even if the version on disk appears to pass the up-to-date check. rdar://135718227

File tree

6 files changed

+109
-4
lines changed

6 files changed

+109
-4
lines changed
 

‎Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

+41-4
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ internal extension InterModuleDependencyGraph {
158158
/// between it and the root (source module being built by this driver
159159
/// instance) must also be re-built.
160160
func computeInvalidatedModuleDependencies(fileSystem: FileSystem,
161+
cas: SwiftScanCAS?,
161162
forRebuild: Bool,
162163
reporter: IncrementalCompilationState.Reporter? = nil)
163164
throws -> Set<ModuleDependencyId> {
@@ -169,7 +170,7 @@ internal extension InterModuleDependencyGraph {
169170
for dependencyId in mainModuleInfo.directDependencies ?? [] {
170171
try outOfDateModuleScan(from: dependencyId, visited: &visited,
171172
modulesRequiringRebuild: &modulesRequiringRebuild,
172-
fileSystem: fileSystem, forRebuild: forRebuild,
173+
fileSystem: fileSystem, cas: cas, forRebuild: forRebuild,
173174
reporter: reporter)
174175
}
175176

@@ -183,10 +184,11 @@ internal extension InterModuleDependencyGraph {
183184
/// filter out those with a fully up-to-date output
184185
func filterMandatoryModuleDependencyCompileJobs(_ allJobs: [Job],
185186
fileSystem: FileSystem,
187+
cas: SwiftScanCAS?,
186188
reporter: IncrementalCompilationState.Reporter? = nil) throws -> [Job] {
187189
// Determine which module pre-build jobs must be re-run
188190
let modulesRequiringReBuild =
189-
try computeInvalidatedModuleDependencies(fileSystem: fileSystem, forRebuild: true, reporter: reporter)
191+
try computeInvalidatedModuleDependencies(fileSystem: fileSystem, cas: cas, forRebuild: true, reporter: reporter)
190192

191193
// Filter the `.generatePCM` and `.compileModuleFromInterface` jobs for
192194
// modules which do *not* need re-building.
@@ -209,6 +211,7 @@ internal extension InterModuleDependencyGraph {
209211
visited: inout Set<ModuleDependencyId>,
210212
modulesRequiringRebuild: inout Set<ModuleDependencyId>,
211213
fileSystem: FileSystem,
214+
cas: SwiftScanCAS?,
212215
forRebuild: Bool,
213216
reporter: IncrementalCompilationState.Reporter? = nil) throws {
214217
let reportOutOfDate = { (name: String, reason: String) in
@@ -227,7 +230,7 @@ internal extension InterModuleDependencyGraph {
227230
if !visited.contains(dependencyId) {
228231
try outOfDateModuleScan(from: dependencyId, visited: &visited,
229232
modulesRequiringRebuild: &modulesRequiringRebuild,
230-
fileSystem: fileSystem, forRebuild: forRebuild,
233+
fileSystem: fileSystem, cas: cas, forRebuild: forRebuild,
231234
reporter: reporter)
232235
}
233236
// Even if we're not revisiting a dependency, we must check if it's already known to be out of date.
@@ -237,7 +240,7 @@ internal extension InterModuleDependencyGraph {
237240
if hasOutOfDateModuleDependency {
238241
reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Invalidated by downstream dependency")
239242
modulesRequiringRebuild.insert(sourceModuleId)
240-
} else if try !verifyModuleDependencyUpToDate(moduleID: sourceModuleId, fileSystem: fileSystem, reporter: reporter) {
243+
} else if try !verifyModuleDependencyUpToDate(moduleID: sourceModuleId, fileSystem: fileSystem, cas:cas, reporter: reporter) {
241244
reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Out-of-date")
242245
modulesRequiringRebuild.insert(sourceModuleId)
243246
}
@@ -246,10 +249,44 @@ internal extension InterModuleDependencyGraph {
246249
visited.insert(sourceModuleId)
247250
}
248251

252+
func outputMissingFromCAS(moduleInfo: ModuleInfo,
253+
cas: SwiftScanCAS?) throws -> Bool {
254+
func casOutputMissing(_ key: String?) throws -> Bool {
255+
// Caching not enabled.
256+
guard let id = key, let cas = cas else { return false }
257+
// Do a local query to see if the output exists.
258+
let result = try cas.queryCacheKey(id, globally: false)
259+
// Make sure all outputs are available in local CAS.
260+
guard let outputs = result else { return true }
261+
return !outputs.allSatisfy { $0.isMaterialized }
262+
}
263+
264+
switch moduleInfo.details {
265+
case .swift(let swiftDetails):
266+
return try casOutputMissing(swiftDetails.moduleCacheKey)
267+
case .clang(let clangDetails):
268+
return try casOutputMissing(clangDetails.moduleCacheKey)
269+
case .swiftPrebuiltExternal(_):
270+
return false;
271+
case .swiftPlaceholder(_):
272+
// TODO: This should never ever happen. Hard error?
273+
return true;
274+
}
275+
}
276+
249277
func verifyModuleDependencyUpToDate(moduleID: ModuleDependencyId,
250278
fileSystem: FileSystem,
279+
cas: SwiftScanCAS?,
251280
reporter: IncrementalCompilationState.Reporter?) throws -> Bool {
252281
let checkedModuleInfo = try moduleInfo(of: moduleID)
282+
// Check if there is a module cache key available, then the content that pointed by the cache key must
283+
// exist for module to be up-to-date. Treat any CAS error as missing.
284+
let missingFromCAS = (try? outputMissingFromCAS(moduleInfo: checkedModuleInfo, cas: cas)) ?? true
285+
if missingFromCAS {
286+
reporter?.reportExplicitDependencyMissingFromCAS(moduleID.moduleName)
287+
return false
288+
}
289+
253290
// Verify that the specified input exists and is older than the specified output
254291
let verifyInputOlderThanOutputModTime: (String, VirtualPath, TimePoint) -> Bool =
255292
{ moduleName, inputPath, outputModTime in

‎Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState+Extensions.swift

+4
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ extension IncrementalCompilationState {
317317
report("Following explicit module dependencies will be re-built: [\(modules.map { $0.moduleNameForDiagnostic }.sorted().joined(separator: ", "))]")
318318
}
319319

320+
func reportExplicitDependencyMissingFromCAS(_ moduleName: String) {
321+
report("Dependency module \(moduleName) is missing from CAS")
322+
}
323+
320324
// Emits a remark indicating incremental compilation has been disabled.
321325
func reportDisablingIncrementalBuild(_ why: String) {
322326
report("Disabling incremental build: \(why)")

‎Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
120120

121121
// Verify that each dependnecy is up-to-date with respect to its inputs
122122
guard try priorInterModuleDependencyGraph.computeInvalidatedModuleDependencies(fileSystem: buildRecordInfo.fileSystem,
123+
cas: driver.cas,
123124
forRebuild: false,
124125
reporter: reporter).isEmpty else {
125126
reporter?.reportExplicitBuildMustReScan("Not all dependencies are up-to-date.")

‎Sources/SwiftDriver/Jobs/Planning.swift

+1
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ extension Driver {
236236
mandatoryModuleCompileJobs =
237237
try resolvedDependencyGraph.filterMandatoryModuleDependencyCompileJobs(modulePrebuildJobs,
238238
fileSystem: fileSystem,
239+
cas: cas,
239240
reporter: reporter)
240241
}
241242
mandatoryModuleCompileJobs.forEach(addJob)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name O -parse-stdlib
3+
public func none() { }

‎Tests/SwiftDriverTests/IncrementalCompilationTests.swift

+59
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ final class IncrementalCompilationTests: XCTestCase {
4949
var priorsPath: AbsolutePath {
5050
derivedDataPath.appending(component: "\(module)-master.priors")
5151
}
52+
var casPath: AbsolutePath {
53+
derivedDataPath.appending(component: "cas")
54+
}
5255
func swiftDepsPath(basename: String) -> AbsolutePath {
5356
derivedDataPath.appending(component: "\(basename).swiftdeps")
5457
}
@@ -594,6 +597,59 @@ extension IncrementalCompilationTests {
594597
}
595598
}
596599

600+
// MARK: - Explicit compilation caching incremental tests
601+
extension IncrementalCompilationTests {
602+
func testIncrementalCompilationCaching() throws {
603+
#if os(Windows)
604+
throw XCTSkip("caching not supported on windows")
605+
#else
606+
let driver = try Driver(args: ["swiftc"])
607+
guard driver.isFeatureSupported(.compilation_caching) else {
608+
throw XCTSkip("caching not supported")
609+
}
610+
#endif
611+
let extraArguments = ["-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true), "-O", "-parse-stdlib"]
612+
replace(contentsOf: "other", with: "import O;")
613+
// Simplified initial build.
614+
try doABuild(
615+
"Initial Simplified Build with Caching",
616+
checkDiagnostics: false,
617+
extraArguments: explicitBuildArgs + extraArguments,
618+
whenAutolinking: autolinkLifecycleExpectedDiags) {
619+
startCompilingExplicitSwiftDependency("O")
620+
finishCompilingExplicitSwiftDependency("O")
621+
compiling("main", "other")
622+
}
623+
624+
// Delete the CAS, touch a file then rebuild.
625+
try localFileSystem.removeFileTree(casPath)
626+
627+
// Deleting the CAS should cause a full rebuild since all modules are missing from CAS.
628+
try doABuild(
629+
"Deleting CAS and rebuild",
630+
checkDiagnostics: false,
631+
extraArguments: explicitBuildArgs + extraArguments,
632+
whenAutolinking: autolinkLifecycleExpectedDiags
633+
) {
634+
readGraph
635+
readInterModuleGraph
636+
explicitDependencyModuleMissingFromCAS("O")
637+
moduleInfoStaleOutOfDate("O")
638+
explicitMustReScanDueToChangedDependencyInput
639+
moduleWillBeRebuiltOutOfDate("O")
640+
explicitModulesWillBeRebuilt(["O"])
641+
compilingExplicitSwiftDependency("O")
642+
foundBatchableJobs(2)
643+
formingOneBatch
644+
addingToBatchThenForming("main", "other")
645+
startCompilingExplicitSwiftDependency("O")
646+
finishCompilingExplicitSwiftDependency("O")
647+
compiling("main", "other")
648+
}
649+
}
650+
}
651+
652+
597653
// MARK: - Simpler incremental tests
598654
extension IncrementalCompilationTests {
599655

@@ -1764,6 +1820,9 @@ extension DiagVerifiable {
17641820
@DiagsBuilder func explicitModulesWillBeRebuilt(_ moduleNames: [String]) -> [Diagnostic.Message] {
17651821
"Incremental compilation: Following explicit module dependencies will be re-built: [\(moduleNames.joined(separator: ", "))]"
17661822
}
1823+
@DiagsBuilder func explicitDependencyModuleMissingFromCAS(_ dependencyModuleName: String) -> [Diagnostic.Message] {
1824+
"Dependency module \(dependencyModuleName) is missing from CAS"
1825+
}
17671826

17681827
// MARK: - misc
17691828
@DiagsBuilder func disabledForRemoval(_ removedInput: String) -> [Diagnostic.Message] {

0 commit comments

Comments
 (0)
Please sign in to comment.