Skip to content

Commit 3bbdc0e

Browse files
committed
[Explicit Module Builds][Incremental Builds] Unify logic used to check if a prior inter-module dep graph is up-to-date with a check to decide which modules to re-build
Using the same 'computeInvalidatedModuleDependencies' routine, which is more thorough and checks module inputs to each dependency as well.
1 parent cc65d79 commit 3bbdc0e

File tree

4 files changed

+93
-125
lines changed

4 files changed

+93
-125
lines changed

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ internal extension InterModuleDependencyGraph {
294294
let sourceModuleInfo = try moduleInfo(of: sourceModuleId)
295295
// Visit the module's dependencies
296296
var hasOutOfDateModuleDependency = false
297-
var mostRecentlyUpdatedDependencyOutput: TimePoint = .zero
298297
for dependencyId in sourceModuleInfo.directDependencies ?? [] {
299298
// If we have not already visited this module, recurse.
300299
if !visited.contains(dependencyId) {
@@ -304,32 +303,101 @@ internal extension InterModuleDependencyGraph {
304303
}
305304
// Even if we're not revisiting a dependency, we must check if it's already known to be out of date.
306305
hasOutOfDateModuleDependency = hasOutOfDateModuleDependency || modulesRequiringRebuild.contains(dependencyId)
307-
308-
// Keep track of dependencies' output file time stamp to determine if it is newer than the current module.
309-
if let depOutputTimeStamp = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleInfo(of: dependencyId).modulePath.path)),
310-
depOutputTimeStamp > mostRecentlyUpdatedDependencyOutput {
311-
mostRecentlyUpdatedDependencyOutput = depOutputTimeStamp
312-
}
313306
}
314307

315308
if hasOutOfDateModuleDependency {
316309
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Invalidated by downstream dependency")
317310
modulesRequiringRebuild.insert(sourceModuleId)
318-
} else if try !IncrementalCompilationState.IncrementalDependencyAndInputSetup.verifyModuleDependencyUpToDate(moduleID: sourceModuleId, moduleInfo: sourceModuleInfo,
319-
fileSystem: fileSystem, reporter: reporter) {
311+
} else if try !verifyModuleDependencyUpToDate(moduleID: sourceModuleId, fileSystem: fileSystem, reporter: reporter) {
320312
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Out-of-date")
321313
modulesRequiringRebuild.insert(sourceModuleId)
322-
} else if let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(sourceModuleInfo.modulePath.path)),
323-
outputModTime < mostRecentlyUpdatedDependencyOutput {
324-
// If a prior variant of this module dependnecy exists, and is older than any of its direct or transitive
325-
// module dependency outputs, it must also be re-built.
326-
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Has newer module dependency inputs")
327-
modulesRequiringRebuild.insert(sourceModuleId)
328314
}
329315

330316
// Now that we've determined if this module must be rebuilt, mark it as visited.
331317
visited.insert(sourceModuleId)
332318
}
319+
320+
func verifyModuleDependencyUpToDate(moduleID: ModuleDependencyId,
321+
fileSystem: FileSystem,
322+
reporter: IncrementalCompilationState.Reporter?) throws -> Bool {
323+
let checkedModuleInfo = try moduleInfo(of: moduleID)
324+
// Verify that the specified input exists and is older than the specified output
325+
let verifyInputOlderThanOutputModTime: (String, VirtualPath, TimePoint) -> Bool =
326+
{ moduleName, inputPath, outputModTime in
327+
guard let inputModTime =
328+
try? fileSystem.lastModificationTime(for: inputPath) else {
329+
reporter?.report("Unable to 'stat' \(inputPath.description)")
330+
return false
331+
}
332+
if inputModTime > outputModTime {
333+
reporter?.reportExplicitDependencyOutOfDate(moduleName,
334+
inputPath: inputPath.description)
335+
return false
336+
}
337+
return true
338+
}
339+
340+
// Check if the output file exists
341+
guard let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(checkedModuleInfo.modulePath.path)) else {
342+
reporter?.report("Module output not found: '\(moduleID.moduleNameForDiagnostic)'")
343+
return false
344+
}
345+
346+
// Check if a dependency of this module has a newer output than this module
347+
for dependencyId in checkedModuleInfo.directDependencies ?? [] {
348+
let dependencyInfo = try moduleInfo(of: dependencyId)
349+
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
350+
VirtualPath.lookup(dependencyInfo.modulePath.path),
351+
outputModTime) {
352+
return false
353+
}
354+
}
355+
356+
// Check if any of the textual sources of this module are newer than this module
357+
switch checkedModuleInfo.details {
358+
case .swift(let swiftDetails):
359+
if let moduleInterfacePath = swiftDetails.moduleInterfacePath {
360+
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
361+
VirtualPath.lookup(moduleInterfacePath.path),
362+
outputModTime) {
363+
return false
364+
}
365+
}
366+
if let bridgingHeaderPath = swiftDetails.bridgingHeaderPath {
367+
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
368+
VirtualPath.lookup(bridgingHeaderPath.path),
369+
outputModTime) {
370+
return false
371+
}
372+
}
373+
for bridgingSourceFile in swiftDetails.bridgingSourceFiles ?? [] {
374+
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
375+
VirtualPath.lookup(bridgingSourceFile.path),
376+
outputModTime) {
377+
return false
378+
}
379+
}
380+
case .clang(_):
381+
for inputSourceFile in checkedModuleInfo.sourceFiles ?? [] {
382+
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
383+
try VirtualPath(path: inputSourceFile),
384+
outputModTime) {
385+
return false
386+
}
387+
}
388+
case .swiftPrebuiltExternal(_):
389+
// TODO: We have to give-up here until we have a way to verify the timestamp of the binary module.
390+
// We can do better here by knowing if this module hasn't changed - which would allows us to not
391+
// invalidate any of the dependencies that depend on it.
392+
reporter?.report("Unable to verify binary module dependency up-to-date: \(moduleID.moduleNameForDiagnostic)")
393+
return false;
394+
case .swiftPlaceholder(_):
395+
// TODO: This should never ever happen. Hard error?
396+
return false;
397+
}
398+
399+
return true
400+
}
333401
}
334402

335403
internal extension InterModuleDependencyGraph {

Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ extension IncrementalCompilationState.FirstWaveComputer {
155155

156156
// Determine which module pre-build jobs must be re-run
157157
let modulesRequiringReBuild =
158-
try moduleDependencyGraph.computeInvalidatedModuleDependencies(fileSystem: fileSystem, reporter: reporter)
158+
try moduleDependencyGraph.computeInvalidatedModuleDependencies(fileSystem: fileSystem,
159+
reporter: reporter)
159160

160161
// Filter the `.generatePCM` and `.compileModuleFromInterface` jobs for
161162
// modules which do *not* need re-building.

Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift

Lines changed: 1 addition & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -120,109 +120,14 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
120120
}
121121

122122
// Verify that each dependnecy is up-to-date with respect to its inputs
123-
guard try verifyInterModuleDependenciesUpToDate(in: priorInterModuleDependencyGraph,
124-
buildRecordInfo: buildRecordInfo,
125-
reporter: reporter) else {
123+
guard try priorInterModuleDependencyGraph.computeInvalidatedModuleDependencies(fileSystem: buildRecordInfo.fileSystem).isEmpty else {
126124
reporter?.reportExplicitBuildMustReScan("Not all dependencies are up-to-date.")
127125
return nil
128126
}
129127

130128
reporter?.report("Confirmed prior inter-module dependency graph is up-to-date at: \(buildRecordInfo.interModuleDependencyGraphPath)")
131129
return priorInterModuleDependencyGraph
132130
}
133-
134-
static func verifyModuleDependencyUpToDate(moduleID: ModuleDependencyId, moduleInfo: ModuleInfo,
135-
fileSystem: FileSystem,
136-
reporter: IncrementalCompilationState.Reporter?) throws -> Bool {
137-
// Verify that the specified input exists and is older than the specified output
138-
let verifyInputOlderThanOutputModTime: (String, VirtualPath, VirtualPath, TimePoint) -> Bool =
139-
{ moduleName, inputPath, outputPath, outputModTime in
140-
guard let inputModTime =
141-
try? fileSystem.lastModificationTime(for: inputPath) else {
142-
reporter?.report("Unable to 'stat' \(inputPath.description)")
143-
return false
144-
}
145-
if inputModTime > outputModTime {
146-
reporter?.reportExplicitDependencyOutOfDate(moduleName,
147-
inputPath: inputPath.description)
148-
return false
149-
}
150-
return true
151-
}
152-
153-
switch moduleInfo.details {
154-
case .swift(let swiftDetails):
155-
guard let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleInfo.modulePath.path)) else {
156-
reporter?.report("Module output not found: '\(moduleID.moduleNameForDiagnostic)'")
157-
return false
158-
}
159-
if let moduleInterfacePath = swiftDetails.moduleInterfacePath {
160-
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
161-
VirtualPath.lookup(moduleInterfacePath.path),
162-
VirtualPath.lookup(moduleInfo.modulePath.path),
163-
outputModTime) {
164-
return false
165-
}
166-
}
167-
if let bridgingHeaderPath = swiftDetails.bridgingHeaderPath {
168-
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
169-
VirtualPath.lookup(bridgingHeaderPath.path),
170-
VirtualPath.lookup(moduleInfo.modulePath.path),
171-
outputModTime) {
172-
return false
173-
}
174-
}
175-
for bridgingSourceFile in swiftDetails.bridgingSourceFiles ?? [] {
176-
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
177-
VirtualPath.lookup(bridgingSourceFile.path),
178-
VirtualPath.lookup(moduleInfo.modulePath.path),
179-
outputModTime) {
180-
return false
181-
}
182-
}
183-
case .clang(_):
184-
guard let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleInfo.modulePath.path)) else {
185-
reporter?.report("Module output not found: '\(moduleID.moduleNameForDiagnostic)'")
186-
return false
187-
}
188-
for inputSourceFile in moduleInfo.sourceFiles ?? [] {
189-
if !verifyInputOlderThanOutputModTime(moduleID.moduleName,
190-
try VirtualPath(path: inputSourceFile),
191-
VirtualPath.lookup(moduleInfo.modulePath.path),
192-
outputModTime) {
193-
return false
194-
}
195-
}
196-
case .swiftPrebuiltExternal(_):
197-
// TODO: We have to give-up here until we have a way to verify the timestamp of the binary module.
198-
// We can do better here by knowing if this module hasn't change - which would allows us to not
199-
// invalidate any of the dependencies that depend on it.
200-
reporter?.report("Unable to verify binary module dependency up-to-date: \(moduleID.moduleNameForDiagnostic)")
201-
return false;
202-
case .swiftPlaceholder(_):
203-
// TODO: This should never ever happen. Hard error?
204-
return false;
205-
}
206-
return true
207-
}
208-
209-
/// For each direct and transitive module dependency, check if any of the inputs are newer than the output
210-
static func verifyInterModuleDependenciesUpToDate(in graph: InterModuleDependencyGraph,
211-
buildRecordInfo: BuildRecordInfo,
212-
reporter: IncrementalCompilationState.Reporter?) throws -> Bool {
213-
for module in graph.modules {
214-
if module.key == .swift(graph.mainModuleName) {
215-
continue
216-
}
217-
if try !verifyModuleDependencyUpToDate(moduleID: module.key,
218-
moduleInfo: module.value,
219-
fileSystem: buildRecordInfo.fileSystem,
220-
reporter: reporter) {
221-
return false
222-
}
223-
}
224-
return true
225-
}
226131
}
227132

228133
/// Builds the `InitialState`

Tests/SwiftDriverTests/IncrementalCompilationTests.swift

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,9 @@ extension IncrementalCompilationTests {
452452
let nameOfGModule = try XCTUnwrap(modCacheEntries.first { $0.hasPrefix("G") && $0.hasSuffix(".swiftmodule")})
453453
let pathToGModule = explicitModuleCacheDir.appending(component: nameOfGModule)
454454
// Just update the time-stamp of one of the module dependencies' outputs.
455-
// Also add a dependency to cause a re-scan.
456455
touch(pathToGModule)
457-
replace(contentsOf: "other", with: "import J;import R")
456+
// Touch one of the inputs to actually trigger the incremental build
457+
touch(inputPath(basename: "other"))
458458

459459
// Changing a dependency will mean that we both re-run the dependency scan,
460460
// and also ensure that all source-files are re-built with a non-cascading build
@@ -468,19 +468,16 @@ extension IncrementalCompilationTests {
468468
readGraph
469469
enablingCrossModule
470470
readInterModuleGraph
471-
explicitMustReScanDueToChangedImports
471+
explicitMustReScanDueToChangedDependencyInput
472472
maySkip("main")
473473
schedulingChangedInitialQueuing("other")
474474
skipping("main")
475+
explicitDependencyModuleOlderThanInput("J")
476+
moduleWillBeRebuiltOutOfDate("J")
477+
explicitModulesWillBeRebuilt(["J"])
478+
compilingExplicitSwiftDependency("J")
475479
findingBatchingCompiling("other")
476480
reading(deps: "other")
477-
fingerprintsChanged("other")
478-
moduleOutputNotFound("R")
479-
moduleWillBeRebuiltOutOfDate("R")
480-
compilingExplicitSwiftDependency("R")
481-
explicitModulesWillBeRebuilt(["J", "R"])
482-
explicitDependencyNewerModuleInputs("J")
483-
compilingExplicitSwiftDependency("J")
484481
skipped("main")
485482
schedulingPostCompileJobs
486483
linking
@@ -1700,9 +1697,6 @@ extension DiagVerifiable {
17001697
@DiagsBuilder func explicitDependencyInvalidatedDownstream(_ moduleName: String) -> [Diagnostic.Message] {
17011698
"Incremental compilation: Dependency module '\(moduleName)' will be re-built: Invalidated by downstream dependency"
17021699
}
1703-
@DiagsBuilder func explicitDependencyNewerModuleInputs(_ moduleName: String) -> [Diagnostic.Message] {
1704-
"Incremental compilation: Dependency module '\(moduleName)' will be re-built: Has newer module dependency inputs"
1705-
}
17061700

17071701
// MARK: - misc
17081702
@DiagsBuilder var enablingCrossModule: [Diagnostic.Message] {

0 commit comments

Comments
 (0)