Skip to content

Commit b1631fd

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 6c83210 commit b1631fd

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
@@ -260,6 +260,7 @@ internal extension InterModuleDependencyGraph {
260260
/// between it and the root (source module being built by this driver
261261
/// instance) must also be re-built.
262262
func computeInvalidatedModuleDependencies(fileSystem: FileSystem,
263+
forRebuild: Bool,
263264
reporter: IncrementalCompilationState.Reporter? = nil)
264265
throws -> Set<ModuleDependencyId> {
265266
let mainModuleInfo = mainModule
@@ -270,10 +271,13 @@ internal extension InterModuleDependencyGraph {
270271
for dependencyId in mainModuleInfo.directDependencies ?? [] {
271272
try outOfDateModuleScan(from: dependencyId, visited: &visited,
272273
modulesRequiringRebuild: &modulesRequiringRebuild,
273-
fileSystem: fileSystem, reporter: reporter)
274+
fileSystem: fileSystem, forRebuild: forRebuild,
275+
reporter: reporter)
274276
}
275277

276-
reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild))
278+
if forRebuild {
279+
reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild))
280+
}
277281
return modulesRequiringRebuild
278282
}
279283

@@ -284,46 +288,124 @@ internal extension InterModuleDependencyGraph {
284288
visited: inout Set<ModuleDependencyId>,
285289
modulesRequiringRebuild: inout Set<ModuleDependencyId>,
286290
fileSystem: FileSystem,
291+
forRebuild: Bool,
287292
reporter: IncrementalCompilationState.Reporter? = nil) throws {
293+
let reportOutOfDate = { (name: String, reason: String) in
294+
if forRebuild {
295+
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: reason)
296+
} else {
297+
reporter?.reportPriorExplicitDependencyStale(sourceModuleId.moduleNameForDiagnostic, reason: reason)
298+
}
299+
}
300+
288301
let sourceModuleInfo = try moduleInfo(of: sourceModuleId)
289302
// Visit the module's dependencies
290303
var hasOutOfDateModuleDependency = false
291-
var mostRecentlyUpdatedDependencyOutput: TimePoint = .zero
292304
for dependencyId in sourceModuleInfo.directDependencies ?? [] {
293305
// If we have not already visited this module, recurse.
294306
if !visited.contains(dependencyId) {
295307
try outOfDateModuleScan(from: dependencyId, visited: &visited,
296308
modulesRequiringRebuild: &modulesRequiringRebuild,
297-
fileSystem: fileSystem, reporter: reporter)
309+
fileSystem: fileSystem, forRebuild: forRebuild,
310+
reporter: reporter)
298311
}
299312
// Even if we're not revisiting a dependency, we must check if it's already known to be out of date.
300313
hasOutOfDateModuleDependency = hasOutOfDateModuleDependency || modulesRequiringRebuild.contains(dependencyId)
301-
302-
// Keep track of dependencies' output file time stamp to determine if it is newer than the current module.
303-
if let depOutputTimeStamp = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleInfo(of: dependencyId).modulePath.path)),
304-
depOutputTimeStamp > mostRecentlyUpdatedDependencyOutput {
305-
mostRecentlyUpdatedDependencyOutput = depOutputTimeStamp
306-
}
307314
}
308315

309316
if hasOutOfDateModuleDependency {
310-
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Invalidated by downstream dependency")
311-
modulesRequiringRebuild.insert(sourceModuleId)
312-
} else if try !IncrementalCompilationState.IncrementalDependencyAndInputSetup.verifyModuleDependencyUpToDate(moduleID: sourceModuleId, moduleInfo: sourceModuleInfo,
313-
fileSystem: fileSystem, reporter: reporter) {
314-
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Out-of-date")
317+
reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Invalidated by downstream dependency")
315318
modulesRequiringRebuild.insert(sourceModuleId)
316-
} else if let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(sourceModuleInfo.modulePath.path)),
317-
outputModTime < mostRecentlyUpdatedDependencyOutput {
318-
// If a prior variant of this module dependnecy exists, and is older than any of its direct or transitive
319-
// module dependency outputs, it must also be re-built.
320-
reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: "Has newer module dependency inputs")
319+
} else if try !verifyModuleDependencyUpToDate(moduleID: sourceModuleId, fileSystem: fileSystem, reporter: reporter) {
320+
reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Out-of-date")
321321
modulesRequiringRebuild.insert(sourceModuleId)
322322
}
323323

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

329411
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)