mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'bugfix/4226' of github.com:pypa/pipenv into bugfix/4226
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Pyenv/asdf can now be used whether or not they are available on PATH. Setting PYENV_ROOT/ASDF_DIR in a Pipenv's .env allows Pipenv to install an interpreter without any shell customizations, so long as pyenv/asdf is installed.
|
||||
+21
-18
@@ -164,7 +164,7 @@ def load_dot_env():
|
||||
err=True,
|
||||
)
|
||||
dotenv.load_dotenv(dotenv_file, override=True)
|
||||
|
||||
six.moves.reload_module(environments)
|
||||
|
||||
def add_to_path(p):
|
||||
"""Adds a given path to the PATH."""
|
||||
@@ -352,15 +352,16 @@ def find_a_system_python(line):
|
||||
|
||||
|
||||
def ensure_python(three=None, python=None):
|
||||
# Support for the PIPENV_PYTHON environment variable.
|
||||
from .environments import PIPENV_PYTHON
|
||||
# Runtime import is necessary due to the possibility that the environments module may have been reloaded.
|
||||
from .environments import PIPENV_PYTHON, PIPENV_YES
|
||||
|
||||
if PIPENV_PYTHON and python is False and three is None:
|
||||
python = PIPENV_PYTHON
|
||||
|
||||
def abort():
|
||||
def abort(msg=''):
|
||||
click.echo(
|
||||
"You can specify specific versions of Python with:\n {0}".format(
|
||||
"{0}\nYou can specify specific versions of Python with:\n{1}".format(
|
||||
crayons.red(msg),
|
||||
crayons.red(
|
||||
"$ pipenv --python {0}".format(
|
||||
os.sep.join(("path", "to", "python"))
|
||||
@@ -395,21 +396,25 @@ def ensure_python(three=None, python=None):
|
||||
err=True,
|
||||
)
|
||||
# check for python installers
|
||||
from .vendor.pythonfinder.environment import PYENV_INSTALLED, ASDF_INSTALLED
|
||||
from .installers import Pyenv, Asdf, InstallerError
|
||||
from .installers import Pyenv, Asdf, InstallerError, InstallerNotFound
|
||||
|
||||
# prefer pyenv if both pyenv and asdf are installed as it's
|
||||
# dedicated to python installs so probably the preferred
|
||||
# method of the user for new python installs.
|
||||
if PYENV_INSTALLED and not PIPENV_DONT_USE_PYENV:
|
||||
installer = Pyenv("pyenv")
|
||||
elif ASDF_INSTALLED and not PIPENV_DONT_USE_ASDF:
|
||||
installer = Asdf("asdf")
|
||||
else:
|
||||
installer = None
|
||||
installer = None
|
||||
if not PIPENV_DONT_USE_PYENV:
|
||||
try:
|
||||
installer = Pyenv()
|
||||
except InstallerNotFound:
|
||||
pass
|
||||
if installer is None and not PIPENV_DONT_USE_ASDF:
|
||||
try:
|
||||
installer = Asdf()
|
||||
except InstallerNotFound:
|
||||
pass
|
||||
|
||||
if not installer:
|
||||
abort()
|
||||
abort("Neither 'pyenv' nor 'asdf' could be found to install Python.")
|
||||
else:
|
||||
if SESSION_IS_INTERACTIVE or PIPENV_YES:
|
||||
try:
|
||||
@@ -417,9 +422,7 @@ def ensure_python(three=None, python=None):
|
||||
except ValueError:
|
||||
abort()
|
||||
except InstallerError as e:
|
||||
click.echo(fix_utf8("Something went wrong…"))
|
||||
click.echo(crayons.blue(e.err), err=True)
|
||||
abort()
|
||||
abort('Something went wrong while installing Python:\n{}'.format(e.err))
|
||||
s = "{0} {1} {2}".format(
|
||||
"Would you like us to install",
|
||||
crayons.green("CPython {0}".format(version)),
|
||||
@@ -434,7 +437,7 @@ def ensure_python(three=None, python=None):
|
||||
u"{0} {1} {2} {3}{4}".format(
|
||||
crayons.normal(u"Installing", bold=True),
|
||||
crayons.green(u"CPython {0}".format(version), bold=True),
|
||||
crayons.normal(u"with {0}".format(installer), bold=True),
|
||||
crayons.normal(u"with {0}".format(installer.cmd), bold=True),
|
||||
crayons.normal(u"(this may take a few minutes)"),
|
||||
crayons.normal(fix_utf8("…"), bold=True),
|
||||
)
|
||||
|
||||
+64
-8
@@ -1,8 +1,12 @@
|
||||
import os
|
||||
import operator
|
||||
import re
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
from .environments import PIPENV_INSTALL_TIMEOUT
|
||||
from .vendor import attr, delegator
|
||||
from .utils import find_windows_executable
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -48,6 +52,10 @@ class Version(object):
|
||||
return (self.major, self.minor) == (other.major, other.minor)
|
||||
|
||||
|
||||
class InstallerNotFound(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class InstallerError(RuntimeError):
|
||||
def __init__(self, desc, c):
|
||||
super(InstallerError, self).__init__(desc)
|
||||
@@ -56,29 +64,70 @@ class InstallerError(RuntimeError):
|
||||
|
||||
|
||||
class Installer(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
def __init__(self, cmd):
|
||||
self._cmd = cmd
|
||||
def __init__(self):
|
||||
self.cmd = self._find_installer()
|
||||
super(Installer, self).__init__()
|
||||
|
||||
def __str__(self):
|
||||
return self._cmd
|
||||
@abstractmethod
|
||||
def _find_installer(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _find_python_installer_by_name_and_env(name, env_var):
|
||||
"""
|
||||
Given a python installer (pyenv or asdf), try to locate the binary for that
|
||||
installer.
|
||||
|
||||
pyenv/asdf are not always present on PATH. Both installers also support a
|
||||
custom environment variable (PYENV_ROOT or ASDF_DIR) which alows them to
|
||||
be installed into a non-default location (the default/suggested source
|
||||
install location is in ~/.pyenv or ~/.asdf).
|
||||
|
||||
For systems without the installers on PATH, and with a custom location
|
||||
(e.g. /opt/pyenv), Pipenv can use those installers without modifications to
|
||||
PATH, if an installer's respective environment variable is present in an
|
||||
environment's .env file.
|
||||
|
||||
This function searches for installer binaries in the following locations,
|
||||
by precedence:
|
||||
1. On PATH, equivalent to which(1).
|
||||
2. In the "bin" subdirectory of PYENV_ROOT or ASDF_DIR, depending on the
|
||||
installer.
|
||||
3. In ~/.pyenv/bin or ~/.asdf/bin, depending on the installer.
|
||||
"""
|
||||
for candidate in (
|
||||
# Look for the Python installer using the equivalent of 'which'. On
|
||||
# Homebrew-installed systems, the env var may not be set, but this
|
||||
# strategy will work.
|
||||
find_windows_executable('', name),
|
||||
# Check for explicitly set install locations (e.g. PYENV_ROOT, ASDF_DIR).
|
||||
os.path.join(os.path.expanduser(os.getenv(env_var, '/dev/null')), 'bin', name),
|
||||
# Check the pyenv/asdf-recommended from-source install locations
|
||||
os.path.join(os.path.expanduser('~/.{}'.format(name)), 'bin', name),
|
||||
):
|
||||
if candidate is not None and os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
||||
return candidate
|
||||
raise InstallerNotFound()
|
||||
|
||||
def _run(self, *args, **kwargs):
|
||||
timeout = kwargs.pop('timeout', delegator.TIMEOUT)
|
||||
if kwargs:
|
||||
k = list(kwargs.keys())[0]
|
||||
raise TypeError('unexpected keyword argument {0!r}'.format(k))
|
||||
args = (self._cmd,) + tuple(args)
|
||||
args = (self.cmd,) + tuple(args)
|
||||
c = delegator.run(args, block=False, timeout=timeout)
|
||||
c.block()
|
||||
if c.return_code != 0:
|
||||
raise InstallerError('faild to run {0}'.format(args), c)
|
||||
raise InstallerError('failed to run {0}'.format(args), c)
|
||||
return c
|
||||
|
||||
@abstractmethod
|
||||
def iter_installable_versions(self):
|
||||
"""Iterate through CPython versions available for Pipenv to install.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
def find_version_to_install(self, name):
|
||||
"""Find a version in the installer from the version supplied.
|
||||
@@ -100,6 +149,7 @@ class Installer(object):
|
||||
)
|
||||
return best_match
|
||||
|
||||
@abstractmethod
|
||||
def install(self, version):
|
||||
"""Install the given version with runner implementation.
|
||||
|
||||
@@ -109,11 +159,14 @@ class Installer(object):
|
||||
A ValueError is raised if the given version does not have a match in
|
||||
the runner. A InstallerError is raised if the runner command fails.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
|
||||
class Pyenv(Installer):
|
||||
|
||||
def _find_installer(self):
|
||||
return self._find_python_installer_by_name_and_env('pyenv', 'PYENV_ROOT')
|
||||
|
||||
def iter_installable_versions(self):
|
||||
"""Iterate through CPython versions available for Pipenv to install.
|
||||
"""
|
||||
@@ -140,6 +193,9 @@ class Pyenv(Installer):
|
||||
|
||||
class Asdf(Installer):
|
||||
|
||||
def _find_installer(self):
|
||||
return self._find_python_installer_by_name_and_env('asdf', 'ASDF_DIR')
|
||||
|
||||
def iter_installable_versions(self):
|
||||
"""Iterate through CPython versions available for asdf to install.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user