diff --git a/news/3718.bugfix.rst b/news/3718.bugfix.rst new file mode 100644 index 00000000..7a90ea50 --- /dev/null +++ b/news/3718.bugfix.rst @@ -0,0 +1 @@ +Fixed a bug which sometimes caused pipenv to fail to respect the ``--site-packages`` flag when passed with ``pipenv install``. diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index d1bcaa93..6d89f15a 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -18,7 +18,7 @@ from .options import ( general_options, install_options, lock_options, pass_state, pypi_mirror_option, python_option, requirementstxt_option, skip_lock_option, sync_options, system_option, three_option, - uninstall_options, verbose_option + uninstall_options, verbose_option, site_packages_option ) @@ -217,6 +217,7 @@ def cli( @system_option @code_option @deploy_option +@site_packages_option @skip_lock_option @install_options @pass_state @@ -249,6 +250,7 @@ def install( extra_index_url=state.extra_index_urls, packages=state.installstate.packages, editable_packages=state.installstate.editables, + site_packages=state.site_packages ) if retcode: ctx.abort() @@ -310,11 +312,10 @@ def lock( ): """Generates Pipfile.lock.""" from ..core import ensure_project, do_init, do_lock - # Ensure that virtualenv is available. ensure_project( three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, - warn=(not state.quiet) + warn=(not state.quiet), site_packages=state.site_packages, clear=state.clear ) if state.installstate.requirementstxt: do_init( @@ -475,8 +476,10 @@ def update( do_sync, project, ) - - ensure_project(three=state.three, python=state.python, warn=True, pypi_mirror=state.pypi_mirror) + ensure_project( + three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, + warn=(not state.quiet), site_packages=state.site_packages, clear=state.clear + ) if not outdated: outdated = bool(dry_run) if outdated: @@ -505,12 +508,13 @@ def update( err=True, ) ctx.abort() - do_lock( + ctx=ctx, clear=state.clear, pre=state.installstate.pre, keep_outdated=state.installstate.keep_outdated, pypi_mirror=state.pypi_mirror, + write=not state.quiet, ) do_sync( ctx=ctx, diff --git a/pipenv/core.py b/pipenv/core.py index cf427728..266a83c2 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1861,6 +1861,7 @@ def do_install( deploy=False, keep_outdated=False, selective_upgrade=False, + site_packages=False, ): from .environments import PIPENV_VIRTUALENV, PIPENV_USE_SYSTEM from .vendor.pip_shims.shims import PipError @@ -1888,6 +1889,7 @@ def do_install( deploy=deploy, skip_requirements=skip_requirements, pypi_mirror=pypi_mirror, + site_packages=site_packages, ) # Don't attempt to install develop and default packages if Pipfile is missing if not project.pipfile_exists and not (package_args or dev) and not code: diff --git a/pipenv/vendor/requirementslib/models/setup_info.py b/pipenv/vendor/requirementslib/models/setup_info.py index 454cbfcc..551f3e87 100644 --- a/pipenv/vendor/requirementslib/models/setup_info.py +++ b/pipenv/vendor/requirementslib/models/setup_info.py @@ -195,7 +195,7 @@ def make_base_requirements(reqs): requirements.add(req) elif isinstance(req, pkg_resources_requirements.Requirement): requirements.add(BaseRequirement.from_req(req)) - elif req and not req.startswith("#"): + elif req and isinstance(req, six.string_types) and not req.startswith("#"): requirements.add(BaseRequirement.from_string(req)) return requirements @@ -240,10 +240,15 @@ def get_package_dir_from_setupcfg(parser, base_dir=None): if "package_dir" in setup_py: package_lookup = setup_py["package_dir"] if not isinstance(package_lookup, Mapping): - return package_lookup - return package_lookup.get( + package_dir = package_lookup + package_dir = package_lookup.get( next(iter(list(package_lookup.keys()))), package_dir ) + if not os.path.isabs(package_dir): + if not base_dir: + package_dir = os.path.join(os.path.getcwd(), package_dir) + else: + package_dir = os.path.join(base_dir, package_dir) return package_dir @@ -296,7 +301,8 @@ def parse_setup_cfg(setup_cfg_path): parser = configparser.ConfigParser(default_opts) parser.read(setup_cfg_path) results = {} - package_dir = get_package_dir_from_setupcfg(parser, base_dir=os.getcwd()) + base_dir = os.path.dirname(os.path.abspath(setup_cfg_path)) + package_dir = get_package_dir_from_setupcfg(parser, base_dir=base_dir) name, version = get_name_and_version_from_setupcfg(parser, package_dir) results["name"] = name results["version"] = version @@ -638,6 +644,8 @@ class Analyzer(ast.NodeVisitor): self.functions = [] self.strings = [] self.assignments = {} + self.binOps = [] + self.binOps_map = {} super(Analyzer, self).__init__() def generic_visit(self, node): @@ -652,6 +660,17 @@ class Analyzer(ast.NodeVisitor): self.assignments.update(ast_unparse(node, initial_mapping=True)) super(Analyzer, self).generic_visit(node) + def visit_BinOp(self, node): + left = ast_unparse(node.left, initial_mapping=True) + right = ast_unparse(node.right, initial_mapping=True) + node.left = left + node.right = right + self.binOps.append(node) + + def unmap_binops(self): + for binop in self.binOps: + self.binOps_map[binop] = ast_unparse(binop, analyzer=self) + def match_assignment_str(self, match): return next( iter(k for k in self.assignments if getattr(k, "id", "") == match), None @@ -678,6 +697,26 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # no unparsed = item.s elif isinstance(item, ast.Subscript): unparsed = unparse(item.value) + elif isinstance(item, ast.BinOp): + if analyzer and item in analyzer.binOps_map: + unparsed = analyzer.binOps_map[item] + elif isinstance(item.op, ast.Add): + if not initial_mapping: + right_item = unparse(item.right) + left_item = unparse(item.left) + if not all( + isinstance(side, (six.string_types, int, float, list, tuple)) + for side in (left_item, right_item) + ): + item.left = left_item + item.right = right_item + unparsed = item + else: + unparsed = right_item + left_item + else: + unparsed = item + elif isinstance(item.op, ast.Sub): + unparsed = unparse(item.left) - unparse(item.right) elif isinstance(item, ast.Name): if not initial_mapping: unparsed = item.id @@ -787,6 +826,7 @@ def ast_parse_setup_py(path): # type: (S) -> Dict[Any, Any] ast_analyzer = ast_parse_file(path) setup = {} # type: Dict[Any, Any] + ast_analyzer.unmap_binops() for k, v in ast_analyzer.function_map.items(): fn_name = "" if isinstance(k, ast.Name):