mirror of
https://github.com/astral-sh/ruff.git
synced 2026-05-08 09:53:00 -04:00
d537f03d59
Closes https://github.com/astral-sh/uv/issues/14874 Closes https://github.com/astral-sh/ruff/issues/23402 uv has fairly extensive test coverage for this functionality but it seems challenging to copy it over My smoke test strategy was to ask an LLM to build the wheel and test all of the cases ``` $ uv build --wheel Building wheel... Successfully built dist/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl $ WHEEL=dist/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl $ uv venv -q .smoke-venv && uv pip install -q --python .smoke-venv $WHEEL $ .smoke-venv/bin/python -c "from ruff import find_ruff_bin; print(find_ruff_bin())" /Users/zb/workspace/ruff/.smoke-venv/bin/ruff $ .smoke-venv/bin/python -m ruff version ruff 0.15.1+81 (1e42d4f11 2026-02-18) $ uv run --no-project --with $WHEEL -- python -c "from ruff import find_ruff_bin; print(find_ruff_bin())" /Users/zb/.cache/uv/archive-v0/zf7_vNji2jmEGEDox-9Vj/bin/ruff $ uv run --no-project --with $WHEEL -- python -m ruff version ruff 0.15.1+81 (1e42d4f11 2026-02-18) $ uv pip install --target .smoke-target $WHEEL $ PYTHONPATH=.smoke-target python3 -c "from ruff import find_ruff_bin; print(find_ruff_bin())" /Users/zb/workspace/ruff/.smoke-target/bin/ruff $ uv pip install --prefix .smoke-prefix $WHEEL $ PYTHONPATH=.smoke-prefix/lib/python3.14/site-packages python3 -c "from ruff import find_ruff_bin; print(find_ruff_bin())" /Users/zb/workspace/ruff/.smoke-prefix/bin/ruff $ python3 -m pip install --user --break-system-packages $WHEEL $ python3 -c "from ruff import find_ruff_bin; print(find_ruff_bin())" /Users/zb/Library/Python/3.13/bin/ruff $ python3 -m ruff version ruff 0.15.1+81 (1e42d4f11 2026-02-18) ```
105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import sysconfig
|
|
|
|
|
|
class RuffNotFound(FileNotFoundError): ...
|
|
|
|
|
|
def find_ruff_bin() -> str:
|
|
"""Return the ruff binary path."""
|
|
|
|
ruff_exe = "ruff" + sysconfig.get_config_var("EXE")
|
|
|
|
targets = [
|
|
# The scripts directory for the current Python
|
|
sysconfig.get_path("scripts"),
|
|
# The scripts directory for the base prefix
|
|
sysconfig.get_path("scripts", vars={"base": sys.base_prefix}),
|
|
# Above the package root, e.g., from `pip install --prefix` or `uv run --with`
|
|
(
|
|
# On Windows, with module path `<prefix>/Lib/site-packages/ruff`
|
|
_join(
|
|
_matching_parents(_module_path(), "Lib/site-packages/ruff"), "Scripts"
|
|
)
|
|
if sys.platform == "win32"
|
|
# On Unix, with module path `<prefix>/lib/python3.13/site-packages/ruff`
|
|
else _join(
|
|
_matching_parents(_module_path(), "lib/python*/site-packages/ruff"),
|
|
"bin",
|
|
)
|
|
),
|
|
# Adjacent to the package root, e.g., from `pip install --target`
|
|
# with module path `<target>/ruff`
|
|
_join(_matching_parents(_module_path(), "ruff"), "bin"),
|
|
# The user scheme scripts directory, e.g., `~/.local/bin`
|
|
sysconfig.get_path("scripts", scheme=_user_scheme()),
|
|
]
|
|
|
|
seen = []
|
|
for target in targets:
|
|
if not target:
|
|
continue
|
|
if target in seen:
|
|
continue
|
|
seen.append(target)
|
|
path = os.path.join(target, ruff_exe)
|
|
if os.path.isfile(path):
|
|
return path
|
|
|
|
locations = "\n".join(f" - {target}" for target in seen)
|
|
raise RuffNotFound(
|
|
f"Could not find the ruff binary in any of the following locations:\n{locations}\n"
|
|
)
|
|
|
|
|
|
def _module_path() -> str | None:
|
|
path = os.path.dirname(__file__)
|
|
return path
|
|
|
|
|
|
def _matching_parents(path: str | None, match: str) -> str | None:
|
|
"""
|
|
Return the parent directory of `path` after trimming a `match` from the end.
|
|
The match is expected to contain `/` as a path separator, while the `path`
|
|
is expected to use the platform's path separator (e.g., `os.sep`). The path
|
|
components are compared case-insensitively and a `*` wildcard can be used
|
|
in the `match`.
|
|
"""
|
|
from fnmatch import fnmatch
|
|
|
|
if not path:
|
|
return None
|
|
parts = path.split(os.sep)
|
|
match_parts = match.split("/")
|
|
if len(parts) < len(match_parts):
|
|
return None
|
|
|
|
if not all(
|
|
fnmatch(part, match_part)
|
|
for part, match_part in zip(reversed(parts), reversed(match_parts))
|
|
):
|
|
return None
|
|
|
|
return os.sep.join(parts[: -len(match_parts)])
|
|
|
|
|
|
def _join(path: str | None, *parts: str) -> str | None:
|
|
if not path:
|
|
return None
|
|
return os.path.join(path, *parts)
|
|
|
|
|
|
def _user_scheme() -> str:
|
|
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: # ty: ignore[unresolved-attribute]
|
|
user_scheme = "osx_framework_user"
|
|
else:
|
|
user_scheme = "posix_user"
|
|
return user_scheme
|