Skip to content

Commit

Permalink
Handle language_version when discovering the lua executable.
Browse files Browse the repository at this point in the history
  • Loading branch information
mblayman committed Dec 26, 2021
1 parent 9f0fe28 commit 7acdae2
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 30 deletions.
68 changes: 42 additions & 26 deletions pre_commit/languages/lua.py
Expand Up @@ -5,6 +5,7 @@
from typing import Sequence
from typing import Tuple

import pre_commit.constants as C
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
Expand All @@ -20,52 +21,63 @@
healthy = helpers.basic_healthy


def _find_lua() -> str: # pragma: win32 no cover
def _find_lua(language_version: str) -> 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.
"""
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)

found_exes = [
exe for exe in choices
if find_executable(exe)
]
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.')
raise ValueError(
'No lua executable found on the system paths '
f'for {language_version} version.',
)


def _get_lua_path_version() -> str: # pragma: win32 no cover
def _get_lua_path_version(
lua_executable: str,
) -> str: # pragma: win32 no cover
"""Get the Lua version used in file paths."""
lua = _find_lua()

# This could sniff out from _VERSION, but checking package.path should
# provide an answer for *exactly* where lua is looking for packages.
_, stdout, _ = cmd_output(lua, '-e', 'print(package.path)')
_, stdout, _ = cmd_output(lua_executable, '-e', 'print(package.path)')
match = re.search(fr'{os.sep}lua{os.sep}(.*?){os.sep}', stdout)
if match:
return match[1]

raise ValueError('Cannot determine lua version for file paths.')


def get_env_patch(env: str) -> PatchesT: # pragma: win32 no cover
version = _get_lua_path_version()
def get_env_patch(
env: str, language_version: str,
) -> PatchesT: # pragma: win32 no cover
lua = _find_lua(language_version)
version = _get_lua_path_version(lua)
return (
('PATH', (os.path.join(env, 'bin'), os.pathsep, Var('PATH'))),
(
Expand All @@ -92,7 +104,11 @@ def in_env(
prefix: Prefix,
language_version: str,
) -> Generator[None, None, None]:
with envcontext(get_env_patch(_envdir(prefix, language_version))):
with envcontext(
get_env_patch(
_envdir(prefix, language_version), language_version,
),
):
yield


Expand Down
16 changes: 13 additions & 3 deletions tests/languages/lua_test.py
Expand Up @@ -3,6 +3,7 @@

import pytest

import pre_commit.constants as C
from pre_commit.languages import lua
from testing.util import xfailif_windows

Expand All @@ -15,10 +16,19 @@ def test_find_lua(tmp_path, lua_name):
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()
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'),
(
Expand All @@ -34,12 +44,12 @@ def test_find_lua_fail(tmp_path, invalid, mode):
non_lua_file.touch(mode)
with mock.patch.dict(os.environ, {'PATH': str(tmp_path)}):
with pytest.raises(ValueError):
lua._find_lua()
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._get_lua_path_version('lua')
2 changes: 1 addition & 1 deletion tests/repository_test.py
Expand Up @@ -1142,7 +1142,7 @@ def test_lua_hook(tempdir_factory, store):

@skipif_cant_run_lua # pragma: win32 no cover
def test_local_lua_additional_dependencies(store):
lua_entry = lua._find_lua()
lua_entry = lua._find_lua(C.DEFAULT)
config = {
'repo': 'local',
'hooks': [{
Expand Down

0 comments on commit 7acdae2

Please sign in to comment.