Skip to content

Commit 0c4c75c

Browse files
committed
Use the new SwiftPM API to load the build plan
We previously skipped building/running tool plugins here, which meant that the compiler arguments for a target also missed any generated sources. Use the new `BuildDescription.load` API from SwiftPM to address this. Resolves rdar://102242345.
1 parent d5978d5 commit 0c4c75c

File tree

2 files changed

+105
-17
lines changed

2 files changed

+105
-17
lines changed

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

+35-17
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ package import BuildServerProtocol
3333
package import Foundation
3434
package import LanguageServerProtocol
3535
package import SKOptions
36-
package import SourceKitLSPAPI
36+
@preconcurrency package import SourceKitLSPAPI
3737
package import ToolchainRegistry
3838
package import class ToolchainRegistry.Toolchain
3939
#else
4040
import BuildServerProtocol
4141
import Foundation
4242
import LanguageServerProtocol
4343
import SKOptions
44-
import SourceKitLSPAPI
44+
@preconcurrency import SourceKitLSPAPI
4545
import ToolchainRegistry
4646
import class ToolchainRegistry.Toolchain
4747
#endif
@@ -131,6 +131,8 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
131131
private let toolchain: Toolchain
132132
private let swiftPMWorkspace: Workspace
133133

134+
private let pluginConfiguration: PluginConfiguration
135+
134136
/// A `ObservabilitySystem` from `SwiftPM` that logs.
135137
private let observabilitySystem: ObservabilitySystem
136138

@@ -291,6 +293,19 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
291293
prepareForIndexing: options.backgroundPreparationModeOrDefault.toSwiftPMPreparation
292294
)
293295

296+
let pluginScriptRunner = DefaultPluginScriptRunner(
297+
fileSystem: localFileSystem,
298+
cacheDir: location.pluginWorkingDirectory.appending("cache"),
299+
toolchain: hostSwiftPMToolchain,
300+
extraPluginSwiftCFlags: [],
301+
enableSandbox: !(options.swiftPMOrDefault.disableSandbox ?? false)
302+
)
303+
self.pluginConfiguration = PluginConfiguration(
304+
scriptRunner: pluginScriptRunner,
305+
workDirectory: location.pluginWorkingDirectory,
306+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false
307+
)
308+
294309
packageLoadingQueue.async {
295310
await orLog("Initial package loading") {
296311
// Schedule an initial generation of the build graph. Once the build graph is loaded, the build system will send
@@ -326,27 +341,30 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
326341
}
327342
}
328343

329-
let modulesGraph = try await self.swiftPMWorkspace.loadPackageGraph(
330-
rootInput: PackageGraphRootInput(packages: [AbsolutePath(validating: projectRoot.filePath)]),
331-
forceResolvedVersions: !isForIndexBuild,
332-
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Load package graph")
333-
)
334-
335-
signposter.emitEvent("Finished loading modules graph", id: signpostID)
336-
337-
let plan = try await BuildPlan(
344+
nonisolated(unsafe) let packageLoader = {
345+
return try await self.swiftPMWorkspace.loadPackageGraph(
346+
rootInput: PackageGraphRootInput(packages: [try AbsolutePath(validating: self.projectRoot.filePath)]),
347+
forceResolvedVersions: !self.isForIndexBuild,
348+
observabilityScope: self.observabilitySystem.topScope.makeChildScope(description: "Load package graph")
349+
)
350+
}
351+
let loaded = try await BuildDescription.load(
352+
location: swiftPMWorkspace.location,
338353
destinationBuildParameters: destinationBuildParameters,
339354
toolsBuildParameters: toolsBuildParameters,
340-
graph: modulesGraph,
355+
packageGraphLoader: packageLoader,
356+
pluginConfiguration: pluginConfiguration,
341357
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
342358
fileSystem: localFileSystem,
343-
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build plan")
359+
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build description")
344360
)
361+
if !loaded.errors.isEmpty {
362+
logger.error("Loading SwiftPM description had errors: \(loaded.errors)")
363+
}
345364

346-
signposter.emitEvent("Finished generating build plan", id: signpostID)
365+
self.buildDescription = loaded.description
347366

348-
let buildDescription = BuildDescription(buildPlan: plan)
349-
self.buildDescription = buildDescription
367+
signposter.emitEvent("Finished generating build description", id: signpostID)
350368

351369
/// Make sure to execute any throwing statements before setting any
352370
/// properties because otherwise we might end up in an inconsistent state
@@ -355,7 +373,7 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
355373
self.swiftPMTargets = [:]
356374
self.targetDependencies = [:]
357375

358-
buildDescription.traverseModules { buildTarget, parent in
376+
loaded.description.traverseModules { buildTarget, parent in
359377
let targetIdentifier = orLog("Getting build target identifier") { try BuildTargetIdentifier(buildTarget) }
360378
guard let targetIdentifier else {
361379
return

Tests/SourceKitLSPTests/SwiftPMIntegrationTests.swift

+70
Original file line numberDiff line numberDiff line change
@@ -359,4 +359,74 @@ final class SwiftPMIntegrationTests: XCTestCase {
359359
["Cannot convert value of type 'Int' to specified type 'String'"]
360360
)
361361
}
362+
363+
func testToolPlugin() async throws {
364+
let project = try await SwiftPMTestProject(
365+
files: [
366+
"Plugins/plugin.swift": #"""
367+
import PackagePlugin
368+
@main struct CodeGeneratorPlugin: BuildToolPlugin {
369+
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
370+
let genSourcesDir = context.pluginWorkDirectoryURL.appending(path: "GeneratedSources")
371+
guard let target = target as? SourceModuleTarget else { return [] }
372+
let codeGenerator = try context.tool(named: "CodeGenerator").url
373+
let generatedFile = genSourcesDir.appending(path: "\(target.name)-generated.swift")
374+
return [.buildCommand(
375+
displayName: "Generating code for \(target.name)",
376+
executable: codeGenerator,
377+
arguments: [
378+
generatedFile.path
379+
],
380+
inputFiles: [],
381+
outputFiles: [generatedFile]
382+
)]
383+
}
384+
}
385+
"""#,
386+
387+
"Sources/CodeGenerator/CodeGenerator.swift": #"""
388+
import Foundation
389+
try "let generated = 1".write(to: URL(fileURLWithPath: CommandLine.arguments[1]), atomically: true, encoding: String.Encoding.utf8)
390+
"""#,
391+
392+
"Sources/TestLib/TestLib.swift": #"""
393+
func useGenerated() {
394+
_ = 2️⃣generated
395+
}
396+
"""#,
397+
],
398+
manifest: """
399+
// swift-tools-version: 6.0
400+
import PackageDescription
401+
let package = Package(
402+
name: "PluginTest",
403+
targets: [
404+
.executableTarget(name: "CodeGenerator"),
405+
.target(
406+
name: "TestLib",
407+
plugins: [.plugin(name: "CodeGeneratorPlugin")]
408+
),
409+
.plugin(
410+
name: "CodeGeneratorPlugin",
411+
capability: .buildTool(),
412+
dependencies: ["CodeGenerator"]
413+
),
414+
]
415+
)
416+
""",
417+
enableBackgroundIndexing: true
418+
)
419+
420+
let (uri, positions) = try project.openDocument("TestLib.swift")
421+
422+
try await project.testClient.send(PollIndexRequest())
423+
424+
let result = try await project.testClient.send(
425+
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
426+
)
427+
let location = try XCTUnwrap(result?.locations?.only)
428+
XCTAssertTrue(location.uri.pseudoPath.hasSuffix("generated.swift"))
429+
XCTAssertEqual(location.range.lowerBound, Position(line: 0, utf16index: 4))
430+
XCTAssertEqual(location.range.upperBound, Position(line: 0, utf16index: 4))
431+
}
362432
}

0 commit comments

Comments
 (0)