Skip to content

Commit e75ee23

Browse files
authored
Merge pull request #1687 from ahoppen/bsp-server-tests
2 parents fd80914 + 5202a8f commit e75ee23

File tree

20 files changed

+388
-593
lines changed

20 files changed

+388
-593
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+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import json
2+
import sys
3+
from typing import Dict, List, Optional
4+
5+
6+
class RequestError(Exception):
7+
"""
8+
An error that can be thrown from a request handling function in `AbstractBuildServer` to return an error response to
9+
SourceKit-LSP.
10+
"""
11+
12+
code: int
13+
message: str
14+
15+
def __init__(self, code: int, message: str):
16+
self.code = code
17+
self.message = message
18+
19+
20+
class AbstractBuildServer:
21+
"""
22+
An abstract class to implement a BSP server in Python for SourceKit-LSP testing purposes.
23+
"""
24+
25+
def run(self):
26+
"""
27+
Run the build server. This should be called from the top-level code of the build server's Python file.
28+
"""
29+
while True:
30+
line = sys.stdin.readline()
31+
if len(line) == 0:
32+
break
33+
34+
assert line.startswith("Content-Length:")
35+
length = int(line[len("Content-Length:") :])
36+
sys.stdin.readline()
37+
message = json.loads(sys.stdin.read(length))
38+
39+
try:
40+
result = self.handle_message(message)
41+
if result:
42+
response_message: Dict[str, object] = {
43+
"jsonrpc": "2.0",
44+
"id": message["id"],
45+
"result": result,
46+
}
47+
self.send_raw_message(response_message)
48+
except RequestError as e:
49+
error_response_message: Dict[str, object] = {
50+
"jsonrpc": "2.0",
51+
"id": message["id"],
52+
"error": {
53+
"code": e.code,
54+
"message": e.message,
55+
},
56+
}
57+
self.send_raw_message(error_response_message)
58+
59+
def handle_message(self, message: Dict[str, object]) -> Optional[Dict[str, object]]:
60+
"""
61+
Dispatch handling of the given method, received from SourceKit-LSP to the message handling function.
62+
"""
63+
method: str = str(message["method"])
64+
params: Dict[str, object] = message["params"] # type: ignore
65+
if method == "build/exit":
66+
return self.exit(params)
67+
elif method == "build/initialize":
68+
return self.initialize(params)
69+
elif method == "build/initialized":
70+
return self.initialized(params)
71+
elif method == "build/shutdown":
72+
return self.shutdown(params)
73+
elif method == "textDocument/registerForChanges":
74+
return self.register_for_changes(params)
75+
76+
# ignore other notifications
77+
if "id" in message:
78+
raise RequestError(code=-32601, message=f"Method not found: {method}")
79+
80+
def send_raw_message(self, message: Dict[str, object]):
81+
"""
82+
Send a raw message to SourceKit-LSP. The message needs to have all JSON-RPC wrapper fields.
83+
84+
Subclasses should not call this directly
85+
"""
86+
message_str = json.dumps(message)
87+
sys.stdout.buffer.write(
88+
f"Content-Length: {len(message_str)}\r\n\r\n{message_str}".encode("utf-8")
89+
)
90+
sys.stdout.flush()
91+
92+
def send_notification(self, method: str, params: Dict[str, object]):
93+
"""
94+
Send a notification with the given method and parameters to SourceKit-LSP.
95+
"""
96+
message: Dict[str, object] = {
97+
"jsonrpc": "2.0",
98+
"method": method,
99+
"params": params,
100+
}
101+
self.send_raw_message(message)
102+
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+
116+
# Message handling functions.
117+
# Subclasses should override these to provide functionality.
118+
119+
def exit(self, notification: Dict[str, object]) -> None:
120+
pass
121+
122+
def initialize(self, request: Dict[str, object]) -> Dict[str, object]:
123+
return {
124+
"displayName": "test server",
125+
"version": "0.1",
126+
"bspVersion": "2.0",
127+
"rootUri": "blah",
128+
"capabilities": {"languageIds": ["a", "b"]},
129+
"data": {
130+
"indexDatabasePath": "some/index/db/path",
131+
"indexStorePath": "some/index/store/path",
132+
},
133+
}
134+
135+
def initialized(self, notification: Dict[str, object]) -> None:
136+
pass
137+
138+
def register_for_changes(self, notification: Dict[str, object]):
139+
pass
140+
141+
def shutdown(self, notification: Dict[str, object]) -> None:
142+
pass

Sources/SKTestSupport/INPUTS/BuildServerBuildSystemTests.testBuildTargetOutputs/buildServer.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

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

Lines changed: 0 additions & 75 deletions
This file was deleted.

0 commit comments

Comments
 (0)