Files
sourcekit-lsp/Sources/SKTestSupport/INPUTS/AbstractBuildServer.py
Alex Hoppen cf828a8135 Consistently add license headers to all files
To prepare for the GitHub Action license header check.
2025-03-06 22:01:00 -08:00

214 lines
7.5 KiB
Python

#!/usr/bin/env python3
##===----------------------------------------------------------------------===##
##
## This source file is part of the Swift.org open source project
##
## Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
## Licensed under Apache License v2.0 with Runtime Library Exception
##
## See https://swift.org/LICENSE.txt for license information
## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
##
##===----------------------------------------------------------------------===##
import json
import sys
from typing import Dict, List, Optional
class RequestError(Exception):
"""
An error that can be thrown from a request handling function in `AbstractBuildServer` to return an error response to
SourceKit-LSP.
"""
code: int
message: str
def __init__(self, code: int, message: str):
self.code = code
self.message = message
class AbstractBuildServer:
"""
An abstract class to implement a BSP server in Python for SourceKit-LSP testing purposes.
"""
def run(self):
"""
Run the build server. This should be called from the top-level code of the build server's Python file.
"""
while True:
line = sys.stdin.readline()
if len(line) == 0:
break
assert line.startswith("Content-Length:")
length = int(line[len("Content-Length:") :])
sys.stdin.readline()
message = json.loads(sys.stdin.read(length))
try:
result = self.handle_message(message)
if result is not None:
response_message: Dict[str, object] = {
"jsonrpc": "2.0",
"id": message["id"],
"result": result,
}
self.send_raw_message(response_message)
except RequestError as e:
error_response_message: Dict[str, object] = {
"jsonrpc": "2.0",
"id": message["id"],
"error": {
"code": e.code,
"message": e.message,
},
}
self.send_raw_message(error_response_message)
def handle_message(self, message: Dict[str, object]) -> Optional[Dict[str, object]]:
"""
Dispatch handling of the given method, received from SourceKit-LSP to the message handling function.
"""
method: str = str(message["method"])
params: Dict[str, object] = message["params"] # type: ignore
if method == "build/exit":
return self.exit(params)
elif method == "build/initialize":
return self.initialize(params)
elif method == "build/initialized":
return self.initialized(params)
elif method == "build/shutdown":
return self.shutdown(params)
elif method == "buildTarget/prepare":
return self.buildtarget_prepare(params)
elif method == "buildTarget/sources":
return self.buildtarget_sources(params)
elif method == "textDocument/registerForChanges":
return self.register_for_changes(params)
elif method == "textDocument/sourceKitOptions":
return self.textdocument_sourcekitoptions(params)
elif method == "workspace/didChangeWatchedFiles":
return self.workspace_did_change_watched_files(params)
elif method == "workspace/buildTargets":
return self.workspace_build_targets(params)
elif method == "workspace/waitForBuildSystemUpdates":
return self.workspace_waitForBuildSystemUpdates(params)
# ignore other notifications
if "id" in message:
raise RequestError(code=-32601, message=f"Method not found: {method}")
def send_raw_message(self, message: Dict[str, object]):
"""
Send a raw message to SourceKit-LSP. The message needs to have all JSON-RPC wrapper fields.
Subclasses should not call this directly
"""
message_str = json.dumps(message)
sys.stdout.buffer.write(
f"Content-Length: {len(message_str)}\r\n\r\n{message_str}".encode("utf-8")
)
sys.stdout.flush()
def send_notification(self, method: str, params: Dict[str, object]):
"""
Send a notification with the given method and parameters to SourceKit-LSP.
"""
message: Dict[str, object] = {
"jsonrpc": "2.0",
"method": method,
"params": params,
}
self.send_raw_message(message)
# Message handling functions.
# Subclasses should override these to provide functionality.
def exit(self, notification: Dict[str, object]) -> None:
pass
def initialize(self, request: Dict[str, object]) -> Dict[str, object]:
return {
"displayName": "test server",
"version": "0.1",
"bspVersion": "2.0",
"rootUri": "blah",
"capabilities": {"languageIds": ["swift", "c", "cpp", "objective-c", "objective-c"]},
"data": {
"sourceKitOptionsProvider": True,
},
}
def initialized(self, notification: Dict[str, object]) -> None:
pass
def register_for_changes(self, notification: Dict[str, object]):
pass
def textdocument_sourcekitoptions(
self, request: Dict[str, object]
) -> Dict[str, object]:
raise RequestError(
code=-32601, message=f"'textDocument/sourceKitOptions' not implemented"
)
def shutdown(self, request: Dict[str, object]) -> Dict[str, object]:
return {}
def buildtarget_prepare(self, request: Dict[str, object]) -> Dict[str, object]:
raise RequestError(
code=-32601, message=f"'buildTarget/prepare' not implemented"
)
def buildtarget_sources(self, request: Dict[str, object]) -> Dict[str, object]:
raise RequestError(
code=-32601, message=f"'buildTarget/sources' not implemented"
)
def workspace_did_change_watched_files(self, notification: Dict[str, object]) -> None:
pass
def workspace_build_targets(self, request: Dict[str, object]) -> Dict[str, object]:
raise RequestError(
code=-32601, message=f"'workspace/buildTargets' not implemented"
)
def workspace_waitForBuildSystemUpdates(self, request: Dict[str, object]) -> Dict[str, object]:
return {}
class LegacyBuildServer(AbstractBuildServer):
def send_sourcekit_options_changed(self, uri: str, options: List[str]):
"""
Send a `build/sourceKitOptionsChanged` notification to SourceKit-LSP, informing it about new build settings
using the old push-based settings model.
"""
self.send_notification(
"build/sourceKitOptionsChanged",
{
"uri": uri,
"updatedOptions": {"options": options},
},
)
"""
A build server that doesn't declare the `sourceKitOptionsProvider` and uses the push-based settings model.
"""
def initialize(self, request: Dict[str, object]) -> Dict[str, object]:
return {
"displayName": "test server",
"version": "0.1",
"bspVersion": "2.0",
"rootUri": "blah",
"capabilities": {"languageIds": ["a", "b"]},
"data": {
"indexDatabasePath": "some/index/db/path",
"indexStorePath": "some/index/store/path",
},
}