Merge pull request #8820 from katzdm/multi-abis-platforms

This commit is contained in:
Pradyun Gedam 2020-10-28 13:51:20 +05:30 committed by GitHub
commit c4dbc7d814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 60 deletions

View File

@ -197,3 +197,32 @@ Examples
C:\> dir pip-8.1.1-py2.py3-none-any.whl C:\> dir pip-8.1.1-py2.py3-none-any.whl
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.rst Normal file
View File

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

View File

@ -97,8 +97,8 @@ def check_dist_restriction(options, check_target=False):
""" """
dist_restriction_set = any([ dist_restriction_set = any([
options.python_version, options.python_version,
options.platform, options.platforms,
options.abi, options.abis,
options.implementation, options.implementation,
]) ])
@ -490,14 +490,16 @@ def only_binary():
) )
platform = partial( platforms = partial(
Option, Option,
'--platform', '--platform',
dest='platform', dest='platforms',
metavar='platform', metavar='platform',
action='append',
default=None, default=None,
help=("Only use wheels compatible with <platform>. " help=("Only use wheels compatible with <platform>. Defaults to the "
"Defaults to the platform of the running system."), "platform of the running system. Use this option multiple times to "
"specify multiple platforms supported by the target interpreter."),
) # type: Callable[..., Option] ) # type: Callable[..., Option]
@ -581,35 +583,36 @@ implementation = partial(
) # type: Callable[..., Option] ) # type: Callable[..., Option]
abi = partial( abis = partial(
Option, Option,
'--abi', '--abi',
dest='abi', dest='abis',
metavar='abi', metavar='abi',
action='append',
default=None, default=None,
help=("Only use wheels compatible with Python " help=("Only use wheels compatible with Python abi <abi>, e.g. 'pypy_41'. "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the " "If not specified, then the current interpreter abi tag is used. "
"current interpreter abi tag is used. Generally " "Use this option multiple times to specify multiple abis supported "
"you will need to specify --implementation, " "by the target interpreter. Generally you will need to specify "
"--platform, and --python-version when using " "--implementation, --platform, and --python-version when using this "
"this option."), "option."),
) # type: Callable[..., Option] ) # type: Callable[..., Option]
def add_target_python_options(cmd_opts): def add_target_python_options(cmd_opts):
# type: (OptionGroup) -> None # type: (OptionGroup) -> None
cmd_opts.add_option(platform()) cmd_opts.add_option(platforms())
cmd_opts.add_option(python_version()) cmd_opts.add_option(python_version())
cmd_opts.add_option(implementation()) cmd_opts.add_option(implementation())
cmd_opts.add_option(abi()) cmd_opts.add_option(abis())
def make_target_python(options): def make_target_python(options):
# type: (Values) -> TargetPython # type: (Values) -> TargetPython
target_python = TargetPython( target_python = TargetPython(
platform=options.platform, platforms=options.platforms,
py_version_info=options.python_version, py_version_info=options.python_version,
abi=options.abi, abis=options.abis,
implementation=options.implementation, implementation=options.implementation,
) )

View File

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

View File

@ -86,6 +86,24 @@ def _get_custom_platforms(arch):
return arches return arches
def _expand_allowed_platforms(platforms):
# type: (Optional[List[str]]) -> Optional[List[str]]
if not platforms:
return None
seen = set()
result = []
for p in platforms:
if p in seen:
continue
additions = [c for c in _get_custom_platforms(p) if c not in seen]
seen.update(additions)
result.extend(additions)
return result
def _get_python_version(version): def _get_python_version(version):
# type: (str) -> PythonVersion # type: (str) -> PythonVersion
if len(version) > 1: if len(version) > 1:
@ -105,9 +123,9 @@ def _get_custom_interpreter(implementation=None, version=None):
def get_supported( def get_supported(
version=None, # type: Optional[str] version=None, # type: Optional[str]
platform=None, # type: Optional[str] platforms=None, # type: Optional[List[str]]
impl=None, # type: Optional[str] impl=None, # type: Optional[str]
abi=None # type: Optional[str] abis=None # type: Optional[List[str]]
): ):
# type: (...) -> List[Tag] # type: (...) -> List[Tag]
"""Return a list of supported tags for each version specified in """Return a list of supported tags for each version specified in
@ -115,11 +133,11 @@ def get_supported(
:param version: a string version, of the form "33" or "32", :param version: a string version, of the form "33" or "32",
or None. The version will be assumed to support our ABI. 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. tags for, or None. If None, use the local system platform.
:param impl: specify the exact implementation you want valid :param impl: specify the exact implementation you want valid
tags for, or None. If None, use the local interpreter impl. 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. tags for, or None. If None, use the local interpreter abi.
""" """
supported = [] # type: List[Tag] supported = [] # type: List[Tag]
@ -130,13 +148,7 @@ def get_supported(
interpreter = _get_custom_interpreter(impl, version) interpreter = _get_custom_interpreter(impl, version)
abis = None # type: Optional[List[str]] platforms = _expand_allowed_platforms(platforms)
if abi is not None:
abis = [abi]
platforms = None # type: Optional[List[str]]
if platform is not None:
platforms = _get_custom_platforms(platform)
is_cpython = (impl or interpreter_name()) == "cp" is_cpython = (impl or interpreter_name()) == "cp"
if is_cpython: if is_cpython:

View File

@ -309,6 +309,21 @@ def test_download_specify_platform(script, data):
Path('scratch') / 'fake-2.0-py2.py3-none-linux_x86_64.whl' Path('scratch') / 'fake-2.0-py2.py3-none-linux_x86_64.whl'
) )
# Test with multiple supported platforms specified.
data.reset()
fake_wheel(data, 'fake-3.0-py2.py3-none-linux_x86_64.whl')
result = script.pip(
'download', '--no-index', '--find-links', data.find_links,
'--only-binary=:all:',
'--dest', '.',
'--platform', 'manylinux1_x86_64', '--platform', 'linux_x86_64',
'--platform', 'any',
'fake==3'
)
result.did_create(
Path('scratch') / 'fake-3.0-py2.py3-none-linux_x86_64.whl'
)
class TestDownloadPlatformManylinuxes(object): class TestDownloadPlatformManylinuxes(object):
""" """
@ -575,6 +590,22 @@ def test_download_specify_abi(script, data):
expect_error=True, expect_error=True,
) )
data.reset()
fake_wheel(data, 'fake-1.0-fk2-otherabi-fake_platform.whl')
result = script.pip(
'download', '--no-index', '--find-links', data.find_links,
'--only-binary=:all:',
'--dest', '.',
'--python-version', '2',
'--implementation', 'fk',
'--platform', 'fake_platform',
'--abi', 'fakeabi', '--abi', 'otherabi', '--abi', 'none',
'fake'
)
result.did_create(
Path('scratch') / 'fake-1.0-fk2-otherabi-fake_platform.whl'
)
def test_download_specify_implementation(script, data): def test_download_specify_implementation(script, data):
""" """

View File

@ -76,7 +76,7 @@ class TestWheelFile(object):
Wheels built for macOS 10.6 are supported on 10.9 Wheels built for macOS 10.6 are supported on 10.9
""" """
tags = compatibility_tags.get_supported( 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') w = Wheel('simple-0.1-cp27-none-macosx_10_6_intel.whl')
assert w.supported(tags=tags) assert w.supported(tags=tags)
@ -88,7 +88,7 @@ class TestWheelFile(object):
Wheels built for macOS 10.9 are not supported on 10.6 Wheels built for macOS 10.9 are not supported on 10.6
""" """
tags = compatibility_tags.get_supported( 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') w = Wheel('simple-0.1-cp27-none-macosx_10_9_intel.whl')
assert not w.supported(tags=tags) assert not w.supported(tags=tags)
@ -98,22 +98,22 @@ class TestWheelFile(object):
Multi-arch wheels (intel) are supported on components (i386, x86_64) Multi-arch wheels (intel) are supported on components (i386, x86_64)
""" """
universal = compatibility_tags.get_supported( 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( 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( 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( 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( 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( 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') 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) Single-arch wheels (x86_64) are not supported on multi-arch (intel)
""" """
universal = compatibility_tags.get_supported( 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( 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') 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(py_version_info=(3, 6)), "version_info='3.6'"),
( (
dict(platform='darwin', py_version_info=(3, 6)), dict(platforms=['darwin'], py_version_info=(3, 6)),
"platform='darwin' version_info='3.6'", "platforms=['darwin'] version_info='3.6'",
), ),
( (
dict( dict(
platform='darwin', py_version_info=(3, 6), abi='cp36m', platforms=['darwin'], py_version_info=(3, 6), abis=['cp36m'],
implementation='cp' implementation='cp'
), ),
( (
"platform='darwin' version_info='3.6' abi='cp36m' " "platforms=['darwin'] version_info='3.6' abis=['cp36m'] "
"implementation='cp'" "implementation='cp'"
), ),
), ),

View File

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