From bfdb9aed87e864c20f4de2f70b027be8e50de2b2 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Tue, 19 Feb 2019 20:29:26 -0500 Subject: [PATCH] Fix installed package discovery - Exclude python when searching virtualenvs created using nested virtualenv interpreters (via `lib-dynload` library directory) Signed-off-by: Dan Ryan --- pipenv/cli/command.py | 3 ++- pipenv/core.py | 14 +++++++------- pipenv/environment.py | 14 ++++++++++---- tests/integration/test_pipenv.py | 1 - 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 22489658..16ce115e 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -630,7 +630,8 @@ def sync( def clean(ctx, state, dry_run=False, bare=False, user=False): """Uninstalls all packages not specified in Pipfile.lock.""" from ..core import do_clean - do_clean(ctx=ctx, three=state.three, python=state.python, dry_run=dry_run) + do_clean(ctx=ctx, three=state.three, python=state.python, dry_run=dry_run, + system=state.system) # Only invoke the "did you mean" when an argument wasn't passed (it breaks those). diff --git a/pipenv/core.py b/pipenv/core.py index 462337ff..22ef34bc 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -2714,7 +2714,10 @@ def do_sync( click.echo(crayons.green("All dependencies are now up-to-date!")) -def do_clean(ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None): +def do_clean( + ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None, + system=False +): # Ensure that virtualenv is available. from packaging.utils import canonicalize_name ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror) @@ -2736,6 +2739,7 @@ def do_clean(ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirro if used_package in installed_package_names: installed_package_names.remove(used_package) failure = False + cmd = [which_pip(allow_global=system), "uninstall", "-y", "-qq"] for apparent_bad_package in installed_package_names: if dry_run and not bare: click.echo(apparent_bad_package) @@ -2747,12 +2751,8 @@ def do_clean(ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirro ) ) # Uninstall the package. - c = delegator.run( - "{0} uninstall {1} -y".format( - escape_grouped_arguments(which_pip()), - apparent_bad_package - ) - ) + cmd_str = Script.parse(cmd + [apparent_bad_package]).cmdify() + c = delegator.run(cmd_str, block=True) if c.return_code != 0: failure = True sys.exit(int(failure)) diff --git a/pipenv/environment.py b/pipenv/environment.py index 3502107a..5e2ec3f5 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -244,7 +244,7 @@ class Environment(object): """ pkg_resources = self.safe_import("pkg_resources") - return pkg_resources.find_distributions(self.paths["PYTHONPATH"]) + return pkg_resources.find_distributions(self.paths["libdirs"]) def find_egg(self, egg_dist): """Find an egg by name in the given environment""" @@ -271,16 +271,22 @@ class Environment(object): def dist_is_in_project(self, dist): """Determine whether the supplied distribution is in the environment.""" from .project import _normalized - prefix = _normalized(self.base_paths["prefix"]) + prefixes = [ + _normalized(prefix) for prefix in self.base_paths["libdirs"] + if _normalized(self.prefix).startswith(_normalized(prefix)) + ] location = self.locate_dist(dist) if not location: return False - return _normalized(location).startswith(prefix) + return any(_normalized(location).startswith(prefix) for prefix in prefixes) def get_installed_packages(self): """Returns all of the installed packages in a given environment""" workingset = self.get_working_set() - packages = [pkg for pkg in workingset if self.dist_is_in_project(pkg)] + packages = [ + pkg for pkg in workingset + if self.dist_is_in_project(pkg) and pkg.key != "python" + ] return packages @contextlib.contextmanager diff --git a/tests/integration/test_pipenv.py b/tests/integration/test_pipenv.py index 3f114288..9db76d28 100644 --- a/tests/integration/test_pipenv.py +++ b/tests/integration/test_pipenv.py @@ -100,7 +100,6 @@ def test_directory_with_leading_dash(PipenvInstance): return mkdtemp(suffix, prefix, dir) with mock.patch('pipenv.vendor.vistir.compat.mkdtemp', side_effect=mocked_mkdtemp): - del os.environ['PIPENV_VENV_IN_PROJECT'] with temp_environ(), PipenvInstance(chdir=True) as p: if "PIPENV_VENV_IN_PROJECT" in os.environ: del os.environ['PIPENV_VENV_IN_PROJECT']