Skip to content

Commit 49d4db3

Browse files
authored
Merge pull request #974 from ahoppen/ahoppen/build-settings-for-workspace-folder
Add support for different arguments per workspace
2 parents 2f6a3d9 + 33f4612 commit 49d4db3

File tree

8 files changed

+168
-16
lines changed

8 files changed

+168
-16
lines changed

Sources/LanguageServerProtocol/SupportTypes/WorkspaceFolder.swift

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,53 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
/// The configuration to build a workspace in.
14+
///
15+
/// **(LSP Extension)**
16+
public enum BuildConfiguration: Hashable, Codable {
17+
case debug
18+
case release
19+
}
20+
21+
/// Build settings that should be used for a workspace.
22+
///
23+
/// **(LSP Extension)**
24+
public struct WorkspaceBuildSetup: Hashable, Codable {
25+
/// The configuration that the workspace should be built in.
26+
public let buildConfiguration: BuildConfiguration?
27+
28+
/// The build directory for the workspace.
29+
public let scratchPath: DocumentURI?
30+
31+
/// Arguments to be passed to any C compiler invocations.
32+
public let cFlags: [String]?
33+
34+
/// Arguments to be passed to any C++ compiler invocations.
35+
public let cxxFlags: [String]?
36+
37+
/// Arguments to be passed to any linker invocations.
38+
public let linkerFlags: [String]?
39+
40+
/// Arguments to be passed to any Swift compiler invocations.
41+
public let swiftFlags: [String]?
42+
43+
public init(
44+
buildConfiguration: BuildConfiguration? = nil,
45+
scratchPath: DocumentURI? = nil,
46+
cFlags: [String]? = nil,
47+
cxxFlags: [String]? = nil,
48+
linkerFlags: [String]? = nil,
49+
swiftFlags: [String]? = nil
50+
) {
51+
self.buildConfiguration = buildConfiguration
52+
self.scratchPath = scratchPath
53+
self.cFlags = cFlags
54+
self.cxxFlags = cxxFlags
55+
self.linkerFlags = linkerFlags
56+
self.swiftFlags = swiftFlags
57+
}
58+
}
59+
1360
/// Unique identifier for a document.
1461
public struct WorkspaceFolder: ResponseType, Hashable, Codable {
1562

@@ -19,13 +66,27 @@ public struct WorkspaceFolder: ResponseType, Hashable, Codable {
1966
/// The name of the workspace (default: basename of url).
2067
public var name: String
2168

22-
public init(uri: DocumentURI, name: String? = nil) {
69+
/// Build settings that should be used for this workspace.
70+
///
71+
/// For arguments that have a single value (like the build configuration), this takes precedence over the global
72+
/// options set when launching sourcekit-lsp. For all other options, the values specified in the workspace-specific
73+
/// build setup are appended to the global options.
74+
///
75+
/// **(LSP Extension)**
76+
public var buildSetup: WorkspaceBuildSetup?
77+
78+
public init(
79+
uri: DocumentURI,
80+
name: String? = nil,
81+
buildSetup: WorkspaceBuildSetup? = nil
82+
) {
2383
self.uri = uri
2484

2585
self.name = name ?? uri.fileURL?.lastPathComponent ?? "unknown_workspace"
2686

2787
if self.name.isEmpty {
2888
self.name = "unknown_workspace"
2989
}
90+
self.buildSetup = buildSetup
3091
}
3192
}

Sources/SKCore/BuildSetup.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,37 @@ public struct BuildSetup {
2020

2121
/// Default configuration
2222
public static let `default` = BuildSetup(
23-
configuration: .debug,
23+
configuration: nil,
2424
path: nil,
2525
flags: BuildFlags()
2626
)
2727

2828
/// Build configuration (debug|release).
29-
public var configuration: BuildConfiguration
29+
public var configuration: BuildConfiguration?
3030

31-
/// Build artefacts directory path. If nil, the build system may choose a default value.
31+
/// Build artifacts directory path. If nil, the build system may choose a default value.
3232
public var path: AbsolutePath?
3333

3434
/// Additional build flags
3535
public var flags: BuildFlags
3636

37-
public init(configuration: BuildConfiguration, path: AbsolutePath?, flags: BuildFlags) {
37+
public init(configuration: BuildConfiguration?, path: AbsolutePath?, flags: BuildFlags) {
3838
self.configuration = configuration
3939
self.path = path
4040
self.flags = flags
4141
}
42+
43+
/// Create a new `BuildSetup` merging this and `other`.
44+
///
45+
/// For any option that only takes a single value (like `configuration`), `other` takes precedence. For all array
46+
/// arguments, `other` is appended to the options provided by this setup.
47+
public func merging(_ other: BuildSetup) -> BuildSetup {
48+
var flags = self.flags
49+
flags = flags.merging(other.flags)
50+
return BuildSetup(
51+
configuration: other.configuration ?? self.configuration,
52+
path: other.path ?? self.path,
53+
flags: flags
54+
)
55+
}
4256
}

Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public actor SwiftPMWorkspace {
136136

137137
let buildConfiguration: PackageModel.BuildConfiguration
138138
switch buildSetup.configuration {
139-
case .debug:
139+
case .debug, nil:
140140
buildConfiguration = .debug
141141
case .release:
142142
buildConfiguration = .release

Sources/SKTestSupport/SwiftPMTestWorkspace.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class SwiftPMTestWorkspace: MultiFileTestWorkspace {
3838
public init(
3939
files: [RelativeFileLocation: String],
4040
manifest: String = SwiftPMTestWorkspace.defaultPackageManifest,
41+
workspaces: (URL) -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] },
4142
build: Bool = false,
4243
testName: String = #function
4344
) async throws {
@@ -56,6 +57,7 @@ public class SwiftPMTestWorkspace: MultiFileTestWorkspace {
5657
filesByPath["Package.swift"] = manifest
5758
try await super.init(
5859
files: filesByPath,
60+
workspaces: workspaces,
5961
testName: testName
6062
)
6163

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SKCore
2121
import SKSupport
2222
import SourceKitD
2323

24+
import struct PackageModel.BuildFlags
2425
import struct TSCBasic.AbsolutePath
2526
import protocol TSCBasic.FileSystem
2627
import var TSCBasic.localFileSystem
@@ -963,24 +964,57 @@ extension SourceKitServer: BuildSystemDelegate {
963964
}
964965
}
965966

967+
extension LanguageServerProtocol.BuildConfiguration {
968+
/// Convert `LanguageServerProtocol.BuildConfiguration` to `SKSupport.BuildConfiguration`.
969+
var configuration: SKSupport.BuildConfiguration {
970+
switch self {
971+
case .debug: return .debug
972+
case .release: return .release
973+
}
974+
}
975+
}
976+
966977
// MARK: - Request and notification handling
967978

968979
extension SourceKitServer {
969980

970981
// MARK: - General
971982

983+
/// Returns the build setup for the parameters specified for the given `WorkspaceFolder`.
984+
private func buildSetup(for workspaceFolder: WorkspaceFolder) -> BuildSetup {
985+
let buildParams = workspaceFolder.buildSetup
986+
let scratchPath: AbsolutePath?
987+
if let scratchPathParam = buildParams?.scratchPath {
988+
scratchPath = try? AbsolutePath(validating: scratchPathParam.pseudoPath)
989+
} else {
990+
scratchPath = nil
991+
}
992+
return SKCore.BuildSetup(
993+
configuration: buildParams?.buildConfiguration?.configuration,
994+
path: scratchPath,
995+
flags: BuildFlags(
996+
cCompilerFlags: buildParams?.cFlags ?? [],
997+
cxxCompilerFlags: buildParams?.cxxFlags ?? [],
998+
swiftCompilerFlags: buildParams?.swiftFlags ?? [],
999+
linkerFlags: buildParams?.linkerFlags ?? [],
1000+
xcbuildFlags: []
1001+
)
1002+
)
1003+
}
1004+
9721005
/// Creates a workspace at the given `uri`.
973-
private func createWorkspace(uri: DocumentURI) async -> Workspace? {
1006+
private func createWorkspace(_ workspaceFolder: WorkspaceFolder) async -> Workspace? {
9741007
guard let capabilityRegistry = capabilityRegistry else {
9751008
logger.log("Cannot open workspace before server is initialized")
9761009
return nil
9771010
}
1011+
let workspaceBuildSetup = self.buildSetup(for: workspaceFolder)
9781012
return try? await Workspace(
9791013
documentManager: self.documentManager,
980-
rootUri: uri,
1014+
rootUri: workspaceFolder.uri,
9811015
capabilityRegistry: capabilityRegistry,
9821016
toolchainRegistry: self.toolchainRegistry,
983-
buildSetup: self.options.buildSetup,
1017+
buildSetup: self.options.buildSetup.merging(workspaceBuildSetup),
9841018
compilationDatabaseSearchPaths: self.options.compilationDatabaseSearchPaths,
9851019
indexOptions: self.options.indexOptions,
9861020
reloadPackageStatusCallback: { status in
@@ -1020,13 +1054,15 @@ extension SourceKitServer {
10201054
capabilityRegistry = CapabilityRegistry(clientCapabilities: req.capabilities)
10211055

10221056
if let workspaceFolders = req.workspaceFolders {
1023-
self.workspaces += await workspaceFolders.asyncCompactMap { await self.createWorkspace(uri: $0.uri) }
1057+
self.workspaces += await workspaceFolders.asyncCompactMap { await self.createWorkspace($0) }
10241058
} else if let uri = req.rootURI {
1025-
if let workspace = await self.createWorkspace(uri: uri) {
1059+
let workspaceFolder = WorkspaceFolder(uri: uri)
1060+
if let workspace = await self.createWorkspace(workspaceFolder) {
10261061
self.workspaces.append(workspace)
10271062
}
10281063
} else if let path = req.rootPath {
1029-
if let workspace = await self.createWorkspace(uri: DocumentURI(URL(fileURLWithPath: path))) {
1064+
let workspaceFolder = WorkspaceFolder(uri: DocumentURI(URL(fileURLWithPath: path)))
1065+
if let workspace = await self.createWorkspace(workspaceFolder) {
10301066
self.workspaces.append(workspace)
10311067
}
10321068
}
@@ -1375,7 +1411,7 @@ extension SourceKitServer {
13751411
}
13761412
}
13771413
if let added = notification.event.added {
1378-
let newWorkspaces = await added.asyncCompactMap { await self.createWorkspace(uri: $0.uri) }
1414+
let newWorkspaces = await added.asyncCompactMap { await self.createWorkspace($0) }
13791415
for workspace in newWorkspaces {
13801416
await workspace.buildSystemManager.setDelegate(self)
13811417
}

Sources/sourcekit-lsp/SourceKitLSP.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ extension PathPrefixMapping: @retroactive ExpressibleByArgument {}
8787
#endif
8888

8989
#if swift(<5.10)
90-
extension BuildConfiguration: ExpressibleByArgument {}
90+
extension SKSupport.BuildConfiguration: ExpressibleByArgument {}
9191
#else
92-
extension BuildConfiguration: @retroactive ExpressibleByArgument {}
92+
extension SKSupport.BuildConfiguration: @retroactive ExpressibleByArgument {}
9393
#endif
9494

9595
@main

Tests/SKSwiftPMWorkspaceTests/SwiftPMWorkspaceTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,5 +739,5 @@ private func buildPath(
739739
platform: String
740740
) -> AbsolutePath {
741741
let buildPath = config.path ?? root.appending(component: ".build")
742-
return buildPath.appending(components: platform, "\(config.configuration)")
742+
return buildPath.appending(components: platform, "\(config.configuration ?? .debug)")
743743
}

Tests/SourceKitLSPTests/WorkspaceTests.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,43 @@ final class WorkspaceTests: XCTestCase {
474474
]
475475
)
476476
}
477+
478+
public func testWorkspaceSpecificBuildSettings() async throws {
479+
let ws = try await SwiftPMTestWorkspace(
480+
files: [
481+
"test.swift": """
482+
#if MY_FLAG
483+
let a: Int = ""
484+
#endif
485+
"""
486+
],
487+
workspaces: {
488+
[
489+
WorkspaceFolder(
490+
uri: DocumentURI($0),
491+
buildSetup: WorkspaceBuildSetup(
492+
buildConfiguration: nil,
493+
scratchPath: nil,
494+
cFlags: nil,
495+
cxxFlags: nil,
496+
linkerFlags: nil,
497+
swiftFlags: ["-DMY_FLAG"]
498+
)
499+
)
500+
]
501+
}
502+
)
503+
504+
_ = try ws.openDocument("test.swift")
505+
let report = try await ws.testClient.send(
506+
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(ws.uri(for: "test.swift")))
507+
)
508+
guard case .full(let fullReport) = report else {
509+
XCTFail("Expected full diagnostics report")
510+
return
511+
}
512+
XCTAssertEqual(fullReport.items.count, 1)
513+
let diag = try XCTUnwrap(fullReport.items.first)
514+
XCTAssertEqual(diag.message, "Cannot convert value of type 'String' to specified type 'Int'")
515+
}
477516
}

0 commit comments

Comments
 (0)