Skip to content

Commit c4dbc7d

Browse files
authored
Merge pull request #8820 from katzdm/multi-abis-platforms
2 parents a0ec4be + 7632c7a commit c4dbc7d

File tree

9 files changed

+136
-60
lines changed

9 files changed

+136
-60
lines changed

docs/html/reference/pip_download.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,32 @@ Examples
197197
198198
C:\> dir pip-8.1.1-py2.py3-none-any.whl
199199
pip-8.1.1-py2.py3-none-any.whl
200+
201+
#. Download a package supporting one of several ABIs and platforms.
202+
This is useful when fetching wheels for a well-defined interpreter, whose
203+
supported ABIs and platforms are known and fixed, different than the one pip is
204+
running under.
205+
206+
.. tab:: Unix/macOS
207+
208+
.. code-block:: console
209+
210+
$ python -m pip download \
211+
--only-binary=:all: \
212+
--platform manylinux1_x86_64 --platform linux_x86_64 --platform any \
213+
--python-version 36 \
214+
--implementation cp \
215+
--abi cp36m --abi cp36 --abi abi3 --abi none \
216+
SomePackage
217+
218+
.. tab:: Windows
219+
220+
.. code-block:: console
221+
222+
C:> py -m pip download ^
223+
--only-binary=:all: ^
224+
--platform manylinux1_x86_64 --platform linux_x86_64 --platform any ^
225+
--python-version 36 ^
226+
--implementation cp ^
227+
--abi cp36m --abi cp36 --abi abi3 --abi none ^
228+
SomePackage

news/6121.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow multiple values for --abi and --platform.

src/pip/_internal/cli/cmdoptions.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ def check_dist_restriction(options, check_target=False):
9797
"""
9898
dist_restriction_set = any([
9999
options.python_version,
100-
options.platform,
101-
options.abi,
100+
options.platforms,
101+
options.abis,
102102
options.implementation,
103103
])
104104

@@ -490,14 +490,16 @@ def only_binary():
490490
)
491491

492492

493-
platform = partial(
493+
platforms = partial(
494494
Option,
495495
'--platform',
496-
dest='platform',
496+
dest='platforms',
497497
metavar='platform',
498+
action='append',
498499
default=None,
499-
help=("Only use wheels compatible with <platform>. "
500-
"Defaults to the platform of the running system."),
500+
help=("Only use wheels compatible with <platform>. Defaults to the "
501+
"platform of the running system. Use this option multiple times to "
502+
"specify multiple platforms supported by the target interpreter."),
501503
) # type: Callable[..., Option]
502504

503505

@@ -581,35 +583,36 @@ def _handle_python_version(option, opt_str, value, parser):
581583
) # type: Callable[..., Option]
582584

583585

584-
abi = partial(
586+
abis = partial(
585587
Option,
586588
'--abi',
587-
dest='abi',
589+
dest='abis',
588590
metavar='abi',
591+
action='append',
589592
default=None,
590-
help=("Only use wheels compatible with Python "
591-
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
592-
"current interpreter abi tag is used. Generally "
593-
"you will need to specify --implementation, "
594-
"--platform, and --python-version when using "
595-
"this option."),
593+
help=("Only use wheels compatible with Python abi <abi>, e.g. 'pypy_41'. "
594+
"If not specified, then the current interpreter abi tag is used. "
595+
"Use this option multiple times to specify multiple abis supported "
596+
"by the target interpreter. Generally you will need to specify "
597+
"--implementation, --platform, and --python-version when using this "
598+
"option."),
596599
) # type: Callable[..., Option]
597600

598601

599602
def add_target_python_options(cmd_opts):
600603
# type: (OptionGroup) -> None
601-
cmd_opts.add_option(platform())
604+
cmd_opts.add_option(platforms())
602605
cmd_opts.add_option(python_version())
603606
cmd_opts.add_option(implementation())
604-
cmd_opts.add_option(abi())
607+
cmd_opts.add_option(abis())
605608

606609

607610
def make_target_python(options):
608611
# type: (Values) -> TargetPython
609612
target_python = TargetPython(
610-
platform=options.platform,
613+
platforms=options.platforms,
611614
py_version_info=options.python_version,
612-
abi=options.abi,
615+
abis=options.abis,
613616
implementation=options.implementation,
614617
)
615618

src/pip/_internal/models/target_python.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,33 @@ class TargetPython(object):
1919

2020
__slots__ = [
2121
"_given_py_version_info",
22-
"abi",
22+
"abis",
2323
"implementation",
24-
"platform",
24+
"platforms",
2525
"py_version",
2626
"py_version_info",
2727
"_valid_tags",
2828
]
2929

3030
def __init__(
3131
self,
32-
platform=None, # type: Optional[str]
32+
platforms=None, # type: Optional[List[str]]
3333
py_version_info=None, # type: Optional[Tuple[int, ...]]
34-
abi=None, # type: Optional[str]
34+
abis=None, # type: Optional[List[str]]
3535
implementation=None, # type: Optional[str]
3636
):
3737
# type: (...) -> None
3838
"""
39-
:param platform: A string or None. If None, searches for packages
40-
that are supported by the current system. Otherwise, will find
41-
packages that can be built on the platform passed in. These
39+
:param platforms: A list of strings or None. If None, searches for
40+
packages that are supported by the current system. Otherwise, will
41+
find packages that can be built on the platforms passed in. These
4242
packages will only be downloaded for distribution: they will
4343
not be built locally.
4444
:param py_version_info: An optional tuple of ints representing the
4545
Python version information to use (e.g. `sys.version_info[:3]`).
4646
This can have length 1, 2, or 3 when provided.
47-
:param abi: A string or None. This is passed to compatibility_tags.py's
48-
get_supported() function as is.
47+
:param abis: A list of strings or None. This is passed to
48+
compatibility_tags.py's get_supported() function as is.
4949
:param implementation: A string or None. This is passed to
5050
compatibility_tags.py's get_supported() function as is.
5151
"""
@@ -59,9 +59,9 @@ def __init__(
5959

6060
py_version = '.'.join(map(str, py_version_info[:2]))
6161

62-
self.abi = abi
62+
self.abis = abis
6363
self.implementation = implementation
64-
self.platform = platform
64+
self.platforms = platforms
6565
self.py_version = py_version
6666
self.py_version_info = py_version_info
6767

@@ -80,9 +80,9 @@ def format_given(self):
8080
)
8181

8282
key_values = [
83-
('platform', self.platform),
83+
('platforms', self.platforms),
8484
('version_info', display_version),
85-
('abi', self.abi),
85+
('abis', self.abis),
8686
('implementation', self.implementation),
8787
]
8888
return ' '.join(
@@ -108,8 +108,8 @@ def get_tags(self):
108108

109109
tags = get_supported(
110110
version=version,
111-
platform=self.platform,
112-
abi=self.abi,
111+
platforms=self.platforms,
112+
abis=self.abis,
113113
impl=self.implementation,
114114
)
115115
self._valid_tags = tags

src/pip/_internal/utils/compatibility_tags.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ def _get_custom_platforms(arch):
8686
return arches
8787

8888

89+
def _expand_allowed_platforms(platforms):
90+
# type: (Optional[List[str]]) -> Optional[List[str]]
91+
if not platforms:
92+
return None
93+
94+
seen = set()
95+
result = []
96+
97+
for p in platforms:
98+
if p in seen:
99+
continue
100+
additions = [c for c in _get_custom_platforms(p) if c not in seen]
101+
seen.update(additions)
102+
result.extend(additions)
103+
104+
return result
105+
106+
89107
def _get_python_version(version):
90108
# type: (str) -> PythonVersion
91109
if len(version) > 1:
@@ -105,21 +123,21 @@ def _get_custom_interpreter(implementation=None, version=None):
105123

106124
def get_supported(
107125
version=None, # type: Optional[str]
108-
platform=None, # type: Optional[str]
126+
platforms=None, # type: Optional[List[str]]
109127
impl=None, # type: Optional[str]
110-
abi=None # type: Optional[str]
128+
abis=None # type: Optional[List[str]]
111129
):
112130
# type: (...) -> List[Tag]
113131
"""Return a list of supported tags for each version specified in
114132
`versions`.
115133
116134
:param version: a string version, of the form "33" or "32",
117135
or None. The version will be assumed to support our ABI.
118-
:param platform: specify the exact platform you want valid
136+
:param platform: specify a list of platforms you want valid
119137
tags for, or None. If None, use the local system platform.
120138
:param impl: specify the exact implementation you want valid
121139
tags for, or None. If None, use the local interpreter impl.
122-
:param abi: specify the exact abi you want valid
140+
:param abis: specify a list of abis you want valid
123141
tags for, or None. If None, use the local interpreter abi.
124142
"""
125143
supported = [] # type: List[Tag]
@@ -130,13 +148,7 @@ def get_supported(
130148

131149
interpreter = _get_custom_interpreter(impl, version)
132150

133-
abis = None # type: Optional[List[str]]
134-
if abi is not None:
135-
abis = [abi]
136-
137-
platforms = None # type: Optional[List[str]]
138-
if platform is not None:
139-
platforms = _get_custom_platforms(platform)
151+
platforms = _expand_allowed_platforms(platforms)
140152

141153
is_cpython = (impl or interpreter_name()) == "cp"
142154
if is_cpython:

tests/functional/test_download.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,21 @@ def test_download_specify_platform(script, data):
309309
Path('scratch') / 'fake-2.0-py2.py3-none-linux_x86_64.whl'
310310
)
311311

312+
# Test with multiple supported platforms specified.
313+
data.reset()
314+
fake_wheel(data, 'fake-3.0-py2.py3-none-linux_x86_64.whl')
315+
result = script.pip(
316+
'download', '--no-index', '--find-links', data.find_links,
317+
'--only-binary=:all:',
318+
'--dest', '.',
319+
'--platform', 'manylinux1_x86_64', '--platform', 'linux_x86_64',
320+
'--platform', 'any',
321+
'fake==3'
322+
)
323+
result.did_create(
324+
Path('scratch') / 'fake-3.0-py2.py3-none-linux_x86_64.whl'
325+
)
326+
312327

313328
class TestDownloadPlatformManylinuxes(object):
314329
"""
@@ -575,6 +590,22 @@ def test_download_specify_abi(script, data):
575590
expect_error=True,
576591
)
577592

593+
data.reset()
594+
fake_wheel(data, 'fake-1.0-fk2-otherabi-fake_platform.whl')
595+
result = script.pip(
596+
'download', '--no-index', '--find-links', data.find_links,
597+
'--only-binary=:all:',
598+
'--dest', '.',
599+
'--python-version', '2',
600+
'--implementation', 'fk',
601+
'--platform', 'fake_platform',
602+
'--abi', 'fakeabi', '--abi', 'otherabi', '--abi', 'none',
603+
'fake'
604+
)
605+
result.did_create(
606+
Path('scratch') / 'fake-1.0-fk2-otherabi-fake_platform.whl'
607+
)
608+
578609

579610
def test_download_specify_implementation(script, data):
580611
"""

tests/unit/test_models_wheel.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def test_supported_osx_version(self):
7676
Wheels built for macOS 10.6 are supported on 10.9
7777
"""
7878
tags = compatibility_tags.get_supported(
79-
'27', platform='macosx_10_9_intel', impl='cp'
79+
'27', platforms=['macosx_10_9_intel'], impl='cp'
8080
)
8181
w = Wheel('simple-0.1-cp27-none-macosx_10_6_intel.whl')
8282
assert w.supported(tags=tags)
@@ -88,7 +88,7 @@ def test_not_supported_osx_version(self):
8888
Wheels built for macOS 10.9 are not supported on 10.6
8989
"""
9090
tags = compatibility_tags.get_supported(
91-
'27', platform='macosx_10_6_intel', impl='cp'
91+
'27', platforms=['macosx_10_6_intel'], impl='cp'
9292
)
9393
w = Wheel('simple-0.1-cp27-none-macosx_10_9_intel.whl')
9494
assert not w.supported(tags=tags)
@@ -98,22 +98,22 @@ def test_supported_multiarch_darwin(self):
9898
Multi-arch wheels (intel) are supported on components (i386, x86_64)
9999
"""
100100
universal = compatibility_tags.get_supported(
101-
'27', platform='macosx_10_5_universal', impl='cp'
101+
'27', platforms=['macosx_10_5_universal'], impl='cp'
102102
)
103103
intel = compatibility_tags.get_supported(
104-
'27', platform='macosx_10_5_intel', impl='cp'
104+
'27', platforms=['macosx_10_5_intel'], impl='cp'
105105
)
106106
x64 = compatibility_tags.get_supported(
107-
'27', platform='macosx_10_5_x86_64', impl='cp'
107+
'27', platforms=['macosx_10_5_x86_64'], impl='cp'
108108
)
109109
i386 = compatibility_tags.get_supported(
110-
'27', platform='macosx_10_5_i386', impl='cp'
110+
'27', platforms=['macosx_10_5_i386'], impl='cp'
111111
)
112112
ppc = compatibility_tags.get_supported(
113-
'27', platform='macosx_10_5_ppc', impl='cp'
113+
'27', platforms=['macosx_10_5_ppc'], impl='cp'
114114
)
115115
ppc64 = compatibility_tags.get_supported(
116-
'27', platform='macosx_10_5_ppc64', impl='cp'
116+
'27', platforms=['macosx_10_5_ppc64'], impl='cp'
117117
)
118118

119119
w = Wheel('simple-0.1-cp27-none-macosx_10_5_intel.whl')
@@ -136,10 +136,10 @@ def test_not_supported_multiarch_darwin(self):
136136
Single-arch wheels (x86_64) are not supported on multi-arch (intel)
137137
"""
138138
universal = compatibility_tags.get_supported(
139-
'27', platform='macosx_10_5_universal', impl='cp'
139+
'27', platforms=['macosx_10_5_universal'], impl='cp'
140140
)
141141
intel = compatibility_tags.get_supported(
142-
'27', platform='macosx_10_5_intel', impl='cp'
142+
'27', platforms=['macosx_10_5_intel'], impl='cp'
143143
)
144144

145145
w = Wheel('simple-0.1-cp27-none-macosx_10_5_i386.whl')

tests/unit/test_target_python.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,16 @@ def test_init__py_version_info_none(self):
4545
({}, ''),
4646
(dict(py_version_info=(3, 6)), "version_info='3.6'"),
4747
(
48-
dict(platform='darwin', py_version_info=(3, 6)),
49-
"platform='darwin' version_info='3.6'",
48+
dict(platforms=['darwin'], py_version_info=(3, 6)),
49+
"platforms=['darwin'] version_info='3.6'",
5050
),
5151
(
5252
dict(
53-
platform='darwin', py_version_info=(3, 6), abi='cp36m',
53+
platforms=['darwin'], py_version_info=(3, 6), abis=['cp36m'],
5454
implementation='cp'
5555
),
5656
(
57-
"platform='darwin' version_info='3.6' abi='cp36m' "
57+
"platforms=['darwin'] version_info='3.6' abis=['cp36m'] "
5858
"implementation='cp'"
5959
),
6060
),

0 commit comments

Comments
 (0)