Skip to content

Commit 0891c39

Browse files
authored
Fix linker swiftmodule registration in incremental builds (#412)
rdar://149248232
1 parent f97263c commit 0891c39

File tree

3 files changed

+137
-13
lines changed

3 files changed

+137
-13
lines changed

Sources/SWBCore/LibSwiftDriver/LibSwiftDriver.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,21 +177,48 @@ public final class SwiftModuleDependencyGraph: SwiftGlobalExplicitDependencyGrap
177177
}
178178
}
179179

180-
public func queryPlanningDependencies(for key: String) throws -> [String] {
180+
public func querySwiftmodulesNeedingRegistrationForDebugging(for key: String) throws -> [String] {
181181
let graph = try registryQueue.blocking_sync {
182182
guard let driver = registry[key] else {
183183
throw StubError.error("Unable to find jobs for key \(key). Be sure to plan the build ahead of fetching results.")
184184
}
185185
return driver.intermoduleDependencyGraph
186186
}
187187
guard let graph else { return [] }
188-
let directDependencies = graph.mainModule.directDependencies ?? []
189-
let transitiveDependencies = Set(directDependencies + SWBUtil.transitiveClosure(directDependencies, successors: { moduleID in graph.modules[moduleID]?.directDependencies ?? [] }).0)
188+
var swiftmodulePaths: [String] = []
189+
swiftmodulePaths.reserveCapacity(graph.modules.values.count)
190+
for (_, moduleInfo) in graph.modules.sorted(byKey: { $0.moduleName < $1.moduleName }) {
191+
guard moduleInfo != graph.mainModule else {
192+
continue
193+
}
194+
switch moduleInfo.details {
195+
case .swift:
196+
if let modulePath = VirtualPath.lookup(moduleInfo.modulePath.path).absolutePath {
197+
swiftmodulePaths.append(modulePath.pathString)
198+
}
199+
case .swiftPrebuiltExternal(let details):
200+
if let modulePath = VirtualPath.lookup(details.compiledModulePath.path).absolutePath {
201+
swiftmodulePaths.append(modulePath.pathString)
202+
}
203+
case .clang, .swiftPlaceholder:
204+
break
205+
}
206+
}
207+
return swiftmodulePaths
208+
}
190209

210+
public func queryPlanningDependencies(for key: String) throws -> [String] {
211+
let graph = try registryQueue.blocking_sync {
212+
guard let driver = registry[key] else {
213+
throw StubError.error("Unable to find jobs for key \(key). Be sure to plan the build ahead of fetching results.")
214+
}
215+
return driver.intermoduleDependencyGraph
216+
}
217+
guard let graph else { return [] }
191218
var fileDependencies: [String] = []
192-
fileDependencies.reserveCapacity(transitiveDependencies.count * 10)
193-
for dependencyID in transitiveDependencies {
194-
guard let moduleInfo = graph.modules[dependencyID] else {
219+
fileDependencies.reserveCapacity(graph.modules.values.count * 10)
220+
for (_, moduleInfo) in graph.modules.sorted(byKey: { $0.moduleName < $1.moduleName }) {
221+
guard moduleInfo != graph.mainModule else {
195222
continue
196223
}
197224
fileDependencies.append(contentsOf: moduleInfo.sourceFiles ?? [])

Sources/SWBTaskExecution/TaskActions/SwiftDriverTaskAction.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,9 @@ final public class SwiftDriverTaskAction: TaskAction, BuildValueValidatingTaskAc
109109

110110
if let linkerResponseFilePath = driverPayload.linkerResponseFilePath {
111111
var responseFileCommandLine: [String] = []
112-
let plannedBuild = try dependencyGraph.queryPlannedBuild(for: driverPayload.uniqueID)
113112
if driverPayload.explicitModulesEnabled {
114-
for job in plannedBuild.explicitModulesPlannedDriverJobs() {
115-
for output in job.driverJob.outputs {
116-
if output.fileExtension == "swiftmodule" {
117-
responseFileCommandLine.append(contentsOf: ["-Xlinker", "-add_ast_path", "-Xlinker", "\(output.str)"])
118-
}
119-
}
113+
for swiftmodulePath in try dependencyGraph.querySwiftmodulesNeedingRegistrationForDebugging(for: driverPayload.uniqueID) {
114+
responseFileCommandLine.append(contentsOf: ["-Xlinker", "-add_ast_path", "-Xlinker", "\(swiftmodulePath)"])
120115
}
121116
}
122117
let contents = ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: responseFileCommandLine))

Tests/SWBBuildSystemTests/SwiftDriverTests.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5041,4 +5041,106 @@ fileprivate struct SwiftDriverTests: CoreBasedTests {
50415041
}
50425042
}
50435043
}
5044+
5045+
@Test(.requireSDKs(.macOS))
5046+
func incrementalExplicitModulesLinkerSwiftmoduleRegistration() async throws {
5047+
try await withTemporaryDirectory { tmpDirPath async throws -> Void in
5048+
let testWorkspace = try await TestWorkspace(
5049+
"Test",
5050+
sourceRoot: tmpDirPath.join("Test"),
5051+
projects: [
5052+
TestProject(
5053+
"aProject",
5054+
groupTree: TestGroup(
5055+
"Sources",
5056+
path: "Sources",
5057+
children: [
5058+
TestFile("fileA1.swift"),
5059+
]),
5060+
buildConfigurations: [
5061+
TestBuildConfiguration(
5062+
"Debug",
5063+
buildSettings: [
5064+
"PRODUCT_NAME": "$(TARGET_NAME)",
5065+
"SWIFT_VERSION": swiftVersion,
5066+
"BUILD_VARIANTS": "normal",
5067+
"SWIFT_USE_INTEGRATED_DRIVER": "YES",
5068+
"SWIFT_ENABLE_EXPLICIT_MODULES": "YES",
5069+
])
5070+
],
5071+
targets: [
5072+
TestStandardTarget(
5073+
"TargetA",
5074+
type: .framework,
5075+
buildPhases: [
5076+
TestSourcesBuildPhase([
5077+
"fileA1.swift",
5078+
]),
5079+
]),
5080+
])
5081+
])
5082+
5083+
let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false)
5084+
tester.userInfo = tester.userInfo.withAdditionalEnvironment(environment: ["SWIFT_FORCE_MODULE_LOADING": "only-interface"])
5085+
let parameters = BuildParameters(configuration: "Debug", overrides: [
5086+
// Redirect the prebuilt cache so we always build modules from source
5087+
"SWIFT_OVERLOAD_PREBUILT_MODULE_CACHE_PATH": tmpDirPath.str
5088+
])
5089+
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)
5090+
let SRCROOT = testWorkspace.sourceRoot.join("aProject")
5091+
5092+
// Create the source files.
5093+
try await tester.fs.writeFileContents(SRCROOT.join("Sources/fileA1.swift")) { file in
5094+
file <<<
5095+
"""
5096+
public struct A {
5097+
public init() { }
5098+
}
5099+
"""
5100+
}
5101+
5102+
var cleanResponseFileContents: ByteString? = nil
5103+
try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in
5104+
var responseFile: Path? = nil
5105+
results.checkTask(.matchRuleType("SwiftDriver Compilation Requirements")) { driverTask in
5106+
responseFile = driverTask.outputPaths.filter { $0.str.hasSuffix("-linker-args.resp") }.only
5107+
}
5108+
try results.checkTask(.matchRuleType("Ld")) { linkTask in
5109+
linkTask.checkCommandLineContains("@\(try #require(responseFile).str)")
5110+
}
5111+
let responseFileContents = try tester.fs.read(try #require(responseFile))
5112+
#expect(!responseFileContents.isEmpty)
5113+
cleanResponseFileContents = responseFileContents
5114+
}
5115+
5116+
try await tester.fs.writeFileContents(SRCROOT.join("Sources/fileA1.swift")) { file in
5117+
file <<<
5118+
"""
5119+
public struct A {
5120+
public init() { }
5121+
}
5122+
5123+
public func foo() { }
5124+
"""
5125+
}
5126+
5127+
var incrementalResponseFileContents: ByteString? = nil
5128+
try await tester.checkBuild(runDestination: .macOS, buildRequest: buildRequest, persistent: true) { results in
5129+
var responseFile: Path? = nil
5130+
results.checkTask(.matchRuleType("SwiftDriver Compilation Requirements")) { driverTask in
5131+
responseFile = driverTask.outputPaths.filter { $0.str.hasSuffix("-linker-args.resp") }.only
5132+
}
5133+
try results.checkTask(.matchRuleType("Ld")) { linkTask in
5134+
linkTask.checkCommandLineContains("@\(try #require(responseFile).str)")
5135+
}
5136+
let responseFileContents = try tester.fs.read(try #require(responseFile))
5137+
#expect(!responseFileContents.isEmpty)
5138+
incrementalResponseFileContents = responseFileContents
5139+
}
5140+
5141+
let cleanContents = try #require(cleanResponseFileContents)
5142+
let incrementalContents = try #require(incrementalResponseFileContents)
5143+
#expect(cleanContents == incrementalContents)
5144+
}
5145+
}
50445146
}

0 commit comments

Comments
 (0)