diff --git a/CHANGES.txt b/CHANGES.txt index e88fb5832..191f071ae 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,9 @@ * Fix a regression on systems with uninitialized locale (:issue:`3575`). +* Use environment markers to filter packages before determining if a + required wheel is supported. Solves (:issue:`3254`). + **8.1.1 (2016-03-17)** diff --git a/pip/req/req_install.py b/pip/req/req_install.py index 19e11bb01..dce9f26ce 100644 --- a/pip/req/req_install.py +++ b/pip/req/req_install.py @@ -27,7 +27,7 @@ import pip.wheel from pip.compat import native_str, get_stdlib, WINDOWS from pip.download import is_url, url_to_path, path_to_url, is_archive_file from pip.exceptions import ( - InstallationError, UninstallationError, UnsupportedWheel, + InstallationError, UninstallationError, ) from pip.locations import ( bin_py, running_under_virtualenv, PIP_DELETE_MARKER_FILENAME, bin_user, @@ -210,11 +210,6 @@ class InstallRequirement(object): # wheel file if link.is_wheel: wheel = Wheel(link.filename) # can raise InvalidWheelFilename - if not wheel.supported(): - raise UnsupportedWheel( - "%s is not a supported wheel on this platform." % - wheel.filename - ) req = "%s==%s" % (wheel.name, wheel.version) else: # set the req to the egg fragment. when it's not there, this diff --git a/pip/req/req_set.py b/pip/req/req_set.py index 03338a7d6..a4e6b0e16 100644 --- a/pip/req/req_set.py +++ b/pip/req/req_set.py @@ -21,7 +21,7 @@ from pip.utils import ( from pip.utils.hashes import MissingHashes from pip.utils.logging import indent_log from pip.vcs import vcs - +from pip.wheel import Wheel logger = logging.getLogger(__name__) @@ -227,6 +227,16 @@ class RequirementSet(object): install_req.markers) return [] + # This check has to come after we filter requirements with the + # environment markers. + if install_req.link and install_req.link.is_wheel: + wheel = Wheel(install_req.link.filename) + if not wheel.supported(): + raise InstallationError( + "%s is not a supported wheel on this platform." % + wheel.filename + ) + install_req.as_egg = self.as_egg install_req.use_user_site = self.use_user_site install_req.target_dir = self.target_dir diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index d228096b2..6ae9ea75c 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -457,3 +457,41 @@ def test_install_distribution_union_conflicting_extras(script, data): expect_error=True) assert 'installed' not in result.stdout assert "Conflict" in result.stderr + + +def test_install_unsupported_wheel_link_with_marker(script, data): + script.scratch_path.join("with-marker.txt").write( + textwrap.dedent("""\ + %s; %s + """) % + ( + 'https://github.com/a/b/c/asdf-1.5.2-cp27-none-xyz.whl', + 'sys_platform == "xyz"' + ) + ) + result = script.pip( + 'install', '-r', script.scratch_path / 'with-marker.txt', + expect_error=False, + expect_stderr=True, + ) + + s = "Ignoring asdf: markers %r don't match your environment" %\ + u'sys_platform == "xyz"' + assert s in result.stderr + assert len(result.files_created) == 0 + + +def test_install_unsupported_wheel_file(script, data): + # Trying to install a local wheel with an incompatible version/type + # should fail. + script.scratch_path.join("wheel-file.txt").write(textwrap.dedent("""\ + %s + """ % data.packages.join("simple.dist-0.1-py1-none-invalid.whl"))) + result = script.pip( + 'install', '-r', script.scratch_path / 'wheel-file.txt', + expect_error=True, + expect_stderr=True, + ) + assert ("simple.dist-0.1-py1-none-invalid.whl is not a supported " + + "wheel on this platform" in result.stderr) + assert len(result.files_created) == 0 diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index bb0584832..40ceb8f6f 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -8,7 +8,7 @@ import pytest from mock import Mock, patch, mock_open from pip.commands.install import InstallCommand from pip.exceptions import (PreviousBuildDirError, InvalidWheelFilename, - InstallationError, UnsupportedWheel, HashErrors) + InstallationError, HashErrors) from pip.download import path_to_url, PipSession from pip.index import PackageFinder from pip.req import (InstallRequirement, RequirementSet, Requirements) @@ -301,6 +301,20 @@ def test_egg_info_data(file_contents, expected): class TestInstallRequirement(object): + def setup(self): + self.tempdir = tempfile.mkdtemp() + + def teardown(self): + shutil.rmtree(self.tempdir, ignore_errors=True) + + def basic_reqset(self, **kwargs): + return RequirementSet( + build_dir=os.path.join(self.tempdir, 'build'), + src_dir=os.path.join(self.tempdir, 'src'), + download_dir=None, + session=PipSession(), + **kwargs + ) def test_url_with_query(self): """InstallRequirement should strip the fragment, but not the query.""" @@ -309,11 +323,29 @@ class TestInstallRequirement(object): req = InstallRequirement.from_line(url + fragment) assert req.link.url == url + fragment, req.link - def test_unsupported_wheel_requirement_raises(self): - with pytest.raises(UnsupportedWheel): - InstallRequirement.from_line( - 'peppercorn-0.4-py2.py3-bogus-any.whl', - ) + def test_unsupported_wheel_link_requirement_raises(self): + reqset = self.basic_reqset() + req = InstallRequirement.from_line( + 'https://whatever.com/peppercorn-0.4-py2.py3-bogus-any.whl', + ) + assert req.link is not None + assert req.link.is_wheel + assert req.link.scheme == "https" + + with pytest.raises(InstallationError): + reqset.add_requirement(req) + + def test_unsupported_wheel_local_file_requirement_raises(self, data): + reqset = self.basic_reqset() + req = InstallRequirement.from_line( + data.packages.join('simple.dist-0.1-py1-none-invalid.whl'), + ) + assert req.link is not None + assert req.link.is_wheel + assert req.link.scheme == "file" + + with pytest.raises(InstallationError): + reqset.add_requirement(req) def test_installed_version_not_installed(self): req = InstallRequirement.from_line('simple-0.1-py2.py3-none-any.whl')