Skip to content

Commit 041c695

Browse files
authoredSep 12, 2024
Merge pull request #97 from TheJJ/use-system-ruff
Fall back to system ruff executable without executable setting
2 parents 790e76c + 3016377 commit 041c695

File tree

2 files changed

+56
-29
lines changed

2 files changed

+56
-29
lines changed
 

‎pylsp_ruff/plugin.py

+40-21
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import enum
2+
import importlib.util
23
import json
34
import logging
45
import re
6+
import shutil
57
import sys
8+
from functools import lru_cache
69
from pathlib import PurePath
710
from subprocess import PIPE, Popen
811
from typing import Dict, Generator, List, Optional
@@ -116,7 +119,6 @@ def pylsp_format_document(workspace: Workspace, document: Document) -> Generator
116119
Document to apply ruff on.
117120
118121
"""
119-
120122
log.debug(f"textDocument/formatting: {document}")
121123
outcome = yield
122124
result = outcome.get_result()
@@ -178,7 +180,6 @@ def pylsp_lint(workspace: Workspace, document: Document) -> List[Dict]:
178180
List of dicts containing the diagnostics.
179181
180182
"""
181-
182183
with workspace.report_progress("lint: ruff"):
183184
settings = load_settings(workspace, document.path)
184185
checks = run_ruff_check(document=document, settings=settings)
@@ -487,6 +488,37 @@ def run_ruff_format(
487488
)
488489

489490

491+
@lru_cache
492+
def find_executable(executable) -> List[str]:
493+
cmd = None
494+
# use the explicit executable configuration
495+
if executable is not None:
496+
exe_path = shutil.which(executable)
497+
if exe_path is not None:
498+
cmd = [exe_path]
499+
else:
500+
log.error(f"Configured ruff executable not found: {executable!r}")
501+
502+
# try the python module
503+
if cmd is None:
504+
if importlib.util.find_spec("ruff") is not None:
505+
cmd = [sys.executable, "-m", "ruff"]
506+
507+
# try system's ruff executable
508+
if cmd is None:
509+
system_exe = shutil.which("ruff")
510+
if system_exe is not None:
511+
cmd = [system_exe]
512+
513+
if cmd is None:
514+
log.error(
515+
"No suitable ruff invocation could be found (executable, python module)."
516+
)
517+
cmd = []
518+
519+
return cmd
520+
521+
490522
def run_ruff(
491523
settings: PluginSettings,
492524
document_path: str,
@@ -522,27 +554,14 @@ def run_ruff(
522554

523555
arguments = subcommand.build_args(document_path, settings, fix, extra_arguments)
524556

525-
p = None
526-
if executable is not None:
527-
log.debug(f"Calling {executable} with args: {arguments} on '{document_path}'")
528-
try:
529-
cmd = [executable, str(subcommand)]
530-
cmd.extend(arguments)
531-
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
532-
except Exception:
533-
log.error(f"Can't execute ruff with given executable '{executable}'.")
534-
if p is None:
535-
log.debug(
536-
f"Calling ruff via '{sys.executable} -m ruff'"
537-
f" with args: {arguments} on '{document_path}'"
538-
)
539-
cmd = [sys.executable, "-m", "ruff", str(subcommand)]
540-
cmd.extend(arguments)
541-
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
542-
(stdout, stderr) = p.communicate(document_source.encode())
557+
cmd = [*find_executable(executable), str(subcommand), *arguments]
558+
559+
log.debug(f"Calling {cmd} on '{document_path}'")
560+
p = Popen(cmd, stdin=PIPE, stdout=PIPE)
561+
(stdout, _) = p.communicate(document_source.encode())
543562

544563
if p.returncode != 0:
545-
log.error(f"Error running ruff: {stderr.decode()}")
564+
log.error(f"Ruff returned {p.returncode} != 0")
546565

547566
return stdout.decode()
548567

‎tests/test_ruff_lint.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright 2021- Python Language Server Contributors.
33

44
import os
5+
import stat
56
import sys
67
import tempfile
78
from unittest.mock import Mock, patch
@@ -100,17 +101,24 @@ def test_ruff_config_param(workspace):
100101

101102
def test_ruff_executable_param(workspace):
102103
with patch("pylsp_ruff.plugin.Popen") as popen_mock:
103-
mock_instance = popen_mock.return_value
104-
mock_instance.communicate.return_value = [bytes(), bytes()]
104+
with tempfile.NamedTemporaryFile() as ruff_exe:
105+
mock_instance = popen_mock.return_value
106+
mock_instance.communicate.return_value = [bytes(), bytes()]
105107

106-
ruff_executable = "/tmp/ruff"
107-
workspace._config.update({"plugins": {"ruff": {"executable": ruff_executable}}})
108+
ruff_executable = ruff_exe.name
109+
# chmod +x the file
110+
st = os.stat(ruff_executable)
111+
os.chmod(ruff_executable, st.st_mode | stat.S_IEXEC)
108112

109-
_name, doc = temp_document(DOC, workspace)
110-
ruff_lint.pylsp_lint(workspace, doc)
113+
workspace._config.update(
114+
{"plugins": {"ruff": {"executable": ruff_executable}}}
115+
)
111116

112-
(call_args,) = popen_mock.call_args[0]
113-
assert ruff_executable in call_args
117+
_name, doc = temp_document(DOC, workspace)
118+
ruff_lint.pylsp_lint(workspace, doc)
119+
120+
(call_args,) = popen_mock.call_args[0]
121+
assert ruff_executable in call_args
114122

115123

116124
def get_ruff_settings(workspace, doc, config_str):

0 commit comments

Comments
 (0)