Skip to content

Commit e865120

Browse files
authored
Merge pull request #1973 from bnbarham/generated-source-should-work
Use the new SwiftPM API to load the build plan
2 parents bac0acc + a534385 commit e865120

File tree

7 files changed

+411
-31
lines changed

7 files changed

+411
-31
lines changed

Documentation/Configuration File.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ The structure of the file is currently not guaranteed to be stable. Options may
2020
- `swiftSDKsDirectory: string`: Equivalent to SwiftPM's `--swift-sdks-path` option.
2121
- `swiftSDK: string`: Equivalent to SwiftPM's `--swift-sdk` option.
2222
- `triple: string`: Equivalent to SwiftPM's `--triple` option.
23+
- `traits: string[]`: Traits to enable for the package. Equivalent to SwiftPM's `--traits` option.
2324
- `cCompilerFlags: string[]`: Extra arguments passed to the compiler for C files. Equivalent to SwiftPM's `-Xcc` option.
2425
- `cxxCompilerFlags: string[]`: Extra arguments passed to the compiler for C++ files. Equivalent to SwiftPM's `-Xcxx` option.
2526
- `swiftCompilerFlags: string[]`: Extra arguments passed to the compiler for Swift files. Equivalent to SwiftPM's `-Xswiftc` option.
2627
- `linkerFlags: string[]`: Extra arguments passed to the linker. Equivalent to SwiftPM's `-Xlinker` option.
28+
- `buildToolsSwiftCompilerFlags: string[]`: Extra arguments passed to the compiler for Swift files or plugins. Equivalent to SwiftPM's `-Xbuild-tools-swiftc` option.
2729
- `disableSandbox: boolean`: Disables running subprocesses from SwiftPM in a sandbox. Equivalent to SwiftPM's `--disable-sandbox` option. Useful when running `sourcekit-lsp` in a sandbox because nested sandboxes are not supported.
2830
- `compilationDatabase`: Dictionary with the following keys, defining options for workspaces with a compilation database.
2931
- `searchPaths: string[]`: Additional paths to search for a compilation database, relative to a workspace root.

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

+66-20
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,9 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
131131
private let toolchain: Toolchain
132132
private let swiftPMWorkspace: Workspace
133133

134+
private let pluginConfiguration: PluginConfiguration
135+
private let traitConfiguration: TraitConfiguration
136+
134137
/// A `ObservabilitySystem` from `SwiftPM` that logs.
135138
private let observabilitySystem: ObservabilitySystem
136139

@@ -170,13 +173,10 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
170173
) async throws {
171174
self.projectRoot = projectRoot
172175
self.options = options
173-
self.fileWatchers =
174-
try ["Package.swift", "Package@swift*.swift", "Package.resolved"].map {
175-
FileSystemWatcher(globPattern: try projectRoot.appendingPathComponent($0).filePath, kind: [.change])
176-
}
177-
+ FileRuleDescription.builtinRules.flatMap({ $0.fileTypes }).map { fileExtension in
178-
FileSystemWatcher(globPattern: "**/*.\(fileExtension)", kind: [.create, .change, .delete])
179-
}
176+
// We could theoretically dynamically register all known files when we get back the build graph, but that seems
177+
// more errorprone than just watching everything and then filtering when we need to (eg. in
178+
// `SemanticIndexManager.filesDidChange`).
179+
self.fileWatchers = [FileSystemWatcher(globPattern: "**/*", kind: [.create, .change, .delete])]
180180
let toolchain = await toolchainRegistry.preferredToolchain(containing: [
181181
\.clang, \.clangd, \.sourcekitd, \.swift, \.swiftc,
182182
])
@@ -250,6 +250,7 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
250250
toolchain: hostSwiftPMToolchain,
251251
isManifestSandboxEnabled: !(options.swiftPMOrDefault.disableSandbox ?? false),
252252
cacheDir: location.sharedManifestsCacheDirectory,
253+
extraManifestFlags: options.swiftPMOrDefault.buildToolsSwiftCompilerFlags,
253254
importRestrictions: configuration.manifestImportRestrictions
254255
)
255256
)
@@ -291,6 +292,23 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
291292
prepareForIndexing: options.backgroundPreparationModeOrDefault.toSwiftPMPreparation
292293
)
293294

295+
let pluginScriptRunner = DefaultPluginScriptRunner(
296+
fileSystem: localFileSystem,
297+
cacheDir: location.pluginWorkingDirectory.appending("cache"),
298+
toolchain: hostSwiftPMToolchain,
299+
extraPluginSwiftCFlags: options.swiftPMOrDefault.buildToolsSwiftCompilerFlags ?? [],
300+
enableSandbox: !(options.swiftPMOrDefault.disableSandbox ?? false)
301+
)
302+
self.pluginConfiguration = PluginConfiguration(
303+
scriptRunner: pluginScriptRunner,
304+
workDirectory: location.pluginWorkingDirectory,
305+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false
306+
)
307+
308+
self.traitConfiguration = TraitConfiguration(
309+
enabledTraits: options.swiftPMOrDefault.traits.flatMap(Set.init)
310+
)
311+
294312
packageLoadingQueue.async {
295313
await orLog("Initial package loading") {
296314
// Schedule an initial generation of the build graph. Once the build graph is loaded, the build system will send
@@ -334,24 +352,48 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
334352

335353
signposter.emitEvent("Finished loading modules graph", id: signpostID)
336354

337-
let plan = try await BuildPlan(
338-
destinationBuildParameters: destinationBuildParameters,
339-
toolsBuildParameters: toolsBuildParameters,
340-
graph: modulesGraph,
341-
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
342-
fileSystem: localFileSystem,
343-
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build plan")
344-
)
355+
// We have a whole separate arena if we're performing background indexing. This allows us to also build and run
356+
// plugins, without having to worry about messing up any regular build state.
357+
let buildDescription: SourceKitLSPAPI.BuildDescription
358+
if isForIndexBuild && !(options.swiftPMOrDefault.skipPlugins ?? false) {
359+
let loaded = try await BuildDescription.load(
360+
destinationBuildParameters: destinationBuildParameters,
361+
toolsBuildParameters: toolsBuildParameters,
362+
packageGraph: modulesGraph,
363+
pluginConfiguration: pluginConfiguration,
364+
traitConfiguration: traitConfiguration,
365+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
366+
scratchDirectory: swiftPMWorkspace.location.scratchDirectory.asURL,
367+
fileSystem: localFileSystem,
368+
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build description")
369+
)
370+
if !loaded.errors.isEmpty {
371+
logger.error("Loading SwiftPM description had errors: \(loaded.errors)")
372+
}
345373

346-
signposter.emitEvent("Finished generating build plan", id: signpostID)
374+
signposter.emitEvent("Finished generating build description", id: signpostID)
347375

348-
let buildDescription = BuildDescription(buildPlan: plan)
349-
self.buildDescription = buildDescription
376+
buildDescription = loaded.description
377+
} else {
378+
let plan = try await BuildPlan(
379+
destinationBuildParameters: destinationBuildParameters,
380+
toolsBuildParameters: toolsBuildParameters,
381+
graph: modulesGraph,
382+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
383+
fileSystem: localFileSystem,
384+
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build plan")
385+
)
386+
387+
signposter.emitEvent("Finished generating build plan", id: signpostID)
388+
389+
buildDescription = BuildDescription(buildPlan: plan)
390+
}
350391

351392
/// Make sure to execute any throwing statements before setting any
352393
/// properties because otherwise we might end up in an inconsistent state
353394
/// with only some properties modified.
354395

396+
self.buildDescription = buildDescription
355397
self.swiftPMTargets = [:]
356398
self.targetDependencies = [:]
357399

@@ -600,10 +642,14 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
600642
if let swiftSDK = options.swiftPMOrDefault.swiftSDK {
601643
arguments += ["--swift-sdk", swiftSDK]
602644
}
645+
if let traits = options.swiftPMOrDefault.traits {
646+
arguments += ["--traits", traits.joined(separator: ",")]
647+
}
603648
arguments += options.swiftPMOrDefault.cCompilerFlags?.flatMap { ["-Xcc", $0] } ?? []
604649
arguments += options.swiftPMOrDefault.cxxCompilerFlags?.flatMap { ["-Xcxx", $0] } ?? []
605650
arguments += options.swiftPMOrDefault.swiftCompilerFlags?.flatMap { ["-Xswiftc", $0] } ?? []
606651
arguments += options.swiftPMOrDefault.linkerFlags?.flatMap { ["-Xlinker", $0] } ?? []
652+
arguments += options.swiftPMOrDefault.buildToolsSwiftCompilerFlags?.flatMap { ["-Xbuild-tools-swiftc", $0] } ?? []
607653
switch options.backgroundPreparationModeOrDefault {
608654
case .build: break
609655
case .noLazy: arguments += ["--experimental-prepare-for-indexing", "--experimental-prepare-for-indexing-no-lazy"]

Sources/SKOptions/SourceKitLSPOptions.swift

+23-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
5353
/// Equivalent to SwiftPM's `--triple` option.
5454
public var triple: String?
5555

56+
/// Traits to enable for the package. Equivalent to SwiftPM's `--traits` option.
57+
public var traits: [String]?
58+
5659
/// Extra arguments passed to the compiler for C files. Equivalent to SwiftPM's `-Xcc` option.
5760
public var cCompilerFlags: [String]?
5861

@@ -65,31 +68,46 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
6568
/// Extra arguments passed to the linker. Equivalent to SwiftPM's `-Xlinker` option.
6669
public var linkerFlags: [String]?
6770

71+
/// Extra arguments passed to the compiler for Swift files or plugins. Equivalent to SwiftPM's
72+
/// `-Xbuild-tools-swiftc` option.
73+
public var buildToolsSwiftCompilerFlags: [String]?
74+
6875
/// Disables running subprocesses from SwiftPM in a sandbox. Equivalent to SwiftPM's `--disable-sandbox` option.
6976
/// Useful when running `sourcekit-lsp` in a sandbox because nested sandboxes are not supported.
7077
public var disableSandbox: Bool?
7178

79+
/// Whether to skip building and running plugins when creating the in-memory build graph.
80+
///
81+
/// - Note: Internal option, only exists as an escape hatch in case this causes unintentional interactions with
82+
/// background indexing.
83+
public var skipPlugins: Bool?
84+
7285
public init(
7386
configuration: BuildConfiguration? = nil,
7487
scratchPath: String? = nil,
7588
swiftSDKsDirectory: String? = nil,
7689
swiftSDK: String? = nil,
7790
triple: String? = nil,
91+
traits: [String]? = nil,
7892
cCompilerFlags: [String]? = nil,
7993
cxxCompilerFlags: [String]? = nil,
8094
swiftCompilerFlags: [String]? = nil,
8195
linkerFlags: [String]? = nil,
82-
disableSandbox: Bool? = nil
96+
buildToolsSwiftCompilerFlags: [String]? = nil,
97+
disableSandbox: Bool? = nil,
98+
skipPlugins: Bool? = nil
8399
) {
84100
self.configuration = configuration
85101
self.scratchPath = scratchPath
86102
self.swiftSDKsDirectory = swiftSDKsDirectory
87103
self.swiftSDK = swiftSDK
88104
self.triple = triple
105+
self.traits = traits
89106
self.cCompilerFlags = cCompilerFlags
90107
self.cxxCompilerFlags = cxxCompilerFlags
91108
self.swiftCompilerFlags = swiftCompilerFlags
92109
self.linkerFlags = linkerFlags
110+
self.buildToolsSwiftCompilerFlags = buildToolsSwiftCompilerFlags
93111
self.disableSandbox = disableSandbox
94112
}
95113

@@ -100,11 +118,14 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
100118
swiftSDKsDirectory: override?.swiftSDKsDirectory ?? base.swiftSDKsDirectory,
101119
swiftSDK: override?.swiftSDK ?? base.swiftSDK,
102120
triple: override?.triple ?? base.triple,
121+
traits: override?.traits ?? base.traits,
103122
cCompilerFlags: override?.cCompilerFlags ?? base.cCompilerFlags,
104123
cxxCompilerFlags: override?.cxxCompilerFlags ?? base.cxxCompilerFlags,
105124
swiftCompilerFlags: override?.swiftCompilerFlags ?? base.swiftCompilerFlags,
106125
linkerFlags: override?.linkerFlags ?? base.linkerFlags,
107-
disableSandbox: override?.disableSandbox ?? base.disableSandbox
126+
buildToolsSwiftCompilerFlags: override?.buildToolsSwiftCompilerFlags ?? base.buildToolsSwiftCompilerFlags,
127+
disableSandbox: override?.disableSandbox ?? base.disableSandbox,
128+
skipPlugins: override?.skipPlugins ?? base.skipPlugins
108129
)
109130
}
110131
}

Sources/SKTestSupport/SkipUnless.swift

+79
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,85 @@ package actor SkipUnless {
270270
}
271271
}
272272
}
273+
274+
package static func canLoadPluginsBuiltByToolchain(
275+
file: StaticString = #filePath,
276+
line: UInt = #line
277+
) async throws {
278+
return try await shared.skipUnlessSupported(file: file, line: line) {
279+
let project = try await SwiftPMTestProject(
280+
files: [
281+
"Plugins/plugin.swift": #"""
282+
import Foundation
283+
import PackagePlugin
284+
@main struct CodeGeneratorPlugin: BuildToolPlugin {
285+
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
286+
let genSourcesDir = context.pluginWorkDirectoryURL.appending(path: "GeneratedSources")
287+
guard let target = target as? SourceModuleTarget else { return [] }
288+
let codeGenerator = try context.tool(named: "CodeGenerator").url
289+
let generatedFile = genSourcesDir.appending(path: "\(target.name)-generated.swift")
290+
return [.buildCommand(
291+
displayName: "Generating code for \(target.name)",
292+
executable: codeGenerator,
293+
arguments: [
294+
generatedFile.path
295+
],
296+
inputFiles: [],
297+
outputFiles: [generatedFile]
298+
)]
299+
}
300+
}
301+
"""#,
302+
303+
"Sources/CodeGenerator/CodeGenerator.swift": #"""
304+
import Foundation
305+
try "let foo = 1".write(
306+
to: URL(fileURLWithPath: CommandLine.arguments[1]),
307+
atomically: true,
308+
encoding: String.Encoding.utf8
309+
)
310+
"""#,
311+
312+
"Sources/TestLib/TestLib.swift": #"""
313+
func useGenerated() {
314+
_ = 1️⃣foo
315+
}
316+
"""#,
317+
],
318+
manifest: """
319+
// swift-tools-version: 6.0
320+
import PackageDescription
321+
let package = Package(
322+
name: "PluginTest",
323+
targets: [
324+
.executableTarget(name: "CodeGenerator"),
325+
.target(
326+
name: "TestLib",
327+
plugins: [.plugin(name: "CodeGeneratorPlugin")]
328+
),
329+
.plugin(
330+
name: "CodeGeneratorPlugin",
331+
capability: .buildTool(),
332+
dependencies: ["CodeGenerator"]
333+
),
334+
]
335+
)
336+
""",
337+
enableBackgroundIndexing: true
338+
)
339+
340+
let (uri, positions) = try project.openDocument("TestLib.swift")
341+
342+
let result = try await project.testClient.send(
343+
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
344+
)
345+
346+
if result?.locations?.only == nil {
347+
return .featureUnsupported(skipMessage: "Skipping because plugin protocols do not match.")
348+
}
349+
return .featureSupported
350+
}
351+
}
273352
}
274353

275354
// MARK: - Parsing Swift compiler version

Sources/SemanticIndex/SemanticIndexManager.swift

+24-9
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)