Skip to content

Commit adce133

Browse files
committed
[Explicit Module Builds][Incremental Builds] Re-compile module dependnecies whose dependencies are up-to-date themselves but are themselves newer
For example consider the following module graph: test \ J \ G Where on an incremental build we detect that although G binary module product is *newer* than its textual source, said binary module product is also *newer* than a prior binary module product of J. Which means that although each of the modules is up-to-date with respect to its own textual source inputs, J's binary dependnecy input has been updated elsewhere and J needs to be re-built. Resolves rdar://129225956
1 parent b0300a8 commit adce133

File tree

2 files changed

+80
-10
lines changed

2 files changed

+80
-10
lines changed

Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,12 @@ extension IncrementalCompilationState.FirstWaveComputer {
149149
throws -> Set<ModuleDependencyId> {
150150
let mainModuleInfo = moduleDependencyGraph.mainModule
151151
var modulesRequiringRebuild: Set<ModuleDependencyId> = []
152-
var visitedModules: Set<ModuleDependencyId> = []
152+
var visited: Set<ModuleDependencyId> = []
153153
// Scan from the main module's dependencies to avoid reporting
154154
// the main module itself in the results.
155155
for dependencyId in mainModuleInfo.directDependencies ?? [] {
156-
try outOfDateModuleScan(on: moduleDependencyGraph, from: dependencyId, visited: &visitedModules,
157-
modulesRequiringRebuild: &modulesRequiringRebuild)
156+
try outOfDateModuleScan(on: moduleDependencyGraph, from: dependencyId,
157+
visited: &visited, modulesRequiringRebuild: &modulesRequiringRebuild)
158158
}
159159

160160
reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild))
@@ -171,24 +171,36 @@ extension IncrementalCompilationState.FirstWaveComputer {
171171
let moduleInfo = try moduleDependencyGraph.moduleInfo(of: moduleId)
172172
// Visit the module's dependencies
173173
var hasOutOfDateModuleDependency = false
174+
var mostRecentlyUpdatedDependencyOutput: TimePoint = .zero
174175
for dependencyId in moduleInfo.directDependencies ?? [] {
175176
// If we have not already visited this module, recurse.
176177
if !visited.contains(dependencyId) {
177178
try outOfDateModuleScan(on: moduleDependencyGraph, from: dependencyId,
178-
visited: &visited,
179-
modulesRequiringRebuild: &modulesRequiringRebuild)
179+
visited: &visited, modulesRequiringRebuild: &modulesRequiringRebuild)
180180
}
181181
// Even if we're not revisiting a dependency, we must check if it's already known to be out of date.
182182
hasOutOfDateModuleDependency = hasOutOfDateModuleDependency || modulesRequiringRebuild.contains(dependencyId)
183+
184+
// Keep track of dependencies' output file time stamp to determine if it is newer than the current module.
185+
if let depOutputTimeStamp = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleDependencyGraph.moduleInfo(of: dependencyId).modulePath.path)),
186+
depOutputTimeStamp > mostRecentlyUpdatedDependencyOutput {
187+
mostRecentlyUpdatedDependencyOutput = depOutputTimeStamp
188+
}
183189
}
184190

185191
if hasOutOfDateModuleDependency {
186-
reporter?.reportExplicitDependencyWillBeReBuilt(moduleId.moduleNameForDiagnostic, reason: "Invalidated by downstream dependency")
187-
modulesRequiringRebuild.insert(moduleId)
192+
reporter?.reportExplicitDependencyWillBeReBuilt(moduleId.moduleNameForDiagnostic, reason: "Invalidated by downstream dependency")
193+
modulesRequiringRebuild.insert(moduleId)
188194
} else if try !IncrementalCompilationState.IncrementalDependencyAndInputSetup.verifyModuleDependencyUpToDate(moduleID: moduleId, moduleInfo: moduleInfo,
189195
fileSystem: fileSystem, reporter: reporter) {
190-
reporter?.reportExplicitDependencyWillBeReBuilt(moduleId.moduleNameForDiagnostic, reason: "Out-of-date")
191-
modulesRequiringRebuild.insert(moduleId)
196+
reporter?.reportExplicitDependencyWillBeReBuilt(moduleId.moduleNameForDiagnostic, reason: "Out-of-date")
197+
modulesRequiringRebuild.insert(moduleId)
198+
} else if let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleInfo.modulePath.path)),
199+
outputModTime < mostRecentlyUpdatedDependencyOutput {
200+
// If a prior variant of this module dependnecy exists, and is older than any of its direct or transitive
201+
// module dependency outputs, it must also be re-built.
202+
reporter?.reportExplicitDependencyWillBeReBuilt(moduleId.moduleNameForDiagnostic, reason: "Has newer module dependency inputs")
203+
modulesRequiringRebuild.insert(moduleId)
192204
}
193205

194206
// Now that we've determined if this module must be rebuilt, mark it as visited.

Tests/SwiftDriverTests/IncrementalCompilationTests.swift

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,6 @@ extension IncrementalCompilationTests {
382382
// On this graph, inputs of 'G' are updated, causing it to be re-built
383383
// as well as all modules on paths from root to it: 'Y', 'H', 'T','J'
384384
func testExplicitIncrementalBuildChangedDependencyInvalidatesUpstreamDependencies() throws {
385-
// Add an import of 'B', 'C' to make sure followup changes has consistent inputs
386385
replace(contentsOf: "other", with: "import Y;import T")
387386
try buildInitialState(checkDiagnostics: false, explicitModuleBuild: true)
388387

@@ -431,6 +430,62 @@ extension IncrementalCompilationTests {
431430
linking
432431
}
433432
}
433+
434+
// A dependency has been re-built to be newer than its dependents
435+
// so we must ensure the dependents get re-built even though all the
436+
// modules are up-to-date with respect to their textual source inputs.
437+
//
438+
// test
439+
// \
440+
// J
441+
// \
442+
// G
443+
//
444+
// On this graph, after the initial build, if G module binary file is newer
445+
// than that of J, even if each of the modules is up-to-date w.r.t. their source inputs
446+
// we still expect that J gets re-built
447+
func testExplicitIncrementalBuildChangedDependencyBinaryInvalidatesUpstreamDependencies() throws {
448+
replace(contentsOf: "other", with: "import J;")
449+
try buildInitialState(checkDiagnostics: false, explicitModuleBuild: true)
450+
451+
let modCacheEntries = try localFileSystem.getDirectoryContents(explicitModuleCacheDir)
452+
let nameOfGModule = try XCTUnwrap(modCacheEntries.first { $0.hasPrefix("G") && $0.hasSuffix(".swiftmodule")})
453+
let pathToGModule = explicitModuleCacheDir.appending(component: nameOfGModule)
454+
// Just update the time-stamp of one of the module dependencies' outputs.
455+
// Also add a dependency to cause a re-scan.
456+
touch(pathToGModule)
457+
replace(contentsOf: "other", with: "import J;import R")
458+
459+
// Changing a dependency will mean that we both re-run the dependency scan,
460+
// and also ensure that all source-files are re-built with a non-cascading build
461+
// since the source files themselves have not changed.
462+
try doABuild(
463+
"update dependency (G) result timestamp",
464+
checkDiagnostics: true,
465+
extraArguments: explicitBuildArgs,
466+
whenAutolinking: autolinkLifecycleExpectedDiags
467+
) {
468+
readGraph
469+
enablingCrossModule
470+
readInterModuleGraph
471+
explicitMustReScanDueToChangedImports
472+
maySkip("main")
473+
schedulingChangedInitialQueuing("other")
474+
skipping("main")
475+
findingBatchingCompiling("other")
476+
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")
484+
skipped("main")
485+
schedulingPostCompileJobs
486+
linking
487+
}
488+
}
434489
}
435490

436491
extension IncrementalCompilationTests {
@@ -1645,6 +1700,9 @@ extension DiagVerifiable {
16451700
@DiagsBuilder func explicitDependencyInvalidatedDownstream(_ moduleName: String) -> [Diagnostic.Message] {
16461701
"Incremental compilation: Dependency module '\(moduleName)' will be re-built: Invalidated by downstream dependency"
16471702
}
1703+
@DiagsBuilder func explicitDependencyNewerModuleInputs(_ moduleName: String) -> [Diagnostic.Message] {
1704+
"Incremental compilation: Dependency module '\(moduleName)' will be re-built: Has newer module dependency inputs"
1705+
}
16481706

16491707
// MARK: - misc
16501708
@DiagsBuilder var enablingCrossModule: [Diagnostic.Message] {

0 commit comments

Comments
 (0)