diff --git a/src/solidlsp/language_servers/clangd_language_server.py b/src/solidlsp/language_servers/clangd_language_server.py index 871d7c4..f787d2e 100644 --- a/src/solidlsp/language_servers/clangd_language_server.py +++ b/src/solidlsp/language_servers/clangd_language_server.py @@ -12,6 +12,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import FileUtils, PlatformUtils +from .common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -47,58 +48,44 @@ class ClangdLanguageServer(SolidLanguageServer): """ platform_id = PlatformUtils.get_platform_id() - runtime_dependencies = [ - { - "id": "Clangd", - "description": "Clangd for Linux (x64)", - "url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-linux-19.1.2.zip", - "platformId": "linux-x64", - "archiveType": "zip", - "binaryName": "clangd", - }, - { - "id": "Clangd", - "description": "Clangd for Windows (x64)", - "url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-windows-19.1.2.zip", - "platformId": "win-x64", - "archiveType": "zip", - "binaryName": "clangd.exe", - }, - { - "id": "Clangd", - "description": "Clangd for macOS (Arm64)", - "url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip", - "platformId": "osx-arm64", - "archiveType": "zip", - "binaryName": "clangd", - }, - ] - - assert platform_id.value in [ - "linux-x64", - "win-x64", - "osx-arm64", - ], ( - "Unsupported platform: " + platform_id.value + deps = RuntimeDependencyCollection( + [ + RuntimeDependency( + id="Clangd", + description="Clangd for Linux (x64)", + url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-linux-19.1.2.zip", + platform_id="linux-x64", + archive_type="zip", + binary_name="clangd_19.1.2/bin/clangd", + ), + RuntimeDependency( + id="Clangd", + description="Clangd for Windows (x64)", + url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-windows-19.1.2.zip", + platform_id="win-x64", + archive_type="zip", + binary_name="clangd_19.1.2/bin/clangd.exe", + ), + RuntimeDependency( + id="Clangd", + description="Clangd for macOS (Arm64)", + url="https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip", + platform_id="osx-arm64", + archive_type="zip", + binary_name="clangd_19.1.2/bin/clangd", + ), + ] ) - runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] - assert len(runtime_dependencies) == 1 - # Select dependency matching the current platform - dependency = next((dep for dep in runtime_dependencies if dep["platformId"] == platform_id.value), None) - if dependency is None: - raise RuntimeError(f"No runtime dependency found for platform {platform_id.value}") - clangd_ls_dir = os.path.join(os.path.dirname(__file__), "static", "clangd") - clangd_executable_path = os.path.join(clangd_ls_dir, "clangd_19.1.2", "bin", dependency["binaryName"]) + dep = deps.single_for_current_platform() + clangd_executable_path = deps.binary_path(clangd_ls_dir) if not os.path.exists(clangd_executable_path): - clangd_url = dependency["url"] - logger.log(f"Clangd executable not found at {clangd_executable_path}. Downloading from {clangd_url}", logging.INFO) - os.makedirs(clangd_ls_dir, exist_ok=True) - if dependency["archiveType"] == "zip": - FileUtils.download_and_extract_archive(logger, clangd_url, clangd_ls_dir, dependency["archiveType"]) - else: - raise RuntimeError(f"Unsupported archive type: {dependency['archiveType']}") + logger.log( + f"Clangd executable not found at {clangd_executable_path}. Downloading from {dep.url}", + logging.INFO, + ) + deps.install(logger, clangd_ls_dir) if not os.path.exists(clangd_executable_path): raise FileNotFoundError( f"Clangd executable not found at {clangd_executable_path}.\n" diff --git a/src/solidlsp/language_servers/clojure_lsp.py b/src/solidlsp/language_servers/clojure_lsp.py index 0fe06e6..5a8d124 100644 --- a/src/solidlsp/language_servers/clojure_lsp.py +++ b/src/solidlsp/language_servers/clojure_lsp.py @@ -14,6 +14,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import FileUtils, PlatformUtils +from .common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -44,32 +45,38 @@ class ClojureLSP(SolidLanguageServer): """ clojure_lsp_releases = "https://github.com/clojure-lsp/clojure-lsp/releases/latest/download" - runtime_dependencies = [ - { - "url": f"{clojure_lsp_releases}/clojure-lsp-native-macos-aarch64.zip", - "platformId": "osx-arm64", - "archiveType": "zip", - "binaryName": "clojure-lsp", - }, - { - "url": f"{clojure_lsp_releases}/clojure-lsp-native-linux-aarch64.zip", - "platformId": "linux-arm64", - "archiveType": "zip", - "binaryName": "clojure-lsp", - }, - { - "url": f"{clojure_lsp_releases}/clojure-lsp-native-linux-amd64.zip", - "platformId": "linux-x64", - "archiveType": "zip", - "binaryName": "clojure-lsp", - }, - { - "url": f"{clojure_lsp_releases}/clojure-lsp-native-windows-amd64.zip", - "platformId": "win-x64", - "archiveType": "zip", - "binaryName": "clojure-lsp.exe", - }, - ] + runtime_dependencies = RuntimeDependencyCollection( + [ + RuntimeDependency( + id="clojure-lsp", + url=f"{clojure_lsp_releases}/clojure-lsp-native-macos-aarch64.zip", + platform_id="osx-arm64", + archive_type="zip", + binary_name="clojure-lsp", + ), + RuntimeDependency( + id="clojure-lsp", + url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-aarch64.zip", + platform_id="linux-arm64", + archive_type="zip", + binary_name="clojure-lsp", + ), + RuntimeDependency( + id="clojure-lsp", + url=f"{clojure_lsp_releases}/clojure-lsp-native-linux-amd64.zip", + platform_id="linux-x64", + archive_type="zip", + binary_name="clojure-lsp", + ), + RuntimeDependency( + id="clojure-lsp", + url=f"{clojure_lsp_releases}/clojure-lsp-native-windows-amd64.zip", + platform_id="win-x64", + archive_type="zip", + binary_name="clojure-lsp.exe", + ), + ] + ) def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str): """ @@ -92,21 +99,18 @@ class ClojureLSP(SolidLanguageServer): def _setup_runtime_dependencies(logger: LanguageServerLogger, config: LanguageServerConfig) -> str: """Setup runtime dependencies for clojure-lsp and return the command to start the server.""" verify_clojure_cli() - platform_id = PlatformUtils.get_platform_id() - - runtime_dependencies = [ - dependency for dependency in ClojureLSP.runtime_dependencies if dependency["platformId"] == platform_id.value - ] - if len(runtime_dependencies) != 1: - raise RuntimeError(f"Could not find a suitable clojure-lsp runtime dependency for platform {platform_id.value}.") - dependency = runtime_dependencies[0] + deps = ClojureLSP.runtime_dependencies + dependency = deps.single_for_current_platform() clojurelsp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "clojure-lsp") - clojurelsp_executable_path = os.path.join(clojurelsp_ls_dir, dependency["binaryName"]) + clojurelsp_executable_path = deps.binary_path(clojurelsp_ls_dir) if not os.path.exists(clojurelsp_executable_path): os.makedirs(clojurelsp_ls_dir, exist_ok=True) - logger.log(f"Downloading and extracting clojure-lsp from {dependency['url']} to {clojurelsp_ls_dir}", logging.INFO) - FileUtils.download_and_extract_archive(logger, dependency["url"], clojurelsp_ls_dir, dependency["archiveType"]) + logger.log( + f"Downloading and extracting clojure-lsp from {dependency.url} to {clojurelsp_ls_dir}", + logging.INFO, + ) + deps.install(logger, clojurelsp_ls_dir) if not os.path.exists(clojurelsp_executable_path): raise FileNotFoundError(f"Download failed? Could not find clojure-lsp executable at {clojurelsp_executable_path}") os.chmod(clojurelsp_executable_path, stat.S_IEXEC) diff --git a/src/solidlsp/language_servers/common.py b/src/solidlsp/language_servers/common.py new file mode 100644 index 0000000..78ae7cb --- /dev/null +++ b/src/solidlsp/language_servers/common.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import os +import subprocess +from dataclasses import dataclass +from typing import Sequence, Dict + +from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_utils import FileUtils, PlatformUtils + + +@dataclass(kw_only=True) +class RuntimeDependency: + """Represents a runtime dependency for a language server.""" + + id: str + platform_id: str | None = None + url: str | None = None + archive_type: str | None = None + binary_name: str | None = None + command: str | None = None + package_name: str | None = None + package_version: str | None = None + extract_path: str | None = None + description: str | None = None + + +class RuntimeDependencyCollection: + """Utility to handle installation of runtime dependencies.""" + + def __init__(self, dependencies: Sequence[RuntimeDependency]): + self._dependencies = list(dependencies) + + 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) + ] + + def for_current_platform(self) -> list[RuntimeDependency]: + return self.for_platform(PlatformUtils.get_platform_id().value) + + def single_for_current_platform(self) -> RuntimeDependency: + deps = self.for_current_platform() + if len(deps) != 1: + raise RuntimeError( + f"Expected exactly one runtime dependency for {PlatformUtils.get_platform_id().value}, found {len(deps)}" + ) + return deps[0] + + def binary_path(self, target_dir: str) -> str: + dep = self.single_for_current_platform() + if not dep.binary_name: + return target_dir + return os.path.join(target_dir, dep.binary_name) + + def install(self, logger: LanguageServerLogger, target_dir: str) -> Dict[str, str]: + """Install all dependencies for the current platform into *target_dir*. + + Returns a mapping from dependency id to the resolved binary path. + """ + os.makedirs(target_dir, exist_ok=True) + results: Dict[str, str] = {} + for dep in self.for_current_platform(): + if dep.url: + self._install_from_url(dep, logger, target_dir) + if dep.command: + self._run_command(dep.command, target_dir) + if dep.binary_name: + results[dep.id] = os.path.join(target_dir, dep.binary_name) + else: + results[dep.id] = target_dir + return results + + @staticmethod + def _run_command(command: str, cwd: str) -> None: + if PlatformUtils.get_platform_id().value.startswith("win"): + subprocess.run( + command, + shell=True, + check=True, + cwd=cwd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + else: + import pwd + + user = pwd.getpwuid(os.getuid()).pw_name + subprocess.run( + command, + shell=True, + check=True, + user=user, + cwd=cwd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + @staticmethod + def _install_from_url(dep: RuntimeDependency, logger: LanguageServerLogger, target_dir: str) -> 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) + else: + FileUtils.download_and_extract_archive(logger, dep.url, target_dir, dep.archive_type or "zip") diff --git a/src/solidlsp/language_servers/csharp_language_server.py b/src/solidlsp/language_servers/csharp_language_server.py index d07e9ba..4ead95d 100644 --- a/src/solidlsp/language_servers/csharp_language_server.py +++ b/src/solidlsp/language_servers/csharp_language_server.py @@ -13,10 +13,11 @@ import tarfile import threading import urllib.request import zipfile -from dataclasses import dataclass from pathlib import Path from typing import cast +from .common import RuntimeDependency + from overrides import override from solidlsp.ls import SolidLanguageServer @@ -28,21 +29,6 @@ from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo -@dataclass(kw_only=True) -class RuntimeDependency: - """Represents a runtime dependency for the C# language server.""" - - id: str - description: str | None - platform_id: str - archive_type: str - binary_name: str - package_name: str | None = None - package_version: str | None = None - extract_path: str | None = None - url: str | None = None - - # Runtime dependencies configuration RUNTIME_DEPENDENCIES = [ RuntimeDependency( diff --git a/src/solidlsp/language_servers/dart_language_server.py b/src/solidlsp/language_servers/dart_language_server.py index 42ba5cf..b369f86 100644 --- a/src/solidlsp/language_servers/dart_language_server.py +++ b/src/solidlsp/language_servers/dart_language_server.py @@ -6,6 +6,7 @@ import stat from solidlsp.ls import SolidLanguageServer from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import FileUtils, PlatformUtils +from .common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -31,60 +32,57 @@ class DartLanguageServer(SolidLanguageServer): def _setup_runtime_dependencies(cls, logger: "LanguageServerLogger") -> str: platform_id = PlatformUtils.get_platform_id() - runtime_dependencies = [ - { - "id": "DartLanguageServer", - "description": "Dart Language Server for Linux (x64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-linux-x64-release.zip", - "platformId": "linux-x64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart", - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for Windows (x64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-x64-release.zip", - "platformId": "win-x64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart.exe", - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for Windows (arm64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-arm64-release.zip", - "platformId": "win-arm64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart.exe", - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for macOS (x64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-x64-release.zip", - "platformId": "osx-x64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart", - }, - { - "id": "DartLanguageServer", - "description": "Dart Language Server for macOS (arm64)", - "url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-arm64-release.zip", - "platformId": "osx-arm64", - "archiveType": "zip", - "binaryName": "dart-sdk/bin/dart", - }, - ] - - runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] - - assert len(runtime_dependencies) == 1 - dependency = runtime_dependencies[0] + deps = RuntimeDependencyCollection( + [ + RuntimeDependency( + id="DartLanguageServer", + description="Dart Language Server for Linux (x64)", + url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-linux-x64-release.zip", + platform_id="linux-x64", + archive_type="zip", + binary_name="dart-sdk/bin/dart", + ), + RuntimeDependency( + id="DartLanguageServer", + description="Dart Language Server for Windows (x64)", + url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-x64-release.zip", + platform_id="win-x64", + archive_type="zip", + binary_name="dart-sdk/bin/dart.exe", + ), + RuntimeDependency( + id="DartLanguageServer", + description="Dart Language Server for Windows (arm64)", + url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-arm64-release.zip", + platform_id="win-arm64", + archive_type="zip", + binary_name="dart-sdk/bin/dart.exe", + ), + RuntimeDependency( + id="DartLanguageServer", + description="Dart Language Server for macOS (x64)", + url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-x64-release.zip", + platform_id="osx-x64", + archive_type="zip", + binary_name="dart-sdk/bin/dart", + ), + RuntimeDependency( + id="DartLanguageServer", + description="Dart Language Server for macOS (arm64)", + url="https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-arm64-release.zip", + platform_id="osx-arm64", + archive_type="zip", + binary_name="dart-sdk/bin/dart", + ), + ] + ) dart_ls_dir = cls.ls_resources_dir() - dart_executable_path = os.path.join(dart_ls_dir, dependency["binaryName"]) + dart_executable_path = deps.binary_path(dart_ls_dir) if not os.path.exists(dart_ls_dir): os.makedirs(dart_ls_dir) - FileUtils.download_and_extract_archive(logger, dependency["url"], dart_ls_dir, dependency["archiveType"]) + deps.install(logger, dart_ls_dir) assert os.path.exists(dart_executable_path) os.chmod(dart_executable_path, stat.S_IEXEC) diff --git a/src/solidlsp/language_servers/intelephense.py b/src/solidlsp/language_servers/intelephense.py index e52c822..277b045 100644 --- a/src/solidlsp/language_servers/intelephense.py +++ b/src/solidlsp/language_servers/intelephense.py @@ -15,6 +15,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import PlatformId, PlatformUtils +from .common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -61,32 +62,16 @@ class Intelephense(SolidLanguageServer): os.makedirs(intelephense_ls_dir, exist_ok=True) intelephense_executable_path = os.path.join(intelephense_ls_dir, "node_modules", ".bin", "intelephense") if not os.path.exists(intelephense_executable_path): - install_command = "npm install --prefix ./ intelephense@1.14.4" - is_windows = PlatformUtils.get_platform_id().value.startswith("win") - # Windows doesn't support the 'user' parameter and doesn't have pwd module - if is_windows: - subprocess.run( - install_command, - shell=True, - check=True, - cwd=intelephense_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - else: - # On Unix-like systems, run as non-root user - import pwd - - user = pwd.getpwuid(os.getuid()).pw_name - subprocess.run( - install_command, - shell=True, - check=True, - user=user, - cwd=intelephense_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) + deps = RuntimeDependencyCollection( + [ + RuntimeDependency( + id="intelephense", + command="npm install --prefix ./ intelephense@1.14.4", + platform_id="any", + ) + ] + ) + deps.install(logger, intelephense_ls_dir) assert os.path.exists( intelephense_executable_path diff --git a/src/solidlsp/language_servers/rust_analyzer.py b/src/solidlsp/language_servers/rust_analyzer.py index 4d968ae..0b8442d 100644 --- a/src/solidlsp/language_servers/rust_analyzer.py +++ b/src/solidlsp/language_servers/rust_analyzer.py @@ -14,6 +14,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import FileUtils, PlatformUtils +from .common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -51,50 +52,47 @@ class RustAnalyzer(SolidLanguageServer): """ platform_id = PlatformUtils.get_platform_id() - runtime_dependencies = [ - { - "id": "RustAnalyzer", - "description": "RustAnalyzer for Linux (x64)", - "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-aarch64-apple-darwin.gz", - "platformId": "osx-arm64", - "archiveType": "gz", - "binaryName": "rust_analyzer", - }, - { - "id": "RustAnalyzer", - "description": "RustAnalyzer for Linux (x64)", - "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-unknown-linux-gnu.gz", - "platformId": "linux-x64", - "archiveType": "gz", - "binaryName": "rust_analyzer", - }, - { - "id": "RustAnalyzer", - "description": "RustAnalyzer for Windows (x64)", - "url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-pc-windows-msvc.zip", - "platformId": "win-x64", - "archiveType": "zip", - "binaryName": "rust-analyzer.exe", - }, - ] + deps = RuntimeDependencyCollection( + [ + RuntimeDependency( + id="RustAnalyzer", + description="RustAnalyzer for macOS (arm64)", + url="https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-aarch64-apple-darwin.gz", + platform_id="osx-arm64", + archive_type="gz", + binary_name="rust_analyzer", + ), + RuntimeDependency( + id="RustAnalyzer", + description="RustAnalyzer for Linux (x64)", + url="https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-unknown-linux-gnu.gz", + platform_id="linux-x64", + archive_type="gz", + binary_name="rust_analyzer", + ), + RuntimeDependency( + id="RustAnalyzer", + description="RustAnalyzer for Windows (x64)", + url="https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-pc-windows-msvc.zip", + platform_id="win-x64", + archive_type="zip", + binary_name="rust-analyzer.exe", + ), + ] + ) # assert platform_id.value in [ # "linux-x64", # "win-x64", # ], "Only linux-x64 and win-x64 platform is supported for in multilspy at the moment" - runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] - assert len(runtime_dependencies) == 1 - dependency = runtime_dependencies[0] + dependency = deps.single_for_current_platform() rustanalyzer_ls_dir = os.path.join(cls.ls_resources_dir(), "RustAnalyzer") - rustanalyzer_executable_path = os.path.join(rustanalyzer_ls_dir, dependency["binaryName"]) + rustanalyzer_executable_path = deps.binary_path(rustanalyzer_ls_dir) if not os.path.exists(rustanalyzer_ls_dir): os.makedirs(rustanalyzer_ls_dir) - if dependency["archiveType"] == "gz": - FileUtils.download_and_extract_archive(logger, dependency["url"], rustanalyzer_executable_path, dependency["archiveType"]) - else: - FileUtils.download_and_extract_archive(logger, dependency["url"], rustanalyzer_ls_dir, dependency["archiveType"]) + deps.install(logger, rustanalyzer_ls_dir) assert os.path.exists(rustanalyzer_executable_path) os.chmod(rustanalyzer_executable_path, stat.S_IEXEC) diff --git a/src/solidlsp/language_servers/terraform_ls.py b/src/solidlsp/language_servers/terraform_ls.py index 14b9092..b60aa2d 100644 --- a/src/solidlsp/language_servers/terraform_ls.py +++ b/src/solidlsp/language_servers/terraform_ls.py @@ -10,6 +10,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import FileUtils, PathUtils, PlatformUtils +from .common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -62,50 +63,48 @@ class TerraformLS(SolidLanguageServer): """ cls._ensure_tf_command_available(logger) platform_id = PlatformUtils.get_platform_id() - runtime_dependencies = [ - { - "id": "TerraformLS", - "description": "terraform-ls for macOS (ARM64)", - "url": "https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_arm64.zip", - "platformId": "osx-arm64", - "archiveType": "zip", - "binaryName": "terraform-ls", - }, - { - "id": "TerraformLS", - "description": "terraform-ls for macOS (x64)", - "url": "https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_amd64.zip", - "platformId": "osx-x64", - "archiveType": "zip", - "binaryName": "terraform-ls", - }, - { - "id": "TerraformLS", - "description": "terraform-ls for Linux (x64)", - "url": "https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_linux_amd64.zip", - "platformId": "linux-x64", - "archiveType": "zip", - "binaryName": "terraform-ls", - }, - { - "id": "TerraformLS", - "description": "terraform-ls for Windows (x64)", - "url": "https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_windows_amd64.zip", - "platformId": "win-x64", - "archiveType": "zip", - "binaryName": "terraform-ls.exe", - }, - ] - runtime_dependencies = [dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value] - assert ( - len(runtime_dependencies) == 1 - ), f"Expected exactly one runtime dependency for platform {platform_id.value}, found {len(runtime_dependencies)}" - dependency = runtime_dependencies[0] + deps = RuntimeDependencyCollection( + [ + RuntimeDependency( + id="TerraformLS", + description="terraform-ls for macOS (ARM64)", + url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_arm64.zip", + platform_id="osx-arm64", + archive_type="zip", + binary_name="terraform-ls", + ), + RuntimeDependency( + id="TerraformLS", + description="terraform-ls for macOS (x64)", + url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_darwin_amd64.zip", + platform_id="osx-x64", + archive_type="zip", + binary_name="terraform-ls", + ), + RuntimeDependency( + id="TerraformLS", + description="terraform-ls for Linux (x64)", + url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_linux_amd64.zip", + platform_id="linux-x64", + archive_type="zip", + binary_name="terraform-ls", + ), + RuntimeDependency( + id="TerraformLS", + description="terraform-ls for Windows (x64)", + url="https://releases.hashicorp.com/terraform-ls/0.36.5/terraform-ls_0.36.5_windows_amd64.zip", + platform_id="win-x64", + archive_type="zip", + binary_name="terraform-ls.exe", + ), + ] + ) + dependency = deps.single_for_current_platform() - terraform_ls_executable_path = os.path.join(cls.ls_resources_dir(), dependency["binaryName"]) + terraform_ls_executable_path = deps.binary_path(cls.ls_resources_dir()) if not os.path.exists(terraform_ls_executable_path): - logger.log(f"Downloading terraform-ls from {dependency['url']}", logging.INFO) - FileUtils.download_and_extract_archive(logger, dependency["url"], cls.ls_resources_dir(), dependency["archiveType"]) + logger.log(f"Downloading terraform-ls from {dependency.url}", logging.INFO) + deps.install(logger, cls.ls_resources_dir()) assert os.path.exists(terraform_ls_executable_path), f"terraform-ls executable not found at {terraform_ls_executable_path}" diff --git a/src/solidlsp/language_servers/typescript_language_server.py b/src/solidlsp/language_servers/typescript_language_server.py index 175adfa..54dd124 100644 --- a/src/solidlsp/language_servers/typescript_language_server.py +++ b/src/solidlsp/language_servers/typescript_language_server.py @@ -16,6 +16,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_utils import PlatformId, PlatformUtils +from .common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo @@ -82,18 +83,22 @@ class TypeScriptLanguageServer(SolidLanguageServer): ] assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment" - runtime_dependencies = [ - { - "id": "typescript", - "description": "typescript package for Linux, OSX, and Windows. Both x64 and arm64 are supported.", - "command": "npm install --prefix ./ typescript@5.5.4", - }, - { - "id": "typescript-language-server", - "description": "typescript-language-server package for Linux, OSX, and Windows. Both x64 and arm64 are supported.", - "command": "npm install --prefix ./ typescript-language-server@4.3.3", - }, - ] + deps = RuntimeDependencyCollection( + [ + RuntimeDependency( + id="typescript", + description="typescript package", + command="npm install --prefix ./ typescript@5.5.4", + platform_id="any", + ), + RuntimeDependency( + id="typescript-language-server", + description="typescript-language-server package", + command="npm install --prefix ./ typescript-language-server@4.3.3", + platform_id="any", + ), + ] + ) tsserver_ls_dir = os.path.join(cls.ls_resources_dir(), "ts-lsp") tsserver_executable_path = os.path.join(tsserver_ls_dir, "typescript-language-server") @@ -106,29 +111,7 @@ class TypeScriptLanguageServer(SolidLanguageServer): # Install typescript and typescript-language-server if not already installed if not os.path.exists(tsserver_ls_dir): os.makedirs(tsserver_ls_dir, exist_ok=True) - for dependency in runtime_dependencies: - # Windows doesn't support the 'user' parameter and doesn't have pwd module - if PlatformUtils.get_platform_id().value.startswith("win"): - subprocess.run( - dependency["command"], - shell=True, - check=True, - cwd=tsserver_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - else: - # On Unix-like systems, run as non-root user - user = pwd.getpwuid(os.getuid()).pw_name - subprocess.run( - dependency["command"], - shell=True, - check=True, - user=user, - cwd=tsserver_ls_dir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) + deps.install(logger, tsserver_ls_dir) tsserver_executable_path = os.path.join(tsserver_ls_dir, "node_modules", ".bin", "typescript-language-server")