Skip to content

Commit

Permalink
Merge pull request #2582 from pypa/pythonfinder-lite
Browse files Browse the repository at this point in the history
Dial back Pythonfinder integration effort
  • Loading branch information
uranusjr authored Jul 30, 2018
2 parents 08477e8 + 147e106 commit a853814
Show file tree
Hide file tree
Showing 23 changed files with 760 additions and 316 deletions.
1 change: 1 addition & 0 deletions news/2582.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed multiple issues with finding the correct system python locations.
4 changes: 4 additions & 0 deletions news/2582.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Greatly enhanced python discovery functionality:

- Added pep514 (windows launcher/finder) support for python discovery.
- Introduced architecture discovery for python installations which support different architectures.
1 change: 1 addition & 0 deletions news/2582.vendor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update ``pythonfinder`` to major release ``1.0.0`` for integration.
159 changes: 42 additions & 117 deletions pipenv/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import shutil
import time
import tempfile
from glob import glob
import json as simplejson
import click
import click_completion
Expand Down Expand Up @@ -51,8 +50,6 @@
PIPENV_SKIP_VALIDATION,
PIPENV_HIDE_EMOJIS,
PIPENV_INSTALL_TIMEOUT,
PYENV_ROOT,
PYENV_INSTALLED,
PIPENV_YES,
PIPENV_DONT_LOAD_ENV,
PIPENV_DEFAULT_PYTHON_VERSION,
Expand Down Expand Up @@ -317,76 +314,38 @@ def ensure_pipfile(validate=True, skip_requirements=False, system=False):
project.write_toml(p)


def find_python_from_py(python):
"""Find a Python executable from on Windows.
def find_a_system_python(line):
"""Find a Python installation from a given line.
Ask py.exe for its opinion.
"""
py = system_which("py")
if not py:
return None

version_args = ["-{0}".format(python[0])]
if len(python) >= 2:
version_args.append("-{0}.{1}".format(python[0], python[2]))
import subprocess

for ver_arg in reversed(version_args):
try:
python_exe = subprocess.check_output(
[py, ver_arg, "-c", "import sys; print(sys.executable)"]
)
except subprocess.CalledProcessError:
continue

if not isinstance(python_exe, str):
python_exe = python_exe.decode(sys.getdefaultencoding())
python_exe = python_exe.strip()
version = python_version(python_exe)
if (version or "").startswith(python):
return python_exe


def find_python_in_path(python):
"""Find a Python executable from a version number.
This tries to parse the line in various of ways:
This uses the PATH environment variable to locate an appropriate Python.
* Looks like an absolute path? Use it directly.
* Looks like a py.exe call? Use py.exe to get the executable.
* Starts with "py" something? Looks like a python command. Try to find it
in PATH, and use it directly.
* Search for "python" and "pythonX.Y" executables in PATH to find a match.
* Nothing fits, return None.
"""
possibilities = ["python", "python{0}".format(python[0])]
if len(python) >= 2:
possibilities.extend(
[
"python{0}{1}".format(python[0], python[2]),
"python{0}.{1}".format(python[0], python[2]),
"python{0}.{1}m".format(python[0], python[2]),
]
)
# Reverse the list, so we find specific ones first.
possibilities = reversed(possibilities)
for possibility in possibilities:
# Windows compatibility.
if os.name == "nt":
possibility = "{0}.exe".format(possibility)
pythons = system_which(possibility, mult=True)
for p in pythons:
version = python_version(p)
if (version or "").startswith(python):
return p


def find_a_system_python(python):
"""Finds a system python, given a version (e.g. 2 / 2.7 / 3.6.2), or a full path."""
if python.startswith("py"):
return system_which(python)

elif os.path.isabs(python):
return python

python_from_py = find_python_from_py(python)
if python_from_py:
return python_from_py

return find_python_in_path(python)
if not line:
return None
if os.path.isabs(line):
return line
from .vendor.pythonfinder import Finder
finder = Finder(system=False, global_search=True)
if ((line.startswith("py ") or line.startswith("py.exe "))
and os.name == 'nt'):
line = line.split(" ", 1)[1].lstrip("-")
elif line.startswith("py"):
python_entry = finder.which(line)
if python_entry:
return python_entry.path.as_posix()
return None
python_entry = finder.find_python_version(line)
if not python_entry:
python_entry = finder.which("python{0}".format(line))
if python_entry:
return python_entry.path.as_posix()
return None


def ensure_python(three=None, python=None):
Expand All @@ -409,35 +368,7 @@ def abort():
)
sys.exit(1)

def activate_pyenv():
from notpip._vendor.packaging.version import parse as parse_version

"""Adds all pyenv installations to the PATH."""
if PYENV_INSTALLED:
if PYENV_ROOT:
pyenv_paths = {}
for found in glob("{0}{1}versions{1}*".format(PYENV_ROOT, os.sep)):
pyenv_paths[os.path.split(found)[1]] = "{0}{1}bin".format(
found, os.sep
)
for version_str, pyenv_path in pyenv_paths.items():
version = parse_version(version_str)
if version.is_prerelease and pyenv_paths.get(version.base_version):
continue

add_to_path(pyenv_path)
else:
click.echo(
"{0}: PYENV_ROOT is not set. New python paths will "
"probably not be exported properly after installation."
"".format(crayons.red("Warning", bold=True)),
err=True,
)

global USING_DEFAULT_PYTHON
# Add pyenv paths to PATH.
activate_pyenv()
path_to_python = None
USING_DEFAULT_PYTHON = three is None and not python
# Find out which python is desired.
if not python:
Expand All @@ -446,8 +377,7 @@ def activate_pyenv():
python = project.required_python_version
if not python:
python = PIPENV_DEFAULT_PYTHON_VERSION
if python:
path_to_python = find_a_system_python(python)
path_to_python = find_a_system_python(python)
if not path_to_python and python is not None:
# We need to install Python.
click.echo(
Expand All @@ -459,6 +389,7 @@ def activate_pyenv():
err=True,
)
# Pyenv is installed
from .vendor.pythonfinder.environment import PYENV_INSTALLED
if not PYENV_INSTALLED:
abort()
else:
Expand Down Expand Up @@ -501,8 +432,6 @@ def activate_pyenv():
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)
Expand Down Expand Up @@ -915,21 +844,17 @@ def do_create_virtualenv(python=None, site_packages=False, pypi_mirror=None):

# Actually create the virtualenv.
with spinner():
try:
c = delegator.run(cmd, block=False, timeout=PIPENV_TIMEOUT, env=pip_config)
except OSError:
click.echo(
"{0}: it looks like {1} is not in your {2}. "
"We cannot continue until this is resolved."
"".format(
crayons.red("Warning", bold=True),
crayons.red(cmd[0]),
crayons.normal("PATH", bold=True),
),
err=True,
)
sys.exit(1)
click.echo(crayons.blue(c.out), err=True)
c = delegator.run(
cmd, block=False, timeout=PIPENV_TIMEOUT, env=pip_config,
)
c.block()
click.echo(crayons.blue("{0}".format(c.out)), err=True)
if c.return_code != 0:
click.echo(crayons.blue("{0}".format(c.err)), err=True)
click.echo(u"{0}: Failed to create virtual environment.".format(
crayons.red("Warning", bold=True),
), err=True)
sys.exit(1)

# Associate project directory with the environment.
# This mimics Pew's "setproject".
Expand Down
7 changes: 0 additions & 7 deletions pipenv/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,5 @@
# Internal, the default shell to use if shell detection fails.
PIPENV_SHELL = os.environ.get("SHELL") or os.environ.get("PYENV_SHELL")

# Internal, to tell if pyenv is installed.
PYENV_ROOT = os.environ.get("PYENV_ROOT", os.path.expanduser("~/.pyenv"))
PYENV_INSTALLED = (
bool(os.environ.get("PYENV_SHELL")) or
bool(os.environ.get("PYENV_ROOT"))
)

# Internal, to tell whether the command line session is interactive.
SESSION_IS_INTERACTIVE = bool(os.isatty(sys.stdout.fileno()))
32 changes: 13 additions & 19 deletions pipenv/help.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# coding: utf-8
import os
import pprint
import sys

import pipenv

from pprint import pprint
from .__version__ import __version__
from .core import project, system_which, find_python_in_path, python_version
from .core import project
from .pep508checker import lookup
from .vendor import pythonfinder


def print_utf(line):
Expand All @@ -19,32 +20,25 @@ def print_utf(line):
def get_pipenv_diagnostics():
print("<details><summary>$ pipenv --support</summary>")
print("")
print("Pipenv version: `{0!r}`".format(__version__))
print("Pipenv version: `{0!r}`".format(pipenv.__version__))
print("")
print("Pipenv location: `{0!r}`".format(os.path.dirname(pipenv.__file__)))
print("")
print("Python location: `{0!r}`".format(sys.executable))
print("")
print("Other Python installations in `PATH`:")
print("")
for python_v in ("2.5", "2.6", "2.7", "3.4", "3.5", "3.6", "3.7"):
found = find_python_in_path(python_v)
if found:
print(" - `{0}`: `{1}`".format(python_v, found))
found = system_which("python{0}".format(python_v), mult=True)
if found:
for f in found:
print(" - `{0}`: `{1}`".format(python_v, f))
print("Python installations found:")
print("")
for p in ("python", "python2", "python3", "py"):
found = system_which(p, mult=True)
for f in found:
print(" - `{0}`: `{1}`".format(python_version(f), f))

finder = pythonfinder.Finder(system=False, global_search=True)
python_paths = finder.find_all_python_versions()
for python in python_paths:
print(" - `{}`: `{}`".format(python.py_version.version, python.path))

print("")
print("PEP 508 Information:")
print("")
print("```")
pprint(lookup)
pprint.pprint(lookup)
print("```")
print("")
print("System environment variables:")
Expand Down
19 changes: 15 additions & 4 deletions pipenv/vendor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

These packages are copied as-is from upstream to reduce Pipenv dependencies.
They should always be kept synced with upstream. DO NOT MODIFY DIRECTLY! If
you need to patch anything, move the package to `patched`.
you need to patch anything, move the package to `patched` and generate a
patch for it using `git diff -p <dependency_root_dir>`. This patch belongs
in `./pipenv/tasks/vendoring/patches/patched/<packagename.patchdesc>.patch`.

Known vendored versions:
To add a vendored dependency or to update a single dependency, use the
vendoring scripts:
```
pipenv run inv vendoring.update --package="pkgname==versionnum"
```

- python-dotenv: 0.8.2
This will automatically pin the package in `./pipenv/vendor/vendor.txt`
or it will update the pin if the package is already present, and it will
then update the package and download any necessary licenses (if available).
Note that this will not download any dependencies, you must add those each
individually.

When updating, update the corresponding LICENSE files as well.
When updating, ensure that the corresponding LICENSE files are still
up-to-date.
12 changes: 12 additions & 0 deletions pipenv/vendor/cached-property.LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Copyright (c) 2015, Daniel Greenfeld
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of cached-property nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Loading

0 comments on commit a853814

Please sign in to comment.