diff --git a/Sources/SWBCore/LibSwiftDriver/LibSwiftDriver.swift b/Sources/SWBCore/LibSwiftDriver/LibSwiftDriver.swift index fdae29f3..fa323660 100644 --- a/Sources/SWBCore/LibSwiftDriver/LibSwiftDriver.swift +++ b/Sources/SWBCore/LibSwiftDriver/LibSwiftDriver.swift @@ -177,7 +177,7 @@ public final class SwiftModuleDependencyGraph: SwiftGlobalExplicitDependencyGrap } } - public func queryPlanningDependencies(for key: String) throws -> [String] { + public func querySwiftmodulesNeedingRegistrationForDebugging(for key: String) throws -> [String] { let graph = try registryQueue.blocking_sync { guard let driver = registry[key] else { throw StubError.error("Unable to find jobs for key \(key). Be sure to plan the build ahead of fetching results.") @@ -185,13 +185,40 @@ public final class SwiftModuleDependencyGraph: SwiftGlobalExplicitDependencyGrap return driver.intermoduleDependencyGraph } guard let graph else { return [] } - let directDependencies = graph.mainModule.directDependencies ?? [] - let transitiveDependencies = Set(directDependencies + SWBUtil.transitiveClosure(directDependencies, successors: { moduleID in graph.modules[moduleID]?.directDependencies ?? [] }).0) + var swiftmodulePaths: [String] = [] + swiftmodulePaths.reserveCapacity(graph.modules.values.count) + for (_, moduleInfo) in graph.modules.sorted(byKey: { $0.moduleName < $1.moduleName }) { + guard moduleInfo != graph.mainModule else { + continue + } + switch moduleInfo.details { + case .swift: + if let modulePath = VirtualPath.lookup(moduleInfo.modulePath.path).absolutePath { + swiftmodulePaths.append(modulePath.pathString) + } + case .swiftPrebuiltExternal(let details): + if let modulePath = VirtualPath.lookup(details.compiledModulePath.path).absolutePath { + swiftmodulePaths.append(modulePath.pathString) + } + case .clang, .swiftPlaceholder: + break + } + } + return swiftmodulePaths + } + public func queryPlanningDependencies(for key: String) throws -> [String] { + let graph = try registryQueue.blocking_sync { + guard let driver = registry[key] else { + throw StubError.error("Unable to find jobs for key \(key). Be sure to plan the build ahead of fetching results.") + } + return driver.intermoduleDependencyGraph + } + guard let graph else { return [] } var fileDependencies: [String] = [] - fileDependencies.reserveCapacity(transitiveDependencies.count * 10) - for dependencyID in transitiveDependencies { - guard let moduleInfo = graph.modules[dependencyID] else { + fileDependencies.reserveCapacity(graph.modules.values.count * 10) + for (_, moduleInfo) in graph.modules.sorted(byKey: { $0.moduleName < $1.moduleName }) { + guard moduleInfo != graph.mainModule else { continue } fileDependencies.append(contentsOf: moduleInfo.sourceFiles ?? []) diff --git a/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift index e1daf0f0..864b1f72 100644 --- a/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift @@ -109,14 +109,9 @@ final public class SwiftDriverTaskAction: TaskAction, BuildValueValidatingTaskAc if let linkerResponseFilePath = driverPayload.linkerResponseFilePath { var responseFileCommandLine: [String] = [] - let plannedBuild = try dependencyGraph.queryPlannedBuild(for: driverPayload.uniqueID) if driverPayload.explicitModulesEnabled { - for job in plannedBuild.explicitModulesPlannedDriverJobs() { - for output in job.driverJob.outputs { - if output.fileExtension == "swiftmodule" { - responseFileCommandLine.append(contentsOf: ["-Xlinker", "-add_ast_path", "-Xlinker", "\(output.str)"]) - } - } + for swiftmodulePath in try dependencyGraph.querySwiftmodulesNeedingRegistrationForDebugging(for: driverPayload.uniqueID) { + responseFileCommandLine.append(contentsOf: ["-Xlinker", "-add_ast_path", "-Xlinker", "\(swiftmodulePath)"]) } } let contents = ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: responseFileCommandLine)) diff --git a/Tests/SWBBuildSystemTests/SwiftDriverTests.swift b/Tests/SWBBuildSystemTests/SwiftDriverTests.swift index dc882a62..6be300a1 100644 --- a/Tests/SWBBuildSystemTests/SwiftDriverTests.swift +++ b/Tests/SWBBuildSystemTests/SwiftDriverTests.swift @@ -5041,4 +5041,106 @@ fileprivate struct SwiftDriverTests: CoreBasedTests { } } } + + @Test(.requireSDKs(.macOS)) + func incrementalExplicitModulesLinkerSwiftmoduleRegistration() async throws { + try await withTemporaryDirectory { tmpDirPath async throws -> Void in + let testWorkspace = try await TestWorkspace( + "Test", + sourceRoot: tmpDirPath.join("Test"), + projects: [ + TestProject( + "aProject", + groupTree: TestGroup( + "Sources", + path: "Sources", + children: [ + TestFile("fileA1.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_VERSION": swiftVersion, + "BUILD_VARIANTS": "normal", + "SWIFT_USE_INTEGRATED_DRIVER": "YES", + "SWIFT_ENABLE_EXPLICIT_MODULES": "YES", + ]) + ], + targets: [ + TestStandardTarget( + "TargetA", + type: .framework, + buildPhases: [ + TestSourcesBuildPhase([ + "fileA1.swift", + ]), + ]), + ]) + ]) + + let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + tester.userInfo = tester.userInfo.withAdditionalEnvironment(environment: ["SWIFT_FORCE_MODULE_LOADING": "only-interface"]) + let parameters = BuildParameters(configuration: "Debug", overrides: [ + // Redirect the prebuilt cache so we always build modules from source + "SWIFT_OVERLOAD_PREBUILT_MODULE_CACHE_PATH": tmpDirPath.str + ]) + let buildRequest = BuildRequest(parameters: parameters, buildTargets: tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }), continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: false, useDryRun: false) + let SRCROOT = testWorkspace.sourceRoot.join("aProject") + + // Create the source files. + try await tester.fs.writeFileContents(SRCROOT.join("Sources/fileA1.swift")) { file in + file <<< + """ + public struct A { + public init() { } + } + """ + } + + var cleanResponseFileContents: ByteString? = nil + try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in + var responseFile: Path? = nil + results.checkTask(.matchRuleType("SwiftDriver Compilation Requirements")) { driverTask in + responseFile = driverTask.outputPaths.filter { $0.str.hasSuffix("-linker-args.resp") }.only + } + try results.checkTask(.matchRuleType("Ld")) { linkTask in + linkTask.checkCommandLineContains("@\(try #require(responseFile).str)") + } + let responseFileContents = try tester.fs.read(try #require(responseFile)) + #expect(!responseFileContents.isEmpty) + cleanResponseFileContents = responseFileContents + } + + try await tester.fs.writeFileContents(SRCROOT.join("Sources/fileA1.swift")) { file in + file <<< + """ + public struct A { + public init() { } + } + + public func foo() { } + """ + } + + var incrementalResponseFileContents: ByteString? = nil + try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in + var responseFile: Path? = nil + results.checkTask(.matchRuleType("SwiftDriver Compilation Requirements")) { driverTask in + responseFile = driverTask.outputPaths.filter { $0.str.hasSuffix("-linker-args.resp") }.only + } + try results.checkTask(.matchRuleType("Ld")) { linkTask in + linkTask.checkCommandLineContains("@\(try #require(responseFile).str)") + } + let responseFileContents = try tester.fs.read(try #require(responseFile)) + #expect(!responseFileContents.isEmpty) + incrementalResponseFileContents = responseFileContents + } + + let cleanContents = try #require(cleanResponseFileContents) + let incrementalContents = try #require(incrementalResponseFileContents) + #expect(cleanContents == incrementalContents) + } + } }