Skip to content

Commit 5202a8f

Browse files
committed
Add test cases that launch a SourceKitLSP server using a BSP server
1 parent 813a10e commit 5202a8f

File tree

11 files changed

+239
-28
lines changed

11 files changed

+239
-28
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Package.resolved
99
/*.sublime-workspace
1010
/.swiftpm
1111
.*.sw?
12+
__pycache__

Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ private func createBuildSystem(
4848
) async -> BuiltInBuildSystem? {
4949
switch buildSystemKind {
5050
case .buildServer(let projectRoot):
51-
return await BuildServerBuildSystem(projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
51+
return await LegacyBuildServerBuildSystem(
52+
projectRoot: projectRoot,
53+
connectionToSourceKitLSP: connectionToSourceKitLSP
54+
)
5255
case .compilationDatabase(let projectRoot):
5356
return CompilationDatabaseBuildSystem(
5457
projectRoot: projectRoot,

Sources/BuildSystemIntegration/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
add_library(BuildSystemIntegration STATIC
3-
BuildServerBuildSystem.swift
43
BuildSettingsLogger.swift
54
BuildSystemManager.swift
65
BuildSystemManagerDelegate.swift
@@ -14,6 +13,7 @@ add_library(BuildSystemIntegration STATIC
1413
FallbackBuildSettings.swift
1514
FileBuildSettings.swift
1615
Language+InferredFromFileExtension.swift
16+
LegacyBuildServerBuildSystem.swift
1717
MainFilesProvider.swift
1818
PathPrefixMapping.swift
1919
PrefixMessageWithTaskEmoji.swift

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift renamed to Sources/BuildSystemIntegration/LegacyBuildServerBuildSystem.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,15 @@ func executable(_ name: String) -> String {
4141
#endif
4242
}
4343

44-
/// A `BuildSystem` based on communicating with a build server
44+
#if compiler(>=6.3)
45+
#warning("We have had a one year transition period to the pull based build server. Consider removing this build server")
46+
#endif
47+
48+
/// A `BuildSystem` based on communicating with a build server using the old push-based settings model.
4549
///
46-
/// Provides build settings from a build server launched based on a
47-
/// `buildServer.json` configuration file provided in the repo root.
48-
package actor BuildServerBuildSystem: MessageHandler {
50+
/// This build server should be phased out in favor of the pull-based settings model described in
51+
/// https://forums.swift.org/t/extending-functionality-of-build-server-protocol-with-sourcekit-lsp/74400
52+
package actor LegacyBuildServerBuildSystem: MessageHandler {
4953
package let projectRoot: AbsolutePath
5054
let serverConfig: BuildServerConfig
5155

@@ -252,7 +256,7 @@ private func readResponseDataKey(data: LSPAny?, key: String) -> String? {
252256
return nil
253257
}
254258

255-
extension BuildServerBuildSystem: BuiltInBuildSystem {
259+
extension LegacyBuildServerBuildSystem: BuiltInBuildSystem {
256260
static package func projectRoot(for workspaceFolder: AbsolutePath, options: SourceKitLSPOptions) -> AbsolutePath? {
257261
guard localFileSystem.isFile(workspaceFolder.appending(component: "buildServer.json")) else {
258262
return nil
@@ -267,7 +271,7 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
267271
return WorkspaceBuildTargetsResponse(targets: [
268272
BuildTarget(
269273
id: .dummy,
270-
displayName: "Compilation database",
274+
displayName: "BuildServer",
271275
baseDirectory: nil,
272276
tags: [.test],
273277
capabilities: BuildTargetCapabilities(),
@@ -279,10 +283,18 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
279283
}
280284

281285
package func buildTargetSources(request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse {
286+
guard request.targets.contains(.dummy) else {
287+
return BuildTargetSourcesResponse(items: [])
288+
}
282289
// BuildServerBuildSystem does not support syntactic test discovery or background indexing.
283290
// (https://github.com/swiftlang/sourcekit-lsp/issues/1173).
284291
// TODO: (BSP migration) Forward this request to the BSP server
285-
return BuildTargetSourcesResponse(items: [])
292+
return BuildTargetSourcesResponse(items: [
293+
SourcesItem(
294+
target: .dummy,
295+
sources: [SourceItem(uri: DocumentURI(self.projectRoot.asURL), kind: .directory, generated: false)]
296+
)
297+
])
286298
}
287299

288300
package func didChangeWatchedFiles(notification: OnWatchedFilesDidChangeNotification) {}
@@ -304,6 +316,7 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
304316
// which renders this code path dead.
305317
let uri = request.textDocument.uri
306318
if !urisRegisteredForChanges.contains(uri) {
319+
urisRegisteredForChanges.insert(uri)
307320
let request = RegisterForChanges(uri: uri, action: .register)
308321
_ = self.buildServer?.send(request) { result in
309322
if let error = result.failure {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import ISDBTestSupport
15+
import XCTest
16+
17+
/// The path to the INPUTS directory of shared test projects.
18+
private let skTestSupportInputsDirectory: URL = {
19+
#if os(macOS)
20+
var resources =
21+
productsDirectory
22+
.appendingPathComponent("SourceKitLSP_SKTestSupport.bundle")
23+
.appendingPathComponent("Contents")
24+
.appendingPathComponent("Resources")
25+
if !FileManager.default.fileExists(atPath: resources.path) {
26+
// Xcode and command-line swiftpm differ about the path.
27+
resources.deleteLastPathComponent()
28+
resources.deleteLastPathComponent()
29+
}
30+
#else
31+
let resources = XCTestCase.productsDirectory
32+
.appendingPathComponent("SourceKitLSP_SKTestSupport.resources")
33+
#endif
34+
guard FileManager.default.fileExists(atPath: resources.path) else {
35+
fatalError("missing resources \(resources.path)")
36+
}
37+
return resources.appendingPathComponent("INPUTS", isDirectory: true).standardizedFileURL
38+
}()
39+
40+
/// Creates a project that uses a BSP server to provide build settings.
41+
///
42+
/// The build server is implemented in Python on top of the code in `AbstractBuildServer.py`.
43+
package class BuildServerTestProject: MultiFileTestProject {
44+
package init(files: [RelativeFileLocation: String], buildServer: String) async throws {
45+
var files = files
46+
files["buildServer.json"] = """
47+
{
48+
"name": "client name",
49+
"version": "10",
50+
"bspVersion": "2.0",
51+
"languages": ["a", "b"],
52+
"argv": ["server.py"]
53+
}
54+
"""
55+
files["server.py"] = """
56+
import sys
57+
from typing import Dict, List, Optional
58+
59+
sys.path.append("\(skTestSupportInputsDirectory.path)")
60+
61+
from AbstractBuildServer import AbstractBuildServer
62+
63+
\(buildServer)
64+
65+
BuildServer().run()
66+
"""
67+
try await super.init(files: files)
68+
}
69+
}

Sources/SKTestSupport/INPUTS/AbstractBuildServer.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
import sys
3-
from typing import Optional
3+
from typing import Dict, List, Optional
44

55

66
class RequestError(Exception):
@@ -39,14 +39,14 @@ def run(self):
3939
try:
4040
result = self.handle_message(message)
4141
if result:
42-
response_message: dict[str, object] = {
42+
response_message: Dict[str, object] = {
4343
"jsonrpc": "2.0",
4444
"id": message["id"],
4545
"result": result,
4646
}
4747
self.send_raw_message(response_message)
4848
except RequestError as e:
49-
error_response_message: dict[str, object] = {
49+
error_response_message: Dict[str, object] = {
5050
"jsonrpc": "2.0",
5151
"id": message["id"],
5252
"error": {
@@ -56,12 +56,12 @@ def run(self):
5656
}
5757
self.send_raw_message(error_response_message)
5858

59-
def handle_message(self, message: dict[str, object]) -> Optional[dict[str, object]]:
59+
def handle_message(self, message: Dict[str, object]) -> Optional[Dict[str, object]]:
6060
"""
6161
Dispatch handling of the given method, received from SourceKit-LSP to the message handling function.
6262
"""
6363
method: str = str(message["method"])
64-
params: dict[str, object] = message["params"] # type: ignore
64+
params: Dict[str, object] = message["params"] # type: ignore
6565
if method == "build/exit":
6666
return self.exit(params)
6767
elif method == "build/initialize":
@@ -77,7 +77,7 @@ def handle_message(self, message: dict[str, object]) -> Optional[dict[str, objec
7777
if "id" in message:
7878
raise RequestError(code=-32601, message=f"Method not found: {method}")
7979

80-
def send_raw_message(self, message: dict[str, object]):
80+
def send_raw_message(self, message: Dict[str, object]):
8181
"""
8282
Send a raw message to SourceKit-LSP. The message needs to have all JSON-RPC wrapper fields.
8383
@@ -89,24 +89,37 @@ def send_raw_message(self, message: dict[str, object]):
8989
)
9090
sys.stdout.flush()
9191

92-
def send_notification(self, method: str, params: dict[str, object]):
92+
def send_notification(self, method: str, params: Dict[str, object]):
9393
"""
9494
Send a notification with the given method and parameters to SourceKit-LSP.
9595
"""
96-
message: dict[str, object] = {
96+
message: Dict[str, object] = {
9797
"jsonrpc": "2.0",
9898
"method": method,
9999
"params": params,
100100
}
101101
self.send_raw_message(message)
102102

103+
def send_sourcekit_options_changed(self, uri: str, options: List[str]):
104+
"""
105+
Send a `build/sourceKitOptionsChanged` notification to SourceKit-LSP, informing it about new build settings
106+
using the old push-based settings model.
107+
"""
108+
self.send_notification(
109+
"build/sourceKitOptionsChanged",
110+
{
111+
"uri": uri,
112+
"updatedOptions": {"options": options},
113+
},
114+
)
115+
103116
# Message handling functions.
104117
# Subclasses should override these to provide functionality.
105118

106-
def exit(self, notification: dict[str, object]) -> None:
119+
def exit(self, notification: Dict[str, object]) -> None:
107120
pass
108121

109-
def initialize(self, request: dict[str, object]) -> dict[str, object]:
122+
def initialize(self, request: Dict[str, object]) -> Dict[str, object]:
110123
return {
111124
"displayName": "test server",
112125
"version": "0.1",
@@ -119,11 +132,11 @@ def initialize(self, request: dict[str, object]) -> dict[str, object]:
119132
},
120133
}
121134

122-
def initialized(self, notification: dict[str, object]) -> None:
135+
def initialized(self, notification: Dict[str, object]) -> None:
123136
pass
124137

125-
def register_for_changes(self, notification: dict[str, object]):
138+
def register_for_changes(self, notification: Dict[str, object]):
126139
pass
127140

128-
def shutdown(self, notification: dict[str, object]) -> None:
141+
def shutdown(self, notification: Dict[str, object]) -> None:
129142
pass

Sources/SKTestSupport/INPUTS/BuildServerBuildSystemTests.testBuildTargetsChanged/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from pathlib import Path
22
import sys
3+
from typing import Dict
34

45
sys.path.append(str(Path(__file__).parent.parent))
56

67
from AbstractBuildServer import AbstractBuildServer
78

89

910
class BuildServer(AbstractBuildServer):
10-
def register_for_changes(self, notification: dict[str, object]):
11+
def register_for_changes(self, notification: Dict[str, object]):
1112
if notification["action"] == "register":
1213
self.send_notification(
1314
"buildTarget/didChange",

Sources/SKTestSupport/INPUTS/BuildServerBuildSystemTests.testFileRegistration/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from pathlib import Path
22
import sys
3+
from typing import Dict
34

45
sys.path.append(str(Path(__file__).parent.parent))
56

67
from AbstractBuildServer import AbstractBuildServer
78

89

910
class BuildServer(AbstractBuildServer):
10-
def register_for_changes(self, notification: dict[str, object]):
11+
def register_for_changes(self, notification: Dict[str, object]):
1112
if notification["action"] == "register":
1213
self.send_notification(
1314
"build/sourceKitOptionsChanged",

Sources/SourceKitLSP/DetermineBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import struct TSCBasic.RelativePath
2222
fileprivate extension WorkspaceType {
2323
var buildSystemType: BuiltInBuildSystem.Type {
2424
switch self {
25-
case .buildServer: return BuildServerBuildSystem.self
25+
case .buildServer: return LegacyBuildServerBuildSystem.self
2626
case .compilationDatabase: return CompilationDatabaseBuildSystem.self
2727
case .swiftPM: return SwiftPMBuildSystem.self
2828
}

0 commit comments

Comments
 (0)