|
11 | 11 | //===----------------------------------------------------------------------===//
|
12 | 12 |
|
13 | 13 | import func TSCBasic.topologicalSort
|
| 14 | +import protocol TSCBasic.FileSystem |
14 | 15 |
|
15 | 16 | @_spi(Testing) public extension InterModuleDependencyGraph {
|
16 | 17 | /// For targets that are built alongside the driver's current module, the scanning action will report them as
|
@@ -255,6 +256,82 @@ extension InterModuleDependencyGraph {
|
255 | 256 | }
|
256 | 257 | }
|
257 | 258 |
|
| 259 | +/// Incremental Build Machinery |
| 260 | +internal extension InterModuleDependencyGraph { |
| 261 | + /// We must determine if any of the module dependencies require re-compilation |
| 262 | + /// Since we know that a prior dependency graph was not completely up-to-date, |
| 263 | + /// there must be at least *some* dependencies that require being re-built. |
| 264 | + /// |
| 265 | + /// If a dependency is deemed as requiring a re-build, then every module |
| 266 | + /// between it and the root (source module being built by this driver |
| 267 | + /// instance) must also be re-built. |
| 268 | + func computeInvalidatedModuleDependencies(fileSystem: FileSystem, |
| 269 | + reporter: IncrementalCompilationState.Reporter? = nil) |
| 270 | + throws -> Set<ModuleDependencyId> { |
| 271 | + let mainModuleInfo = mainModule |
| 272 | + var modulesRequiringRebuild: Set<ModuleDependencyId> = [] |
| 273 | + var visited: Set<ModuleDependencyId> = [] |
| 274 | + // Scan from the main module's dependencies to avoid reporting |
| 275 | + // the main module itself in the results. |
| 276 | + for dependencyId in mainModuleInfo.directDependencies ?? [] { |
| 277 | + try outOfDateModuleScan(from: dependencyId, visited: &visited, |
| 278 | + modulesRequiringRebuild: &modulesRequiringRebuild, |
| 279 | + fileSystem: fileSystem, reporter: reporter) |
| 280 | + } |
| 281 | + |
| 282 | + reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild)) |
| 283 | + return modulesRequiringRebuild |
| 284 | + } |
| 285 | + |
| 286 | + /// Perform a postorder DFS to locate modules which are out-of-date with respect |
| 287 | + /// to their inputs. Upon encountering such a module, add it to the set of invalidated |
| 288 | + /// modules, along with the path from the root to this module. |
| 289 | + func outOfDateModuleScan(from sourceModuleId: ModuleDependencyId, |
| 290 | + visited: inout Set<ModuleDependencyId>, |
| 291 | + modulesRequiringRebuild: inout Set<ModuleDependencyId>, |
| 292 | + fileSystem: FileSystem, |
| 293 | + reporter: IncrementalCompilationState.Reporter? = nil) throws { |
| 294 | + let sourceModuleInfo = try moduleInfo(of: sourceModuleId) |
| 295 | + // Visit the module's dependencies |
| 296 | + var hasOutOfDateModuleDependency = false |
| 297 | + var mostRecentlyUpdatedDependencyOutput: TimePoint = .zero |
| 298 | + for dependencyId in sourceModuleInfo.directDependencies ?? [] { |
| 299 | + // If we have not already visited this module, recurse. |
| 300 | + if !visited.contains(dependencyId) { |
| 301 | + try outOfDateModuleScan(from: dependencyId, visited: &visited, |
| 302 | + modulesRequiringRebuild: &modulesRequiringRebuild, |
| 303 | + fileSystem: fileSystem, reporter: reporter) |
| 304 | + } |
| 305 | + // Even if we're not revisiting a dependency, we must check if it's already known to be out of date. |
| 306 | + 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 | + } |
| 313 | + } |
| 314 | + |
| 315 | + 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") |
| 321 | + 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) |
| 328 | + } |
| 329 | + |
| 330 | + // Now that we've determined if this module must be rebuilt, mark it as visited. |
| 331 | + visited.insert(sourceModuleId) |
| 332 | + } |
| 333 | +} |
| 334 | + |
258 | 335 | internal extension InterModuleDependencyGraph {
|
259 | 336 | func explainDependency(dependencyModuleName: String) throws -> [[ModuleDependencyId]]? {
|
260 | 337 | guard modules.contains(where: { $0.key.moduleName == dependencyModuleName }) else { return nil }
|
|
0 commit comments