Skip to content

Commit

Permalink
feat: use external pip if available
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
  • Loading branch information
henryiii committed Feb 19, 2024
1 parent ac57b94 commit a68a36f
Showing 1 changed file with 37 additions and 9 deletions.
46 changes: 37 additions & 9 deletions src/build/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _minimum_pip_version() -> str:
return '19.1.0'


def _has_valid_pip(purelib: str) -> bool:
def _has_valid_pip(**distargs: str) -> bool:
"""
Given a path, see if Pip is present and return True if the version is
sufficient for build, False if it is not.
Expand All @@ -83,13 +83,29 @@ def _has_valid_pip(purelib: str) -> bool:
else:
from importlib import metadata

pip_distribution = next(iter(metadata.distributions(name='pip', path=[purelib])))
pip_distribution = next(iter(metadata.distributions(name='pip', **distargs)))

current_pip_version = packaging.version.Version(pip_distribution.version)

return current_pip_version >= packaging.version.Version(_minimum_pip_version())


@functools.lru_cache(maxsize=None)
def _valid_global_pip() -> bool | None:
"""
This checks for a valid global pip. Returns None if the prerequisites are
not available (Python 3.7 only) or pip is missing, False if Pip is too old,
and True if it can be used.
"""

try:
return _has_valid_pip()
except ModuleNotFoundError: # Python 3.7 only
return None
except StopIteration:
return None


def _subprocess(cmd: list[str]) -> None:
"""Invoke subprocess and output stdout and stderr if it fails."""
try:
Expand Down Expand Up @@ -139,6 +155,12 @@ def python_executable(self) -> str:
"""The python executable of the isolated build environment."""
return self._python_executable

def _pip_args(self, *, isolate: bool = False) -> list[str]:
if _valid_global_pip():
return [sys.executable, '-Im' if isolate else '-m', 'pip', '--python', self.python_executable]
else:
return [self.python_executable, '-Im' if isolate else '-m', 'pip']

def make_extra_environ(self) -> dict[str, str]:
path = os.environ.get('PATH')
return {'PATH': os.pathsep.join([self._scripts_dir, path]) if path is not None else self._scripts_dir}
Expand All @@ -163,9 +185,7 @@ def install(self, requirements: Collection[str]) -> None:
req_file.write(os.linesep.join(requirements))
try:
cmd = [
self.python_executable,
'-Im',
'pip',
*self._pip_args(isolate=True),
'install',
'--use-pep517',
'--no-warn-script-location',
Expand Down Expand Up @@ -201,7 +221,11 @@ def _create_isolated_env_virtualenv(path: str) -> tuple[str, str]:
"""
import virtualenv

cmd = [str(path), '--no-setuptools', '--no-wheel', '--activators', '']
if _valid_global_pip():
cmd = [str(path), '--no-seed', '--activators', '']
else:
cmd = [str(path), '--no-setuptools', '--no-wheel', '--activators', '']

result = virtualenv.cli_run(cmd, setup_logging=False)
executable = str(result.creator.exe)
script_dir = str(result.creator.script_dir)
Expand Down Expand Up @@ -240,18 +264,22 @@ def _create_isolated_env_venv(path: str) -> tuple[str, str]:
with warnings.catch_warnings():
if sys.version_info[:3] == (3, 11, 0):
warnings.filterwarnings('ignore', 'check_home argument is deprecated and ignored.', DeprecationWarning)
venv.EnvBuilder(with_pip=True, symlinks=symlinks).create(path)
venv.EnvBuilder(with_pip=not _valid_global_pip(), symlinks=symlinks).create(path)
except subprocess.CalledProcessError as exc:
raise FailedProcessError(exc, 'Failed to create venv. Maybe try installing virtualenv.') from None

executable, script_dir, purelib = _find_executable_and_scripts(path)

# Get the version of pip in the environment
if not _has_valid_pip(purelib):
if not _valid_global_pip() and not _has_valid_pip(path=[purelib]):
_subprocess([executable, '-m', 'pip', 'install', f'pip>={_minimum_pip_version()}'])

# Avoid the setuptools from ensurepip to break the isolation
_subprocess([executable, '-m', 'pip', 'uninstall', 'setuptools', '-y'])
if _valid_global_pip():
_subprocess([sys.executable, '-m', 'pip', '--python', executable, 'uninstall', 'setuptools', '-y'])
else:
_subprocess([executable, '-m', 'pip', 'uninstall', 'setuptools', '-y'])

return executable, script_dir


Expand Down

0 comments on commit a68a36f

Please sign in to comment.