Skip to content

Commit 02ee070

Browse files
authored
Merge pull request #1981 from bnbarham/6.1-generated-source-should-work
[6.1] Use the new SwiftPM API to load the build plan
2 parents c19f78d + c065093 commit 02ee070

File tree

8 files changed

+415
-37
lines changed

8 files changed

+415
-37
lines changed

Documentation/Configuration File.md

+2-1
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.
@@ -38,7 +40,6 @@ The structure of the file is currently not guaranteed to be stable. Options may
3840
- `indexStorePath: string`: Directory in which a separate compilation stores the index store. By default, inferred from the build system.
3941
- `indexDatabasePath: string`: Directory in which the indexstore-db should be stored. By default, inferred from the build system.
4042
- `indexPrefixMap: [string: string]`: Path remappings for remapping index data for local use.
41-
- `maxCoresPercentageToUseForBackgroundIndexing: number`: A hint indicating how many cores background indexing should use at most (value between 0 and 1). Background indexing is not required to honor this setting.
4243
- `updateIndexStoreTimeout: integer`: Number of seconds to wait for an update index store task to finish before killing it.
4344
- `logging`: Options related to logging, changing SourceKit-LSP’s logging behavior on non-Apple platforms. On Apple platforms, logging is done through the [system log](Diagnose%20Bundle.md#Enable%20Extended%20Logging). These options can only be set globally and not per workspace.
4445
- `level: "debug"|"info"|"default"|"error"|"fault"`: The level from which one onwards log messages should be written.

SourceKitLSPDevUtils/Sources/ConfigSchemaGen/OptionSchema.swift

+3
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ struct OptionSchemaContext {
188188
let name = binding.pattern.trimmed.description
189189
let defaultValue = binding.initializer?.value.description
190190
let description = Self.extractDocComment(variable.leadingTrivia)
191+
if description?.contains("- Note: Internal option") ?? false {
192+
continue
193+
}
191194
let typeInfo = try resolveType(type.type)
192195
properties.append(
193196
.init(name: name, type: typeInfo, description: description, defaultValue: defaultValue)

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

+64-19
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,9 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
153153
private let toolchain: Toolchain
154154
private let swiftPMWorkspace: Workspace
155155

156+
private let pluginConfiguration: PluginConfiguration
157+
private let traitConfiguration: TraitConfiguration
158+
156159
/// A `ObservabilitySystem` from `SwiftPM` that logs.
157160
private let observabilitySystem: ObservabilitySystem
158161

@@ -192,13 +195,10 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
192195
) async throws {
193196
self.projectRoot = projectRoot
194197
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-
}
198+
// We could theoretically dynamically register all known files when we get back the build graph, but that seems
199+
// more errorprone than just watching everything and then filtering when we need to (eg. in
200+
// `SemanticIndexManager.filesDidChange`).
201+
self.fileWatchers = [FileSystemWatcher(globPattern: "**/*", kind: [.create, .change, .delete])]
202202
let toolchain = await toolchainRegistry.preferredToolchain(containing: [
203203
\.clang, \.clangd, \.sourcekitd, \.swift, \.swiftc,
204204
])
@@ -272,6 +272,7 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
272272
toolchain: hostSwiftPMToolchain,
273273
isManifestSandboxEnabled: !(options.swiftPMOrDefault.disableSandbox ?? false),
274274
cacheDir: location.sharedManifestsCacheDirectory,
275+
extraManifestFlags: options.swiftPMOrDefault.buildToolsSwiftCompilerFlags,
275276
importRestrictions: configuration.manifestImportRestrictions
276277
)
277278
)
@@ -313,6 +314,23 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
313314
prepareForIndexing: options.backgroundPreparationModeOrDefault.toSwiftPMPreparation
314315
)
315316

317+
let pluginScriptRunner = DefaultPluginScriptRunner(
318+
fileSystem: localFileSystem,
319+
cacheDir: location.pluginWorkingDirectory.appending("cache"),
320+
toolchain: hostSwiftPMToolchain,
321+
extraPluginSwiftCFlags: options.swiftPMOrDefault.buildToolsSwiftCompilerFlags ?? [],
322+
enableSandbox: !(options.swiftPMOrDefault.disableSandbox ?? false)
323+
)
324+
self.pluginConfiguration = PluginConfiguration(
325+
scriptRunner: pluginScriptRunner,
326+
workDirectory: location.pluginWorkingDirectory,
327+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false
328+
)
329+
330+
self.traitConfiguration = TraitConfiguration(
331+
enabledTraits: options.swiftPMOrDefault.traits.flatMap(Set.init)
332+
)
333+
316334
packageLoadingQueue.async {
317335
await orLog("Initial package loading") {
318336
// Schedule an initial generation of the build graph. Once the build graph is loaded, the build system will send
@@ -350,21 +368,44 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
350368
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Load package graph")
351369
)
352370

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
371+
// We have a whole separate arena if we're performing background indexing. This allows us to also build and run
372+
// plugins, without having to worry about messing up any regular build state.
373+
let buildDescription: SourceKitLSPAPI.BuildDescription
374+
if isForIndexBuild && !(options.swiftPMOrDefault.skipPlugins ?? false) {
375+
let loaded = try await BuildDescription.load(
376+
destinationBuildParameters: destinationBuildParameters,
377+
toolsBuildParameters: toolsBuildParameters,
378+
packageGraph: modulesGraph,
379+
pluginConfiguration: pluginConfiguration,
380+
traitConfiguration: traitConfiguration,
381+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
382+
scratchDirectory: swiftPMWorkspace.location.scratchDirectory.asURL,
383+
fileSystem: localFileSystem,
384+
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build description")
385+
)
386+
if !loaded.errors.isEmpty {
387+
logger.error("Loading SwiftPM description had errors: \(loaded.errors)")
388+
}
389+
390+
buildDescription = loaded.description
391+
} else {
392+
let plan = try await BuildPlan(
393+
destinationBuildParameters: destinationBuildParameters,
394+
toolsBuildParameters: toolsBuildParameters,
395+
graph: modulesGraph,
396+
disableSandbox: options.swiftPMOrDefault.disableSandbox ?? false,
397+
fileSystem: localFileSystem,
398+
observabilityScope: observabilitySystem.topScope.makeChildScope(description: "Create SwiftPM build plan")
399+
)
400+
401+
buildDescription = BuildDescription(buildPlan: plan)
402+
}
363403

364404
/// Make sure to execute any throwing statements before setting any
365405
/// properties because otherwise we might end up in an inconsistent state
366406
/// with only some properties modified.
367407

408+
self.buildDescription = buildDescription
368409
self.swiftPMTargets = [:]
369410
self.targetDependencies = [:]
370411

@@ -626,10 +667,14 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
626667
if let swiftSDK = options.swiftPMOrDefault.swiftSDK {
627668
arguments += ["--swift-sdk", swiftSDK]
628669
}
670+
if let traits = options.swiftPMOrDefault.traits {
671+
arguments += ["--traits", traits.joined(separator: ",")]
672+
}
629673
arguments += options.swiftPMOrDefault.cCompilerFlags?.flatMap { ["-Xcc", $0] } ?? []
630674
arguments += options.swiftPMOrDefault.cxxCompilerFlags?.flatMap { ["-Xcxx", $0] } ?? []
631675
arguments += options.swiftPMOrDefault.swiftCompilerFlags?.flatMap { ["-Xswiftc", $0] } ?? []
632676
arguments += options.swiftPMOrDefault.linkerFlags?.flatMap { ["-Xlinker", $0] } ?? []
677+
arguments += options.swiftPMOrDefault.buildToolsSwiftCompilerFlags?.flatMap { ["-Xbuild-tools-swiftc", $0] } ?? []
633678
switch options.backgroundPreparationModeOrDefault {
634679
case .build: break
635680
case .noLazy: arguments += ["--experimental-prepare-for-indexing", "--experimental-prepare-for-indexing-no-lazy"]

Sources/SKOptions/SourceKitLSPOptions.swift

+25-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
}
@@ -168,6 +189,8 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
168189
/// Path remappings for remapping index data for local use.
169190
public var indexPrefixMap: [String: String]?
170191
/// A hint indicating how many cores background indexing should use at most (value between 0 and 1). Background indexing is not required to honor this setting.
192+
///
193+
/// - Note: Internal option, may not work as intended
171194
public var maxCoresPercentageToUseForBackgroundIndexing: Double?
172195
/// Number of seconds to wait for an update index store task to finish before killing it.
173196
public var updateIndexStoreTimeout: Int?

Sources/SKTestSupport/SkipUnless.swift

+79
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

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