Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fall back to system ruff executable without executable setting #97

Merged
merged 3 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions pylsp_ruff/plugin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import enum
import importlib.util
import json
import logging
import re
import shutil
import sys
from functools import lru_cache
from pathlib import PurePath
from subprocess import PIPE, Popen
from typing import Dict, Generator, List, Optional
Expand Down Expand Up @@ -116,7 +119,6 @@ def pylsp_format_document(workspace: Workspace, document: Document) -> Generator
Document to apply ruff on.

"""

log.debug(f"textDocument/formatting: {document}")
outcome = yield
result = outcome.get_result()
Expand Down Expand Up @@ -178,7 +180,6 @@ def pylsp_lint(workspace: Workspace, document: Document) -> List[Dict]:
List of dicts containing the diagnostics.

"""

with workspace.report_progress("lint: ruff"):
settings = load_settings(workspace, document.path)
checks = run_ruff_check(document=document, settings=settings)
Expand Down Expand Up @@ -487,6 +488,37 @@ def run_ruff_format(
)


@lru_cache
def find_executable(executable) -> List[str]:
cmd = None
# use the explicit executable configuration
if executable is not None:
exe_path = shutil.which(executable)
if exe_path is not None:
cmd = [exe_path]
else:
log.error(f"Configured ruff executable not found: {executable!r}")

# try the python module
if cmd is None:
if importlib.util.find_spec("ruff") is not None:
cmd = [sys.executable, "-m", "ruff"]

# try system's ruff executable
if cmd is None:
system_exe = shutil.which("ruff")
if system_exe is not None:
cmd = [system_exe]

if cmd is None:
log.error(
"No suitable ruff invocation could be found (executable, python module)."
)
cmd = []

return cmd


def run_ruff(
settings: PluginSettings,
document_path: str,
Expand Down Expand Up @@ -522,27 +554,14 @@ def run_ruff(

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

p = None
if executable is not None:
log.debug(f"Calling {executable} with args: {arguments} on '{document_path}'")
try:
cmd = [executable, str(subcommand)]
cmd.extend(arguments)
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
except Exception:
log.error(f"Can't execute ruff with given executable '{executable}'.")
if p is None:
log.debug(
f"Calling ruff via '{sys.executable} -m ruff'"
f" with args: {arguments} on '{document_path}'"
)
cmd = [sys.executable, "-m", "ruff", str(subcommand)]
cmd.extend(arguments)
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
(stdout, stderr) = p.communicate(document_source.encode())
cmd = [*find_executable(executable), str(subcommand), *arguments]

log.debug(f"Calling {cmd} on '{document_path}'")
p = Popen(cmd, stdin=PIPE, stdout=PIPE)
(stdout, _) = p.communicate(document_source.encode())

if p.returncode != 0:
log.error(f"Error running ruff: {stderr.decode()}")
log.error(f"Ruff returned {p.returncode} != 0")

return stdout.decode()

Expand Down
24 changes: 16 additions & 8 deletions tests/test_ruff_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright 2021- Python Language Server Contributors.

import os
import stat
import sys
import tempfile
from unittest.mock import Mock, patch
Expand Down Expand Up @@ -100,17 +101,24 @@ def test_ruff_config_param(workspace):

def test_ruff_executable_param(workspace):
with patch("pylsp_ruff.plugin.Popen") as popen_mock:
mock_instance = popen_mock.return_value
mock_instance.communicate.return_value = [bytes(), bytes()]
with tempfile.NamedTemporaryFile() as ruff_exe:
mock_instance = popen_mock.return_value
mock_instance.communicate.return_value = [bytes(), bytes()]

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

_name, doc = temp_document(DOC, workspace)
ruff_lint.pylsp_lint(workspace, doc)
workspace._config.update(
{"plugins": {"ruff": {"executable": ruff_executable}}}
)

(call_args,) = popen_mock.call_args[0]
assert ruff_executable in call_args
_name, doc = temp_document(DOC, workspace)
ruff_lint.pylsp_lint(workspace, doc)

(call_args,) = popen_mock.call_args[0]
assert ruff_executable in call_args


def get_ruff_settings(workspace, doc, config_str):
Expand Down
Loading