Merge pull request #2677 from taoufik07/fix_cmd_norm_env

Fix command normalization when a custom env is passed
This commit is contained in:
Anthony Sottile 2023-01-06 10:03:08 -05:00 committed by GitHub
commit dc667ab9fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 27 deletions

View file

@ -20,13 +20,13 @@ def parse_filename(filename: str) -> tuple[str, ...]:
def find_executable( def find_executable(
exe: str, _environ: Mapping[str, str] | None = None, exe: str, *, env: Mapping[str, str] | None = None,
) -> str | None: ) -> str | None:
exe = os.path.normpath(exe) exe = os.path.normpath(exe)
if os.sep in exe: if os.sep in exe:
return exe return exe
environ = _environ if _environ is not None else os.environ environ = env if env is not None else os.environ
if 'PATHEXT' in environ: if 'PATHEXT' in environ:
exts = environ['PATHEXT'].split(os.pathsep) exts = environ['PATHEXT'].split(os.pathsep)
@ -43,12 +43,12 @@ def find_executable(
return None return None
def normexe(orig: str) -> str: def normexe(orig: str, *, env: Mapping[str, str] | None = None) -> str:
def _error(msg: str) -> NoReturn: def _error(msg: str) -> NoReturn:
raise ExecutableNotFoundError(f'Executable `{orig}` {msg}') raise ExecutableNotFoundError(f'Executable `{orig}` {msg}')
if os.sep not in orig and (not os.altsep or os.altsep not in orig): if os.sep not in orig and (not os.altsep or os.altsep not in orig):
exe = find_executable(orig) exe = find_executable(orig, env=env)
if exe is None: if exe is None:
_error('not found') _error('not found')
return exe return exe
@ -62,7 +62,11 @@ def normexe(orig: str) -> str:
return orig return orig
def normalize_cmd(cmd: tuple[str, ...]) -> tuple[str, ...]: def normalize_cmd(
cmd: tuple[str, ...],
*,
env: Mapping[str, str] | None = None,
) -> tuple[str, ...]:
"""Fixes for the following issues on windows """Fixes for the following issues on windows
- https://bugs.python.org/issue8557 - https://bugs.python.org/issue8557
- windows does not parse shebangs - windows does not parse shebangs
@ -70,12 +74,12 @@ def normalize_cmd(cmd: tuple[str, ...]) -> tuple[str, ...]:
This function also makes deep-path shebangs work just fine This function also makes deep-path shebangs work just fine
""" """
# Use PATH to determine the executable # Use PATH to determine the executable
exe = normexe(cmd[0]) exe = normexe(cmd[0], env=env)
# Figure out the shebang from the resulting command # Figure out the shebang from the resulting command
cmd = parse_filename(exe) + (exe,) + cmd[1:] cmd = parse_filename(exe) + (exe,) + cmd[1:]
# This could have given us back another bare executable # This could have given us back another bare executable
exe = normexe(cmd[0]) exe = normexe(cmd[0], env=env)
return (exe,) + cmd[1:] return (exe,) + cmd[1:]

View file

@ -99,7 +99,7 @@ def cmd_output_b(
_setdefault_kwargs(kwargs) _setdefault_kwargs(kwargs)
try: try:
cmd = parse_shebang.normalize_cmd(cmd) cmd = parse_shebang.normalize_cmd(cmd, env=kwargs.get('env'))
except parse_shebang.ExecutableNotFoundError as e: except parse_shebang.ExecutableNotFoundError as e:
returncode, stdout_b, stderr_b = e.to_output() returncode, stdout_b, stderr_b = e.to_output()
else: else:

View file

@ -248,7 +248,7 @@ def test_install_idempotent(tempdir_factory, store):
def _path_without_us(): def _path_without_us():
# Choose a path which *probably* doesn't include us # Choose a path which *probably* doesn't include us
env = dict(os.environ) env = dict(os.environ)
exe = find_executable('pre-commit', _environ=env) exe = find_executable('pre-commit', env=env)
while exe: while exe:
parts = env['PATH'].split(os.pathsep) parts = env['PATH'].split(os.pathsep)
after = [ after = [
@ -258,7 +258,7 @@ def _path_without_us():
if parts == after: if parts == after:
raise AssertionError(exe, parts) raise AssertionError(exe, parts)
env['PATH'] = os.pathsep.join(after) env['PATH'] = os.pathsep.join(after)
exe = find_executable('pre-commit', _environ=env) exe = find_executable('pre-commit', env=env)
return env['PATH'] return env['PATH']
@ -276,18 +276,19 @@ def test_environment_not_sourced(tempdir_factory, store):
# Use a specific homedir to ignore --user installs # Use a specific homedir to ignore --user installs
homedir = tempdir_factory.get() homedir = tempdir_factory.get()
ret, out = git_commit( env = {
env={ 'HOME': homedir,
'HOME': homedir, 'PATH': _path_without_us(),
'PATH': _path_without_us(), # Git needs this to make a commit
# Git needs this to make a commit 'GIT_AUTHOR_NAME': os.environ['GIT_AUTHOR_NAME'],
'GIT_AUTHOR_NAME': os.environ['GIT_AUTHOR_NAME'], 'GIT_COMMITTER_NAME': os.environ['GIT_COMMITTER_NAME'],
'GIT_COMMITTER_NAME': os.environ['GIT_COMMITTER_NAME'], 'GIT_AUTHOR_EMAIL': os.environ['GIT_AUTHOR_EMAIL'],
'GIT_AUTHOR_EMAIL': os.environ['GIT_AUTHOR_EMAIL'], 'GIT_COMMITTER_EMAIL': os.environ['GIT_COMMITTER_EMAIL'],
'GIT_COMMITTER_EMAIL': os.environ['GIT_COMMITTER_EMAIL'], }
}, if os.name == 'nt' and 'PATHEXT' in os.environ: # pragma: no cover
check=False, env['PATHEXT'] = os.environ['PATHEXT']
)
ret, out = git_commit(env=env, check=False)
assert ret == 1 assert ret == 1
assert out == ( assert out == (
'`pre-commit` not found. ' '`pre-commit` not found. '

View file

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import Mapping
from unittest import mock from unittest import mock
import pytest import pytest
@ -48,7 +49,9 @@ def test_installs_with_bootstrapped_rustup(tmpdir, language_version):
original_find_executable = parse_shebang.find_executable original_find_executable = parse_shebang.find_executable
def mocked_find_executable(exe: str) -> str | None: def mocked_find_executable(
exe: str, *, env: Mapping[str, str] | None = None,
) -> str | None:
""" """
Return `None` the first time `find_executable` is called to ensure Return `None` the first time `find_executable` is called to ensure
that the bootstrapping code is executed, then just let the function that the bootstrapping code is executed, then just let the function
@ -59,7 +62,7 @@ def test_installs_with_bootstrapped_rustup(tmpdir, language_version):
find_executable_exes.append(exe) find_executable_exes.append(exe)
if len(find_executable_exes) == 1: if len(find_executable_exes) == 1:
return None return None
return original_find_executable(exe) return original_find_executable(exe, env=env)
with mock.patch.object(parse_shebang, 'find_executable') as find_exe_mck: with mock.patch.object(parse_shebang, 'find_executable') as find_exe_mck:
find_exe_mck.side_effect = mocked_find_executable find_exe_mck.side_effect = mocked_find_executable

View file

@ -75,10 +75,10 @@ def test_find_executable_path_ext(in_tmpdir):
env_path = {'PATH': os.path.dirname(exe_path)} env_path = {'PATH': os.path.dirname(exe_path)}
env_path_ext = dict(env_path, PATHEXT=os.pathsep.join(('.exe', '.myext'))) env_path_ext = dict(env_path, PATHEXT=os.pathsep.join(('.exe', '.myext')))
assert parse_shebang.find_executable('run') is None assert parse_shebang.find_executable('run') is None
assert parse_shebang.find_executable('run', _environ=env_path) is None assert parse_shebang.find_executable('run', env=env_path) is None
ret = parse_shebang.find_executable('run.myext', _environ=env_path) ret = parse_shebang.find_executable('run.myext', env=env_path)
assert ret == exe_path assert ret == exe_path
ret = parse_shebang.find_executable('run', _environ=env_path_ext) ret = parse_shebang.find_executable('run', env=env_path_ext)
assert ret == exe_path assert ret == exe_path