mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge pull request #2572 from pypa/pyenv-autodetect
New, better pyenv module
This commit is contained in:
+12
-31
@@ -458,27 +458,15 @@ def ensure_python(three=None, python=None):
|
||||
abort()
|
||||
else:
|
||||
if (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES):
|
||||
version_map = {
|
||||
# TODO: Keep this up to date!
|
||||
# These versions appear incompatible with virtualenv:
|
||||
# '2.5': '2.5.6',
|
||||
"2.6": "2.6.9",
|
||||
"2.7": "2.7.15",
|
||||
# '3.1': '3.1.5',
|
||||
# '3.2': '3.2.6',
|
||||
"3.3": "3.3.7",
|
||||
"3.4": "3.4.8",
|
||||
"3.5": "3.5.5",
|
||||
"3.6": "3.6.6",
|
||||
"3.7": "3.7.0",
|
||||
}
|
||||
from .pyenv import Runner, PyenvError
|
||||
pyenv = Runner("pyenv")
|
||||
try:
|
||||
if len(python.split(".")) == 2:
|
||||
# Find the latest version of Python available.
|
||||
version = version_map[python]
|
||||
else:
|
||||
version = python
|
||||
except KeyError:
|
||||
version = pyenv.find_version_to_install(python)
|
||||
except ValueError:
|
||||
abort()
|
||||
except PyenvError as e:
|
||||
click.echo(u"Something went wrong…")
|
||||
click.echo(crayons.blue(e.err), err=True)
|
||||
abort()
|
||||
s = "{0} {1} {2}".format(
|
||||
"Would you like us to install",
|
||||
@@ -500,24 +488,17 @@ def ensure_python(three=None, python=None):
|
||||
)
|
||||
)
|
||||
with spinner():
|
||||
# Install Python.
|
||||
c = delegator.run(
|
||||
"pyenv install {0} -s".format(version),
|
||||
timeout=PIPENV_INSTALL_TIMEOUT,
|
||||
block=False,
|
||||
)
|
||||
# Wait until the process has finished…
|
||||
c.block()
|
||||
try:
|
||||
assert c.return_code == 0
|
||||
except AssertionError:
|
||||
c = pyenv.install(version)
|
||||
except PyenvError as e:
|
||||
click.echo(u"Something went wrong…")
|
||||
click.echo(crayons.blue(c.err), err=True)
|
||||
click.echo(crayons.blue(e.err), err=True)
|
||||
# Print the results, in a beautiful blue…
|
||||
click.echo(crayons.blue(c.out), err=True)
|
||||
# Add new paths to PATH.
|
||||
activate_pyenv()
|
||||
# Find the newly installed Python, hopefully.
|
||||
version = str(version)
|
||||
path_to_python = find_a_system_python(version)
|
||||
try:
|
||||
assert python_version(path_to_python) == version
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import operator
|
||||
import re
|
||||
|
||||
from .vendor import attr, delegator
|
||||
|
||||
from .environments import PIPENV_INSTALL_TIMEOUT
|
||||
|
||||
|
||||
@attr.s
|
||||
class Version(object):
|
||||
|
||||
major = attr.ib()
|
||||
minor = attr.ib()
|
||||
patch = attr.ib()
|
||||
|
||||
def __str__(self):
|
||||
parts = [self.major, self.minor]
|
||||
if self.patch is not None:
|
||||
parts.append(self.patch)
|
||||
return '.'.join(str(p) for p in parts)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, name):
|
||||
"""Parse an X.Y.Z or X.Y string into a version tuple.
|
||||
"""
|
||||
match = re.match(r'^(\d+)\.(\d+)(?:\.(\d+))?$', name)
|
||||
if not match:
|
||||
raise ValueError('invalid version name {0!r}'.format(name))
|
||||
major = int(match.group(1))
|
||||
minor = int(match.group(2))
|
||||
patch = match.group(3)
|
||||
if patch is not None:
|
||||
patch = int(patch)
|
||||
return cls(major, minor, patch)
|
||||
|
||||
@property
|
||||
def cmpkey(self):
|
||||
"""Make the version a comparable tuple.
|
||||
|
||||
Some old Python versions does not have a patch part, e.g. 2.7.0 is
|
||||
named "2.7" in pyenv. Fix that, otherwise `None` will fail to compare
|
||||
with int.
|
||||
"""
|
||||
return (self.major, self.minor, self.patch or 0)
|
||||
|
||||
def matches_minor(self, other):
|
||||
"""Check whether this version matches the other in (major, minor).
|
||||
"""
|
||||
return (self.major, self.minor) == (other.major, other.minor)
|
||||
|
||||
|
||||
class PyenvError(RuntimeError):
|
||||
def __init__(self, desc, c):
|
||||
super(PyenvError, self).__init__(desc)
|
||||
self.out = c.out
|
||||
self.err = c.err
|
||||
|
||||
|
||||
class Runner(object):
|
||||
|
||||
def __init__(self, pyenv):
|
||||
self._cmd = pyenv
|
||||
|
||||
def _pyenv(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)
|
||||
c = delegator.run(args, block=False, timeout=timeout)
|
||||
c.block()
|
||||
if c.return_code != 0:
|
||||
raise PyenvError('faild to run {0}'.format(args), c)
|
||||
return c
|
||||
|
||||
def iter_installable_versions(self):
|
||||
"""Iterate through CPython versions available for Pipenv to install.
|
||||
"""
|
||||
for name in self._pyenv('install', '--list').out.splitlines():
|
||||
try:
|
||||
version = Version.parse(name.strip())
|
||||
except ValueError:
|
||||
continue
|
||||
yield version
|
||||
|
||||
def find_version_to_install(self, name):
|
||||
"""Find a version in pyenv from the version supplied.
|
||||
|
||||
A ValueError is raised if a matching version cannot be found.
|
||||
"""
|
||||
version = Version.parse(name)
|
||||
if version.patch is not None:
|
||||
return name
|
||||
try:
|
||||
best_match = max((
|
||||
inst_version
|
||||
for inst_version in self.iter_installable_versions()
|
||||
if inst_version.matches_minor(version)
|
||||
), key=operator.attrgetter('cmpkey'))
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'no installable version found for {0!r}'.format(name),
|
||||
)
|
||||
return best_match
|
||||
|
||||
def install(self, version):
|
||||
"""Install the given version with pyenv.
|
||||
|
||||
The version must be a ``Version`` instance representing a version
|
||||
found in pyenv.
|
||||
|
||||
A ValueError is raised if the given version does not have a match in
|
||||
pyenv. A PyenvError is raised if the pyenv command fails.
|
||||
"""
|
||||
c = self._pyenv(
|
||||
'install', '-s', str(version),
|
||||
timeout=PIPENV_INSTALL_TIMEOUT,
|
||||
)
|
||||
return c
|
||||
Reference in New Issue
Block a user