diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift index dc650a222..c9145ea51 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import func TSCBasic.topologicalSort -import protocol TSCBasic.FileSystem @_spi(Testing) public extension InterModuleDependencyGraph { /// For targets that are built alongside the driver's current module, the scanning action will report them as @@ -256,164 +255,6 @@ extension InterModuleDependencyGraph { } } -/// Incremental Build Machinery -internal extension InterModuleDependencyGraph { - /// We must determine if any of the module dependencies require re-compilation - /// Since we know that a prior dependency graph was not completely up-to-date, - /// there must be at least *some* dependencies that require being re-built. - /// - /// If a dependency is deemed as requiring a re-build, then every module - /// between it and the root (source module being built by this driver - /// instance) must also be re-built. - func computeInvalidatedModuleDependencies(fileSystem: FileSystem, - forRebuild: Bool, - reporter: IncrementalCompilationState.Reporter? = nil) - throws -> Set { - let mainModuleInfo = mainModule - var modulesRequiringRebuild: Set = [] - var visited: Set = [] - // Scan from the main module's dependencies to avoid reporting - // the main module itself in the results. - for dependencyId in mainModuleInfo.directDependencies ?? [] { - try outOfDateModuleScan(from: dependencyId, visited: &visited, - modulesRequiringRebuild: &modulesRequiringRebuild, - fileSystem: fileSystem, forRebuild: forRebuild, - reporter: reporter) - } - - if forRebuild { - reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild)) - } - return modulesRequiringRebuild - } - - /// Perform a postorder DFS to locate modules which are out-of-date with respect - /// to their inputs. Upon encountering such a module, add it to the set of invalidated - /// modules, along with the path from the root to this module. - func outOfDateModuleScan(from sourceModuleId: ModuleDependencyId, - visited: inout Set, - modulesRequiringRebuild: inout Set, - fileSystem: FileSystem, - forRebuild: Bool, - reporter: IncrementalCompilationState.Reporter? = nil) throws { - let reportOutOfDate = { (name: String, reason: String) in - if forRebuild { - reporter?.reportExplicitDependencyWillBeReBuilt(sourceModuleId.moduleNameForDiagnostic, reason: reason) - } else { - reporter?.reportPriorExplicitDependencyStale(sourceModuleId.moduleNameForDiagnostic, reason: reason) - } - } - - let sourceModuleInfo = try moduleInfo(of: sourceModuleId) - // Visit the module's dependencies - var hasOutOfDateModuleDependency = false - for dependencyId in sourceModuleInfo.directDependencies ?? [] { - // If we have not already visited this module, recurse. - if !visited.contains(dependencyId) { - try outOfDateModuleScan(from: dependencyId, visited: &visited, - modulesRequiringRebuild: &modulesRequiringRebuild, - fileSystem: fileSystem, forRebuild: forRebuild, - reporter: reporter) - } - // Even if we're not revisiting a dependency, we must check if it's already known to be out of date. - hasOutOfDateModuleDependency = hasOutOfDateModuleDependency || modulesRequiringRebuild.contains(dependencyId) - } - - if hasOutOfDateModuleDependency { - reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Invalidated by downstream dependency") - modulesRequiringRebuild.insert(sourceModuleId) - } else if try !verifyModuleDependencyUpToDate(moduleID: sourceModuleId, fileSystem: fileSystem, reporter: reporter) { - reportOutOfDate(sourceModuleId.moduleNameForDiagnostic, "Out-of-date") - modulesRequiringRebuild.insert(sourceModuleId) - } - - // Now that we've determined if this module must be rebuilt, mark it as visited. - visited.insert(sourceModuleId) - } - - func verifyModuleDependencyUpToDate(moduleID: ModuleDependencyId, - fileSystem: FileSystem, - reporter: IncrementalCompilationState.Reporter?) throws -> Bool { - let checkedModuleInfo = try moduleInfo(of: moduleID) - // Verify that the specified input exists and is older than the specified output - let verifyInputOlderThanOutputModTime: (String, VirtualPath, TimePoint) -> Bool = - { moduleName, inputPath, outputModTime in - guard let inputModTime = - try? fileSystem.lastModificationTime(for: inputPath) else { - reporter?.report("Unable to 'stat' \(inputPath.description)") - return false - } - if inputModTime > outputModTime { - reporter?.reportExplicitDependencyOutOfDate(moduleName, - inputPath: inputPath.description) - return false - } - return true - } - - // Check if the output file exists - guard let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(checkedModuleInfo.modulePath.path)) else { - reporter?.report("Module output not found: '\(moduleID.moduleNameForDiagnostic)'") - return false - } - - // Check if a dependency of this module has a newer output than this module - for dependencyId in checkedModuleInfo.directDependencies ?? [] { - let dependencyInfo = try moduleInfo(of: dependencyId) - if !verifyInputOlderThanOutputModTime(moduleID.moduleName, - VirtualPath.lookup(dependencyInfo.modulePath.path), - outputModTime) { - return false - } - } - - // Check if any of the textual sources of this module are newer than this module - switch checkedModuleInfo.details { - case .swift(let swiftDetails): - if let moduleInterfacePath = swiftDetails.moduleInterfacePath { - if !verifyInputOlderThanOutputModTime(moduleID.moduleName, - VirtualPath.lookup(moduleInterfacePath.path), - outputModTime) { - return false - } - } - if let bridgingHeaderPath = swiftDetails.bridgingHeaderPath { - if !verifyInputOlderThanOutputModTime(moduleID.moduleName, - VirtualPath.lookup(bridgingHeaderPath.path), - outputModTime) { - return false - } - } - for bridgingSourceFile in swiftDetails.bridgingSourceFiles ?? [] { - if !verifyInputOlderThanOutputModTime(moduleID.moduleName, - VirtualPath.lookup(bridgingSourceFile.path), - outputModTime) { - return false - } - } - case .clang(_): - for inputSourceFile in checkedModuleInfo.sourceFiles ?? [] { - if !verifyInputOlderThanOutputModTime(moduleID.moduleName, - try VirtualPath(path: inputSourceFile), - outputModTime) { - return false - } - } - case .swiftPrebuiltExternal(_): - // TODO: We have to give-up here until we have a way to verify the timestamp of the binary module. - // We can do better here by knowing if this module hasn't changed - which would allows us to not - // invalidate any of the dependencies that depend on it. - reporter?.report("Unable to verify binary module dependency up-to-date: \(moduleID.moduleNameForDiagnostic)") - return false; - case .swiftPlaceholder(_): - // TODO: This should never ever happen. Hard error? - return false; - } - - return true - } -} - internal extension InterModuleDependencyGraph { func explainDependency(dependencyModuleName: String) throws -> [[ModuleDependencyId]]? { guard modules.contains(where: { $0.key.moduleName == dependencyModuleName }) else { return nil } diff --git a/Sources/SwiftDriver/IncrementalCompilation/BuildRecordInfo.swift b/Sources/SwiftDriver/IncrementalCompilation/BuildRecordInfo.swift index 59afb612e..f327faa78 100644 --- a/Sources/SwiftDriver/IncrementalCompilation/BuildRecordInfo.swift +++ b/Sources/SwiftDriver/IncrementalCompilation/BuildRecordInfo.swift @@ -184,7 +184,7 @@ import class Dispatch.DispatchQueue try? fileSystem.removeFileTree(absPath) } - func readPriorInterModuleDependencyGraph( + func readOutOfDateInterModuleDependencyGraph( reporter: IncrementalCompilationState.Reporter? ) -> InterModuleDependencyGraph? { let decodedGraph: InterModuleDependencyGraph diff --git a/Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift b/Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift index 9b26b547f..aa94cfdac 100644 --- a/Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift +++ b/Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift @@ -138,6 +138,63 @@ extension IncrementalCompilationState.FirstWaveComputer { mandatoryJobsInOrder: mandatoryJobsInOrder) } + /// We must determine if any of the module dependencies require re-compilation + /// Since we know that a prior dependency graph was not completely up-to-date, + /// there must be at least *some* dependencies that require being re-built. + /// + /// If a dependency is deemed as requiring a re-build, then every module + /// between it and the root (source module being built by this driver + /// instance) must also be re-built. + private func computeInvalidatedModuleDependencies(on moduleDependencyGraph: InterModuleDependencyGraph) + throws -> Set { + let mainModuleInfo = moduleDependencyGraph.mainModule + var modulesRequiringRebuild: Set = [] + var visitedModules: Set = [] + // Scan from the main module's dependencies to avoid reporting + // the main module itself in the results. + for dependencyId in mainModuleInfo.directDependencies ?? [] { + try outOfDateModuleScan(on: moduleDependencyGraph, from: dependencyId, visited: &visitedModules, + modulesRequiringRebuild: &modulesRequiringRebuild) + } + + reporter?.reportExplicitDependencyReBuildSet(Array(modulesRequiringRebuild)) + return modulesRequiringRebuild + } + + /// Perform a postorder DFS to locate modules which are out-of-date with respect + /// to their inputs. Upon encountering such a module, add it to the set of invalidated + /// modules, along with the path from the root to this module. + private func outOfDateModuleScan(on moduleDependencyGraph: InterModuleDependencyGraph, + from moduleId: ModuleDependencyId, + visited: inout Set, + modulesRequiringRebuild: inout Set) throws { + let moduleInfo = try moduleDependencyGraph.moduleInfo(of: moduleId) + // Visit the module's dependencies + var hasOutOfDateModuleDependency = false + for dependencyId in moduleInfo.directDependencies ?? [] { + // If we have not already visited this module, recurse. + if !visited.contains(dependencyId) { + try outOfDateModuleScan(on: moduleDependencyGraph, from: dependencyId, + visited: &visited, + modulesRequiringRebuild: &modulesRequiringRebuild) + } + // Even if we're not revisiting a dependency, we must check if it's already known to be out of date. + hasOutOfDateModuleDependency = hasOutOfDateModuleDependency || modulesRequiringRebuild.contains(dependencyId) + } + + if hasOutOfDateModuleDependency { + reporter?.reportExplicitDependencyWillBeReBuilt(moduleId.moduleNameForDiagnostic, reason: "Invalidated by downstream dependency") + modulesRequiringRebuild.insert(moduleId) + } else if try !IncrementalCompilationState.IncrementalDependencyAndInputSetup.verifyModuleDependencyUpToDate(moduleID: moduleId, moduleInfo: moduleInfo, + fileSystem: fileSystem, reporter: reporter) { + reporter?.reportExplicitDependencyWillBeReBuilt(moduleId.moduleNameForDiagnostic, reason: "Out-of-date") + modulesRequiringRebuild.insert(moduleId) + } + + // Now that we've determined if this module must be rebuilt, mark it as visited. + visited.insert(moduleId) + } + /// In an explicit module build, filter out dependency module pre-compilation tasks /// for modules up-to-date from a prior compile. private func computeMandatoryBeforeCompilesJobs() throws -> [Job] { @@ -155,9 +212,7 @@ extension IncrementalCompilationState.FirstWaveComputer { // Determine which module pre-build jobs must be re-run let modulesRequiringReBuild = - try moduleDependencyGraph.computeInvalidatedModuleDependencies(fileSystem: fileSystem, - forRebuild: true, - reporter: reporter) + try computeInvalidatedModuleDependencies(on: moduleDependencyGraph) // Filter the `.generatePCM` and `.compileModuleFromInterface` jobs for // modules which do *not* need re-building. diff --git a/Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState+Extensions.swift b/Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState+Extensions.swift index 63a9b30a6..a8731eda9 100644 --- a/Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState+Extensions.swift +++ b/Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState+Extensions.swift @@ -308,11 +308,6 @@ extension IncrementalCompilationState { report("Dependency module '\(moduleOutputPath)' will be re-built: \(reason)") } - func reportPriorExplicitDependencyStale(_ moduleOutputPath: String, - reason: String) { - report("Dependency module '\(moduleOutputPath)' info is stale: \(reason)") - } - func reportExplicitDependencyReBuildSet(_ modules: [ModuleDependencyId]) { report("Following explicit module dependencies will be re-built: [\(modules.map { $0.moduleNameForDiagnostic }.sorted().joined(separator: ", "))]") } diff --git a/Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift b/Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift index be08fd947..89297c777 100644 --- a/Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift +++ b/Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift @@ -106,7 +106,7 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup { ) throws -> InterModuleDependencyGraph? { // Attempt to read a serialized inter-module dependency graph from a prior build guard let priorInterModuleDependencyGraph = - buildRecordInfo.readPriorInterModuleDependencyGraph(reporter: reporter), + buildRecordInfo.readOutOfDateInterModuleDependencyGraph(reporter: reporter), let priorImports = priorInterModuleDependencyGraph.mainModule.directDependencies?.map({ $0.moduleName }) else { reporter?.reportExplicitBuildMustReScan("Could not read inter-module dependency graph at \(buildRecordInfo.interModuleDependencyGraphPath)") return nil @@ -120,9 +120,9 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup { } // Verify that each dependnecy is up-to-date with respect to its inputs - guard try priorInterModuleDependencyGraph.computeInvalidatedModuleDependencies(fileSystem: buildRecordInfo.fileSystem, - forRebuild: false, - reporter: reporter).isEmpty else { + guard try verifyInterModuleDependenciesUpToDate(in: priorInterModuleDependencyGraph, + buildRecordInfo: buildRecordInfo, + reporter: reporter) else { reporter?.reportExplicitBuildMustReScan("Not all dependencies are up-to-date.") return nil } @@ -130,6 +130,99 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup { reporter?.report("Confirmed prior inter-module dependency graph is up-to-date at: \(buildRecordInfo.interModuleDependencyGraphPath)") return priorInterModuleDependencyGraph } + + static func verifyModuleDependencyUpToDate(moduleID: ModuleDependencyId, moduleInfo: ModuleInfo, + fileSystem: FileSystem, + reporter: IncrementalCompilationState.Reporter?) throws -> Bool { + // Verify that the specified input exists and is older than the specified output + let verifyInputOlderThanOutputModTime: (String, VirtualPath, VirtualPath, TimePoint) -> Bool = + { moduleName, inputPath, outputPath, outputModTime in + guard let inputModTime = + try? fileSystem.lastModificationTime(for: inputPath) else { + reporter?.report("Unable to 'stat' \(inputPath.description)") + return false + } + if inputModTime > outputModTime { + reporter?.reportExplicitDependencyOutOfDate(moduleName, + inputPath: inputPath.description) + return false + } + return true + } + + switch moduleInfo.details { + case .swift(let swiftDetails): + guard let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleInfo.modulePath.path)) else { + reporter?.report("Module output not found: '\(moduleID.moduleNameForDiagnostic)'") + return false + } + if let moduleInterfacePath = swiftDetails.moduleInterfacePath { + if !verifyInputOlderThanOutputModTime(moduleID.moduleName, + VirtualPath.lookup(moduleInterfacePath.path), + VirtualPath.lookup(moduleInfo.modulePath.path), + outputModTime) { + return false + } + } + if let bridgingHeaderPath = swiftDetails.bridgingHeaderPath { + if !verifyInputOlderThanOutputModTime(moduleID.moduleName, + VirtualPath.lookup(bridgingHeaderPath.path), + VirtualPath.lookup(moduleInfo.modulePath.path), + outputModTime) { + return false + } + } + for bridgingSourceFile in swiftDetails.bridgingSourceFiles ?? [] { + if !verifyInputOlderThanOutputModTime(moduleID.moduleName, + VirtualPath.lookup(bridgingSourceFile.path), + VirtualPath.lookup(moduleInfo.modulePath.path), + outputModTime) { + return false + } + } + case .clang(_): + guard let outputModTime = try? fileSystem.lastModificationTime(for: VirtualPath.lookup(moduleInfo.modulePath.path)) else { + reporter?.report("Module output not found: '\(moduleID.moduleNameForDiagnostic)'") + return false + } + for inputSourceFile in moduleInfo.sourceFiles ?? [] { + if !verifyInputOlderThanOutputModTime(moduleID.moduleName, + try VirtualPath(path: inputSourceFile), + VirtualPath.lookup(moduleInfo.modulePath.path), + outputModTime) { + return false + } + } + case .swiftPrebuiltExternal(_): + // TODO: We have to give-up here until we have a way to verify the timestamp of the binary module. + // We can do better here by knowing if this module hasn't change - which would allows us to not + // invalidate any of the dependencies that depend on it. + reporter?.report("Unable to verify binary module dependency up-to-date: \(moduleID.moduleNameForDiagnostic)") + return false; + case .swiftPlaceholder(_): + // TODO: This should never ever happen. Hard error? + return false; + } + return true + } + + /// For each direct and transitive module dependency, check if any of the inputs are newer than the output + static func verifyInterModuleDependenciesUpToDate(in graph: InterModuleDependencyGraph, + buildRecordInfo: BuildRecordInfo, + reporter: IncrementalCompilationState.Reporter?) throws -> Bool { + for module in graph.modules { + if module.key == .swift(graph.mainModuleName) { + continue + } + if try !verifyModuleDependencyUpToDate(moduleID: module.key, + moduleInfo: module.value, + fileSystem: buildRecordInfo.fileSystem, + reporter: reporter) { + return false + } + } + return true + } } /// Builds the `InitialState` diff --git a/Tests/SwiftDriverTests/IncrementalCompilationTests.swift b/Tests/SwiftDriverTests/IncrementalCompilationTests.swift index 4f51d25c2..c9a33249e 100644 --- a/Tests/SwiftDriverTests/IncrementalCompilationTests.swift +++ b/Tests/SwiftDriverTests/IncrementalCompilationTests.swift @@ -351,7 +351,6 @@ extension IncrementalCompilationTests { readInterModuleGraph // Ensure the above 'touch' is detected and causes a re-scan explicitDependencyModuleOlderThanInput("E") - moduleInfoStaleOutOfDate("E") explicitMustReScanDueToChangedDependencyInput noFingerprintInSwiftModule("E.swiftinterface") dependencyNewerThanNode("E.swiftinterface") @@ -383,6 +382,7 @@ extension IncrementalCompilationTests { // On this graph, inputs of 'G' are updated, causing it to be re-built // as well as all modules on paths from root to it: 'Y', 'H', 'T','J' func testExplicitIncrementalBuildChangedDependencyInvalidatesUpstreamDependencies() throws { + // Add an import of 'B', 'C' to make sure followup changes has consistent inputs replace(contentsOf: "other", with: "import Y;import T") try buildInitialState(checkDiagnostics: false, explicitModuleBuild: true) @@ -403,11 +403,6 @@ extension IncrementalCompilationTests { readInterModuleGraph // Ensure the above 'touch' is detected and causes a re-scan explicitDependencyModuleOlderThanInput("G") - moduleInfoStaleOutOfDate("G") - moduleInfoStaleInvalidatedDownstream("J") - moduleInfoStaleInvalidatedDownstream("T") - moduleInfoStaleInvalidatedDownstream("Y") - moduleInfoStaleInvalidatedDownstream("H") explicitMustReScanDueToChangedDependencyInput noFingerprintInSwiftModule("G.swiftinterface") dependencyNewerThanNode("G.swiftinterface") @@ -421,10 +416,10 @@ extension IncrementalCompilationTests { queuingInitial("main", "other") findingBatchingCompiling("main", "other") explicitDependencyModuleOlderThanInput("G") - moduleWillBeRebuiltInvalidatedDownstream("J") - moduleWillBeRebuiltInvalidatedDownstream("T") - moduleWillBeRebuiltInvalidatedDownstream("Y") - moduleWillBeRebuiltInvalidatedDownstream("H") + explicitDependencyInvalidatedDownstream("J") + explicitDependencyInvalidatedDownstream("T") + explicitDependencyInvalidatedDownstream("Y") + explicitDependencyInvalidatedDownstream("H") explicitModulesWillBeRebuilt(["G", "H", "J", "T", "Y"]) moduleWillBeRebuiltOutOfDate("G") compilingExplicitSwiftDependency("G") @@ -436,61 +431,6 @@ extension IncrementalCompilationTests { linking } } - - // A dependency has been re-built to be newer than its dependents - // so we must ensure the dependents get re-built even though all the - // modules are up-to-date with respect to their textual source inputs. - // - // test - // \ - // J - // \ - // G - // - // On this graph, after the initial build, if G module binary file is newer - // than that of J, even if each of the modules is up-to-date w.r.t. their source inputs - // we still expect that J gets re-built - func testExplicitIncrementalBuildChangedDependencyBinaryInvalidatesUpstreamDependencies() throws { - replace(contentsOf: "other", with: "import J;") - try buildInitialState(checkDiagnostics: false, explicitModuleBuild: true) - - let modCacheEntries = try localFileSystem.getDirectoryContents(explicitModuleCacheDir) - let nameOfGModule = try XCTUnwrap(modCacheEntries.first { $0.hasPrefix("G") && $0.hasSuffix(".swiftmodule")}) - let pathToGModule = explicitModuleCacheDir.appending(component: nameOfGModule) - // Just update the time-stamp of one of the module dependencies' outputs. - touch(pathToGModule) - // Touch one of the inputs to actually trigger the incremental build - touch(inputPath(basename: "other")) - - // Changing a dependency will mean that we both re-run the dependency scan, - // and also ensure that all source-files are re-built with a non-cascading build - // since the source files themselves have not changed. - try doABuild( - "update dependency (G) result timestamp", - checkDiagnostics: true, - extraArguments: explicitBuildArgs, - whenAutolinking: autolinkLifecycleExpectedDiags - ) { - readGraph - enablingCrossModule - readInterModuleGraph - explicitDependencyModuleOlderThanInput("J") - moduleInfoStaleOutOfDate("J") - explicitMustReScanDueToChangedDependencyInput - maySkip("main") - schedulingChangedInitialQueuing("other") - skipping("main") - explicitDependencyModuleOlderThanInput("J") - moduleWillBeRebuiltOutOfDate("J") - explicitModulesWillBeRebuilt(["J"]) - compilingExplicitSwiftDependency("J") - findingBatchingCompiling("other") - reading(deps: "other") - skipped("main") - schedulingPostCompileJobs - linking - } - } } extension IncrementalCompilationTests { @@ -1699,18 +1639,12 @@ extension DiagVerifiable { @DiagsBuilder func moduleWillBeRebuiltOutOfDate(_ moduleName: String) -> [Diagnostic.Message] { "Incremental compilation: Dependency module '\(moduleName)' will be re-built: Out-of-date" } - @DiagsBuilder func moduleWillBeRebuiltInvalidatedDownstream(_ moduleName: String) -> [Diagnostic.Message] { - "Incremental compilation: Dependency module '\(moduleName)' will be re-built: Invalidated by downstream dependency" - } - @DiagsBuilder func moduleInfoStaleOutOfDate(_ moduleName: String) -> [Diagnostic.Message] { - "Incremental compilation: Dependency module '\(moduleName)' info is stale: Out-of-date" - } - @DiagsBuilder func moduleInfoStaleInvalidatedDownstream(_ moduleName: String) -> [Diagnostic.Message] { - "Incremental compilation: Dependency module '\(moduleName)' info is stale: Invalidated by downstream dependency" - } @DiagsBuilder func explicitModulesWillBeRebuilt(_ moduleNames: [String]) -> [Diagnostic.Message] { "Incremental compilation: Following explicit module dependencies will be re-built: [\(moduleNames.joined(separator: ", "))]" } + @DiagsBuilder func explicitDependencyInvalidatedDownstream(_ moduleName: String) -> [Diagnostic.Message] { + "Incremental compilation: Dependency module '\(moduleName)' will be re-built: Invalidated by downstream dependency" + } // MARK: - misc @DiagsBuilder var enablingCrossModule: [Diagnostic.Message] {