get lua version from luarocks itself

This commit is contained in:
Anthony Sottile 2022-01-17 15:46:36 -05:00
parent 3f8be7400d
commit 54331dca6f
3 changed files with 27 additions and 144 deletions

View file

@ -1,6 +1,6 @@
import contextlib import contextlib
import os import os
import re import sys
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple from typing import Tuple
@ -11,7 +11,6 @@ from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import helpers from pre_commit.languages import helpers
from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure from pre_commit.util import clean_path_on_failure
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
@ -21,95 +20,38 @@ get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy healthy = helpers.basic_healthy
def _find_lua(language_version: str) -> str: # pragma: win32 no cover def _get_lua_version() -> str: # pragma: win32 no cover
"""Find a lua executable.
Lua doesn't always have a plain `lua` executable.
Some OS vendors will ship the binary as `lua#.#` (e.g., lua5.3)
so discovery is needed to find a valid executable.
"""
if language_version == C.DEFAULT:
choices = ['lua']
for path in os.environ.get('PATH', '').split(os.pathsep):
try:
candidates = os.listdir(path)
except OSError:
# Invalid path on PATH or lacking permissions.
continue
for candidate in candidates:
# The Lua executable might look like `lua#.#` or `lua-#.#`.
if re.search(r'^lua[-]?\d+\.\d+', candidate):
choices.append(candidate)
else:
# Prefer version specific executables first if available.
# This should avoid the corner case where a user requests a language
# version, gets a `lua` executable, but that executable is actually
# for a different version and package.path would patch LUA_PATH
# incorrectly.
choices = [f'lua{language_version}', 'lua-{language_version}', 'lua']
found_exes = [exe for exe in choices if find_executable(exe)]
if found_exes:
return found_exes[0]
raise ValueError(
'No lua executable found on the system paths '
f'for {language_version} version.',
)
def _get_lua_path_version(
lua_executable: str,
) -> str: # pragma: win32 no cover
"""Get the Lua version used in file paths.""" """Get the Lua version used in file paths."""
# This could sniff out from _VERSION, but checking package.path should _, stdout, _ = cmd_output('luarocks', 'config', '--lua-ver')
# provide an answer for *exactly* where lua is looking for packages. return stdout.strip()
_, stdout, _ = cmd_output(lua_executable, '-e', 'print(package.path)')
sep = os.sep if os.name != 'nt' else os.sep * 2
match = re.search(fr'{sep}lua{sep}(.*?){sep}', stdout)
if match:
return match[1]
raise ValueError('Cannot determine lua version for file paths.')
def get_env_patch( def get_env_patch(d: str) -> PatchesT: # pragma: win32 no cover
env: str, language_version: str, version = _get_lua_version()
) -> PatchesT: # pragma: win32 no cover so_ext = 'dll' if sys.platform == 'win32' else 'so'
lua = _find_lua(language_version)
version = _get_lua_path_version(lua)
return ( return (
('PATH', (os.path.join(env, 'bin'), os.pathsep, Var('PATH'))), ('PATH', (os.path.join(d, 'bin'), os.pathsep, Var('PATH'))),
( (
'LUA_PATH', ( 'LUA_PATH', (
os.path.join(env, 'share', 'lua', version, '?.lua;'), os.path.join(d, 'share', 'lua', version, '?.lua;'),
os.path.join(env, 'share', 'lua', version, '?', 'init.lua;;'), os.path.join(d, 'share', 'lua', version, '?', 'init.lua;;'),
), ),
), ),
( (
'LUA_CPATH', ( 'LUA_CPATH',
os.path.join(env, 'lib', 'lua', version, '?.so;;'), (os.path.join(d, 'lib', 'lua', version, f'?.{so_ext};;'),),
),
), ),
) )
def _envdir(prefix: Prefix, version: str) -> str: # pragma: win32 no cover def _envdir(prefix: Prefix) -> str: # pragma: win32 no cover
directory = helpers.environment_dir(ENVIRONMENT_DIR, version) directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT)
return prefix.path(directory) return prefix.path(directory)
@contextlib.contextmanager # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover
def in_env( def in_env(prefix: Prefix) -> Generator[None, None, None]:
prefix: Prefix, with envcontext(get_env_patch(_envdir(prefix))):
language_version: str,
) -> Generator[None, None, None]:
with envcontext(
get_env_patch(
_envdir(prefix, language_version), language_version,
),
):
yield yield
@ -120,19 +62,17 @@ def install_environment(
) -> None: # pragma: win32 no cover ) -> None: # pragma: win32 no cover
helpers.assert_version_default('lua', version) helpers.assert_version_default('lua', version)
envdir = _envdir(prefix, version) envdir = _envdir(prefix)
with clean_path_on_failure(envdir): with clean_path_on_failure(envdir):
with in_env(prefix, version): with in_env(prefix):
# luarocks doesn't bootstrap a tree prior to installing # luarocks doesn't bootstrap a tree prior to installing
# so ensure the directory exists. # so ensure the directory exists.
os.makedirs(envdir, exist_ok=True) os.makedirs(envdir, exist_ok=True)
make_cmd = ['luarocks', '--tree', envdir, 'make'] # Older luarocks (e.g., 2.4.2) expect the rockspec as an arg
# Older luarocks (e.g., 2.4.2) expect the rockspec as an argument. for rockspec in prefix.star('.rockspec'):
filenames = prefix.star('.rockspec') make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec)
make_cmd.extend(filenames[:1]) helpers.run_setup_cmd(prefix, make_cmd)
helpers.run_setup_cmd(prefix, tuple(make_cmd))
# luarocks can't install multiple packages at once # luarocks can't install multiple packages at once
# so install them individually. # so install them individually.
@ -146,5 +86,5 @@ def run_hook(
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: # pragma: win32 no cover ) -> Tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix, hook.language_version): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,55 +0,0 @@
import os
from unittest import mock
import pytest
import pre_commit.constants as C
from pre_commit.languages import lua
from testing.util import xfailif_windows
@pytest.mark.parametrize(
'lua_name', ('lua', 'lua5.4', 'lua-5.4', 'lua5.4.exe'),
)
def test_find_lua(tmp_path, lua_name):
"""The language support can find common lua executable names."""
lua_file = tmp_path / lua_name
lua_file.touch(0o555)
with mock.patch.dict(os.environ, {'PATH': str(tmp_path)}):
lua_executable = lua._find_lua(C.DEFAULT)
assert lua_name in lua_executable
def test_find_lua_language_version(tmp_path):
"""Language discovery can find a specific version."""
lua_file = tmp_path / 'lua5.99'
lua_file.touch(0o555)
with mock.patch.dict(os.environ, {'PATH': str(tmp_path)}):
lua_executable = lua._find_lua('5.99')
assert 'lua5.99' in lua_executable
@pytest.mark.parametrize(
('invalid', 'mode'),
(
('foobar', 0o555),
('luac', 0o555),
# Windows doesn't respect the executable checking.
pytest.param('lua5.4', 0o444, marks=xfailif_windows),
),
)
def test_find_lua_fail(tmp_path, invalid, mode):
"""No lua executable on the system will fail."""
non_lua_file = tmp_path / invalid
non_lua_file.touch(mode)
with mock.patch.dict(os.environ, {'PATH': str(tmp_path)}):
with pytest.raises(ValueError):
lua._find_lua(C.DEFAULT)
@mock.patch.object(lua, 'cmd_output')
def test_bad_package_path(mock_cmd_output):
"""A package path missing path info returns an unknown version."""
mock_cmd_output.return_value = (0, '', '')
with pytest.raises(ValueError):
lua._get_lua_path_version('lua')

View file

@ -17,7 +17,6 @@ from pre_commit.envcontext import envcontext
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import golang from pre_commit.languages import golang
from pre_commit.languages import helpers from pre_commit.languages import helpers
from pre_commit.languages import lua
from pre_commit.languages import node from pre_commit.languages import node
from pre_commit.languages import python from pre_commit.languages import python
from pre_commit.languages import ruby from pre_commit.languages import ruby
@ -1142,18 +1141,17 @@ def test_lua_hook(tempdir_factory, store):
@skipif_cant_run_lua # pragma: win32 no cover @skipif_cant_run_lua # pragma: win32 no cover
def test_local_lua_additional_dependencies(store): def test_local_lua_additional_dependencies(store):
lua_entry = lua._find_lua(C.DEFAULT)
config = { config = {
'repo': 'local', 'repo': 'local',
'hooks': [{ 'hooks': [{
'id': 'local-lua', 'id': 'local-lua',
'name': 'local-lua', 'name': 'local-lua',
'entry': lua_entry, 'entry': 'luacheck --version',
'language': 'lua', 'language': 'lua',
'args': ['-e', 'require "inspect"; print("hello world")'], 'additional_dependencies': ['luacheck'],
'additional_dependencies': ['inspect'],
}], }],
} }
hook = _get_hook(config, store, 'local-lua') hook = _get_hook(config, store, 'local-lua')
ret, out = _hook_run(hook, (), color=False) ret, out = _hook_run(hook, (), color=False)
assert (ret, _norm_out(out)) == (0, b'hello world\n') assert b'Luacheck' in out
assert ret == 0