Skip to content

Commit 3be4396

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 3be4396

File tree

5 files changed

+141
-137
lines changed

5 files changed

+141
-137
lines changed

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ internal extension InterModuleDependencyGraph {
266266
/// between it and the root (source module being built by this driver
267267
/// instance) must also be re-built.
268268
func computeInvalidatedModuleDependencies(fileSystem: FileSystem,
269+
forRebuild: Bool,
269270
reporter: IncrementalCompilationState.Reporter? = nil)
270271
throws -> Set<ModuleDependencyId> {
271272
let mainModuleInfo = mainModule
@@ -276,10 +277,13 @@ internal extension InterModuleDependencyGraph {
276277
for dependencyId in mainModuleInfo.directDependencies ?? [] {
277278
try outOfDateModuleScan(from: dependencyId, visited: &visited,
278279
modulesRequiringRebuild: &modulesRequiringRebuild,
279-
fileSystem: fileSystem, reporter: reporter)
280+
fileSystem: fileSystem, forRebuild: forRebuild,
281+
reporter: reporter)
280282
}
281283

282-
reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild))
284+
if forRebuild {
285+
reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild))
286+
}
283287
return modulesRequiringRebuild
284288
}
285289

@@ -290,46 +294,124 @@ internal extension InterModuleDependencyGraph {
290294
visited: inout Set<ModuleDependencyId>,
291295
modulesRequiringRebuild: inout Set<ModuleDependencyId>,
292296
fileSystem: FileSystem,
297+
forRebuild: Bool,
293298
reporter: IncrementalCompilationState.Reporter? = nil) throws {
299+
let reportOutOfDate = { (name: String, reason: String) in
300+
if forRebuild {
301+
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: reason)
302+
} else {
303+
reporter?.reportPriorExplicitDependencyStale(sourceModuleId.moduleNameForDiagnostic, reason: reason)
304+
}
305+
}
306+
294307
let sourceModuleInfo = try moduleInfo(of: sourceModuleId)
295308
// Visit the module's dependencies
296309
var hasOutOfDateModuleDependency = false
297-
var mostRecentlyUpdatedDependencyOutput: TimePoint = .zero
298310
for dependencyId in sourceModuleInfo.directDependencies ?? [] {
299311
// If we have not already visited this module, recurse.
300312
if !visited.contains(dependencyId) {
301313
try outOfDateModuleScan(from: dependencyId, visited: &visited,
302314
modulesRequiringRebuild: &modulesRequiringRebuild,
303-
fileSystem: fileSystem, reporter: reporter)
315+
fileSystem: fileSystem, forRebuild: forRebuild,
316+
reporter: reporter)
304317
}
305318
// Even if we're not revisiting a dependency, we must check if it's already known to be out of date.
306319
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-
}
313320
}
314321

315322
if hasOutOfDateModuleDependency {
316-
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Invalidated by downstream dependency")
317-
modulesRequiringRebuild.insert(sourceModuleId)
318-
} else if try !IncrementalCompilationState.IncrementalDependencyAndInputSetup.verifyModuleDependencyUpToDate(moduleID: sourceModuleId, moduleInfo: sourceModuleInfo,
319-
fileSystem: fileSystem, reporter: reporter) {
320-
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Out-of-date")
323+
reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Invalidated by downstream dependency")
321324
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")
325+
} else if try !verifyModuleDependencyUpToDate(moduleID: sourceModuleId, fileSystem: fileSystem, reporter: reporter) {
326+
reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Out-of-date")
327327
modulesRequiringRebuild.insert(sourceModuleId)
328328
}
329329

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

335417
internal extension InterModuleDependencyGraph {

Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ 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+
forRebuild: true,
160+
reporter: reporter)
159161

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

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState+Extensions.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ extension IncrementalCompilationState {
308308
report("Dependency module '\(moduleOutputPath)' will be re-built: \(reason)")
309309
}
310310

311+
func reportPriorExplicitDependencyStale(_ moduleOutputPath: String,
312+
reason: String) {
313+
report("Dependency module '\(moduleOutputPath)' info is stale: \(reason)")
314+
}
315+
311316
func reportExplicitDependencyReBuildSet(_ modules: [ModuleDependencyId]) {
312317
report("Following explicit module dependencies will be re-built: [\(modules.map { $0.moduleNameForDiagnostic }.sorted().joined(separator: ", "))]")
313318
}

Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift

Lines changed: 3 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -120,109 +120,16 @@ 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,
124+
forRebuild: false,
125+
reporter: reporter).isEmpty else {
126126
reporter?.reportExplicitBuildMustReScan("Not all dependencies are up-to-date.")
127127
return nil
128128
}
129129

130130
reporter?.report("Confirmed prior inter-module dependency graph is up-to-date at: \(buildRecordInfo.interModuleDependencyGraphPath)")
131131
return priorInterModuleDependencyGraph
132132
}
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-
}
226133
}
227134

228135
/// Builds the `InitialState`

0 commit comments

Comments
 (0)