Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dial back Pythonfinder integration effort #2582

Merged
merged 57 commits into from
Jul 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
0d2ceb6
Pull in PythonFinder updates
uranusjr Jul 14, 2018
6d7b26a
Minimal Pythonfinder intergration
uranusjr Jul 14, 2018
ac41cdc
Remove duplicate and unused code
uranusjr Jul 14, 2018
59b974b
Use Pythonfinder in help
uranusjr Jul 14, 2018
d5cebed
Keep logic of using actual Python commands
uranusjr Jul 14, 2018
b82d558
Tape directly into Pythonfinder instead
uranusjr Jul 14, 2018
d3d5d56
Update pythonfinder and make full use of it in support calls
techalchemy Jul 15, 2018
33328c4
Fully integrate pythonfinder for system pythons
techalchemy Jul 15, 2018
aeb07cd
Fix search path
techalchemy Jul 15, 2018
26c3464
Updated buildkite to echo out the path
techalchemy Jul 15, 2018
5da85d3
Add some debugging
techalchemy Jul 15, 2018
c6a3284
Updated buildkite to set home properly
techalchemy Jul 15, 2018
dd06342
More debug info for buildkite
techalchemy Jul 15, 2018
684475e
More debugging
techalchemy Jul 15, 2018
c6fb1ea
Fix path search to include prereleases
techalchemy Jul 15, 2018
5366253
More robust path searching
techalchemy Jul 16, 2018
50aff8e
Update pythonfinder
techalchemy Jul 16, 2018
b93b287
Try the new code
techalchemy Jul 16, 2018
597cd2d
Cleanup core and help to use new finder
techalchemy Jul 16, 2018
0d7afad
Update pythonfinder
techalchemy Jul 16, 2018
08cb159
Windows fix
techalchemy Jul 16, 2018
2de9505
Fix system path iterator
techalchemy Jul 16, 2018
8bb39a4
Fix version iteration in pythonfinder
techalchemy Jul 16, 2018
861c075
Use new api in help methods
techalchemy Jul 16, 2018
27028e1
Possibly stray echo
uranusjr Jul 16, 2018
9cb3f97
Clean up help module
uranusjr Jul 16, 2018
a2f8442
We DONT want sys.executable when searching
uranusjr Jul 16, 2018
0b7b0c9
Refactor and docstring for find_a_system_python
uranusjr Jul 16, 2018
07a6477
Unused
uranusjr Jul 16, 2018
3a734f1
Proper subprocess decoding
uranusjr Jul 16, 2018
10749cc
Don't fallback if a command is not found
uranusjr Jul 16, 2018
a709bf8
Update pythonfinder
techalchemy Jul 17, 2018
6518325
Stop using subprocess to interface with windows finder
techalchemy Jul 17, 2018
febfc6e
Better algorithm for find_all_versions
techalchemy Jul 17, 2018
cc9c5a3
Strip dashes from `py -n` commands
techalchemy Jul 17, 2018
95f0df7
Fix pythonfinder bug unnesting python versions
techalchemy Jul 17, 2018
b5bd420
Fix click encoding for terminal outputs
techalchemy Jul 17, 2018
9e10e72
Block before getting outputs
techalchemy Jul 17, 2018
2f85e9b
Add quotes
techalchemy Jul 17, 2018
4b26aa8
Fix aggregation for support
techalchemy Jul 17, 2018
216f639
Don't process the py line twice
uranusjr Jul 17, 2018
6e4205a
Pass arch into Windows finder
uranusjr Jul 17, 2018
bf8d0b9
Update pythonfinder for better windows support
techalchemy Jul 17, 2018
21cbf0c
Update pythonfinder to default patch versions to 0
techalchemy Jul 18, 2018
b7daa25
Update pythonfinder
techalchemy Jul 18, 2018
1b69089
Cached properties!
techalchemy Jul 18, 2018
dbcb5a7
Add cached property to vendored deps
techalchemy Jul 18, 2018
b8aa62a
Python
uranusjr Jul 18, 2018
29326d5
Block inside spinner block
uranusjr Jul 18, 2018
c9277c5
More isdigit() fixes
uranusjr Jul 18, 2018
e3f37be
Fix missing arch arguments
uranusjr Jul 25, 2018
f9655cb
Update vendoring tasks for updating single vendored deps
techalchemy Jul 25, 2018
f5df34f
Update vendoring instructions
techalchemy Jul 25, 2018
ef060c4
Update pythonfinder
techalchemy Jul 25, 2018
5ff8bde
Update vendor.txt
techalchemy Jul 25, 2018
ed8e862
isnumeric -> isdigit
uranusjr Jul 26, 2018
147e106
Merge branch 'master' into pythonfinder-lite
uranusjr Jul 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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