Skip to content

Commit 6e481b1

Browse files
committed
Merge pull request pypa#8 from pfmoore/wheel_install_platcheck
Add platform checks to wheel location code
2 parents aaf18e3 + 50c2111 commit 6e481b1

File tree

4 files changed

+128
-12
lines changed

4 files changed

+128
-12
lines changed

pip/index.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
product, url2pathname)
2626
from pip.backwardcompat import Empty as QueueEmpty
2727
from pip.download import urlopen, path_to_url2, url_to_path, geturl, Urllib2HeadRequest
28+
import pip.pep425tags
2829

2930
__all__ = ['PackageFinder']
3031

@@ -329,17 +330,15 @@ def _link_package_versions(self, link, search_name):
329330
wheel_info = self._wheel_info_re.match(link.filename)
330331
if wheel_info.group('name').replace('_', '-').lower() == search_name.lower():
331332
version = wheel_info.group('ver')
332-
nodot = sys.version[:3].replace('.', '')
333333
pyversions = wheel_info.group('pyver').split('.')
334-
ok = False
335-
for pv in pyversions:
336-
# TODO: Doesn't check Python implementation
337-
if nodot.startswith(pv[2:]):
338-
ok = True
339-
break
340-
if not ok:
341-
logger.debug('Skipping %s because Python version is incorrect' % link)
342-
return []
334+
abis = wheel_info.group('abi').split('.')
335+
plats = wheel_info.group('plat').split('.')
336+
wheel_supports = set((x, y, z) for x in pyversions for y
337+
in abis for z in plats)
338+
supported = set(pip.pep425tags.get_supported())
339+
if not supported.intersection(wheel_supports):
340+
logger.debug('Skipping %s because it is not compatible with this Python' % link)
341+
return []
343342
if not version:
344343
version = self._egg_info_matches(egg_info, search_name, link)
345344
if version is None:

pip/pep425tags.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Generate and work with PEP 425 Compatibility Tags."""
2+
3+
import sys
4+
5+
try:
6+
import sysconfig
7+
except ImportError: # pragma nocover
8+
# Python < 2.7
9+
import distutils.sysconfig as sysconfig
10+
import distutils.util
11+
12+
13+
def get_abbr_impl():
14+
"""Return abbreviated implementation name."""
15+
if hasattr(sys, 'pypy_version_info'):
16+
pyimpl = 'pp'
17+
elif sys.platform.startswith('java'):
18+
pyimpl = 'jy'
19+
elif sys.platform == 'cli':
20+
pyimpl = 'ip'
21+
else:
22+
pyimpl = 'cp'
23+
return pyimpl
24+
25+
26+
def get_impl_ver():
27+
"""Return implementation version."""
28+
impl_ver = sysconfig.get_config_var("py_version_nodot")
29+
if not impl_ver:
30+
impl_ver = ''.join(map(str, sys.version_info[:2]))
31+
return impl_ver
32+
33+
34+
def get_platform():
35+
"""Return our platform name 'win32', 'linux_x86_64'"""
36+
# XXX remove distutils dependency
37+
return distutils.util.get_platform().replace('.', '_').replace('-', '_')
38+
39+
40+
def get_supported(versions=None):
41+
"""Return a list of supported tags for each version specified in
42+
`versions`.
43+
44+
:param versions: a list of string versions, of the form ["33", "32"],
45+
or None. The first version will be assumed to support our ABI.
46+
"""
47+
supported = []
48+
49+
# Versions must be given with respect to the preference
50+
if versions is None:
51+
versions = []
52+
major = sys.version_info[0]
53+
# Support all previous minor Python versions.
54+
for minor in range(sys.version_info[1], -1, -1):
55+
versions.append(''.join(map(str, (major, minor))))
56+
57+
impl = get_abbr_impl()
58+
59+
abis = []
60+
61+
soabi = sysconfig.get_config_var('SOABI')
62+
if soabi and soabi.startswith('cpython-'):
63+
abis[0:0] = ['cp' + soabi.split('-', 1)[-1]]
64+
65+
abi3s = set()
66+
import imp
67+
for suffix in imp.get_suffixes():
68+
if suffix[0].startswith('.abi'):
69+
abi3s.add(suffix[0].split('.', 2)[1])
70+
71+
abis.extend(sorted(list(abi3s)))
72+
73+
abis.append('none')
74+
75+
arch = get_platform()
76+
77+
# Current version, current API (built specifically for our Python):
78+
for abi in abis:
79+
supported.append(('%s%s' % (impl, versions[0]), abi, arch))
80+
81+
# No abi / arch, but requires our implementation:
82+
for i, version in enumerate(versions):
83+
supported.append(('%s%s' % (impl, version), 'none', 'any'))
84+
if i == 0:
85+
# Tagged specifically as being cross-version compatible
86+
# (with just the major version specified)
87+
supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any'))
88+
89+
# No abi / arch, generic Python
90+
for i, version in enumerate(versions):
91+
supported.append(('py%s' % (version,), 'none', 'any'))
92+
if i == 0:
93+
supported.append(('py%s' % (version[0]), 'none', 'any'))
94+
95+
return supported
96+
97+
Binary file not shown.

tests/test_finder.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
from pip.backwardcompat import urllib
33
from pip.req import InstallRequirement
44
from pip.index import PackageFinder
5-
from pip.exceptions import BestVersionAlreadyInstalled
5+
from pip.exceptions import BestVersionAlreadyInstalled, DistributionNotFound
66
from tests.path import Path
77
from tests.test_pip import here
88
from nose.tools import assert_raises
9-
from mock import Mock
9+
from mock import Mock, patch
10+
import os
1011

1112
find_links = 'file://' + urllib.quote(str(Path(here).abspath/'packages').replace('\\', '/'))
1213
find_links2 = 'file://' + urllib.quote(str(Path(here).abspath/'packages2').replace('\\', '/'))
@@ -75,3 +76,22 @@ def test_finder_detects_latest_already_satisfied_pypi_links():
7576
req.satisfied_by = satisfied_by
7677
finder = PackageFinder([], ["http://pypi.python.org/simple"])
7778
assert_raises(BestVersionAlreadyInstalled, finder.find_requirement, req, True)
79+
80+
@patch('pip.pep425tags.get_supported')
81+
def test_find_wheel(mock_get_supported):
82+
"""
83+
Test finding wheels.
84+
"""
85+
find_links_url = 'file://' + os.path.join(here, 'packages')
86+
find_links = [find_links_url]
87+
req = InstallRequirement.from_line("simple.dist")
88+
finder = PackageFinder(find_links, [], use_wheel=True)
89+
mock_get_supported.return_value = [('py1', 'none', 'any')]
90+
assert_raises(DistributionNotFound, finder.find_requirement, req, True)
91+
mock_get_supported.return_value = [('py2', 'none', 'any')]
92+
found = finder.find_requirement(req, True)
93+
assert found.url.endswith("simple.dist-0.1-py2.py3-none-any.whl"), found
94+
mock_get_supported.return_value = [('py3', 'none', 'any')]
95+
found = finder.find_requirement(req, True)
96+
assert found.url.endswith("simple.dist-0.1-py2.py3-none-any.whl"), found
97+

0 commit comments

Comments
 (0)