mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 22:16:23 +00:00
refactor: use rustup-managed rust-analyzer instead of bundling (#495)
rust-analyzer now requires rustup to be installed ## Motivation The previous implementation bundled rust-analyzer binaries that quickly became outdated and required manual updates to Serena's codebase. This caused version mismatches between the bundled rust-analyzer and users' actual Rust toolchain versions. ## Changes - Remove RuntimeDependency-based binary bundling for rust-analyzer - Use rustup-managed rust-analyzer from the user's toolchain - Add automatic installation of rust-analyzer component if missing - Install rustup with minimal profile in Docker images ## Rationale Rust development universally relies on rustup for toolchain management. Unlike some other languages where system packages or bundled binaries make sense, Rust developers already have rustup installed and expect tools to use their toolchain's versions. This approach ensures: - rust-analyzer always matches the user's Rust version - No outdated binaries shipped with Serena - Automatic updates via `rustup update` - Consistency with Rust ecosystem expectations The LSP API of rust-analyzer is stable enough that version differences are not a concern. If users don't have rustup, they'll get a clear error message - this is acceptable since you cannot meaningfully develop Rust without rustup. ## Docker Support Added rustup installation to Docker images to ensure Rust support works in containerized environments. Uses minimal profile to reduce image size while still providing full rust-analyzer functionality.
This commit is contained in:
1 parent
6ebff74f9d
commit
807ff1f56e
5 files changed
+72
-73
No files matched your search
@@ -1,6 +1,10 @@
|
||||
# latest
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
* **Rust support now requires rustup**: The rust-analyzer is no longer bundled with Serena. Instead, it uses the rust-analyzer from your Rust toolchain managed by rustup. This ensures compatibility with your Rust version and eliminates outdated bundled binaries. If you don't have rustup installed, you'll need to install it from https://rustup.rs/
|
||||
|
||||
# 0.1.4
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -13,7 +13,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
**Test Markers:**
|
||||
Available pytest markers for selective testing:
|
||||
- `python`, `go`, `java`, `rust`, `typescript`, `php`, `csharp`, `elixir`, `terraform`, `clojure`
|
||||
- `python`, `go`, `java`, `rust`, `typescript`, `php`, `csharp`, `elixir`, `terraform`, `clojure`, `swift`, `bash`, `ruby`
|
||||
- `snapshot` - for symbolic editing operation tests
|
||||
|
||||
**Project Management:**
|
||||
@@ -100,7 +100,7 @@ Configuration is loaded from (in order of precedence):
|
||||
- **Symbol-based editing** - Uses LSP for precise code manipulation
|
||||
- **Caching strategy** - Reduces language server overhead
|
||||
- **Error recovery** - Automatic language server restart on crashes
|
||||
- **Multi-language support** - 13+ languages with LSP integration
|
||||
- **Multi-language support** - 16+ languages with LSP integration
|
||||
- **MCP protocol** - Exposes tools to AI agents via Model Context Protocol
|
||||
- **Async operation** - Non-blocking language server interactions
|
||||
|
||||
|
||||
@@ -41,6 +41,15 @@ ENV PATH="${PATH}:/root/.local/bin"
|
||||
# Install the latest version of uv
|
||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# Install Rust and rustup for rust-analyzer support (minimal profile)
|
||||
ENV RUSTUP_HOME=/usr/local/rustup
|
||||
ENV CARGO_HOME=/usr/local/cargo
|
||||
ENV PATH="${CARGO_HOME}/bin:${PATH}"
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
|
||||
--default-toolchain stable \
|
||||
--profile minimal \
|
||||
&& rustup component add rust-analyzer
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /workspaces/serena
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ With Serena, we provide
|
||||
* TypeScript/Javascript
|
||||
* PHP (uses Intelephense LSP; set `INTELEPHENSE_LICENSE_KEY` environment variable for premium features)
|
||||
* Go (requires installation of gopls)
|
||||
* Rust
|
||||
* Rust (requires [rustup](https://rustup.rs/) - uses rust-analyzer from your toolchain)
|
||||
* C#
|
||||
* Ruby
|
||||
* Swift
|
||||
|
||||
@@ -5,6 +5,8 @@ Provides Rust specific instantiation of the LanguageServer class. Contains vario
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from overrides import override
|
||||
@@ -16,21 +18,72 @@ from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
|
||||
class RustAnalyzer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _get_rustup_version():
|
||||
"""Get installed rustup version or None if not found."""
|
||||
try:
|
||||
result = subprocess.run(["rustup", "--version"], capture_output=True, text=True, check=False)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_rust_analyzer_path():
|
||||
"""Get rust-analyzer path via rustup or system PATH."""
|
||||
# First try rustup
|
||||
try:
|
||||
result = subprocess.run(["rustup", "which", "rust-analyzer"], capture_output=True, text=True, check=False)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
# Fallback to system PATH
|
||||
return shutil.which("rust-analyzer")
|
||||
|
||||
@staticmethod
|
||||
def _ensure_rust_analyzer_installed():
|
||||
"""Ensure rust-analyzer is available, install via rustup if needed."""
|
||||
path = RustAnalyzer._get_rust_analyzer_path()
|
||||
if path:
|
||||
return path
|
||||
|
||||
# Check if rustup is available
|
||||
if not RustAnalyzer._get_rustup_version():
|
||||
raise RuntimeError(
|
||||
"Neither rust-analyzer nor rustup is installed.\n"
|
||||
"Please install Rust via https://rustup.rs/ or install rust-analyzer separately."
|
||||
)
|
||||
|
||||
# Try to install rust-analyzer component
|
||||
result = subprocess.run(["rustup", "component", "add", "rust-analyzer"], check=False, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Failed to install rust-analyzer via rustup: {result.stderr}")
|
||||
|
||||
# Try again after installation
|
||||
path = RustAnalyzer._get_rust_analyzer_path()
|
||||
if not path:
|
||||
raise RuntimeError("rust-analyzer installation succeeded but binary not found in PATH")
|
||||
|
||||
return path
|
||||
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
rustanalyzer_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
rustanalyzer_executable_path = self._ensure_rust_analyzer_installed()
|
||||
logger.log(f"Using rust-analyzer at: {rustanalyzer_executable_path}", logging.INFO)
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
@@ -48,73 +101,6 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["target"]
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for rust_analyzer and return the command to start the server.
|
||||
"""
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
id="RustAnalyzer",
|
||||
description="RustAnalyzer for macOS (x64)",
|
||||
url="https://github.com/rust-lang/rust-analyzer/releases/download/2025-08-04/rust-analyzer-x86_64-apple-darwin.gz",
|
||||
platform_id="osx-x64",
|
||||
archive_type="gz",
|
||||
binary_name="rust_analyzer",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="RustAnalyzer",
|
||||
description="RustAnalyzer for macOS (arm64)",
|
||||
url="https://github.com/rust-lang/rust-analyzer/releases/download/2025-08-04/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/2025-08-04/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 Linux (arm64)",
|
||||
url="https://github.com/rust-lang/rust-analyzer/releases/download/2025-08-04/rust-analyzer-aarch64-unknown-linux-gnu.gz",
|
||||
platform_id="linux-arm64",
|
||||
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/2025-08-04/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"
|
||||
|
||||
rustanalyzer_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "RustAnalyzer")
|
||||
rustanalyzer_executable_path = deps.binary_path(rustanalyzer_ls_dir)
|
||||
if not os.path.exists(rustanalyzer_ls_dir):
|
||||
os.makedirs(rustanalyzer_ls_dir)
|
||||
deps.install(logger, rustanalyzer_ls_dir)
|
||||
assert os.path.exists(rustanalyzer_executable_path)
|
||||
os.chmod(rustanalyzer_executable_path, 0o755)
|
||||
|
||||
return rustanalyzer_executable_path
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
|
||||
Reference in new issue
Block a user