Support multiple `abi` and `platform` values for `pip download`.

This commit is contained in:
Daniel Katz 2020-08-28 12:55:17 -04:00
parent a0ec4be98b
commit cea9f32dae
8 changed files with 84 additions and 46 deletions

View File

@ -197,3 +197,32 @@ Examples
C:\> dir pip-8.1.1-py2.py3-none-any.whl
pip-8.1.1-py2.py3-none-any.whl
#. Download a package supporting one of several ABIs and platforms.
This is useful when fetching wheels for a well-defined interpreter, whose
supported ABIs and platforms are known and fixed, different than the one pip is
running under.
.. tab:: Unix/macOS
.. code-block:: console
$ python -m pip download \
--only-binary=:all: \
--platform manylinux1_x86_64 --platform linux_x86_64 --platform any \
--python-version 36 \
--implementation cp \
--abi cp36m --abi cp36 --abi abi3 --abi none \
SomePackage
.. tab:: Windows
.. code-block:: console
C:> py -m pip download ^
--only-binary=:all: ^
--platform manylinux1_x86_64 --platform linux_x86_64 --platform any ^
--python-version 36 ^
--implementation cp ^
--abi cp36m --abi cp36 --abi abi3 --abi none ^
SomePackage

1
news/6121.feature Normal file
View File

@ -0,0 +1 @@
Allow comma-separated values for --abi and --platform.

View File

@ -31,7 +31,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import OptionParser, Values
from typing import Any, Callable, Dict, Optional, Tuple
from typing import Any, Callable, Dict, List, Optional, Tuple
from pip._internal.cli.parser import ConfigOptionParser
@ -588,7 +588,7 @@ abi = partial(
metavar='abi',
default=None,
help=("Only use wheels compatible with Python "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
"abi <abi>, e.g. 'pypy_41,none'. If not specified, then the "
"current interpreter abi tag is used. Generally "
"you will need to specify --implementation, "
"--platform, and --python-version when using "
@ -606,10 +606,21 @@ def add_target_python_options(cmd_opts):
def make_target_python(options):
# type: (Values) -> TargetPython
# abi can be a comma-separated list of values.
abis = options.abi # type: Optional[List[str]]
if options.abi:
abis = options.abi.split(',')
# platform can also be a comma-separated list of values.
platforms = options.platform # type: Optional[List[str]]
if options.platform:
platforms = options.platform.split(',')
target_python = TargetPython(
platform=options.platform,
platforms=platforms,
py_version_info=options.python_version,
abi=options.abi,
abis=abis,
implementation=options.implementation,
)

View File

@ -19,9 +19,9 @@ class TargetPython(object):
__slots__ = [
"_given_py_version_info",
"abi",
"abis",
"implementation",
"platform",
"platforms",
"py_version",
"py_version_info",
"_valid_tags",
@ -29,23 +29,23 @@ class TargetPython(object):
def __init__(
self,
platform=None, # type: Optional[str]
platforms=None, # type: Optional[List[str]]
py_version_info=None, # type: Optional[Tuple[int, ...]]
abi=None, # type: Optional[str]
abis=None, # type: Optional[List[str]]
implementation=None, # type: Optional[str]
):
# type: (...) -> None
"""
:param platform: A string or None. If None, searches for packages
that are supported by the current system. Otherwise, will find
packages that can be built on the platform passed in. These
:param platforms: A list of strings or None. If None, searches for
packages that are supported by the current system. Otherwise, will
find packages that can be built on the platforms passed in. These
packages will only be downloaded for distribution: they will
not be built locally.
:param py_version_info: An optional tuple of ints representing the
Python version information to use (e.g. `sys.version_info[:3]`).
This can have length 1, 2, or 3 when provided.
:param abi: A string or None. This is passed to compatibility_tags.py's
get_supported() function as is.
:param abis: A list of strings or None. This is passed to
compatibility_tags.py's get_supported() function as is.
:param implementation: A string or None. This is passed to
compatibility_tags.py's get_supported() function as is.
"""
@ -59,9 +59,9 @@ class TargetPython(object):
py_version = '.'.join(map(str, py_version_info[:2]))
self.abi = abi
self.abis = abis
self.implementation = implementation
self.platform = platform
self.platforms = platforms
self.py_version = py_version
self.py_version_info = py_version_info
@ -80,9 +80,9 @@ class TargetPython(object):
)
key_values = [
('platform', self.platform),
('platforms', self.platforms),
('version_info', display_version),
('abi', self.abi),
('abis', self.abis),
('implementation', self.implementation),
]
return ' '.join(
@ -108,8 +108,8 @@ class TargetPython(object):
tags = get_supported(
version=version,
platform=self.platform,
abi=self.abi,
platforms=self.platforms,
abis=self.abis,
impl=self.implementation,
)
self._valid_tags = tags

View File

@ -105,9 +105,9 @@ def _get_custom_interpreter(implementation=None, version=None):
def get_supported(
version=None, # type: Optional[str]
platform=None, # type: Optional[str]
platforms=None, # type: Optional[List[str]]
impl=None, # type: Optional[str]
abi=None # type: Optional[str]
abis=None # type: Optional[List[str]]
):
# type: (...) -> List[Tag]
"""Return a list of supported tags for each version specified in
@ -115,11 +115,11 @@ def get_supported(
:param version: a string version, of the form "33" or "32",
or None. The version will be assumed to support our ABI.
:param platform: specify the exact platform you want valid
:param platform: specify a list of platforms you want valid
tags for, or None. If None, use the local system platform.
:param impl: specify the exact implementation you want valid
tags for, or None. If None, use the local interpreter impl.
:param abi: specify the exact abi you want valid
:param abis: specify a list of abis you want valid
tags for, or None. If None, use the local interpreter abi.
"""
supported = [] # type: List[Tag]
@ -130,13 +130,10 @@ def get_supported(
interpreter = _get_custom_interpreter(impl, version)
abis = None # type: Optional[List[str]]
if abi is not None:
abis = [abi]
platforms = None # type: Optional[List[str]]
if platform is not None:
platforms = _get_custom_platforms(platform)
if platforms and len(platforms) == 1:
# Only expand list of platforms if a single platform was provided.
# Otherwise, assume that the list provided is comprehensive.
platforms = _get_custom_platforms(platforms[0])
is_cpython = (impl or interpreter_name()) == "cp"
if is_cpython:

View File

@ -76,7 +76,7 @@ class TestWheelFile(object):
Wheels built for macOS 10.6 are supported on 10.9
"""
tags = compatibility_tags.get_supported(
'27', platform='macosx_10_9_intel', impl='cp'
'27', platforms=['macosx_10_9_intel'], impl='cp'
)
w = Wheel('simple-0.1-cp27-none-macosx_10_6_intel.whl')
assert w.supported(tags=tags)
@ -88,7 +88,7 @@ class TestWheelFile(object):
Wheels built for macOS 10.9 are not supported on 10.6
"""
tags = compatibility_tags.get_supported(
'27', platform='macosx_10_6_intel', impl='cp'
'27', platforms=['macosx_10_6_intel'], impl='cp'
)
w = Wheel('simple-0.1-cp27-none-macosx_10_9_intel.whl')
assert not w.supported(tags=tags)
@ -98,22 +98,22 @@ class TestWheelFile(object):
Multi-arch wheels (intel) are supported on components (i386, x86_64)
"""
universal = compatibility_tags.get_supported(
'27', platform='macosx_10_5_universal', impl='cp'
'27', platforms=['macosx_10_5_universal'], impl='cp'
)
intel = compatibility_tags.get_supported(
'27', platform='macosx_10_5_intel', impl='cp'
'27', platforms=['macosx_10_5_intel'], impl='cp'
)
x64 = compatibility_tags.get_supported(
'27', platform='macosx_10_5_x86_64', impl='cp'
'27', platforms=['macosx_10_5_x86_64'], impl='cp'
)
i386 = compatibility_tags.get_supported(
'27', platform='macosx_10_5_i386', impl='cp'
'27', platforms=['macosx_10_5_i386'], impl='cp'
)
ppc = compatibility_tags.get_supported(
'27', platform='macosx_10_5_ppc', impl='cp'
'27', platforms=['macosx_10_5_ppc'], impl='cp'
)
ppc64 = compatibility_tags.get_supported(
'27', platform='macosx_10_5_ppc64', impl='cp'
'27', platforms=['macosx_10_5_ppc64'], impl='cp'
)
w = Wheel('simple-0.1-cp27-none-macosx_10_5_intel.whl')
@ -136,10 +136,10 @@ class TestWheelFile(object):
Single-arch wheels (x86_64) are not supported on multi-arch (intel)
"""
universal = compatibility_tags.get_supported(
'27', platform='macosx_10_5_universal', impl='cp'
'27', platforms=['macosx_10_5_universal'], impl='cp'
)
intel = compatibility_tags.get_supported(
'27', platform='macosx_10_5_intel', impl='cp'
'27', platforms=['macosx_10_5_intel'], impl='cp'
)
w = Wheel('simple-0.1-cp27-none-macosx_10_5_i386.whl')

View File

@ -45,16 +45,16 @@ class TestTargetPython:
({}, ''),
(dict(py_version_info=(3, 6)), "version_info='3.6'"),
(
dict(platform='darwin', py_version_info=(3, 6)),
"platform='darwin' version_info='3.6'",
dict(platforms=['darwin'], py_version_info=(3, 6)),
"platforms=['darwin'] version_info='3.6'",
),
(
dict(
platform='darwin', py_version_info=(3, 6), abi='cp36m',
platforms=['darwin'], py_version_info=(3, 6), abis=['cp36m'],
implementation='cp'
),
(
"platform='darwin' version_info='3.6' abi='cp36m' "
"platforms=['darwin'] version_info='3.6' abis=['cp36m'] "
"implementation='cp'"
),
),

View File

@ -63,7 +63,7 @@ class TestManylinux2010Tags(object):
Specifying manylinux2010 implies manylinux1.
"""
groups = {}
supported = compatibility_tags.get_supported(platform=manylinux2010)
supported = compatibility_tags.get_supported(platforms=[manylinux2010])
for tag in supported:
groups.setdefault(
(tag.interpreter, tag.abi), []
@ -87,7 +87,7 @@ class TestManylinux2014Tags(object):
Specifying manylinux2014 implies manylinux2010/manylinux1.
"""
groups = {}
supported = compatibility_tags.get_supported(platform=manylinuxA)
supported = compatibility_tags.get_supported(platforms=[manylinuxA])
for tag in supported:
groups.setdefault(
(tag.interpreter, tag.abi), []