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

Experimental ruff server now uses local ruff binaries when available #443

Merged
merged 8 commits into from
Apr 11, 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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Ruff extension for Visual Studio Code

[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![image](https://img.shields.io/pypi/v/ruff/0.3.3.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/l/ruff/0.3.3.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/pyversions/ruff/0.3.3.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/v/ruff/0.3.6.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/l/ruff/0.3.6.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/pyversions/ruff/0.3.6.svg)](https://pypi.python.org/pypi/ruff)
[![Actions status](https://github.com/astral-sh/ruff-vscode/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff-vscode/actions)

A Visual Studio Code extension for [Ruff](https://github.com/astral-sh/ruff), an extremely fast
Expand All @@ -12,7 +12,7 @@ Python linter and code formatter, written in Rust. Available on the [Visual Stud
Ruff can be used to replace Flake8 (plus dozens of plugins), Black, isort, pyupgrade, and more,
all while executing tens or hundreds of times faster than any individual tool.

The extension ships with `ruff==0.3.3`.
The extension ships with `ruff==0.3.6`.

(Interested in using [Ruff](https://github.com/astral-sh/ruff) with another editor? Check out
[`ruff-lsp`](https://github.com/astral-sh/ruff-lsp).)
Expand Down Expand Up @@ -294,7 +294,7 @@ At the moment, the experimental server has the following known limitations:
- Commands like `Fix all` and `Organize Imports` have not yet been implemented. (Quick Fixes should still work, though)
- Hierarchial configuration for individual files is not yet supported. At the moment, the language server uses the `ruff.toml` / `pyproject.toml` at the workspace root to configure the formatter and linter.
- Jupyter Notebook files are not supported yet.
- Using local Ruff binaries is not yet supported. At the moment, the extension will always use the bundled Ruff binary. (`v0.3.3`)
- Using local Ruff binaries is not yet supported. At the moment, the extension will always use the bundled Ruff binary. (`v0.3.6`)

## Commands

Expand Down
92 changes: 92 additions & 0 deletions bundled/tool/ruff_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import os
Copy link

@T-256 T-256 May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IF we could convert this file into typescript, then extension wouldn't need Python installation/activation on target system at all.
Currently Ruff extension needs python interpreter to start the server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, our eventual plan is to move this logic to Typescript 😄

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #479 to track it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be an awesome change.

import shutil
import site
import subprocess
import sys
import sysconfig
from pathlib import Path

RUFF_EXE = "ruff.exe" if sys.platform == "win32" else "ruff"

BUNDLE_DIR = Path(__file__).parent.parent


def update_sys_path(path_to_add: str) -> None:
"""Add given path to `sys.path`."""
if os.path.isdir(path_to_add):
# The `site` module adds the directory at the end, if not yet present; we want
# it to be at the beginning, so that it takes precedence over any other
# installed versions.
sys.path.insert(0, path_to_add)

# Allow development versions of libraries to be imported.
site.addsitedir(path_to_add)


# This is separate from the 'main' entrypoint because we need
# to update the system path _before_ importing `pacakging`
if __name__ == "__main__":
# Ensure that we can import bundled libraries like `packaging`
update_sys_path(os.fspath(BUNDLE_DIR / "libs"))

snowsignal marked this conversation as resolved.
Show resolved Hide resolved

from packaging.specifiers import SpecifierSet
from packaging.version import Version

# This is subject to change in the future
RUFF_VERSION_REQUIREMENT = SpecifierSet(">=0.3.5")


def executable_version(executable: str) -> Version:
"""Return the version of the executable at the given path."""
output = subprocess.check_output([executable, "--version"]).decode().strip()
version = output.replace("ruff ", "")
return Version(version)


def check_compatibility(
executable: str,
requirement: SpecifierSet,
) -> None:
"""Check the executable for compatibility against various version specifiers."""
version = executable_version(executable)
if not requirement.contains(version, prereleases=True):
message = f"Ruff {requirement} required, but found {version} at {executable}"
raise RuntimeError(message)


def find_ruff_bin(fallback: Path) -> Path:
"""Return the ruff binary path."""
path = Path(sysconfig.get_path("scripts")) / RUFF_EXE
if path.is_file():
return path
snowsignal marked this conversation as resolved.
Show resolved Hide resolved

if sys.version_info >= (3, 10):
user_scheme = sysconfig.get_preferred_scheme("user")
elif os.name == "nt":
user_scheme = "nt_user"
elif sys.platform == "darwin" and sys._framework:
user_scheme = "osx_framework_user"
else:
user_scheme = "posix_user"

path = Path(sysconfig.get_path("scripts", scheme=user_scheme)) / RUFF_EXE
if path.is_file():
return path

path = shutil.which("ruff")
if path:
return path

return fallback


if __name__ == "__main__":
ruff = os.fsdecode(
find_ruff_bin(
Path(BUNDLE_DIR / "libs" / "bin" / RUFF_EXE),
),
)
check_compatibility(ruff, RUFF_VERSION_REQUIREMENT)
completed_process = subprocess.run([ruff, *sys.argv[1:]], check=False)
sys.exit(completed_process.returncode)
snowsignal marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [{ name = "Charlie Marsh", email = "[email protected]" }]
maintainers = [{ name = "Charlie Marsh", email = "[email protected]" }]
requires-python = ">=3.7"
license = "MIT"
dependencies = ["packaging>=23.1", "ruff-lsp==0.0.53", "ruff==0.3.3"]
dependencies = ["packaging>=23.1", "ruff-lsp==0.0.53", "ruff==0.3.6"]

[project.optional-dependencies]
dev = ["mypy==1.2.0", "python-lsp-jsonrpc==1.0.0"]
Expand Down
36 changes: 18 additions & 18 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,24 @@ python-lsp-jsonrpc==1.0.0 \
--hash=sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7 \
--hash=sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd
# via ruff-vscode (./pyproject.toml)
ruff==0.3.3 \
--hash=sha256:0171aab5fecdc54383993389710a3d1227f2da124d76a2784a7098e818f92d61 \
--hash=sha256:0da458989ce0159555ef224d5b7c24d3d2e4bf4c300b85467b08c3261c6bc6a8 \
--hash=sha256:1eca7ff7a47043cf6ce5c7f45f603b09121a7cc047447744b029d1b719278eb5 \
--hash=sha256:2700a804d5336bcffe063fd789ca2c7b02b552d2e323a336700abb8ae9e6a3f8 \
--hash=sha256:352e95ead6964974b234e16ba8a66dad102ec7bf8ac064a23f95371d8b198aab \
--hash=sha256:38671be06f57a2f8aba957d9f701ea889aa5736be806f18c0cd03d6ff0cbca8d \
--hash=sha256:45817af234605525cdf6317005923bf532514e1ea3d9270acf61ca2440691376 \
--hash=sha256:5a6cbf216b69c7090f0fe4669501a27326c34e119068c1494f35aaf4cc683778 \
--hash=sha256:79bca3a03a759cc773fca69e0bdeac8abd1c13c31b798d5bb3c9da4a03144a9f \
--hash=sha256:8d6ab88c81c4040a817aa432484e838aaddf8bfd7ca70e4e615482757acb64f8 \
--hash=sha256:973a0e388b7bc2e9148c7f9be8b8c6ae7471b9be37e1cc732f8f44a6f6d7720d \
--hash=sha256:b24c19e8598916d9c6f5a5437671f55ee93c212a2c4c569605dc3842b6820386 \
--hash=sha256:be90bcae57c24d9f9d023b12d627e958eb55f595428bafcb7fec0791ad25ddfc \
--hash=sha256:cfa60d23269d6e2031129b053fdb4e5a7b0637fc6c9c0586737b962b2f834493 \
--hash=sha256:e7d3f6762217c1da954de24b4a1a70515630d29f71e268ec5000afe81377642d \
--hash=sha256:f2831ec6a580a97f1ea82ea1eda0401c3cdf512cf2045fa3c85e8ef109e87de0 \
--hash=sha256:fd66469f1a18fdb9d32e22b79f486223052ddf057dc56dea0caaf1a47bdfaf4e
ruff==0.3.6 \
--hash=sha256:26071fb530038602b984e3bbe1443ef82a38450c4dcb1344a9caf67234ff9756 \
--hash=sha256:28ccf3fb6d1162a73cd286c63a5e4d885f46a1f99f0b392924bc95ccbd18ea8f \
--hash=sha256:2b0c4c70578ef1871a9ac5c85ed7a8c33470e976c73ba9211a111d2771b5f787 \
--hash=sha256:4056480f5cf38ad278667c31b0ef334c29acdfcea617cb89c4ccbc7d96f1637f \
--hash=sha256:647f1fb5128a3e24ce68878b8050bb55044c45bb3f3ae4710d4da9ca96ede5cb \
--hash=sha256:732ef99984275534f9466fbc01121523caf72aa8c2bdeb36fd2edf2bc294a992 \
--hash=sha256:7c8a2a0e0cab077a07465259ffe3b3c090e747ca8097c5dc4c36ca0fdaaac90d \
--hash=sha256:878ef1a55ce931f3ca23b690b159cd0659f495a4c231a847b00ca55e4c688baf \
--hash=sha256:93699d61116807edc5ca1cdf9d2d22cf8d93335d59e3ff0ca7aee62c1818a736 \
--hash=sha256:b11e09439d9df6cc12d9f622065834654417c40216d271f639512d80e80e3e53 \
--hash=sha256:b2e79f8e1b6bd5411d7ddad3f2abff3f9d371beda29daef86400d416dedb7e02 \
--hash=sha256:c466a52c522e6a08df0af018f550902f154f5649ad09e7f0d43da766e7399ebc \
--hash=sha256:cf48ec2c4bfae7837dc325c431a2932dc23a1485e71c59591c1df471ba234e0e \
--hash=sha256:e3da499ded004d0b956ab04248b2ae17e54a67ffc81353514ac583af5959a255 \
--hash=sha256:ecb87788284af96725643eae9ab3ac746d8cc09aad140268523b019f7ac3cd98 \
--hash=sha256:f1aa621beed533f46e9c7d6fe00e7f6e4570155b61d8f020387b72ace2b42e04 \
--hash=sha256:fc4006cbc6c11fefc25f122d2eb4731d7a3d815dc74d67c54991cc3f99c90177
# via
# ruff-lsp
# ruff-vscode (./pyproject.toml)
Expand Down
36 changes: 18 additions & 18 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,24 @@ pygls==1.2.1 \
--hash=sha256:04f9b9c115b622dcc346fb390289066565343d60245a424eca77cb429b911ed8 \
--hash=sha256:7dcfcf12b6f15beb606afa46de2ed348b65a279c340ef2242a9a35c22eeafe94
# via ruff-lsp
ruff==0.3.3 \
--hash=sha256:0171aab5fecdc54383993389710a3d1227f2da124d76a2784a7098e818f92d61 \
--hash=sha256:0da458989ce0159555ef224d5b7c24d3d2e4bf4c300b85467b08c3261c6bc6a8 \
--hash=sha256:1eca7ff7a47043cf6ce5c7f45f603b09121a7cc047447744b029d1b719278eb5 \
--hash=sha256:2700a804d5336bcffe063fd789ca2c7b02b552d2e323a336700abb8ae9e6a3f8 \
--hash=sha256:352e95ead6964974b234e16ba8a66dad102ec7bf8ac064a23f95371d8b198aab \
--hash=sha256:38671be06f57a2f8aba957d9f701ea889aa5736be806f18c0cd03d6ff0cbca8d \
--hash=sha256:45817af234605525cdf6317005923bf532514e1ea3d9270acf61ca2440691376 \
--hash=sha256:5a6cbf216b69c7090f0fe4669501a27326c34e119068c1494f35aaf4cc683778 \
--hash=sha256:79bca3a03a759cc773fca69e0bdeac8abd1c13c31b798d5bb3c9da4a03144a9f \
--hash=sha256:8d6ab88c81c4040a817aa432484e838aaddf8bfd7ca70e4e615482757acb64f8 \
--hash=sha256:973a0e388b7bc2e9148c7f9be8b8c6ae7471b9be37e1cc732f8f44a6f6d7720d \
--hash=sha256:b24c19e8598916d9c6f5a5437671f55ee93c212a2c4c569605dc3842b6820386 \
--hash=sha256:be90bcae57c24d9f9d023b12d627e958eb55f595428bafcb7fec0791ad25ddfc \
--hash=sha256:cfa60d23269d6e2031129b053fdb4e5a7b0637fc6c9c0586737b962b2f834493 \
--hash=sha256:e7d3f6762217c1da954de24b4a1a70515630d29f71e268ec5000afe81377642d \
--hash=sha256:f2831ec6a580a97f1ea82ea1eda0401c3cdf512cf2045fa3c85e8ef109e87de0 \
--hash=sha256:fd66469f1a18fdb9d32e22b79f486223052ddf057dc56dea0caaf1a47bdfaf4e
ruff==0.3.6 \
--hash=sha256:26071fb530038602b984e3bbe1443ef82a38450c4dcb1344a9caf67234ff9756 \
--hash=sha256:28ccf3fb6d1162a73cd286c63a5e4d885f46a1f99f0b392924bc95ccbd18ea8f \
--hash=sha256:2b0c4c70578ef1871a9ac5c85ed7a8c33470e976c73ba9211a111d2771b5f787 \
--hash=sha256:4056480f5cf38ad278667c31b0ef334c29acdfcea617cb89c4ccbc7d96f1637f \
--hash=sha256:647f1fb5128a3e24ce68878b8050bb55044c45bb3f3ae4710d4da9ca96ede5cb \
--hash=sha256:732ef99984275534f9466fbc01121523caf72aa8c2bdeb36fd2edf2bc294a992 \
--hash=sha256:7c8a2a0e0cab077a07465259ffe3b3c090e747ca8097c5dc4c36ca0fdaaac90d \
--hash=sha256:878ef1a55ce931f3ca23b690b159cd0659f495a4c231a847b00ca55e4c688baf \
--hash=sha256:93699d61116807edc5ca1cdf9d2d22cf8d93335d59e3ff0ca7aee62c1818a736 \
--hash=sha256:b11e09439d9df6cc12d9f622065834654417c40216d271f639512d80e80e3e53 \
--hash=sha256:b2e79f8e1b6bd5411d7ddad3f2abff3f9d371beda29daef86400d416dedb7e02 \
--hash=sha256:c466a52c522e6a08df0af018f550902f154f5649ad09e7f0d43da766e7399ebc \
--hash=sha256:cf48ec2c4bfae7837dc325c431a2932dc23a1485e71c59591c1df471ba234e0e \
--hash=sha256:e3da499ded004d0b956ab04248b2ae17e54a67ffc81353514ac583af5959a255 \
--hash=sha256:ecb87788284af96725643eae9ab3ac746d8cc09aad140268523b019f7ac3cd98 \
--hash=sha256:f1aa621beed533f46e9c7d6fe00e7f6e4570155b61d8f020387b72ace2b42e04 \
--hash=sha256:fc4006cbc6c11fefc25f122d2eb4731d7a3d815dc74d67c54991cc3f99c90177
# via
# ruff-lsp
# ruff-vscode (./pyproject.toml)
Expand Down
6 changes: 5 additions & 1 deletion src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const DEBUG_SERVER_SCRIPT_PATH = path.join(
"tool",
`_debug_server.py`,
);
export const RUFF_BIN_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, "libs", "bin", "ruff");
export const EXPERIMENTAL_SERVER_SCRIPT_PATH = path.join(
BUNDLED_PYTHON_SCRIPTS_DIR,
"tool",
"ruff_server.py",
);
export const RUFF_SERVER_CMD = "server";
export const RUFF_SERVER_REQUIRED_ARGS = ["--preview"];
41 changes: 29 additions & 12 deletions src/common/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
import {
BUNDLED_PYTHON_SCRIPTS_DIR,
DEBUG_SERVER_SCRIPT_PATH,
RUFF_BIN_PATH,
RUFF_SERVER_REQUIRED_ARGS,
RUFF_SERVER_CMD,
SERVER_SCRIPT_PATH,
EXPERIMENTAL_SERVER_SCRIPT_PATH,
} from "./constants";
import { traceError, traceInfo, traceVerbose } from "./log/logging";
import { getDebuggerPath } from "./python";
Expand All @@ -39,17 +39,34 @@ async function createExperimentalServer(
outputChannel: LogOutputChannel,
initializationOptions: IInitOptions,
): Promise<LanguageClient> {
const command = RUFF_BIN_PATH;
const cwd = settings.cwd;
const args = [RUFF_SERVER_CMD, ...RUFF_SERVER_REQUIRED_ARGS];

const serverOptions: ServerOptions = {
command,
args,
options: { cwd, env: process.env },
};

traceInfo(`Server run command: ${[command, ...args].join(" ")}`);
let serverOptions: ServerOptions;
// If the user provided a binary path, we'll try to call that path directly.
if (settings.path[0]) {
const command = settings.path[0];
const cwd = settings.cwd;
const args = [RUFF_SERVER_CMD, ...RUFF_SERVER_REQUIRED_ARGS];
serverOptions = {
command,
args,
options: { cwd, env: process.env },
};

traceInfo(`Server run command: ${[command, ...args].join(" ")}`);
} else {
// Otherwise, we'll call a Python script that tries to locate
// a binary, falling back to the bundled version if no local executable is found.
const command = settings.interpreter[0];
snowsignal marked this conversation as resolved.
Show resolved Hide resolved
const cwd = settings.cwd;
const args = [EXPERIMENTAL_SERVER_SCRIPT_PATH, RUFF_SERVER_CMD, ...RUFF_SERVER_REQUIRED_ARGS];

serverOptions = {
command,
args,
options: { cwd, env: process.env },
};

traceInfo(`Server run command: ${[command, ...args].join(" ")}`);
}
snowsignal marked this conversation as resolved.
Show resolved Hide resolved

const clientOptions = {
// Register the server for python documents
Expand Down