diff --git a/CHANGELOG.md b/CHANGELOG.md index ce43a05e..c39a82d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Status of the `main` branch. Changes prior to the next official version change w - The `languages` key in project configurations was changed to `language_servers` to better reflect the actual semantics (configurations are automatically migrated) - Fix: glob matching bare `*` and `?` in non-`**` patterns matched across `/`, contradicting documented behaviour #1732 + - Language servers and their dependency providers now go through the `subprocess_run` helper instead of + calling `subprocess.run` directly, so all such subprocesses get `stdin=DEVNULL` and can no longer + interfere with the stdio MCP connection (as happened for Julia, #1577) #1748 * Language Servers: - Allow language server priorities to be configured in `serena_config.yml` (for auto-detection during diff --git a/src/serena/util/dotnet.py b/src/serena/util/dotnet.py index 99b34526..595fdf68 100644 --- a/src/serena/util/dotnet.py +++ b/src/serena/util/dotnet.py @@ -8,6 +8,7 @@ from pathlib import Path from serena.util.version import Version from solidlsp.ls_exceptions import SolidLSPException +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -27,7 +28,7 @@ class DotNETUtil: def _determine_installed_versions(self) -> list[Version]: if self._system_dotnet: try: - result = subprocess.run([self._system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True) + result = subprocess_run([self._system_dotnet, "--list-runtimes"], capture_output=True, text=True, check=True) version_strings = re.findall(r"Microsoft.NETCore.App\s+([^\s]+)", result.stdout) log.info("Installed .NET runtime versions: %s", version_strings) return [Version(v) for v in version_strings] @@ -138,7 +139,7 @@ class DotNETUtil: # Run the install script log.info("Running .NET install script: %s", cmd) - result = subprocess.run(cmd, capture_output=True, text=True, check=True) + result = subprocess_run(cmd, capture_output=True, text=True, check=True) log.debug(f"Install script output: {result.stdout}") if not dotnet_exe.exists(): diff --git a/src/solidlsp/language_servers/bsl_language_server.py b/src/solidlsp/language_servers/bsl_language_server.py index b3fc8f89..4d40acdf 100644 --- a/src/solidlsp/language_servers/bsl_language_server.py +++ b/src/solidlsp/language_servers/bsl_language_server.py @@ -27,6 +27,7 @@ from solidlsp.ls import ( ) from solidlsp.ls_config import LanguageServerConfig from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -50,7 +51,7 @@ def _get_java_major_version(java_exe: str) -> int | None: :param java_exe: path to the ``java`` executable. """ try: - result = subprocess.run( + result = subprocess_run( [java_exe, "-version"], capture_output=True, text=True, diff --git a/src/solidlsp/language_servers/clojure_lsp.py b/src/solidlsp/language_servers/clojure_lsp.py index 2a8554d1..a8cb9d83 100644 --- a/src/solidlsp/language_servers/clojure_lsp.py +++ b/src/solidlsp/language_servers/clojure_lsp.py @@ -15,6 +15,7 @@ from overrides import override from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run from .common import RuntimeDependency, RuntimeDependencyCollection @@ -57,8 +58,13 @@ CLOJURE_LSP_ALLOWED_HOSTS = ( def run_command(cmd: list, capture_output: bool = True) -> subprocess.CompletedProcess: - return subprocess.run( - cmd, stdout=subprocess.PIPE if capture_output else None, stderr=subprocess.STDOUT if capture_output else None, text=True, check=True + return subprocess_run( + cmd, + capture_output=False, + stdout=subprocess.PIPE if capture_output else None, + stderr=subprocess.STDOUT if capture_output else None, + text=True, + check=True, ) diff --git a/src/solidlsp/language_servers/common.py b/src/solidlsp/language_servers/common.py index 4a28cca3..e5e7ca84 100644 --- a/src/solidlsp/language_servers/common.py +++ b/src/solidlsp/language_servers/common.py @@ -110,12 +110,13 @@ class RuntimeDependencyCollection: command = subprocess_util.convert_shell_cmd(command) log.info("Running command %s in '%s'", f"'{command}'" if isinstance(command, str) else command, cwd) - completed_process = subprocess.run( + completed_process = subprocess_util.subprocess_run( command, shell=True, check=False, cwd=cwd, - stdin=subprocess.DEVNULL, + capture_output=False, + text=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs, diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py index b4b86e96..a67392fb 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls.py @@ -27,6 +27,7 @@ from solidlsp.ls_types import UnifiedSymbolInformation from solidlsp.ls_utils import FileUtils, PlatformUtils from solidlsp.lsp_protocol_handler.lsp_types import DocumentSymbol, SymbolInformation from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -730,7 +731,7 @@ class EclipseJDTLS(SolidLanguageServer): """ try: # both -XshowSettings:properties and -version write to stderr by convention - result = subprocess.run( + result = subprocess_run( [java_exe, "-XshowSettings:properties", "-version"], capture_output=True, text=True, diff --git a/src/solidlsp/language_servers/elixir_tools/elixir_tools.py b/src/solidlsp/language_servers/elixir_tools/elixir_tools.py index 01e64513..4ef80629 100644 --- a/src/solidlsp/language_servers/elixir_tools/elixir_tools.py +++ b/src/solidlsp/language_servers/elixir_tools/elixir_tools.py @@ -2,7 +2,6 @@ import logging import os import pathlib import stat -import subprocess import threading from collections.abc import Hashable from typing import Any @@ -14,6 +13,7 @@ from solidlsp.ls_config import LanguageServerConfig, LanguageServerId from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run from ..common import RuntimeDependency @@ -64,7 +64,7 @@ class ElixirTools(SolidLanguageServer): def _get_elixir_version(cls) -> str | None: """Get the installed Elixir version or None if not found.""" try: - result = subprocess.run(["elixir", "--version"], capture_output=True, text=True, check=False) + result = subprocess_run(["elixir", "--version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: diff --git a/src/solidlsp/language_servers/erlang_language_server.py b/src/solidlsp/language_servers/erlang_language_server.py index ebf9008c..833e0c8d 100644 --- a/src/solidlsp/language_servers/erlang_language_server.py +++ b/src/solidlsp/language_servers/erlang_language_server.py @@ -14,6 +14,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -50,7 +51,7 @@ class ErlangLanguageServer(SolidLanguageServer): def _check_erlang_installation(self) -> bool: """Check if Erlang/OTP is available.""" try: - result = subprocess.run(["erl", "-version"], check=False, capture_output=True, text=True, timeout=10) + result = subprocess_run(["erl", "-version"], check=False, capture_output=True, text=True, timeout=10) return result.returncode == 0 except (subprocess.SubprocessError, FileNotFoundError): return False @@ -59,7 +60,7 @@ class ErlangLanguageServer(SolidLanguageServer): def _get_erlang_version(cls) -> str | None: """Get the installed Erlang/OTP version or None if not found.""" try: - result = subprocess.run(["erl", "-version"], check=False, capture_output=True, text=True, timeout=10) + result = subprocess_run(["erl", "-version"], check=False, capture_output=True, text=True, timeout=10) if result.returncode == 0: return result.stderr.strip() # erl -version outputs to stderr except (subprocess.SubprocessError, FileNotFoundError): @@ -70,7 +71,7 @@ class ErlangLanguageServer(SolidLanguageServer): def _check_rebar3_available(cls) -> bool: """Check if rebar3 build tool is available.""" try: - result = subprocess.run(["rebar3", "version"], check=False, capture_output=True, text=True, timeout=10) + result = subprocess_run(["rebar3", "version"], check=False, capture_output=True, text=True, timeout=10) return result.returncode == 0 except (subprocess.SubprocessError, FileNotFoundError): return False diff --git a/src/solidlsp/language_servers/fsharp_language_server.py b/src/solidlsp/language_servers/fsharp_language_server.py index 7a07bfdc..0094ada3 100644 --- a/src/solidlsp/language_servers/fsharp_language_server.py +++ b/src/solidlsp/language_servers/fsharp_language_server.py @@ -5,6 +5,7 @@ Provides F# specific instantiation of the LanguageServer class. import logging import os import shutil +import subprocess import threading from pathlib import Path @@ -17,6 +18,7 @@ from solidlsp.ls_config import LanguageServerConfig, LanguageServerId from solidlsp.ls_exceptions import SolidLSPException from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -104,9 +106,7 @@ class FSharpLanguageServer(SolidLanguageServer): # Install FsAutoComplete using dotnet tool install try: - import subprocess - - result = subprocess.run( + result = subprocess_run( [dotnet_exe, "tool", "install", "--tool-path", fsharp_ls_dir, "fsautocomplete", "--version", fsautocomplete_version], cwd=fsharp_ls_dir, capture_output=True, @@ -271,9 +271,7 @@ class FSharpLanguageServer(SolidLanguageServer): if dotnet_exe: # Try to get the installation path try: - import subprocess - - result = subprocess.run([dotnet_exe, "--info"], capture_output=True, text=True, check=True) + result = subprocess_run([dotnet_exe, "--info"], capture_output=True, text=True, check=True) lines = result.stdout.split("\n") for line in lines: if "Base Path:" in line or "Base path:" in line: diff --git a/src/solidlsp/language_servers/gopls.py b/src/solidlsp/language_servers/gopls.py index 8ebd7f38..1dccdb0c 100644 --- a/src/solidlsp/language_servers/gopls.py +++ b/src/solidlsp/language_servers/gopls.py @@ -3,7 +3,6 @@ import json import logging import os import re -import subprocess from collections.abc import Hashable from typing import Any @@ -14,6 +13,7 @@ from solidlsp.ls import DocumentSymbols, LSPFileBuffer, RawDocumentSymbol, Solid from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -63,7 +63,7 @@ class Gopls(SolidLanguageServer): def _get_go_version() -> str | None: """Get the installed Go version or None if not found.""" try: - result = subprocess.run(["go", "version"], capture_output=True, text=True, check=False) + result = subprocess_run(["go", "version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: @@ -74,7 +74,7 @@ class Gopls(SolidLanguageServer): def _get_gopls_version() -> str | None: """Get the installed gopls version or None if not found.""" try: - result = subprocess.run(["gopls", "version"], capture_output=True, text=True, check=False) + result = subprocess_run(["gopls", "version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: diff --git a/src/solidlsp/language_servers/julia_server.py b/src/solidlsp/language_servers/julia_server.py index cde52982..c84ecda4 100644 --- a/src/solidlsp/language_servers/julia_server.py +++ b/src/solidlsp/language_servers/julia_server.py @@ -12,6 +12,7 @@ from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.lsp_types import DiagnosticTag from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -84,7 +85,7 @@ class JuliaLanguageServer(SolidLanguageServer): # initialize ("tools fetch failed"). See https://github.com/oraios/serena/issues/1577 check_cmd = [julia_path, "-e", "using LanguageServer"] try: - result = subprocess.run(check_cmd, check=False, capture_output=True, text=True, timeout=10, stdin=subprocess.DEVNULL) + result = subprocess_run(check_cmd, check=False, capture_output=True, text=True, timeout=10) if result.returncode != 0: # LanguageServer.jl not found, install it JuliaLanguageServer._install_language_server(julia_path) @@ -102,9 +103,7 @@ class JuliaLanguageServer(SolidLanguageServer): install_cmd = [julia_path, "-e", 'using Pkg; Pkg.add("LanguageServer")'] try: - result = subprocess.run( - install_cmd, check=False, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL - ) # 5 minutes for installation + result = subprocess_run(install_cmd, check=False, capture_output=True, text=True, timeout=300) # 5 minutes for installation if result.returncode == 0: log.info("LanguageServer.jl installed successfully!") diff --git a/src/solidlsp/language_servers/lean4_language_server.py b/src/solidlsp/language_servers/lean4_language_server.py index 4f4af40a..77800988 100644 --- a/src/solidlsp/language_servers/lean4_language_server.py +++ b/src/solidlsp/language_servers/lean4_language_server.py @@ -5,13 +5,13 @@ Uses the built-in Lean 4 language server (lean --server). import logging import shutil -import subprocess from overrides import override from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -51,7 +51,7 @@ class Lean4LanguageServer(SolidLanguageServer): log.warning("lake not found on PATH; cross-file references may not work") return env try: - result = subprocess.run( + result = subprocess_run( [lake_path, "env"], check=False, cwd=self._repository_root_path, diff --git a/src/solidlsp/language_servers/ocaml_lsp_server.py b/src/solidlsp/language_servers/ocaml_lsp_server.py index 3a26e50e..56968123 100644 --- a/src/solidlsp/language_servers/ocaml_lsp_server.py +++ b/src/solidlsp/language_servers/ocaml_lsp_server.py @@ -19,7 +19,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings -from solidlsp.util.subprocess_util import subprocess_kwargs +from solidlsp.util.subprocess_util import subprocess_kwargs, subprocess_run log = logging.getLogger(__name__) @@ -61,7 +61,7 @@ class OcamlLanguageServer(SolidLanguageServer): Raises RuntimeError if version cannot be determined. """ try: - result = subprocess.run( + result = subprocess_run( ["opam", "exec", "--", "ocaml", "-version"], check=True, capture_output=True, @@ -111,7 +111,7 @@ class OcamlLanguageServer(SolidLanguageServer): Raises RuntimeError if version cannot be determined. """ try: - result = subprocess.run( + result = subprocess_run( ["opam", "list", "-i", "ocaml-lsp-server", "--columns=version", "--short"], check=True, capture_output=True, @@ -148,7 +148,7 @@ class OcamlLanguageServer(SolidLanguageServer): """ # Check if ocaml-lsp-server is installed try: - result = subprocess.run( + result = subprocess_run( ["opam", "list", "-i", "ocaml-lsp-server"], check=False, capture_output=True, @@ -176,7 +176,7 @@ class OcamlLanguageServer(SolidLanguageServer): # Find the executable path try: if platform.system() == "Windows": - result = subprocess.run( + result = subprocess_run( ["opam", "exec", "--", "where", "ocamllsp"], check=True, capture_output=True, @@ -186,7 +186,7 @@ class OcamlLanguageServer(SolidLanguageServer): ) executable_path = result.stdout.strip().split("\n")[0] else: - result = subprocess.run( + result = subprocess_run( ["opam", "exec", "--", "which", "ocamllsp"], check=True, capture_output=True, @@ -252,7 +252,7 @@ class OcamlLanguageServer(SolidLanguageServer): """ log.info("Building OCaml index for cross-file references (dune build @ocaml-index)...") try: - result = subprocess.run( + result = subprocess_run( ["opam", "exec", "--", "dune", "build", "@ocaml-index"], cwd=repository_root_path, capture_output=True, diff --git a/src/solidlsp/language_servers/perl_language_server.py b/src/solidlsp/language_servers/perl_language_server.py index 54496f60..cce7a041 100644 --- a/src/solidlsp/language_servers/perl_language_server.py +++ b/src/solidlsp/language_servers/perl_language_server.py @@ -5,7 +5,6 @@ Note: Windows is not supported as Nix itself doesn't support Windows natively. """ import logging -import subprocess import time from typing import Any @@ -17,6 +16,7 @@ from solidlsp.ls_utils import PlatformId, PlatformUtils from solidlsp.lsp_protocol_handler.lsp_types import DidChangeConfigurationParams from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -45,7 +45,7 @@ class PerlLanguageServer(SolidLanguageServer): def _get_perl_version() -> str | None: """Get the installed Perl version or None if not found.""" try: - result = subprocess.run(["perl", "-v"], capture_output=True, text=True, check=False) + result = subprocess_run(["perl", "-v"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: @@ -56,7 +56,7 @@ class PerlLanguageServer(SolidLanguageServer): def _get_perl_language_server_version() -> str | None: """Get the installed Perl::LanguageServer version or None if not found.""" try: - result = subprocess.run( + result = subprocess_run( ["perl", "-MPerl::LanguageServer", "-e", "print $Perl::LanguageServer::VERSION"], capture_output=True, text=True, diff --git a/src/solidlsp/language_servers/phpactor.py b/src/solidlsp/language_servers/phpactor.py index 7104baa6..0070e9f7 100644 --- a/src/solidlsp/language_servers/phpactor.py +++ b/src/solidlsp/language_servers/phpactor.py @@ -7,7 +7,6 @@ import os import re import shutil import stat -import subprocess from overrides import override @@ -15,6 +14,7 @@ from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependen from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_utils import FileUtils from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -68,7 +68,7 @@ class PhpactorServer(SolidLanguageServer): ) # Check PHP version (Phpactor requires PHP 8.1+) - result = subprocess.run(["php", "--version"], capture_output=True, text=True, check=False) + result = subprocess_run(["php", "--version"], capture_output=True, text=True, check=False) php_version_output = result.stdout.strip() log.info(f"PHP version: {php_version_output}") version_match = re.search(r"PHP (\d+)\.(\d+)", php_version_output) diff --git a/src/solidlsp/language_servers/powershell_language_server.py b/src/solidlsp/language_servers/powershell_language_server.py index 7927a6ff..5e0d4aee 100644 --- a/src/solidlsp/language_servers/powershell_language_server.py +++ b/src/solidlsp/language_servers/powershell_language_server.py @@ -13,7 +13,6 @@ import logging import os import platform import shutil -import subprocess import tempfile import threading from collections.abc import Hashable @@ -28,6 +27,7 @@ from solidlsp.ls_types import SymbolKind from solidlsp.ls_utils import FileUtils from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -190,7 +190,7 @@ class PowerShellLanguageServer(SolidLanguageServer): psscriptanalyzer_path = Path(bundled_modules_path) / "PSScriptAnalyzer" / psscriptanalyzer_version if not psscriptanalyzer_path.exists(): log.info(f"PSScriptAnalyzer {psscriptanalyzer_version} not found. Installing...") - subprocess.run( + subprocess_run( [ pwsh_path, "-NoLogo", @@ -206,6 +206,7 @@ class PowerShellLanguageServer(SolidLanguageServer): ), ], check=True, + capture_output=False, ) return pwsh_path, pses_path, bundled_modules_path diff --git a/src/solidlsp/language_servers/r_language_server.py b/src/solidlsp/language_servers/r_language_server.py index 8caeeed1..0ac34135 100644 --- a/src/solidlsp/language_servers/r_language_server.py +++ b/src/solidlsp/language_servers/r_language_server.py @@ -1,5 +1,4 @@ import logging -import subprocess from typing import Any from overrides import override @@ -8,6 +7,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -34,12 +34,12 @@ class RLanguageServer(SolidLanguageServer): """Check if R and languageserver are available.""" try: # Check R installation - result = subprocess.run(["R", "--version"], capture_output=True, text=True, check=False) + result = subprocess_run(["R", "--version"], capture_output=True, text=True, check=False) if result.returncode != 0: raise RuntimeError("R is not installed or not in PATH") # Check languageserver package - result = subprocess.run( + result = subprocess_run( ["R", "--vanilla", "--quiet", "--slave", "-e", "if (!require('languageserver', quietly=TRUE)) quit(status=1)"], capture_output=True, text=True, diff --git a/src/solidlsp/language_servers/ruby_lsp.py b/src/solidlsp/language_servers/ruby_lsp.py index 008637b5..bc511c84 100644 --- a/src/solidlsp/language_servers/ruby_lsp.py +++ b/src/solidlsp/language_servers/ruby_lsp.py @@ -22,6 +22,7 @@ from solidlsp.ls_config import LanguageServerConfig, LanguageServerId from solidlsp.lsp_protocol_handler.lsp_types import InitializeResult from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -163,7 +164,7 @@ class RubyLsp(SolidLanguageServer): # Check if Ruby is installed try: - result = subprocess.run(ruby_cmd + ["--version"], check=True, capture_output=True, cwd=repository_root_path, text=True) + result = subprocess_run(ruby_cmd + ["--version"], check=True, capture_output=True, cwd=repository_root_path, text=True) ruby_version = result.stdout.strip() log.info(f"Ruby version: {ruby_version}") @@ -246,7 +247,7 @@ class RubyLsp(SolidLanguageServer): # Try to install ruby-lsp globally log.info("ruby-lsp not found, attempting to install globally...") try: - subprocess.run( + subprocess_run( ["gem", "install", "ruby-lsp", "-v", ruby_lsp_version], check=True, capture_output=True, diff --git a/src/solidlsp/language_servers/rust_analyzer.py b/src/solidlsp/language_servers/rust_analyzer.py index d7cd97fc..1ee08896 100644 --- a/src/solidlsp/language_servers/rust_analyzer.py +++ b/src/solidlsp/language_servers/rust_analyzer.py @@ -15,6 +15,7 @@ from overrides import override from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -49,7 +50,7 @@ class RustAnalyzer(SolidLanguageServer): def _get_rustup_version() -> str | None: """Get installed rustup version or None if not found.""" try: - result = subprocess.run(["rustup", "--version"], capture_output=True, text=True, check=False) + result = subprocess_run(["rustup", "--version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: @@ -60,7 +61,7 @@ class RustAnalyzer(SolidLanguageServer): def _get_rust_analyzer_via_rustup() -> str | None: """Get rust-analyzer path via rustup. Returns None if not found.""" try: - result = subprocess.run(["rustup", "which", "rust-analyzer"], capture_output=True, text=True, check=False) + result = subprocess_run(["rustup", "which", "rust-analyzer"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: @@ -75,7 +76,7 @@ class RustAnalyzer(SolidLanguageServer): that fails because the component is not installed. """ try: - result = subprocess.run([path, "--version"], capture_output=True, text=True, check=False, timeout=10) + result = subprocess_run([path, "--version"], capture_output=True, text=True, check=False, timeout=10) return result.returncode == 0 except (FileNotFoundError, OSError, subprocess.TimeoutExpired): return False @@ -101,7 +102,7 @@ class RustAnalyzer(SolidLanguageServer): # If rustup is available but rust-analyzer not installed, auto-install it BEFORE # checking PATH. This ensures we get the correct version matching the toolchain. if RustAnalyzer.DependencyProvider._get_rustup_version(): - result = subprocess.run(["rustup", "component", "add", "rust-analyzer"], check=False, capture_output=True, text=True) + result = subprocess_run(["rustup", "component", "add", "rust-analyzer"], check=False, capture_output=True, text=True) if result.returncode == 0: # Verify installation worked rustup_path = RustAnalyzer.DependencyProvider._get_rust_analyzer_via_rustup() diff --git a/src/solidlsp/language_servers/scala_language_server.py b/src/solidlsp/language_servers/scala_language_server.py index e9b03ace..2a7d6330 100644 --- a/src/solidlsp/language_servers/scala_language_server.py +++ b/src/solidlsp/language_servers/scala_language_server.py @@ -15,6 +15,7 @@ from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_utils import PlatformUtils from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run if not PlatformUtils.get_platform_id().value.startswith("win"): pass @@ -220,7 +221,7 @@ class ScalaLanguageServer(SolidLanguageServer): log.info("'cs' command not found. Trying to install it using 'coursier'.") try: log.info("Running 'coursier setup --yes' to install 'cs'...") - subprocess.run([coursier_command_path, "setup", "--yes"], check=True, capture_output=True, text=True) + subprocess_run([coursier_command_path, "setup", "--yes"], check=True, capture_output=True, text=True) except subprocess.CalledProcessError as e: raise RuntimeError(f"Failed to set up 'cs' command with 'coursier setup'. Stderr: {e.stderr}") @@ -232,7 +233,7 @@ class ScalaLanguageServer(SolidLanguageServer): log.info("'cs' command installed successfully.") log.info(f"metals executable not found at {metals_executable}, bootstrapping...") - subprocess.run(["mkdir", "-p", os.path.join(metals_home, metals_version)], check=True) + subprocess_run(["mkdir", "-p", os.path.join(metals_home, metals_version)], check=True, capture_output=False) artifact = f"org.scalameta:metals_2.13:{metals_version}" cmd = [ cs_command_path, @@ -253,7 +254,7 @@ class ScalaLanguageServer(SolidLanguageServer): "-f", ] log.info("Bootstrapping metals...") - subprocess.run(cmd, cwd=metals_home, check=True) + subprocess_run(cmd, cwd=metals_home, check=True, capture_output=False) log.info("Bootstrapping metals finished.") return [metals_executable] diff --git a/src/solidlsp/language_servers/solargraph.py b/src/solidlsp/language_servers/solargraph.py index 969123a4..2abf0194 100644 --- a/src/solidlsp/language_servers/solargraph.py +++ b/src/solidlsp/language_servers/solargraph.py @@ -17,6 +17,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -71,7 +72,7 @@ class Solargraph(SolidLanguageServer): """ # Check if Ruby is installed try: - result = subprocess.run(["ruby", "--version"], check=True, capture_output=True, cwd=repository_root_path, text=True) + result = subprocess_run(["ruby", "--version"], check=True, capture_output=True, cwd=repository_root_path, text=True) ruby_version = result.stdout.strip() log.info(f"Ruby version: {ruby_version}") @@ -178,12 +179,12 @@ class Solargraph(SolidLanguageServer): dependency = runtime_dependencies[0] try: - result = subprocess.run( + result = subprocess_run( ["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path ) if result.stdout.strip() == "false": log.info("Installing Solargraph...") - subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path) + subprocess_run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path) return "gem exec solargraph" except subprocess.CalledProcessError as e: diff --git a/src/solidlsp/language_servers/sourcekit_lsp.py b/src/solidlsp/language_servers/sourcekit_lsp.py index e77ba74e..7dea0cbd 100644 --- a/src/solidlsp/language_servers/sourcekit_lsp.py +++ b/src/solidlsp/language_servers/sourcekit_lsp.py @@ -1,6 +1,5 @@ import logging import os -import subprocess import time from collections.abc import Hashable @@ -12,6 +11,7 @@ from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_types import SymbolKind from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -34,7 +34,7 @@ class SourceKitLSP(SolidLanguageServer): def _get_sourcekit_lsp_version() -> str: """Get the installed sourcekit-lsp version or raise error if sourcekit was not found.""" try: - result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False) + result = subprocess_run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() else: diff --git a/src/solidlsp/language_servers/systemverilog_server.py b/src/solidlsp/language_servers/systemverilog_server.py index dd7f7b0c..4a08f19e 100644 --- a/src/solidlsp/language_servers/systemverilog_server.py +++ b/src/solidlsp/language_servers/systemverilog_server.py @@ -5,12 +5,12 @@ SystemVerilog language server using verible-verilog-ls. import logging import os import shutil -import subprocess from typing import Any from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run from .common import RuntimeDependency, RuntimeDependencyCollection @@ -42,7 +42,7 @@ class SystemVerilogLanguageServer(SolidLanguageServer): if system_verible: # Log version information try: - result = subprocess.run( + result = subprocess_run( [system_verible, "--version"], capture_output=True, text=True, diff --git a/src/solidlsp/language_servers/zls.py b/src/solidlsp/language_servers/zls.py index c05ec371..5480723a 100644 --- a/src/solidlsp/language_servers/zls.py +++ b/src/solidlsp/language_servers/zls.py @@ -7,7 +7,6 @@ import os import pathlib import platform import shutil -import subprocess from overrides import override @@ -15,6 +14,7 @@ from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -37,7 +37,7 @@ class ZigLanguageServer(SolidLanguageServer): def _get_zig_version() -> str | None: """Get the installed Zig version or None if not found.""" try: - result = subprocess.run(["zig", "version"], capture_output=True, text=True, check=False) + result = subprocess_run(["zig", "version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: @@ -48,7 +48,7 @@ class ZigLanguageServer(SolidLanguageServer): def _get_zls_version() -> str | None: """Get the installed ZLS version or None if not found.""" try: - result = subprocess.run(["zls", "--version"], capture_output=True, text=True, check=False) + result = subprocess_run(["zls", "--version"], capture_output=True, text=True, check=False) if result.returncode == 0: return result.stdout.strip() except FileNotFoundError: diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index c195d401..b07abd8d 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -23,6 +23,7 @@ import requests from solidlsp.ls_exceptions import InvalidTextLocationError, SolidLSPException from solidlsp.ls_types import UnifiedSymbolInformation +from solidlsp.util.subprocess_util import subprocess_run log = logging.getLogger(__name__) @@ -740,9 +741,9 @@ class PlatformUtils: Returns the dotnet version for the current system """ try: - result = subprocess.run(["dotnet", "--list-runtimes"], capture_output=True, check=True) + result = subprocess_run(["dotnet", "--list-runtimes"], capture_output=True, check=True) available_version_cmd_output = [] - for line in result.stdout.decode("utf-8").split("\n"): + for line in result.stdout.split("\n"): if line.startswith("Microsoft.NETCore.App"): version_cmd_output = line.split(" ")[1] available_version_cmd_output.append(version_cmd_output) @@ -769,7 +770,7 @@ class PlatformUtils: ) except (FileNotFoundError, subprocess.CalledProcessError): try: - result = subprocess.run(["mono", "--version"], capture_output=True, check=True) + result = subprocess_run(["mono", "--version"], capture_output=True, check=True) return DotnetVersion.VMONO except (FileNotFoundError, subprocess.CalledProcessError): raise SolidLSPException("dotnet or mono not found on the system") diff --git a/test/solidlsp/rust/test_rust_analyzer_detection.py b/test/solidlsp/rust/test_rust_analyzer_detection.py index fea2bb0e..3196c323 100644 --- a/test/solidlsp/rust/test_rust_analyzer_detection.py +++ b/test/solidlsp/rust/test_rust_analyzer_detection.py @@ -358,7 +358,10 @@ class TestRustAnalyzerDetection: def mock_access(path, mode): return path == scoop_path - with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None): + with ( + patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None), + patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value=None), + ): with patch("platform.system", return_value="Windows"): with patch("shutil.which", return_value=None): with patch("os.path.isfile", side_effect=mock_isfile): @@ -390,7 +393,10 @@ class TestRustAnalyzerDetection: def mock_access(path, mode): return path == cargo_path - with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None): + with ( + patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None), + patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value=None), + ): with patch("platform.system", return_value="Windows"): with patch("shutil.which", return_value=None): with patch("os.path.isfile", side_effect=mock_isfile):