mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-02-17 08:14:42 +04:00
Merge pull request #1431 from pre-commit/better_healthy
Improve python healthy() and eliminate python_venv
This commit is contained in:
commit
f455312944
8 changed files with 164 additions and 146 deletions
|
|
@ -14,7 +14,6 @@ from pre_commit.languages import node
|
|||
from pre_commit.languages import perl
|
||||
from pre_commit.languages import pygrep
|
||||
from pre_commit.languages import python
|
||||
from pre_commit.languages import python_venv
|
||||
from pre_commit.languages import ruby
|
||||
from pre_commit.languages import rust
|
||||
from pre_commit.languages import script
|
||||
|
|
@ -49,7 +48,6 @@ languages = {
|
|||
'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, healthy=perl.healthy, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501
|
||||
'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501
|
||||
'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501
|
||||
'python_venv': Language(name='python_venv', ENVIRONMENT_DIR=python_venv.ENVIRONMENT_DIR, get_default_version=python_venv.get_default_version, healthy=python_venv.healthy, install_environment=python_venv.install_environment, run_hook=python_venv.run_hook), # noqa: E501
|
||||
'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501
|
||||
'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501
|
||||
'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501
|
||||
|
|
@ -57,4 +55,6 @@ languages = {
|
|||
'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, healthy=system.healthy, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501
|
||||
# END GENERATED
|
||||
}
|
||||
# TODO: fully deprecate `python_venv`
|
||||
languages['python_venv'] = languages['python']
|
||||
all_languages = sorted(languages)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import contextlib
|
|||
import functools
|
||||
import os
|
||||
import sys
|
||||
from typing import Callable
|
||||
from typing import ContextManager
|
||||
from typing import Dict
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
|
@ -26,6 +25,28 @@ from pre_commit.util import cmd_output_b
|
|||
ENVIRONMENT_DIR = 'py_env'
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _version_info(exe: str) -> str:
|
||||
prog = 'import sys;print(".".join(str(p) for p in sys.version_info))'
|
||||
try:
|
||||
return cmd_output(exe, '-S', '-c', prog)[1].strip()
|
||||
except CalledProcessError:
|
||||
return f'<<error retrieving version from {exe}>>'
|
||||
|
||||
|
||||
def _read_pyvenv_cfg(filename: str) -> Dict[str, str]:
|
||||
ret = {}
|
||||
with open(filename) as f:
|
||||
for line in f:
|
||||
try:
|
||||
k, v = line.split('=')
|
||||
except ValueError: # blank line / comment / etc.
|
||||
continue
|
||||
else:
|
||||
ret[k.strip()] = v.strip()
|
||||
return ret
|
||||
|
||||
|
||||
def bin_dir(venv: str) -> str:
|
||||
"""On windows there's a different directory for the virtualenv"""
|
||||
bin_part = 'Scripts' if os.name == 'nt' else 'bin'
|
||||
|
|
@ -116,6 +137,9 @@ def _sys_executable_matches(version: str) -> bool:
|
|||
|
||||
|
||||
def norm_version(version: str) -> str:
|
||||
if version == C.DEFAULT:
|
||||
return os.path.realpath(sys.executable)
|
||||
|
||||
# first see if our current executable is appropriate
|
||||
if _sys_executable_matches(version):
|
||||
return sys.executable
|
||||
|
|
@ -140,70 +164,59 @@ def norm_version(version: str) -> str:
|
|||
return os.path.expanduser(version)
|
||||
|
||||
|
||||
def py_interface(
|
||||
_dir: str,
|
||||
_make_venv: Callable[[str, str], None],
|
||||
) -> Tuple[
|
||||
Callable[[Prefix, str], ContextManager[None]],
|
||||
Callable[[Prefix, str], bool],
|
||||
Callable[[Hook, Sequence[str], bool], Tuple[int, bytes]],
|
||||
Callable[[Prefix, str, Sequence[str]], None],
|
||||
]:
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
envdir = prefix.path(helpers.environment_dir(_dir, language_version))
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
def healthy(prefix: Prefix, language_version: str) -> bool:
|
||||
envdir = helpers.environment_dir(_dir, language_version)
|
||||
exe_name = 'python.exe' if sys.platform == 'win32' else 'python'
|
||||
py_exe = prefix.path(bin_dir(envdir), exe_name)
|
||||
with in_env(prefix, language_version):
|
||||
retcode, _, _ = cmd_output_b(
|
||||
py_exe, '-c', 'import ctypes, datetime, io, os, ssl, weakref',
|
||||
cwd='/',
|
||||
retcode=None,
|
||||
)
|
||||
return retcode == 0
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> Tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
directory = helpers.environment_dir(_dir, version)
|
||||
install = ('python', '-mpip', 'install', '.', *additional_dependencies)
|
||||
|
||||
env_dir = prefix.path(directory)
|
||||
with clean_path_on_failure(env_dir):
|
||||
if version != C.DEFAULT:
|
||||
python = norm_version(version)
|
||||
else:
|
||||
python = os.path.realpath(sys.executable)
|
||||
_make_venv(env_dir, python)
|
||||
with in_env(prefix, version):
|
||||
helpers.run_setup_cmd(prefix, install)
|
||||
|
||||
return in_env, healthy, run_hook, install_environment
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version)
|
||||
envdir = prefix.path(directory)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
def make_venv(envdir: str, python: str) -> None:
|
||||
env = dict(os.environ, VIRTUALENV_NO_DOWNLOAD='1')
|
||||
cmd = (sys.executable, '-mvirtualenv', envdir, '-p', python)
|
||||
cmd_output_b(*cmd, env=env, cwd='/')
|
||||
def healthy(prefix: Prefix, language_version: str) -> bool:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version)
|
||||
envdir = prefix.path(directory)
|
||||
pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg')
|
||||
|
||||
# created with "old" virtualenv
|
||||
if not os.path.exists(pyvenv_cfg):
|
||||
return False
|
||||
|
||||
exe_name = 'python.exe' if sys.platform == 'win32' else 'python'
|
||||
py_exe = prefix.path(bin_dir(envdir), exe_name)
|
||||
cfg = _read_pyvenv_cfg(pyvenv_cfg)
|
||||
|
||||
return (
|
||||
'version_info' in cfg and
|
||||
_version_info(py_exe) == cfg['version_info'] and (
|
||||
'base-executable' not in cfg or
|
||||
_version_info(cfg['base-executable']) == cfg['version_info']
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
_interface = py_interface(ENVIRONMENT_DIR, make_venv)
|
||||
in_env, healthy, run_hook, install_environment = _interface
|
||||
def install_environment(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
|
||||
python = norm_version(version)
|
||||
venv_cmd = (sys.executable, '-mvirtualenv', envdir, '-p', python)
|
||||
install_cmd = ('python', '-mpip', 'install', '.', *additional_dependencies)
|
||||
|
||||
with clean_path_on_failure(envdir):
|
||||
cmd_output_b(*venv_cmd, cwd='/')
|
||||
with in_env(prefix, version):
|
||||
helpers.run_setup_cmd(prefix, install_cmd)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> Tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import os.path
|
||||
|
||||
from pre_commit.languages import python
|
||||
from pre_commit.util import CalledProcessError
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import cmd_output_b
|
||||
|
||||
ENVIRONMENT_DIR = 'py_venv'
|
||||
get_default_version = python.get_default_version
|
||||
|
||||
|
||||
def orig_py_exe(exe: str) -> str: # pragma: no cover (platform specific)
|
||||
"""A -mvenv virtualenv made from a -mvirtualenv virtualenv installs
|
||||
packages to the incorrect location. Attempt to find the _original_ exe
|
||||
and invoke `-mvenv` from there.
|
||||
|
||||
See:
|
||||
- https://github.com/pre-commit/pre-commit/issues/755
|
||||
- https://github.com/pypa/virtualenv/issues/1095
|
||||
- https://bugs.python.org/issue30811
|
||||
"""
|
||||
try:
|
||||
prefix_script = 'import sys; print(sys.real_prefix)'
|
||||
_, prefix, _ = cmd_output(exe, '-c', prefix_script)
|
||||
prefix = prefix.strip()
|
||||
except CalledProcessError:
|
||||
# not created from -mvirtualenv
|
||||
return exe
|
||||
|
||||
if os.name == 'nt':
|
||||
expected = os.path.join(prefix, 'python.exe')
|
||||
else:
|
||||
expected = os.path.join(prefix, 'bin', os.path.basename(exe))
|
||||
|
||||
if os.path.exists(expected):
|
||||
return expected
|
||||
else:
|
||||
return exe
|
||||
|
||||
|
||||
def make_venv(envdir: str, python: str) -> None:
|
||||
cmd_output_b(orig_py_exe(python), '-mvenv', envdir, cwd='/')
|
||||
|
||||
|
||||
_interface = python.py_interface(ENVIRONMENT_DIR, make_venv)
|
||||
in_env, healthy, run_hook, install_environment = _interface
|
||||
Loading…
Add table
Add a link
Reference in a new issue