mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 03:13:51 +00:00
feat(kotlin): update language server to 262.9593.0 (#1811)
* feat(kotlin): update language server to 262.9593.0 Note: download URL structure differs depending on version --------- Co-authored-by: Dominik Jain <dominik.jain@oraios-ai.de>
This commit is contained in:
1 parent
8364fc5548
commit
217517ecdf
9 files changed
+537
-57
No files matched your search
@@ -41,6 +41,8 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
- Add `python_basedpyright` as an alternative Python language server
|
||||
- Java/JDTLS: stop downloading and loading the unused IntelliCode completion-ranking bundle; the retired
|
||||
`intellicode_version`, `intellicode_xmx` and `intellicode_xms` settings remain accepted but are ignored #1821
|
||||
- Kotlin: update the managed Kotlin LSP from `261.13587.0` to `262.9593.0`, including support for the
|
||||
new platform-specific archive layout and Windows ARM64 builds
|
||||
- Nix/nixd: support custom `ls_path` launchers and external JSON settings through `config_path` #1737
|
||||
- Fix: Nix/nixd diagnostics now use published diagnostics instead of the unsupported
|
||||
`textDocument/diagnostic` request, which terminated nixd #1802
|
||||
|
||||
@@ -759,15 +759,20 @@ Supported settings:
|
||||
| Setting | Default | Description |
|
||||
|---|---|---|
|
||||
| `ls_path` | managed download | Override the Kotlin Language Server executable path. |
|
||||
| `kotlin_lsp_version` | `261.13587.0` | Override the Kotlin Language Server version Serena downloads when `ls_path` is not set. |
|
||||
| `kotlin_lsp_version` | `262.9593.0` | Override the Kotlin Language Server version Serena downloads when `ls_path` is not set. |
|
||||
| `jvm_options` | `-Xmx2G` | Value assigned to `JAVA_TOOL_OPTIONS` for the Kotlin LS process. Set to `""` to disable JVM options entirely. |
|
||||
|
||||
The managed `262.9593.0` packages include a bundled JBR. For a custom `ls_path`, point directly to
|
||||
`bin/intellij-server` (`bin/intellij-server.exe` on Windows). Serena also retains the legacy download
|
||||
layout for custom Kotlin LSP versions older than `262.4739.0`. The pinned current and frozen initial
|
||||
releases are checksum-verified; arbitrary custom versions are downloaded without checksum verification.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
ls_specific_settings:
|
||||
kotlin:
|
||||
kotlin_lsp_version: "261.13587.0"
|
||||
kotlin_lsp_version: "262.9593.0"
|
||||
jvm_options: "-Xmx4G -XX:+UseG1GC"
|
||||
```
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from sensai.util import logging
|
||||
|
||||
from solidlsp.language_servers.eclipse_jdtls import EclipseJDTLS
|
||||
from solidlsp.language_servers.kotlin_language_server import KotlinLanguageServer
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.configure()
|
||||
EclipseJDTLS.DependencyProvider.update_dep_hashes()
|
||||
KotlinLanguageServer.DependencyProvider.update_dep_hashes()
|
||||
@@ -5,8 +5,8 @@ You can configure the following options in ls_specific_settings (in serena_confi
|
||||
|
||||
ls_specific_settings:
|
||||
kotlin:
|
||||
ls_path: '/path/to/kotlin-lsp.sh' # Custom path to Kotlin Language Server executable
|
||||
kotlin_lsp_version: '261.13587.0' # Kotlin Language Server version (default: current bundled version)
|
||||
ls_path: '/path/to/bin/intellij-server' # Custom path to Kotlin Language Server executable
|
||||
kotlin_lsp_version: '262.9593.0' # Kotlin Language Server version (default: current bundled version)
|
||||
jvm_options: '-Xmx2G' # JVM options for Kotlin Language Server (default: -Xmx2G)
|
||||
|
||||
Example configuration for large projects:
|
||||
@@ -21,16 +21,18 @@ import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.dependency_provider import DownloadedDependency, DownloadedDependencyHashDatabase
|
||||
from solidlsp.ls import (
|
||||
LanguageServerDependencyProvider,
|
||||
LanguageServerDependencyProviderSinglePath,
|
||||
SolidLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -44,34 +46,21 @@ KOTLIN_LSP_ALLOWED_HOSTS = ("download-cdn.jetbrains.com",)
|
||||
# Version pinning convention (see eclipse_jdtls.py for the full spec):
|
||||
# INITIAL_* — frozen forever; legacy unversioned install dir is reserved for it.
|
||||
# DEFAULT_* — bumped on upgrades; goes into a versioned subdir.
|
||||
# NOTE: After changing either pinned version, run scripts/update_downloaded_dependency_hashes.py.
|
||||
INITIAL_KOTLIN_LSP_VERSION = "261.13587.0"
|
||||
INITIAL_KOTLIN_LSP_SHA256_BY_SUFFIX = {
|
||||
"win-x64": "2806c2bd4810bd8e7ccc27d8c0ca4a5232a1c4f26ea1f4ba40e578b60860ccad",
|
||||
"linux-x64": "dc0ed2e70cb0d61fdabb26aefce8299b7a75c0dcfffb9413715e92caec6e83ec",
|
||||
"linux-aarch64": "d1dceb000fe06c5e2c30b95e7f4ab01d05101bd03ed448167feeb544a9f1d651",
|
||||
"mac-x64": "a3972f27229eba2c226060e54baea1c958c82c326dfc971bf53f72a74d0564a3",
|
||||
"mac-aarch64": "d4ea28b22b29cf906fe16d23698a8468f11646a6a66dcb15584f306aaefbee6c",
|
||||
}
|
||||
DEFAULT_KOTLIN_LSP_VERSION = "261.13587.0"
|
||||
DEFAULT_KOTLIN_LSP_SHA256_BY_SUFFIX = {
|
||||
"win-x64": "2806c2bd4810bd8e7ccc27d8c0ca4a5232a1c4f26ea1f4ba40e578b60860ccad",
|
||||
"linux-x64": "dc0ed2e70cb0d61fdabb26aefce8299b7a75c0dcfffb9413715e92caec6e83ec",
|
||||
"linux-aarch64": "d1dceb000fe06c5e2c30b95e7f4ab01d05101bd03ed448167feeb544a9f1d651",
|
||||
"mac-x64": "a3972f27229eba2c226060e54baea1c958c82c326dfc971bf53f72a74d0564a3",
|
||||
"mac-aarch64": "d4ea28b22b29cf906fe16d23698a8468f11646a6a66dcb15584f306aaefbee6c",
|
||||
}
|
||||
DEFAULT_KOTLIN_LSP_VERSION = "262.9593.0"
|
||||
|
||||
# Versions before this one use kotlin-lsp-{version}-{platform}.zip and a kotlin-lsp script.
|
||||
# Starting with 262.4739.0, JetBrains publishes kotlin-server archives with platform-specific
|
||||
# formats, a bundled JBR, and bin/intellij-server as the launcher.
|
||||
KOTLIN_SERVER_PACKAGING_MIN_VERSION = (262, 4739, 0)
|
||||
|
||||
def _kotlin_lsp_sha(version: str, kotlin_suffix: str) -> str | None:
|
||||
if version == INITIAL_KOTLIN_LSP_VERSION:
|
||||
return INITIAL_KOTLIN_LSP_SHA256_BY_SUFFIX.get(kotlin_suffix)
|
||||
if version == DEFAULT_KOTLIN_LSP_VERSION:
|
||||
return DEFAULT_KOTLIN_LSP_SHA256_BY_SUFFIX.get(kotlin_suffix)
|
||||
return None
|
||||
# The first modern archives remained under the legacy /kotlin-lsp CDN path.
|
||||
# Starting with 262.8190.0, JetBrains moved them to /language-server/kotlin-server.
|
||||
KOTLIN_SERVER_CDN_PATH_MIN_VERSION = (262, 8190, 0)
|
||||
|
||||
|
||||
# Platform-specific Kotlin LSP download suffixes
|
||||
PLATFORM_KOTLIN_SUFFIX = {
|
||||
# Platform-specific suffixes used by legacy Kotlin LSP ZIP archives.
|
||||
LEGACY_PLATFORM_KOTLIN_SUFFIX = {
|
||||
"win-x64": "win-x64",
|
||||
"linux-x64": "linux-x64",
|
||||
"linux-arm64": "linux-aarch64",
|
||||
@@ -79,6 +68,22 @@ PLATFORM_KOTLIN_SUFFIX = {
|
||||
"osx-arm64": "mac-aarch64",
|
||||
}
|
||||
|
||||
# Modern archive filename suffix, FileUtils archive type, and launcher path within the archive.
|
||||
KOTLIN_SERVER_ARTIFACT_BY_PLATFORM: dict[str, tuple[str, FileUtils.ArchiveType, tuple[str, ...]]] = {
|
||||
"win-x64": (".win.zip", "zip", ("bin", "intellij-server.exe")),
|
||||
"win-arm64": ("-aarch64.win.zip", "zip", ("bin", "intellij-server.exe")),
|
||||
"linux-x64": (".tar.gz", "gztar", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
"linux-arm64": ("-aarch64.tar.gz", "gztar", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
"osx-x64": (".sit", "zip", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
"osx-arm64": ("-aarch64.sit", "zip", ("kotlin-server-{version}", "bin", "intellij-server")),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class KotlinLSPArtifact:
|
||||
dependency: DownloadedDependency
|
||||
launcher_parts: tuple[str, ...]
|
||||
|
||||
|
||||
class KotlinLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
@@ -102,15 +107,78 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
# set again once all progress tokens have ended.
|
||||
self._indexing_complete = threading.Event()
|
||||
self._indexing_complete.set()
|
||||
self._intellij_server_ready = threading.Event()
|
||||
self._active_progress_tokens: set[str] = set()
|
||||
self._progress_lock = threading.Lock()
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir, str(self.cache_dir))
|
||||
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
def __init__(self, custom_settings: SolidLSPSettings.CustomLSSettings, ls_resources_dir: str):
|
||||
def __init__(self, custom_settings: SolidLSPSettings.CustomLSSettings, ls_resources_dir: str, project_cache_dir: str):
|
||||
super().__init__(custom_settings, ls_resources_dir)
|
||||
self._project_cache_dir = project_cache_dir
|
||||
|
||||
@classmethod
|
||||
def _create_artifact(cls, version: str, platform_id: PlatformId) -> KotlinLSPArtifact:
|
||||
"""Build download and launcher metadata for one Kotlin LSP release.
|
||||
|
||||
JetBrains has three publishing layouts: legacy ZIPs before 262.4739.0,
|
||||
modern platform archives under ``/kotlin-lsp`` through 262.7569.0,
|
||||
and modern archives under ``/language-server/kotlin-server`` from
|
||||
262.8190.0 onward. Serena verifies the frozen initial and current default
|
||||
releases; arbitrary user-selected versions are unverified by design.
|
||||
"""
|
||||
try:
|
||||
version_parts = tuple(int(part) for part in version.split("."))
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Kotlin LSP version must contain only dot-separated integers: {version!r}") from exc
|
||||
|
||||
verified = version in {INITIAL_KOTLIN_LSP_VERSION, DEFAULT_KOTLIN_LSP_VERSION}
|
||||
if version_parts >= KOTLIN_SERVER_PACKAGING_MIN_VERSION:
|
||||
artifact_config = KOTLIN_SERVER_ARTIFACT_BY_PLATFORM.get(platform_id.value)
|
||||
if artifact_config is None:
|
||||
raise ValueError(f"Unsupported platform for Kotlin LSP {version}: {platform_id.value}")
|
||||
|
||||
asset_suffix, archive_type, launcher_parts = artifact_config
|
||||
asset_name = f"kotlin-server-{version}{asset_suffix}"
|
||||
cdn_path = "language-server/kotlin-server" if version_parts >= KOTLIN_SERVER_CDN_PATH_MIN_VERSION else "kotlin-lsp"
|
||||
return KotlinLSPArtifact(
|
||||
dependency=DownloadedDependency(
|
||||
url=f"https://download-cdn.jetbrains.com/{cdn_path}/{version}/{asset_name}",
|
||||
archive_type=archive_type,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
verified=verified,
|
||||
),
|
||||
launcher_parts=tuple(part.format(version=version) for part in launcher_parts),
|
||||
)
|
||||
|
||||
kotlin_suffix = LEGACY_PLATFORM_KOTLIN_SUFFIX.get(platform_id.value)
|
||||
if kotlin_suffix is None:
|
||||
raise ValueError(f"Unsupported platform for Kotlin LSP {version}: {platform_id.value}")
|
||||
|
||||
return KotlinLSPArtifact(
|
||||
dependency=DownloadedDependency(
|
||||
url=f"https://download-cdn.jetbrains.com/kotlin-lsp/{version}/kotlin-lsp-{version}-{kotlin_suffix}.zip",
|
||||
archive_type="zip",
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
verified=verified,
|
||||
),
|
||||
launcher_parts=("kotlin-lsp.cmd",) if platform_id.is_windows() else ("kotlin-lsp.sh",),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_dep_hashes(cls) -> None:
|
||||
pinned_artifacts = [
|
||||
*(cls._create_artifact(INITIAL_KOTLIN_LSP_VERSION, PlatformId(platform)) for platform in LEGACY_PLATFORM_KOTLIN_SUFFIX),
|
||||
*(
|
||||
cls._create_artifact(DEFAULT_KOTLIN_LSP_VERSION, PlatformId(platform))
|
||||
for platform in KOTLIN_SERVER_ARTIFACT_BY_PLATFORM
|
||||
),
|
||||
]
|
||||
with DownloadedDependencyHashDatabase.get_instance().update_context() as database:
|
||||
for artifact in pinned_artifacts:
|
||||
database.update(artifact.dependency)
|
||||
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
"""
|
||||
@@ -118,16 +186,9 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
# Verify platform support
|
||||
assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
|
||||
"Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment"
|
||||
)
|
||||
|
||||
kotlin_suffix = PLATFORM_KOTLIN_SUFFIX.get(platform_id.value)
|
||||
assert kotlin_suffix, f"Unsupported platform for Kotlin LSP: {platform_id.value}"
|
||||
|
||||
# Setup paths for dependencies; legacy unversioned dir reserved for INITIAL only
|
||||
kotlin_lsp_version = self._custom_settings.get("kotlin_lsp_version", DEFAULT_KOTLIN_LSP_VERSION)
|
||||
artifact = self._create_artifact(kotlin_lsp_version, platform_id)
|
||||
ls_dirname = (
|
||||
"kotlin_language_server"
|
||||
if kotlin_lsp_version == INITIAL_KOTLIN_LSP_VERSION
|
||||
@@ -137,22 +198,13 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
|
||||
# Setup Kotlin Language Server
|
||||
kotlin_script_name = "kotlin-lsp.cmd" if platform_id.value.startswith("win-") else "kotlin-lsp.sh"
|
||||
kotlin_script = os.path.join(static_dir, kotlin_script_name)
|
||||
kotlin_script = os.path.join(static_dir, *artifact.launcher_parts)
|
||||
|
||||
if not os.path.exists(kotlin_script):
|
||||
kotlin_url = f"https://download-cdn.jetbrains.com/kotlin-lsp/{kotlin_lsp_version}/kotlin-lsp-{kotlin_lsp_version}-{kotlin_suffix}.zip"
|
||||
expected_sha256 = _kotlin_lsp_sha(kotlin_lsp_version, kotlin_suffix)
|
||||
log.info("Downloading Kotlin Language Server...")
|
||||
FileUtils.download_and_extract_archive_verified(
|
||||
kotlin_url,
|
||||
static_dir,
|
||||
"zip",
|
||||
expected_sha256=expected_sha256,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
artifact.dependency.download_to(static_dir)
|
||||
|
||||
if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"):
|
||||
if os.path.exists(kotlin_script) and not platform_id.is_windows():
|
||||
os.chmod(
|
||||
kotlin_script,
|
||||
stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH,
|
||||
@@ -165,10 +217,18 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
return kotlin_script
|
||||
|
||||
def _create_launch_command(self, core_path: str) -> list[str]:
|
||||
return [core_path, "--stdio"]
|
||||
command = [core_path, "--stdio"]
|
||||
# A custom ls_path is independent of Serena's managed version. Select
|
||||
# arguments from the actual launcher so existing kotlin-lsp.sh/.cmd
|
||||
# configurations do not inherit IntelliJ-server-only options.
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
intellij_launcher_name = "intellij-server.exe" if platform_id.is_windows() else "intellij-server"
|
||||
if os.path.basename(core_path).lower() == intellij_launcher_name:
|
||||
command.extend(["--system-path", os.path.join(self._project_cache_dir, "kotlin-lsp-system")])
|
||||
return command
|
||||
|
||||
def create_launch_command_env(self) -> dict[str, str]:
|
||||
"""Provides JAVA_HOME and JVM options for the Kotlin Language Server process."""
|
||||
"""Provides JVM options for the Kotlin Language Server process."""
|
||||
env: dict[str, str] = {}
|
||||
|
||||
# Get JVM options from settings or use default
|
||||
@@ -427,6 +487,7 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Starts the Kotlin Language Server
|
||||
"""
|
||||
self._intellij_server_ready.clear()
|
||||
|
||||
def execute_client_command_handler(params: dict) -> list:
|
||||
return []
|
||||
@@ -437,6 +498,11 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
def window_log_message(msg: dict) -> None:
|
||||
log.info(f"LSP: window/logMessage: {msg}")
|
||||
|
||||
def intellij_server_ready(_params: dict | None) -> None:
|
||||
"""Mark the modern IntelliJ-based server ready after its startup/import phase."""
|
||||
log.info("Kotlin IntelliJ language server reported that it is ready")
|
||||
self._intellij_server_ready.set()
|
||||
|
||||
def work_done_progress_create(params: dict) -> dict:
|
||||
"""Handle window/workDoneProgress/create: the server is about to report async progress.
|
||||
Clear the indexing-complete event so _start_server waits until all tokens finish.
|
||||
@@ -487,9 +553,12 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
self.server.on_request("workspace/codeLens/refresh", do_nothing)
|
||||
self.server.on_notification("language/status", do_nothing)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_notification("window/showMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_request("workspace/diagnostic/refresh", do_nothing)
|
||||
self.server.on_request("window/workDoneProgress/create", work_done_progress_create)
|
||||
self.server.on_notification("$/progress", progress_handler)
|
||||
self.server.on_notification("intellij/ready-for-test", intellij_server_ready)
|
||||
self.server.on_notification("$/logTrace", do_nothing)
|
||||
self.server.on_notification("$/cancelRequest", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
@@ -503,6 +572,8 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
capabilities = init_response["capabilities"]
|
||||
server_info = init_response.get("serverInfo", {})
|
||||
wait_for_intellij_ready = isinstance(server_info, dict) and server_info.get("name") == "IntelliJ Language Server by JetBrains"
|
||||
assert "textDocumentSync" in capabilities, "Server must support textDocumentSync"
|
||||
assert "hoverProvider" in capabilities, "Server must support hover"
|
||||
assert "completionProvider" in capabilities, "Server must support code completion"
|
||||
@@ -515,14 +586,17 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
|
||||
self.server.notify.initialized({})
|
||||
|
||||
# Wait for any async indexing to complete.
|
||||
# Wait for workspace import and async indexing to complete.
|
||||
# - Older KLS (0.253.x): indexing is synchronous inside `initialize`, no $/progress is sent,
|
||||
# _indexing_complete stays SET -> wait() returns immediately.
|
||||
# - Newer KLS (261+): server sends window/workDoneProgress/create after initialized,
|
||||
# which clears the event; wait() blocks until all progress tokens end.
|
||||
# - IntelliJ-based KLS (262.4739+): server sends intellij/ready-for-test after
|
||||
# its workspace import and indexing phase completes.
|
||||
_INDEXING_TIMEOUT = 120.0
|
||||
log.info("Waiting for Kotlin LSP indexing to complete (if async)...")
|
||||
if self._indexing_complete.wait(timeout=_INDEXING_TIMEOUT):
|
||||
ready_event = self._intellij_server_ready if wait_for_intellij_ready else self._indexing_complete
|
||||
if ready_event.wait(timeout=_INDEXING_TIMEOUT):
|
||||
log.info("Kotlin LSP ready")
|
||||
else:
|
||||
log.warning("Kotlin LSP did not signal indexing completion within %.0fs; proceeding anyway", _INDEXING_TIMEOUT)
|
||||
|
||||
@@ -4,5 +4,16 @@
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-darwin-x64-1.54.0-923.vsix": "dfc98abc4e54165a78372e280242a039671729b1b03420608df3b10c6b629fb6",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-linux-arm64-1.54.0-923.vsix": "e2bb22c427d90da8dbb1afff72ff1e2dce38d50b76deb02d7bc313a330a1330c",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-linux-x64-1.54.0-923.vsix": "9d4b15da54e25a0192f9bac073f086c015397d3676623b68dbf83a5dbaf5132b",
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-win32-x64-1.54.0-923.vsix": "66f3914987edeccfee8a2558470e0fde4f8c4154232ff4baa5d73373ebc819d4"
|
||||
"https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-win32-x64-1.54.0-923.vsix": "66f3914987edeccfee8a2558470e0fde4f8c4154232ff4baa5d73373ebc819d4",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-win-x64.zip": "2806c2bd4810bd8e7ccc27d8c0ca4a5232a1c4f26ea1f4ba40e578b60860ccad",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-linux-x64.zip": "dc0ed2e70cb0d61fdabb26aefce8299b7a75c0dcfffb9413715e92caec6e83ec",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-linux-aarch64.zip": "d1dceb000fe06c5e2c30b95e7f4ab01d05101bd03ed448167feeb544a9f1d651",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-mac-x64.zip": "a3972f27229eba2c226060e54baea1c958c82c326dfc971bf53f72a74d0564a3",
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/261.13587.0/kotlin-lsp-261.13587.0-mac-aarch64.zip": "d4ea28b22b29cf906fe16d23698a8468f11646a6a66dcb15584f306aaefbee6c",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0.win.zip": "f2daaa476f26d99301b406f76de6d87c437d04dc72f06845154619d8f991c51f",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0-aarch64.win.zip": "73a552a6a420158622e5ad8d96b53da8aa8ced3f88a24fded01575927a2fd8e7",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0.tar.gz": "2d99d8e198fbe4aa8f4481e37799724ce94803b4ea12a60b416040e3fcd7cc5e",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0-aarch64.tar.gz": "2317831c6e5607d05b7ebc1da655330125ce0e3d66fbf24517dfce442debc14e",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0.sit": "17369fda97c85418ac24ab38a9df56b21522a3468dfe193832fe455c13920745",
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.9593.0/kotlin-server-262.9593.0-aarch64.sit": "6ba6021a706b21e64cef33f7e2b79f187c0910320722bb2d3ed05ad1115ec43f"
|
||||
}
|
||||
@@ -10,7 +10,7 @@ from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
# Kotlin LSP (IntelliJ-based, pre-alpha v261) crashes on JVM restart under CI resource constraints
|
||||
# Kotlin LSP (IntelliJ-based, pre-alpha v262) crashes on JVM restart under CI resource constraints
|
||||
# (2 CPUs, 7GB RAM). First start succeeds but subsequent starts fail with cancelled (-32800).
|
||||
# Tests pass reliably on developer machines. See PR #1061 for investigation details.
|
||||
# (The CI quarantine lives centrally in test/conftest.py::_determine_disabled_languages.)
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
"""Tests for Kotlin Language Server dependency resolution and installation."""
|
||||
|
||||
from contextlib import nullcontext
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.dependency_provider import DownloadedDependency, DownloadedDependencyHashDatabase
|
||||
from solidlsp.language_servers.kotlin_language_server import (
|
||||
DEFAULT_KOTLIN_LSP_VERSION,
|
||||
INITIAL_KOTLIN_LSP_VERSION,
|
||||
KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
KotlinLanguageServer,
|
||||
)
|
||||
from solidlsp.ls_utils import PlatformId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
def _make_provider(
|
||||
tmp_path: Path,
|
||||
custom_settings: dict[str, str] | None = None,
|
||||
) -> KotlinLanguageServer.DependencyProvider:
|
||||
return KotlinLanguageServer.DependencyProvider(
|
||||
custom_settings=SolidLSPSettings.CustomLSSettings(custom_settings or {}),
|
||||
ls_resources_dir=str(tmp_path),
|
||||
project_cache_dir=str(tmp_path / "project-cache"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.kotlin
|
||||
class TestKotlinDependencyProvider:
|
||||
@pytest.mark.parametrize(
|
||||
("platform_id", "asset_suffix", "archive_type", "launcher_parts", "sha256"),
|
||||
[
|
||||
(
|
||||
PlatformId.WIN_x64,
|
||||
".win.zip",
|
||||
"zip",
|
||||
("bin", "intellij-server.exe"),
|
||||
"f2daaa476f26d99301b406f76de6d87c437d04dc72f06845154619d8f991c51f",
|
||||
),
|
||||
(
|
||||
PlatformId.WIN_arm64,
|
||||
"-aarch64.win.zip",
|
||||
"zip",
|
||||
("bin", "intellij-server.exe"),
|
||||
"73a552a6a420158622e5ad8d96b53da8aa8ced3f88a24fded01575927a2fd8e7",
|
||||
),
|
||||
(
|
||||
PlatformId.LINUX_x64,
|
||||
".tar.gz",
|
||||
"gztar",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"2d99d8e198fbe4aa8f4481e37799724ce94803b4ea12a60b416040e3fcd7cc5e",
|
||||
),
|
||||
(
|
||||
PlatformId.LINUX_arm64,
|
||||
"-aarch64.tar.gz",
|
||||
"gztar",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"2317831c6e5607d05b7ebc1da655330125ce0e3d66fbf24517dfce442debc14e",
|
||||
),
|
||||
(
|
||||
PlatformId.OSX_x64,
|
||||
".sit",
|
||||
"zip",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"17369fda97c85418ac24ab38a9df56b21522a3468dfe193832fe455c13920745",
|
||||
),
|
||||
(
|
||||
PlatformId.OSX_arm64,
|
||||
"-aarch64.sit",
|
||||
"zip",
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
"6ba6021a706b21e64cef33f7e2b79f187c0910320722bb2d3ed05ad1115ec43f",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_default_artifacts_match_jetbrains_release_matrix(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
platform_id: PlatformId,
|
||||
asset_suffix: str,
|
||||
archive_type: str,
|
||||
launcher_parts: tuple[str, ...],
|
||||
sha256: str,
|
||||
) -> None:
|
||||
artifact = KotlinLanguageServer.DependencyProvider._create_artifact(DEFAULT_KOTLIN_LSP_VERSION, platform_id)
|
||||
url = (
|
||||
f"https://download-cdn.jetbrains.com/language-server/kotlin-server/{DEFAULT_KOTLIN_LSP_VERSION}/"
|
||||
f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}{asset_suffix}"
|
||||
)
|
||||
|
||||
with patch("solidlsp.dependency_provider.FileUtils.download_and_extract_archive_verified") as download:
|
||||
artifact.dependency.download_to(tmp_path)
|
||||
|
||||
download.assert_called_once_with(
|
||||
url,
|
||||
str(tmp_path),
|
||||
archive_type=archive_type,
|
||||
expected_sha256=sha256,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
assert artifact.launcher_parts == launcher_parts
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("version", "platform_id", "url", "launcher_parts"),
|
||||
[
|
||||
(
|
||||
"262.2310.0",
|
||||
PlatformId.LINUX_x64,
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/262.2310.0/kotlin-lsp-262.2310.0-linux-x64.zip",
|
||||
("kotlin-lsp.sh",),
|
||||
),
|
||||
(
|
||||
"262.4739.0",
|
||||
PlatformId.LINUX_x64,
|
||||
"https://download-cdn.jetbrains.com/kotlin-lsp/262.4739.0/kotlin-server-262.4739.0.tar.gz",
|
||||
("kotlin-server-262.4739.0", "bin", "intellij-server"),
|
||||
),
|
||||
(
|
||||
"262.8190.0",
|
||||
PlatformId.OSX_arm64,
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.8190.0/kotlin-server-262.8190.0-aarch64.sit",
|
||||
("kotlin-server-262.8190.0", "bin", "intellij-server"),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_version_boundaries_select_the_published_layout(
|
||||
self,
|
||||
version: str,
|
||||
platform_id: PlatformId,
|
||||
url: str,
|
||||
launcher_parts: tuple[str, ...],
|
||||
) -> None:
|
||||
artifact = KotlinLanguageServer.DependencyProvider._create_artifact(version, platform_id)
|
||||
|
||||
assert artifact.dependency.get_url() == url
|
||||
assert artifact.launcher_parts == launcher_parts
|
||||
|
||||
def test_custom_versions_skip_hash_lookup_by_design(self, tmp_path: Path) -> None:
|
||||
artifact = KotlinLanguageServer.DependencyProvider._create_artifact("262.8190.1", PlatformId.LINUX_x64)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"solidlsp.dependency_provider.DownloadedDependencyHashDatabase.get_instance",
|
||||
side_effect=AssertionError("custom versions must not consult the pinned hash database"),
|
||||
),
|
||||
patch("solidlsp.dependency_provider.FileUtils.download_and_extract_archive_verified") as download,
|
||||
):
|
||||
artifact.dependency.download_to(tmp_path)
|
||||
|
||||
download.assert_called_once_with(
|
||||
"https://download-cdn.jetbrains.com/language-server/kotlin-server/262.8190.1/kotlin-server-262.8190.1.tar.gz",
|
||||
str(tmp_path),
|
||||
archive_type="gztar",
|
||||
expected_sha256=None,
|
||||
allowed_hosts=KOTLIN_LSP_ALLOWED_HOSTS,
|
||||
)
|
||||
|
||||
def test_hash_updater_registers_frozen_and_default_artifacts(self) -> None:
|
||||
updated_asset_names: list[str] = []
|
||||
|
||||
class RecordingUpdater:
|
||||
def update(self, dependency: DownloadedDependency) -> None:
|
||||
updated_asset_names.append(dependency.get_url().rsplit("/", 1)[-1])
|
||||
|
||||
with patch.object(DownloadedDependencyHashDatabase, "get_instance") as get_hash_database:
|
||||
get_hash_database.return_value.update_context.return_value = nullcontext(RecordingUpdater())
|
||||
KotlinLanguageServer.DependencyProvider.update_dep_hashes()
|
||||
|
||||
assert set(updated_asset_names) == {
|
||||
"kotlin-lsp-261.13587.0-win-x64.zip",
|
||||
"kotlin-lsp-261.13587.0-linux-x64.zip",
|
||||
"kotlin-lsp-261.13587.0-linux-aarch64.zip",
|
||||
"kotlin-lsp-261.13587.0-mac-x64.zip",
|
||||
"kotlin-lsp-261.13587.0-mac-aarch64.zip",
|
||||
"kotlin-server-262.9593.0.win.zip",
|
||||
"kotlin-server-262.9593.0-aarch64.win.zip",
|
||||
"kotlin-server-262.9593.0.tar.gz",
|
||||
"kotlin-server-262.9593.0-aarch64.tar.gz",
|
||||
"kotlin-server-262.9593.0.sit",
|
||||
"kotlin-server-262.9593.0-aarch64.sit",
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("settings", "platform_id", "relative_launcher", "expected_directory"),
|
||||
[
|
||||
(
|
||||
{"kotlin_lsp_version": INITIAL_KOTLIN_LSP_VERSION},
|
||||
PlatformId.LINUX_x64,
|
||||
("kotlin-lsp.sh",),
|
||||
"kotlin_language_server",
|
||||
),
|
||||
(
|
||||
{},
|
||||
PlatformId.LINUX_arm64,
|
||||
(f"kotlin-server-{DEFAULT_KOTLIN_LSP_VERSION}", "bin", "intellij-server"),
|
||||
f"kotlin_language_server-{DEFAULT_KOTLIN_LSP_VERSION}",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_installs_pinned_versions_in_compatible_directories(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
settings: dict[str, str],
|
||||
platform_id: PlatformId,
|
||||
relative_launcher: tuple[str, ...],
|
||||
expected_directory: str,
|
||||
) -> None:
|
||||
provider = _make_provider(tmp_path, settings)
|
||||
|
||||
def fake_download(
|
||||
_url: str,
|
||||
target_path: str,
|
||||
archive_type: str,
|
||||
expected_sha256: str | None = None,
|
||||
allowed_hosts: tuple[str, ...] | list[str] | None = None,
|
||||
) -> None:
|
||||
del archive_type, expected_sha256, allowed_hosts
|
||||
launcher = Path(target_path).joinpath(*relative_launcher)
|
||||
launcher.parent.mkdir(parents=True, exist_ok=True)
|
||||
launcher.write_text("#!/bin/sh\n", encoding="utf-8")
|
||||
|
||||
with (
|
||||
patch(
|
||||
"solidlsp.language_servers.kotlin_language_server.PlatformUtils.get_platform_id",
|
||||
return_value=platform_id,
|
||||
),
|
||||
patch(
|
||||
"solidlsp.dependency_provider.FileUtils.download_and_extract_archive_verified",
|
||||
side_effect=fake_download,
|
||||
),
|
||||
):
|
||||
launcher_path = provider._get_or_install_core_dependency()
|
||||
|
||||
assert launcher_path == str((tmp_path / expected_directory).joinpath(*relative_launcher))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("platform_id", "modern_launcher", "other_os_launcher"),
|
||||
[
|
||||
(PlatformId.LINUX_x64, "/path/to/intellij-server", "/path/to/intellij-server.exe"),
|
||||
(PlatformId.WIN_x64, "/path/to/intellij-server.exe", "/path/to/intellij-server"),
|
||||
],
|
||||
)
|
||||
def test_custom_launcher_arguments_use_the_active_os_name(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
platform_id: PlatformId,
|
||||
modern_launcher: str,
|
||||
other_os_launcher: str,
|
||||
) -> None:
|
||||
modern_provider = _make_provider(tmp_path, {"ls_path": modern_launcher})
|
||||
other_os_provider = _make_provider(tmp_path, {"ls_path": other_os_launcher})
|
||||
|
||||
with patch(
|
||||
"solidlsp.language_servers.kotlin_language_server.PlatformUtils.get_platform_id",
|
||||
return_value=platform_id,
|
||||
):
|
||||
assert modern_provider.create_launch_command() == [
|
||||
modern_launcher,
|
||||
"--stdio",
|
||||
"--system-path",
|
||||
str(tmp_path / "project-cache" / "kotlin-lsp-system"),
|
||||
]
|
||||
assert other_os_provider.create_launch_command() == [other_os_launcher, "--stdio"]
|
||||
|
||||
def test_invalid_version_is_rejected_before_download(self) -> None:
|
||||
with pytest.raises(ValueError, match="dot-separated integers"):
|
||||
KotlinLanguageServer.DependencyProvider._create_artifact("latest", PlatformId.OSX_arm64)
|
||||
|
||||
def test_unsupported_modern_platform_is_rejected(self) -> None:
|
||||
with pytest.raises(ValueError, match="Unsupported platform"):
|
||||
KotlinLanguageServer.DependencyProvider._create_artifact(DEFAULT_KOTLIN_LSP_VERSION, PlatformId.LINUX_x86)
|
||||
@@ -2,9 +2,11 @@ import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
from test.conftest import language_server_tests_enabled
|
||||
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
|
||||
@pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.KOTLIN), reason="Kotlin tests are disabled")
|
||||
@pytest.mark.kotlin
|
||||
class TestKotlinDiagnostics:
|
||||
@pytest.mark.parametrize("language_server", [LanguageServerId.KOTLIN], indirect=True)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Regression tests for Kotlin LSP startup readiness handling."""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.language_servers.kotlin_language_server import KotlinLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
pytestmark = pytest.mark.kotlin
|
||||
|
||||
_REQUIRED_CAPABILITIES = {
|
||||
"textDocumentSync": 1,
|
||||
"hoverProvider": True,
|
||||
"completionProvider": {},
|
||||
"signatureHelpProvider": {},
|
||||
"definitionProvider": True,
|
||||
"referencesProvider": True,
|
||||
"documentSymbolProvider": True,
|
||||
"workspaceSymbolProvider": True,
|
||||
"semanticTokensProvider": {},
|
||||
}
|
||||
|
||||
|
||||
def _make_server(tmp_path: Path) -> tuple[KotlinLanguageServer, Mock, Mock]:
|
||||
settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(tmp_path / "global"),
|
||||
project_data_path=str(tmp_path / "project"),
|
||||
ls_specific_settings={LanguageServerId.KOTLIN: {}},
|
||||
)
|
||||
server_interface = Mock()
|
||||
with patch.object(KotlinLanguageServer, "_create_language_server_interface", return_value=server_interface):
|
||||
server = KotlinLanguageServer(
|
||||
LanguageServerConfig(ls_id=LanguageServerId.KOTLIN),
|
||||
str(tmp_path),
|
||||
settings,
|
||||
)
|
||||
|
||||
indexing_complete = Mock()
|
||||
indexing_complete.wait.return_value = True
|
||||
intellij_server_ready = Mock()
|
||||
intellij_server_ready.wait.return_value = True
|
||||
server_state = cast(Any, server)
|
||||
server_state._indexing_complete = indexing_complete
|
||||
server_state._intellij_server_ready = intellij_server_ready
|
||||
return server, indexing_complete, intellij_server_ready
|
||||
|
||||
|
||||
def _configure_initialize(server: KotlinLanguageServer, server_name: str | None) -> dict[str, Any]:
|
||||
response: dict[str, Any] = {"capabilities": _REQUIRED_CAPABILITIES}
|
||||
if server_name is not None:
|
||||
response["serverInfo"] = {"name": server_name}
|
||||
language_server_interface = cast(Any, server.server)
|
||||
language_server_interface.send.initialize.return_value = response
|
||||
return response
|
||||
|
||||
|
||||
def test_modern_server_registers_and_waits_for_explicit_ready_notification(tmp_path: Path) -> None:
|
||||
server, indexing_complete, intellij_server_ready = _make_server(tmp_path)
|
||||
_configure_initialize(server, "IntelliJ Language Server by JetBrains")
|
||||
events: list[str] = []
|
||||
notification_handlers: dict[str, Any] = {}
|
||||
language_server_interface = cast(Any, server.server)
|
||||
|
||||
def on_notification(method: str, handler: Any) -> None:
|
||||
events.append(f"notification:{method}")
|
||||
notification_handlers[method] = handler
|
||||
|
||||
language_server_interface.on_notification.side_effect = on_notification
|
||||
language_server_interface.start.side_effect = lambda: events.append("start")
|
||||
language_server_interface.send.initialize.side_effect = lambda _params: (
|
||||
events.append("initialize")
|
||||
or {"capabilities": _REQUIRED_CAPABILITIES, "serverInfo": {"name": "IntelliJ Language Server by JetBrains"}}
|
||||
)
|
||||
|
||||
server._start_server()
|
||||
|
||||
assert events.index("notification:intellij/ready-for-test") < events.index("start") < events.index("initialize")
|
||||
intellij_server_ready.clear.assert_called_once_with()
|
||||
intellij_server_ready.wait.assert_called_once_with(timeout=120.0)
|
||||
indexing_complete.wait.assert_not_called()
|
||||
|
||||
notification_handlers["intellij/ready-for-test"](None)
|
||||
intellij_server_ready.set.assert_called_once_with()
|
||||
|
||||
|
||||
def test_legacy_server_keeps_progress_event_wait(tmp_path: Path) -> None:
|
||||
server, indexing_complete, intellij_server_ready = _make_server(tmp_path)
|
||||
_configure_initialize(server, None)
|
||||
|
||||
server._start_server()
|
||||
|
||||
indexing_complete.wait.assert_called_once_with(timeout=120.0)
|
||||
intellij_server_ready.wait.assert_not_called()
|
||||
|
||||
|
||||
def test_modern_ready_timeout_warns_and_continues(tmp_path: Path, caplog: Any) -> None:
|
||||
server, _indexing_complete, intellij_server_ready = _make_server(tmp_path)
|
||||
_configure_initialize(server, "IntelliJ Language Server by JetBrains")
|
||||
intellij_server_ready.wait.return_value = False
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
server._start_server()
|
||||
|
||||
assert "Kotlin LSP did not signal indexing completion within 120s; proceeding anyway" in caplog.text
|
||||
Reference in new issue
Block a user