diff --git a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift index dc650a222..caf2ec276 100644 --- a/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift +++ b/Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift @@ -367,7 +367,7 @@ internal extension InterModuleDependencyGraph { } } - // Check if any of the textual sources of this module are newer than this module + // Check if any of the input sources of this module are newer than this module switch checkedModuleInfo.details { case .swift(let swiftDetails): if let moduleInterfacePath = swiftDetails.moduleInterfacePath { @@ -400,11 +400,12 @@ internal extension InterModuleDependencyGraph { } } 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; + // We do not verify the binary module itself being out-of-date if we do not have a textual + // interface it was built from, but we can safely treat it as up-to-date, particularly + // because if it is newer than any of the modules they depend on it, they will + // still get invalidated in the check above for whether a module has + // any dependencies newer than it. + return true; case .swiftPlaceholder(_): // TODO: This should never ever happen. Hard error? return false; diff --git a/Tests/SwiftDriverTests/IncrementalCompilationTests.swift b/Tests/SwiftDriverTests/IncrementalCompilationTests.swift index 57de8be9a..7d6d8a5e4 100644 --- a/Tests/SwiftDriverTests/IncrementalCompilationTests.swift +++ b/Tests/SwiftDriverTests/IncrementalCompilationTests.swift @@ -516,6 +516,56 @@ extension IncrementalCompilationTests { linking } } + + func testExplicitIncrementalBuildUnchangedBinaryDependencyDoesNotInvalidateUpstreamDependencies() throws { + replace(contentsOf: "other", with: "import J;") + + // After an initial build, replace the G.swiftinterface with G.swiftmodule + // and repeat the initial build to settle into the "initial" state for the test + 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) + // Rename the binary module to G.swiftmodule so that the next build's scan finds it. + let newPathToGModule = explicitSwiftDependenciesPath.appending(component: "G.swiftmodule") + try! localFileSystem.move(from: pathToGModule, to: newPathToGModule) + // Delete the textual interface it was built from so that it is treated as a binary-only dependency now. + try! localFileSystem.removeFileTree(try AbsolutePath(validating: explicitSwiftDependenciesPath.appending(component: "G.swiftinterface").pathString)) + try buildInitialState(checkDiagnostics: false, explicitModuleBuild: true) + + // Touch one of the inputs to actually trigger the incremental build, so that we can ensure + // no module deps get re-built + touch(inputPath(basename: "other")) + + try doABuild( + "Unchanged binary dependency (G)", + checkDiagnostics: true, + extraArguments: explicitBuildArgs, + whenAutolinking: autolinkLifecycleExpectedDiags + ) { + readGraph + enablingCrossModule + noFingerprintInSwiftModule("G.swiftinterface") + dependencyNewerThanNode("G.swiftinterface") + dependencyNewerThanNode("G.swiftinterface") // FIXME: Why do we see this twice? + readInterModuleGraph + interModuleDependencyGraphUpToDate // Graph declared up-to-date despite a downstream dependency on a binary Swift module dependency + maySkip("main") + schedulingChangedInitialQueuing("other") + fingerprintsMissingOfTopLevelName(name: "foo", "main") + invalidatedExternally("main", "other") + queuingInitial("main") + foundBatchableJobs(2) + formingOneBatch + addingToBatchThenForming("main", "other") + compiling("main", "other") + reading(deps: "main") + reading(deps: "other") + schedulingPostCompileJobs + linking + + } + } } extension IncrementalCompilationTests {