diff --git a/pipenv/core.py b/pipenv/core.py index e92b762c3e..7a12ae1eff 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -25,10 +25,10 @@ from .cmdparse import Script from .environments import ( PIPENV_CACHE_DIR, PIPENV_COLORBLIND, PIPENV_DEFAULT_PYTHON_VERSION, - PIPENV_DONT_USE_PYENV, PIPENV_HIDE_EMOJIS, PIPENV_MAX_SUBPROCESS, - PIPENV_PYUP_API_KEY, PIPENV_SHELL_FANCY, PIPENV_SKIP_VALIDATION, - PIPENV_YES, SESSION_IS_INTERACTIVE, PIP_EXISTS_ACTION, PIPENV_RESOLVE_VCS, - is_type_checking + PIPENV_DONT_USE_PYENV, PIPENV_DONT_USE_ASDF, PIPENV_HIDE_EMOJIS, + PIPENV_MAX_SUBPROCESS, PIPENV_PYUP_API_KEY, PIPENV_SHELL_FANCY, + PIPENV_SKIP_VALIDATION, PIPENV_YES, SESSION_IS_INTERACTIVE, PIP_EXISTS_ACTION, + PIPENV_RESOLVE_VCS, is_type_checking ) from .project import Project, SourceNotFound from .utils import ( @@ -393,76 +393,79 @@ def abort(): ), err=True, ) - # Pyenv is installed - from .vendor.pythonfinder.environment import PYENV_INSTALLED + # Pyenv/Asdf is installed + from .vendor.pythonfinder.environment import PYENV_INSTALLED, ASDF_INSTALLED + from .runner import RunnerError, Pyenv, Asdf + + if PYENV_INSTALLED and (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES): + name = "pyenv" + runner = Pyenv(name) + elif ASDF_INSTALLED and (not PIPENV_DONT_USE_ASDF) and (SESSION_IS_INTERACTIVE or PIPENV_YES): + name = "asdf" + runner = Asdf(name) + else: + abort() - if not PYENV_INSTALLED: + try: + version = runner.find_version_to_install(python) + except ValueError: + abort() + except RunnerError as e: + click.echo(fix_utf8("Something went wrong…")) + click.echo(crayons.blue(e.err), err=True) + abort() + s = "{0} {1} {2}".format( + "Would you like us to install", + crayons.green("CPython {0}".format(version)), + "with {0}?".format(name), + ) + # Prompt the user to continue… + if not (PIPENV_YES or click.confirm(s, default=True)): abort() else: - if (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES): - from .pyenv import Runner, PyenvError - - pyenv = Runner("pyenv") + # Tell the user we're installing Python. + click.echo( + 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(name), bold=True), + crayons.normal(u"(this may take a few minutes)"), + crayons.normal(fix_utf8("…"), bold=True), + ) + ) + with create_spinner("Installing python...") as sp: try: - version = pyenv.find_version_to_install(python) - except ValueError: - abort() - except PyenvError as e: - click.echo(fix_utf8("Something went wrong…")) + c = runner.install(version) + except RunnerError as e: + sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( + "Failed...") + ) + click.echo(fix_utf8("Something went wrong…"), err=True) click.echo(crayons.blue(e.err), err=True) - abort() - s = "{0} {1} {2}".format( - "Would you like us to install", - crayons.green("CPython {0}".format(version)), - "with pyenv?", - ) - # Prompt the user to continue… - if not (PIPENV_YES or click.confirm(s, default=True)): - abort() else: - # Tell the user we're installing Python. - click.echo( - 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 pyenv", bold=True), - crayons.normal(u"(this may take a few minutes)"), - crayons.normal(fix_utf8("…"), bold=True), - ) - ) - with create_spinner("Installing python...") as sp: - try: - c = pyenv.install(version) - except PyenvError as e: - sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( - "Failed...") - ) - click.echo(fix_utf8("Something went wrong…"), err=True) - click.echo(crayons.blue(e.err), err=True) - else: - sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!")) - # Print the results, in a beautiful blue… - click.echo(crayons.blue(c.out), err=True) - # Clear the pythonfinder caches - from .vendor.pythonfinder import Finder - finder = Finder(system=False, global_search=True) - finder.find_python_version.cache_clear() - finder.find_all_python_versions.cache_clear() - # 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 - except AssertionError: - click.echo( - "{0}: The Python you just installed is not available on your {1}, apparently." - "".format( - crayons.red("Warning", bold=True), - crayons.normal("PATH", bold=True), - ), - err=True, - ) - sys.exit(1) + sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!")) + # Print the results, in a beautiful blue… + click.echo(crayons.blue(c.out), err=True) + # Clear the pythonfinder caches + from .vendor.pythonfinder import Finder + finder = Finder(system=False, global_search=True) + finder.find_python_version.cache_clear() + finder.find_all_python_versions.cache_clear() + # 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 + except AssertionError: + click.echo( + "{0}: The Python you just installed is not available on your {1}, apparently." + "".format( + crayons.red("Warning", bold=True), + crayons.normal("PATH", bold=True), + ), + err=True, + ) + sys.exit(1) return path_to_python diff --git a/pipenv/environments.py b/pipenv/environments.py index e59ed63a69..d05ddcaa04 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -68,6 +68,12 @@ def _is_env_truthy(name): Default is to install Python automatically via pyenv when needed, if possible. """ +PIPENV_DONT_USE_ASDF = bool(os.environ.get("PIPENV_DONT_USE_ASDF")) +"""If set, Pipenv does not attempt to install Python with asdf. + +Default is to install Python automatically via asdf when needed, if possible. +""" + PIPENV_DOTENV_LOCATION = os.environ.get("PIPENV_DOTENV_LOCATION") """If set, Pipenv loads the ``.env`` file at the specified location. diff --git a/pipenv/pyenv.py b/pipenv/runner.py similarity index 63% rename from pipenv/pyenv.py rename to pipenv/runner.py index 941e599183..3dd6965fe9 100644 --- a/pipenv/pyenv.py +++ b/pipenv/runner.py @@ -48,19 +48,12 @@ def matches_minor(self, other): 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 __init__(self, cmd): + self._cmd = cmd - def _pyenv(self, *args, **kwargs): + def _run(self, *args, **kwargs): timeout = kwargs.pop('timeout', delegator.TIMEOUT) if kwargs: k = list(kwargs.keys())[0] @@ -69,21 +62,16 @@ def _pyenv(self, *args, **kwargs): c = delegator.run(args, block=False, timeout=timeout) c.block() if c.return_code != 0: - raise PyenvError('faild to run {0}'.format(args), c) + raise RunnerError('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 + raise NotImplementedError def find_version_to_install(self, name): - """Find a version in pyenv from the version supplied. + """Find a version in runner from the version supplied. A ValueError is raised if a matching version cannot be found. """ @@ -102,6 +90,37 @@ def find_version_to_install(self, name): ) return best_match + def install(self, version): + """Install the given version with asdf. + + The version must be a ``Version`` instance representing a version + found in asdf. + + A ValueError is raised if the given version does not have a match in + asdf. A RunnerError is raised if the asdf command fails. + """ + raise NotImplementedError + + +class RunnerError(RuntimeError): + def __init__(self, desc, c): + super(RunnerError, self).__init__(desc) + self.out = c.out + self.err = c.err + + +class Pyenv(Runner): + + def iter_installable_versions(self): + """Iterate through CPython versions available for Pipenv to install. + """ + for name in self._run('install', '--list').out.splitlines(): + try: + version = Version.parse(name.strip()) + except ValueError: + continue + yield version + def install(self, version): """Install the given version with pyenv. @@ -109,10 +128,38 @@ def install(self, 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. + pyenv. A RunnerError is raised if the pyenv command fails. """ - c = self._pyenv( + c = self._run( 'install', '-s', str(version), timeout=PIPENV_INSTALL_TIMEOUT, ) return c + + +class Asdf(Runner): + + def iter_installable_versions(self): + """Iterate through CPython versions available for asdf to install. + """ + for name in self._run('list-all', 'python').out.splitlines(): + try: + version = Version.parse(name.strip()) + except ValueError: + continue + yield version + + def install(self, version): + """Install the given version with asdf. + + The version must be a ``Version`` instance representing a version + found in asdf. + + A ValueError is raised if the given version does not have a match in + asdf. A RunnerError is raised if the asdf command fails. + """ + c = self._run( + 'install', 'python', str(version), + timeout=PIPENV_INSTALL_TIMEOUT, + ) + return c