Skip to content

Commit 165b4bd

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. (cherry picked from commit f525da5)
1 parent c19f78d commit 165b4bd

File tree

4 files changed

+363
-28
lines changed

4 files changed

+363
-28
lines changed

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

Lines changed: 59 additions & 19 deletions
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
@@ -153,6 +153,8 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
153153
private let toolchain: Toolchain
154154
private let swiftPMWorkspace: Workspace
155155

156+
private let pluginConfiguration: PluginConfiguration
157+
156158
/// A `ObservabilitySystem` from `SwiftPM` that logs.
157159
private let observabilitySystem: ObservabilitySystem
158160

@@ -192,13 +194,10 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
192194
) async throws {
193195
self.projectRoot = projectRoot
194196
self.options = options
195-
self.fileWatchers =
196-
try ["Package.swift", "Package@swift*.swift", "Package.resolved"].map {
197-
FileSystemWatcher(globPattern: try projectRoot.appendingPathComponent($0).filePath, kind: [.change])
198-
}
199-
+ FileRuleDescription.builtinRules.flatMap({ $0.fileTypes }).map { fileExtension in
200-
FileSystemWatcher(globPattern: "**/*.\(fileExtension)", kind: [.create, .change, .delete])
201-
}
197+
// We could theoretically dynamically register all known files when we get back the build graph, but that seems
198+
// more errorprone than just watching everything and then filtering when we need to (eg. in
199+
// `SemanticIndexManager.filesDidChange`).
200+
self.fileWatchers = [FileSystemWatcher(globPattern: "**/*", kind: [.create, .change, .delete])]
202201
let toolchain = await toolchainRegistry.preferredToolchain(containing: [
203202
\.clang, \.clangd, \.sourcekitd, \.swift, \.swiftc,
204203
])
@@ -313,6 +312,19 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
313312
prepareForIndexing: options.backgroundPreparationModeOrDefault.toSwiftPMPreparation
314313
)
315314

315+
let pluginScriptRunner = DefaultPluginScriptRunner(
316+
fileSystem: localFileSystem,
317+
cacheDir: location.pluginWorkingDirectory.appending("cache"),
318+
toolchain: hostSwiftPMToolchain,
319+
extraPluginSwiftCFlags: [],
320+
enableSandbox: !(options.swiftPMOrDefault.disableSandbox ?? false)
321+
)
322+
self.pluginConfiguration = PluginConfiguration(
323+
scriptRunner: pluginScriptRunner,
324+
workDirectory: location.pluginWorkingDirectory,
325+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false
326+
)
327+
316328
packageLoadingQueue.async {
317329
await orLog("Initial package loading") {
318330
// Schedule an initial generation of the build graph. Once the build graph is loaded, the build system will send
@@ -350,21 +362,49 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
350362
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Load package graph")
351363
)
352364

353-
let plan = try await BuildPlan(
354-
destinationBuildParameters: destinationBuildParameters,
355-
toolsBuildParameters: toolsBuildParameters,
356-
graph: modulesGraph,
357-
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
358-
fileSystem: localFileSystem,
359-
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build plan")
360-
)
361-
let buildDescription = BuildDescription(buildPlan: plan)
362-
self.buildDescription = buildDescription
365+
signposter.emitEvent("Finished loading modules graph", id: signpostID)
366+
367+
// We have a whole separate arena if we're performing background indexing. This allows us to also build and run
368+
// plugins, without having to worry about messing up any regular build state.
369+
let buildDescription: SourceKitLSPAPI.BuildDescription
370+
if isForIndexBuild {
371+
let loaded = try await BuildDescription.load(
372+
destinationBuildParameters: destinationBuildParameters,
373+
toolsBuildParameters: toolsBuildParameters,
374+
packageGraph: modulesGraph,
375+
pluginConfiguration: pluginConfiguration,
376+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
377+
scratchDirectory: swiftPMWorkspace.location.scratchDirectory.asURL,
378+
fileSystem: localFileSystem,
379+
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build description")
380+
)
381+
if !loaded.errors.isEmpty {
382+
logger.error("Loading SwiftPM description had errors: \(loaded.errors)")
383+
}
384+
385+
signposter.emitEvent("Finished generating build description", id: signpostID)
386+
387+
buildDescription = loaded.description
388+
} else {
389+
let plan = try await BuildPlan(
390+
destinationBuildParameters: destinationBuildParameters,
391+
toolsBuildParameters: toolsBuildParameters,
392+
graph: modulesGraph,
393+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
394+
fileSystem: localFileSystem,
395+
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build plan")
396+
)
397+
398+
signposter.emitEvent("Finished generating build plan", id: signpostID)
399+
400+
buildDescription = BuildDescription(buildPlan: plan)
401+
}
363402

364403
/// Make sure to execute any throwing statements before setting any
365404
/// properties because otherwise we might end up in an inconsistent state
366405
/// with only some properties modified.
367406

407+
self.buildDescription = buildDescription
368408
self.swiftPMTargets = [:]
369409
self.targetDependencies = [:]
370410

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,85 @@ package actor SkipUnless {
565565
return locations.count > 0
566566
}
567567
}
568+
569+
package static func canLoadPluginsBuiltByToolchain(
570+
file: StaticString = #filePath,
571+
line: UInt = #line
572+
) async throws {
573+
return try await shared.skipUnlessSupported(file: file, line: line) {
574+
let project = try await SwiftPMTestProject(
575+
files: [
576+
"Plugins/plugin.swift": #"""
577+
import Foundation
578+
import PackagePlugin
579+
@main struct CodeGeneratorPlugin: BuildToolPlugin {
580+
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
581+
let genSourcesDir = context.pluginWorkDirectoryURL.appending(path: "GeneratedSources")
582+
guard let target = target as? SourceModuleTarget else { return [] }
583+
let codeGenerator = try context.tool(named: "CodeGenerator").url
584+
let generatedFile = genSourcesDir.appending(path: "\(target.name)-generated.swift")
585+
return [.buildCommand(
586+
displayName: "Generating code for \(target.name)",
587+
executable: codeGenerator,
588+
arguments: [
589+
generatedFile.path
590+
],
591+
inputFiles: [],
592+
outputFiles: [generatedFile]
593+
)]
594+
}
595+
}
596+
"""#,
597+
598+
"Sources/CodeGenerator/CodeGenerator.swift": #"""
599+
import Foundation
600+
try "let foo = 1".write(
601+
to: URL(fileURLWithPath: CommandLine.arguments[1]),
602+
atomically: true,
603+
encoding: String.Encoding.utf8
604+
)
605+
"""#,
606+
607+
"Sources/TestLib/TestLib.swift": #"""
608+
func useGenerated() {
609+
_ = 1️⃣foo
610+
}
611+
"""#,
612+
],
613+
manifest: """
614+
// swift-tools-version: 6.0
615+
import PackageDescription
616+
let package = Package(
617+
name: "PluginTest",
618+
targets: [
619+
.executableTarget(name: "CodeGenerator"),
620+
.target(
621+
name: "TestLib",
622+
plugins: [.plugin(name: "CodeGeneratorPlugin")]
623+
),
624+
.plugin(
625+
name: "CodeGeneratorPlugin",
626+
capability: .buildTool(),
627+
dependencies: ["CodeGenerator"]
628+
),
629+
]
630+
)
631+
""",
632+
enableBackgroundIndexing: true
633+
)
634+
635+
let (uri, positions) = try project.openDocument("TestLib.swift")
636+
637+
let result = try await project.testClient.send(
638+
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
639+
)
640+
641+
if result?.locations?.only == nil {
642+
return .featureUnsupported(skipMessage: "Skipping because plugin protocols do not match.")
643+
}
644+
return .featureSupported
645+
}
646+
}
568647
}
569648

570649
// MARK: - Parsing Swift compiler version

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import LanguageServerProtocolExtensions
14+
1315
#if compiler(>=6)
1416
package import BuildServerProtocol
1517
package import BuildSystemIntegration
@@ -385,15 +387,28 @@ package final actor SemanticIndexManager {
385387
let changedFiles = events.map(\.uri)
386388
await indexStoreUpToDateTracker.markOutOfDate(changedFiles)
387389

388-
let targets = await changedFiles.asyncMap { await buildSystemManager.targets(for: $0) }.flatMap { $0 }
389-
let dependentTargets = await buildSystemManager.targets(dependingOn: Set(targets))
390-
logger.info(
391-
"""
392-
Marking targets as out-of-date: \
393-
\(String(dependentTargets.map(\.uri.stringValue).joined(separator: ", ")))
394-
"""
395-
)
396-
await preparationUpToDateTracker.markOutOfDate(dependentTargets)
390+
// Preparation tracking should be per file. For now consider any non-known-language change as having to re-prepare
391+
// the target itself so that we re-prepare potential input files to plugins.
392+
// https://github.com/swiftlang/sourcekit-lsp/issues/1975
393+
var outOfDateTargets = Set<BuildTargetIdentifier>()
394+
for file in changedFiles {
395+
let changedTargets = await buildSystemManager.targets(for: file)
396+
if Language(inferredFromFileExtension: file) == nil {
397+
outOfDateTargets.formUnion(changedTargets)
398+
}
399+
400+
let dependentTargets = await buildSystemManager.targets(dependingOn: changedTargets)
401+
outOfDateTargets.formUnion(dependentTargets)
402+
}
403+
if !outOfDateTargets.isEmpty {
404+
logger.info(
405+
"""
406+
Marking dependent targets as out-of-date: \
407+
\(String(outOfDateTargets.map(\.uri.stringValue).joined(separator: ", ")))
408+
"""
409+
)
410+
await preparationUpToDateTracker.markOutOfDate(outOfDateTargets)
411+
}
397412

398413
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
399414
filesToIndex: changedFiles,

0 commit comments

Comments
 (0)