diff --git a/news/4885.bugfix.rst b/news/4885.bugfix.rst new file mode 100644 index 0000000000..77ebf82cba --- /dev/null +++ b/news/4885.bugfix.rst @@ -0,0 +1 @@ +Fix regression where lockfiles would only include the hashes for releases for the platform generating the lockfile diff --git a/pipenv/utils.py b/pipenv/utils.py index cb2cecd1d0..1f6556f908 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -428,6 +428,7 @@ def __init__( self._parsed_constraints = None self._resolver = None self._finder = None + self._ignore_compatibility_finder = None self._session = None self._constraint_file = None self._pip_options = None @@ -798,6 +799,22 @@ def finder(self): ) return self._finder + @property + def ignore_compatibility_finder(self): + from pipenv.vendor.pip_shims import shims + if self._ignore_compatibility_finder is None: + ignore_compatibility_finder = shims.get_package_finder( + install_cmd=self.pip_command, + options=self.pip_options, + session=self.session, + ) + # It would be nice if `shims.get_package_finder` took an + # `ignore_compatibility` parameter, but that's some vendorered code + # we'd rather avoid touching. + ignore_compatibility_finder._ignore_compatibility = True + self._ignore_compatibility_finder = ignore_compatibility_finder + return self._ignore_compatibility_finder + @property def parsed_constraints(self): from pipenv.vendor.pip_shims import shims @@ -950,7 +967,7 @@ def collect_hashes(self, ireq): if hashes: return hashes - applicable_candidates = self.finder.find_best_candidate( + applicable_candidates = self.ignore_compatibility_finder.find_best_candidate( ireq.name, ireq.specifier ).iter_applicable() return { diff --git a/pipenv/vendor/pip_shims/compat.py b/pipenv/vendor/pip_shims/compat.py index f01f5feaec..4dd596ea7b 100644 --- a/pipenv/vendor/pip_shims/compat.py +++ b/pipenv/vendor/pip_shims/compat.py @@ -693,6 +693,7 @@ def get_package_finder( and "ignore_requires_python" in builder_args.args ): build_kwargs["ignore_requires_python"] = ignore_requires_python + return install_cmd._build_package_finder(**build_kwargs) # type: ignore diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index 9168b6b7c4..e1235f5401 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -5,6 +5,7 @@ import pytest +import pytest_pypi.app from flaky import flaky from vistir.misc import to_text from pipenv.utils import temp_environ @@ -57,6 +58,41 @@ def test_lock_requirements_file(PipenvInstance): assert req in d.stdout +@pytest.mark.lock +def test_lock_includes_hashes_for_all_platforms(PipenvInstance): + """ Locking should include hashes for *all* platforms, not just the + platform we're running lock on. """ + + releases = pytest_pypi.app.packages['yarl'].releases + def get_hash(release_name): + # Convert a specific filename to a hash like what would show up in a Pipfile.lock. + # For example: + # 'yarl-1.3.0-cp35-cp35m-manylinux1_x86_64.whl' -> 'sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb' + return f"sha256:{releases[release_name].hash}" + + with PipenvInstance() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[packages] +yarl = "==1.3.0" + """.strip() + f.write(contents) + + c = p.pipenv('lock') + assert c.returncode == 0 + + lock = p.lockfile + assert 'yarl' in lock['default'] + assert set(lock['default']['yarl']['hashes']) == { + get_hash('yarl-1.3.0-cp35-cp35m-manylinux1_x86_64.whl'), + get_hash('yarl-1.3.0-cp35-cp35m-win_amd64.whl'), + get_hash('yarl-1.3.0-cp36-cp36m-manylinux1_x86_64.whl'), + get_hash('yarl-1.3.0-cp36-cp36m-win_amd64.whl'), + get_hash('yarl-1.3.0-cp37-cp37m-win_amd64.whl'), + get_hash('yarl-1.3.0.tar.gz'), + } + + @pytest.mark.lock @pytest.mark.keep_outdated def test_lock_keep_outdated(PipenvInstance):