mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 04:16:34 +00:00
Moved logic of handling updates to csharp dependencies to RuntimeDependencyCollection
Removed the RuntimeDependencyLoader
This commit is contained in:
1 parent
a80fa38d3a
commit
96aec3f885
6 files changed
+76
-129
No files matched your search
@@ -89,7 +89,7 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
)
|
||||
|
||||
clangd_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "clangd")
|
||||
dep = deps.single_for_current_platform()
|
||||
dep = deps.get_single_dep_for_current_platform()
|
||||
clangd_executable_path = deps.binary_path(clangd_ls_dir)
|
||||
if not os.path.exists(clangd_executable_path):
|
||||
logger.log(
|
||||
|
||||
@@ -112,7 +112,7 @@ class ClojureLSP(SolidLanguageServer):
|
||||
"""Setup runtime dependencies for clojure-lsp and return the command to start the server."""
|
||||
verify_clojure_cli()
|
||||
deps = ClojureLSP.runtime_dependencies
|
||||
dependency = deps.single_for_current_platform()
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
clojurelsp_ls_dir = cls.ls_resources_dir(solidlsp_settings)
|
||||
clojurelsp_executable_path = deps.binary_path(clojurelsp_ls_dir)
|
||||
|
||||
@@ -4,8 +4,10 @@ import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from copy import copy
|
||||
from dataclasses import dataclass, replace
|
||||
from typing import Any, cast
|
||||
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
@@ -33,23 +35,49 @@ class RuntimeDependency:
|
||||
class RuntimeDependencyCollection:
|
||||
"""Utility to handle installation of runtime dependencies."""
|
||||
|
||||
def __init__(self, dependencies: Sequence[RuntimeDependency]):
|
||||
self._dependencies = list(dependencies)
|
||||
def __init__(self, dependencies: Sequence[RuntimeDependency], overrides: Iterable[Mapping[str, Any]] = ()) -> None:
|
||||
"""Initialize the collection with a list of dependencies and optional overrides.
|
||||
|
||||
def for_platform(self, platform_id: str) -> list[RuntimeDependency]:
|
||||
return [d for d in self._dependencies if d.platform_id in (platform_id, "any", "platform-agnostic", None)]
|
||||
:param dependencies: List of base RuntimeDependency instances. The combination of 'id' and 'platform_id' must be unique.
|
||||
:param overrides: List of dictionaries which represent overrides or additions to the base dependencies.
|
||||
Each entry must contain at least the 'id' key, and optionally 'platform_id' to uniquely identify the dependency to override.
|
||||
"""
|
||||
self._id_and_platform_id_to_dep: dict[tuple[str, str | None], RuntimeDependency] = {}
|
||||
for dep in dependencies:
|
||||
dep_key = (dep.id, dep.platform_id)
|
||||
if dep_key in self._id_and_platform_id_to_dep:
|
||||
raise ValueError(f"Duplicate runtime dependency with id '{dep.id}' and platform_id '{dep.platform_id}':\n{dep}")
|
||||
self._id_and_platform_id_to_dep[dep_key] = dep
|
||||
|
||||
def for_current_platform(self) -> list[RuntimeDependency]:
|
||||
return self.for_platform(PlatformUtils.get_platform_id().value)
|
||||
for dep_values_override in overrides:
|
||||
override_key = cast(tuple[str, str | None], (dep_values_override["id"], dep_values_override.get("platform_id")))
|
||||
base_dep = self._id_and_platform_id_to_dep.get(override_key)
|
||||
if base_dep is None:
|
||||
new_runtime_dep = RuntimeDependency(**dep_values_override)
|
||||
self._id_and_platform_id_to_dep[override_key] = new_runtime_dep
|
||||
else:
|
||||
updated_dep = copy(base_dep)
|
||||
replace(updated_dep, **dep_values_override)
|
||||
self._id_and_platform_id_to_dep[override_key] = updated_dep
|
||||
|
||||
def single_for_current_platform(self) -> RuntimeDependency:
|
||||
deps = self.for_current_platform()
|
||||
def get_dependencies_for_platform(self, platform_id: str) -> list[RuntimeDependency]:
|
||||
return [d for d in self._id_and_platform_id_to_dep.values() if d.platform_id in (platform_id, "any", "platform-agnostic", None)]
|
||||
|
||||
def get_dependencies_for_current_platform(self) -> list[RuntimeDependency]:
|
||||
return self.get_dependencies_for_platform(PlatformUtils.get_platform_id().value)
|
||||
|
||||
def get_single_dep_for_current_platform(self, dependency_id: str | None = None) -> RuntimeDependency:
|
||||
deps = self.get_dependencies_for_current_platform()
|
||||
if dependency_id is not None:
|
||||
deps = [d for d in deps if d.id == dependency_id]
|
||||
if len(deps) != 1:
|
||||
raise RuntimeError(f"Expected exactly one runtime dependency for {PlatformUtils.get_platform_id().value}, found {len(deps)}")
|
||||
raise RuntimeError(
|
||||
f"Expected exactly one runtime dependency for platform-{PlatformUtils.get_platform_id().value} and {dependency_id=}, found {len(deps)}"
|
||||
)
|
||||
return deps[0]
|
||||
|
||||
def binary_path(self, target_dir: str) -> str:
|
||||
dep = self.single_for_current_platform()
|
||||
dep = self.get_single_dep_for_current_platform()
|
||||
if not dep.binary_name:
|
||||
return target_dir
|
||||
return os.path.join(target_dir, dep.binary_name)
|
||||
@@ -61,7 +89,7 @@ class RuntimeDependencyCollection:
|
||||
"""
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
results: dict[str, str] = {}
|
||||
for dep in self.for_current_platform():
|
||||
for dep in self.get_dependencies_for_current_platform():
|
||||
if dep.url:
|
||||
self._install_from_url(dep, logger, target_dir)
|
||||
if dep.command:
|
||||
@@ -107,6 +135,7 @@ class RuntimeDependencyCollection:
|
||||
|
||||
@staticmethod
|
||||
def _install_from_url(dep: RuntimeDependency, logger: LanguageServerLogger, target_dir: str) -> None:
|
||||
assert dep.url is not None
|
||||
if dep.archive_type == "gz" and dep.binary_name:
|
||||
dest = os.path.join(target_dir, dep.binary_name)
|
||||
FileUtils.download_and_extract_archive(logger, dep.url, dest, dep.archive_type)
|
||||
|
||||
@@ -13,7 +13,7 @@ import threading
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
from typing import Any, cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
@@ -27,10 +27,9 @@ from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from solidlsp.util.zip import SafeZipExtractor
|
||||
|
||||
from .common import RuntimeDependency
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
# Runtime dependencies configuration
|
||||
RUNTIME_DEPENDENCIES = [
|
||||
_RUNTIME_DEPENDENCIES = [
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Microsoft.CodeAnalysis.LanguageServer for Windows (x64)",
|
||||
@@ -180,12 +179,22 @@ def find_solution_or_project_file(root_dir) -> str | None:
|
||||
|
||||
class CSharpLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides C# specific instantiation of the LanguageServer class using Microsoft.CodeAnalysis.LanguageServer.
|
||||
This is the official Roslyn-based language server from Microsoft.
|
||||
Provides C# specific instantiation of the LanguageServer class using `Microsoft.CodeAnalysis.LanguageServer`,
|
||||
the official Roslyn-based language server from Microsoft.
|
||||
|
||||
You can pass the following entries in ls_specific_settings["csharp"]:
|
||||
- dotnet_runtime_url: overrides the .NET runtime download URL
|
||||
- runtime_dependencies: list of dependency definitions merged with RUNTIME_DEPENDENCIES
|
||||
You can pass a list of runtime dependency overrides in ls_specific_settings["csharp"]. This is a list of
|
||||
dicts, each containing at least the "id" key, and optionally "platform_id" to uniquely identify the dependency to override.
|
||||
For example, to override the URL of the .NET runtime on windows-x64, add the entry:
|
||||
|
||||
```
|
||||
{
|
||||
"id": "DotNetRuntime",
|
||||
"platform_id": "win-x64",
|
||||
"url": "https://example.com/custom-dotnet-runtime.zip"
|
||||
}
|
||||
```
|
||||
|
||||
See the `_RUNTIME_DEPENDENCIES` variable above for the available dependency ids and platform_ids.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -238,88 +247,43 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
Ensure .NET runtime and Microsoft.CodeAnalysis.LanguageServer are available.
|
||||
Returns a tuple of (dotnet_path, language_server_dll_path).
|
||||
"""
|
||||
runtime_id = CSharpLanguageServer._get_runtime_id()
|
||||
|
||||
from solidlsp.runtime_config import RuntimeDependencyLoader
|
||||
|
||||
language_enum = cls.get_language_enum_instance()
|
||||
ls_specific_settings = solidlsp_settings.ls_specific_settings or {}
|
||||
language_specific_config = ls_specific_settings.get(language_enum)
|
||||
if language_specific_config is None:
|
||||
language_specific_config = ls_specific_settings.get(str(language_enum))
|
||||
|
||||
runtime_overrides = None
|
||||
if isinstance(language_specific_config, dict):
|
||||
runtime_overrides = cast(list[dict[str, object]] | None, language_specific_config.get("runtime_dependencies"))
|
||||
language_specific_config = ls_specific_settings.get(language_enum, {})
|
||||
runtime_dependency_overrides = cast(list[dict[str, Any]], language_specific_config.get("runtime_dependencies", []))
|
||||
|
||||
logger.log("Resolving runtime dependencies", logging.DEBUG)
|
||||
if language_specific_config is not None:
|
||||
if language_specific_config:
|
||||
logger.log(
|
||||
f"Language-specific config for {language_enum}: {language_specific_config}",
|
||||
logging.DEBUG,
|
||||
)
|
||||
|
||||
all_dependencies = RuntimeDependencyLoader.load_dependencies(
|
||||
base_dependencies=RUNTIME_DEPENDENCIES,
|
||||
override_entries=runtime_overrides,
|
||||
runtime_dependencies = RuntimeDependencyCollection(
|
||||
_RUNTIME_DEPENDENCIES,
|
||||
overrides=runtime_dependency_overrides,
|
||||
)
|
||||
|
||||
logger.log(
|
||||
f"Available runtime dependencies: {[(dep.id, dep.platform_id) for dep in all_dependencies]}",
|
||||
f"Available runtime dependencies: {runtime_dependencies.get_dependencies_for_current_platform}",
|
||||
logging.DEBUG,
|
||||
)
|
||||
|
||||
# Find the dependencies for our platform
|
||||
lang_server_dep = None
|
||||
dotnet_runtime_dep = None
|
||||
|
||||
for dep in all_dependencies:
|
||||
logger.log(
|
||||
f"Inspecting dependency {dep.id} for platform {dep.platform_id}",
|
||||
logging.DEBUG,
|
||||
)
|
||||
if dep.id == "CSharpLanguageServer" and dep.platform_id == runtime_id:
|
||||
lang_server_dep = dep
|
||||
elif dep.id == "DotNetRuntime" and dep.platform_id == runtime_id:
|
||||
dotnet_runtime_dep = dep
|
||||
|
||||
if not lang_server_dep:
|
||||
logger.log(
|
||||
f"No C# language server dependency matched runtime {runtime_id}",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise SolidLSPException(f"No C# language server dependency found for platform {runtime_id}")
|
||||
if not dotnet_runtime_dep:
|
||||
logger.log(
|
||||
f"No .NET runtime dependency matched runtime {runtime_id}",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise SolidLSPException(f"No .NET runtime dependency found for platform {runtime_id}")
|
||||
lang_server_dep = runtime_dependencies.get_single_dep_for_current_platform("CSharpLanguageServer")
|
||||
dotnet_runtime_dep = runtime_dependencies.get_single_dep_for_current_platform("DotNetRuntime")
|
||||
dotnet_path = CSharpLanguageServer._ensure_dotnet_runtime(logger, dotnet_runtime_dep, solidlsp_settings)
|
||||
server_dll_path = CSharpLanguageServer._ensure_language_server(logger, lang_server_dep, solidlsp_settings)
|
||||
|
||||
return dotnet_path, server_dll_path
|
||||
|
||||
@staticmethod
|
||||
def _get_runtime_id() -> str:
|
||||
"""Determine the runtime ID based on the platform."""
|
||||
system = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if system == "windows":
|
||||
return "win-x64" if machine in ["amd64", "x86_64"] else "win-arm64"
|
||||
elif system == "darwin":
|
||||
return "osx-x64" if machine in ["x86_64"] else "osx-arm64"
|
||||
elif system == "linux":
|
||||
return "linux-x64" if machine in ["x86_64", "amd64"] else "linux-arm64"
|
||||
else:
|
||||
raise SolidLSPException(f"Unsupported platform: {system} {machine}")
|
||||
|
||||
@classmethod
|
||||
def _ensure_dotnet_runtime(
|
||||
cls, logger: LanguageServerLogger, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""Ensure .NET runtime is available and return the dotnet executable path."""
|
||||
# TODO: use RuntimeDependency util methods instead of custom validation/download logic
|
||||
|
||||
# Check if dotnet is already available on the system
|
||||
system_dotnet = shutil.which("dotnet")
|
||||
if system_dotnet:
|
||||
@@ -458,6 +422,8 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
Ensure .NET 9 runtime is available using runtime dependency configuration.
|
||||
Returns the path to the dotnet executable.
|
||||
"""
|
||||
# TODO: use RuntimeDependency util methods instead of custom download logic
|
||||
|
||||
# Check if dotnet is already available on the system
|
||||
system_dotnet = shutil.which("dotnet")
|
||||
if system_dotnet:
|
||||
|
||||
@@ -108,7 +108,7 @@ class TerraformLS(SolidLanguageServer):
|
||||
),
|
||||
]
|
||||
)
|
||||
dependency = deps.single_for_current_platform()
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
terraform_ls_executable_path = deps.binary_path(cls.ls_resources_dir(solidlsp_settings))
|
||||
if not os.path.exists(terraform_ls_executable_path):
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
"""Runtime dependency configuration loader for language servers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable, Mapping
|
||||
from dataclasses import asdict
|
||||
from typing import Optional
|
||||
|
||||
from .language_servers.common import RuntimeDependency
|
||||
|
||||
|
||||
class RuntimeDependencyLoader:
|
||||
"""Utility helpers to merge runtime dependency overrides."""
|
||||
|
||||
@staticmethod
|
||||
def load_dependencies(
|
||||
base_dependencies: Iterable[RuntimeDependency],
|
||||
override_entries: Optional[Iterable[Mapping[str, object]]] = None,
|
||||
) -> list[RuntimeDependency]:
|
||||
"""Merge *override_entries* into *base_dependencies*.
|
||||
|
||||
Entries are matched by the tuple ``(id, platform_id)``. If an override provides only a
|
||||
subset of the ``RuntimeDependency`` fields, the remaining values will fall back to the
|
||||
corresponding base dependency.
|
||||
"""
|
||||
# Ensure we work on a copy in order to keep the base definition immutable for callers
|
||||
merged: dict[tuple[str | None, str | None], RuntimeDependency] = {(dep.id, dep.platform_id): dep for dep in base_dependencies}
|
||||
|
||||
if not override_entries:
|
||||
return list(merged.values())
|
||||
|
||||
for entry in override_entries:
|
||||
entry_id = entry.get("id")
|
||||
if entry_id is None:
|
||||
raise ValueError("Runtime dependency override requires an 'id' value")
|
||||
|
||||
key = (entry_id, entry.get("platform_id"))
|
||||
base_dep = merged.get(key)
|
||||
|
||||
if base_dep is not None:
|
||||
dep_data = asdict(base_dep)
|
||||
dep_data.update(entry)
|
||||
else:
|
||||
dep_data = dict(entry)
|
||||
|
||||
merged[key] = RuntimeDependency(**dep_data)
|
||||
|
||||
return list(merged.values())
|
||||
Reference in new issue
Block a user