Merge pull request #5404 from sixninetynine/feature/platforms_for_target

Open up plat/abi/impl options to `install --target`
This commit is contained in:
Pradyun Gedam 2018-08-12 22:09:01 +05:30 committed by GitHub
commit 20127bfe98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 186 additions and 72 deletions

1
news/5355.feature Normal file
View File

@ -0,0 +1 @@
Allows dist options (--abi, --python-version, --platform, --implementation) when installing with --target

View File

@ -13,6 +13,7 @@ import warnings
from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup
from pip._internal.exceptions import CommandError
from pip._internal.index import (
FormatControl, fmt_ctl_handle_mutual_exclude, fmt_ctl_no_binary,
)
@ -60,6 +61,45 @@ def check_install_build_global(options, check_options=None):
)
def check_dist_restriction(options, check_target=False):
"""Function for determining if custom platform options are allowed.
:param options: The OptionParser options.
:param check_target: Whether or not to check if --target is being used.
"""
dist_restriction_set = any([
options.python_version,
options.platform,
options.abi,
options.implementation,
])
binary_only = FormatControl(set(), {':all:'})
sdist_dependencies_allowed = (
options.format_control != binary_only and
not options.ignore_dependencies
)
# Installations or downloads using dist restrictions must not combine
# source distributions and dist-specific wheels, as they are not
# gauranteed to be locally compatible.
if dist_restriction_set and sdist_dependencies_allowed:
raise CommandError(
"When restricting platform and interpreter constraints using "
"--python-version, --platform, --abi, or --implementation, "
"either --no-deps must be set, or --only-binary=:all: must be "
"set and --no-binary must not be set (or must be set to "
":none:)."
)
if check_target:
if dist_restriction_set and not options.target_dir:
raise CommandError(
"Can not use any platform or abi specific options unless "
"installing via '--target'"
)
###########
# options #
###########
@ -406,6 +446,61 @@ def only_binary():
)
platform = partial(
Option,
'--platform',
dest='platform',
metavar='platform',
default=None,
help=("Only use wheels compatible with <platform>. "
"Defaults to the platform of the running system."),
)
python_version = partial(
Option,
'--python-version',
dest='python_version',
metavar='python_version',
default=None,
help=("Only use wheels compatible with Python "
"interpreter version <version>. If not specified, then the "
"current system interpreter minor version is used. A major "
"version (e.g. '2') can be specified to match all "
"minor revs of that major version. A minor version "
"(e.g. '34') can also be specified."),
)
implementation = partial(
Option,
'--implementation',
dest='implementation',
metavar='implementation',
default=None,
help=("Only use wheels compatible with Python "
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
" or 'ip'. If not specified, then the current "
"interpreter implementation is used. Use 'py' to force "
"implementation-agnostic wheels."),
)
abi = partial(
Option,
'--abi',
dest='abi',
metavar='abi',
default=None,
help=("Only use wheels compatible with Python "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
"current interpreter abi tag is used. Generally "
"you will need to specify --implementation, "
"--platform, and --python-version when using "
"this option."),
)
def prefer_binary():
return Option(
"--prefer-binary",

View File

@ -5,8 +5,6 @@ import os
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import RequirementCommand
from pip._internal.exceptions import CommandError
from pip._internal.index import FormatControl
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req import RequirementSet
from pip._internal.req.req_tracker import RequirementTracker
@ -69,52 +67,10 @@ class DownloadCommand(RequirementCommand):
help=("Download packages into <dir>."),
)
cmd_opts.add_option(
'--platform',
dest='platform',
metavar='platform',
default=None,
help=("Only download wheels compatible with <platform>. "
"Defaults to the platform of the running system."),
)
cmd_opts.add_option(
'--python-version',
dest='python_version',
metavar='python_version',
default=None,
help=("Only download wheels compatible with Python "
"interpreter version <version>. If not specified, then the "
"current system interpreter minor version is used. A major "
"version (e.g. '2') can be specified to match all "
"minor revs of that major version. A minor version "
"(e.g. '34') can also be specified."),
)
cmd_opts.add_option(
'--implementation',
dest='implementation',
metavar='implementation',
default=None,
help=("Only download wheels compatible with Python "
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
" or 'ip'. If not specified, then the current "
"interpreter implementation is used. Use 'py' to force "
"implementation-agnostic wheels."),
)
cmd_opts.add_option(
'--abi',
dest='abi',
metavar='abi',
default=None,
help=("Only download wheels compatible with Python "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
"current interpreter abi tag is used. Generally "
"you will need to specify --implementation, "
"--platform, and --python-version when using "
"this option."),
)
cmd_opts.add_option(cmdoptions.platform())
cmd_opts.add_option(cmdoptions.python_version())
cmd_opts.add_option(cmdoptions.implementation())
cmd_opts.add_option(cmdoptions.abi())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@ -135,25 +91,7 @@ class DownloadCommand(RequirementCommand):
else:
python_versions = None
dist_restriction_set = any([
options.python_version,
options.platform,
options.abi,
options.implementation,
])
binary_only = FormatControl(set(), {':all:'})
no_sdist_dependencies = (
options.format_control != binary_only and
not options.ignore_dependencies
)
if dist_restriction_set and no_sdist_dependencies:
raise CommandError(
"When restricting platform and interpreter constraints using "
"--python-version, --platform, --abi, or --implementation, "
"either --no-deps must be set, or --only-binary=:all: must be "
"set and --no-binary must not be set (or must be set to "
":none:)."
)
cmdoptions.check_dist_restriction(options)
options.src_dir = os.path.abspath(options.src_dir)
options.download_dir = normalize_path(options.download_dir)

View File

@ -83,6 +83,11 @@ class InstallCommand(RequirementCommand):
'<dir>. Use --upgrade to replace existing packages in <dir> '
'with new versions.'
)
cmd_opts.add_option(cmdoptions.platform())
cmd_opts.add_option(cmdoptions.python_version())
cmd_opts.add_option(cmdoptions.implementation())
cmd_opts.add_option(cmdoptions.abi())
cmd_opts.add_option(
'--user',
dest='use_user_site',
@ -204,7 +209,6 @@ class InstallCommand(RequirementCommand):
def run(self, options, args):
cmdoptions.check_install_build_global(options)
upgrade_strategy = "to-satisfy-only"
if options.upgrade:
upgrade_strategy = options.upgrade_strategy
@ -212,6 +216,13 @@ class InstallCommand(RequirementCommand):
if options.build_dir:
options.build_dir = os.path.abspath(options.build_dir)
cmdoptions.check_dist_restriction(options, check_target=True)
if options.python_version:
python_versions = [options.python_version]
else:
python_versions = None
options.src_dir = os.path.abspath(options.src_dir)
install_options = options.install_options or []
if options.use_user_site:
@ -246,7 +257,14 @@ class InstallCommand(RequirementCommand):
global_options = options.global_options or []
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
finder = self._build_package_finder(
options=options,
session=session,
platform=options.platform,
python_versions=python_versions,
abi=options.abi,
implementation=options.implementation,
)
build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control)
@ -266,6 +284,7 @@ class InstallCommand(RequirementCommand):
) as directory:
requirement_set = RequirementSet(
require_hashes=options.require_hashes,
check_supported_wheels=not options.target_dir,
)
try:

View File

@ -12,12 +12,13 @@ logger = logging.getLogger(__name__)
class RequirementSet(object):
def __init__(self, require_hashes=False):
def __init__(self, require_hashes=False, check_supported_wheels=True):
"""Create a RequirementSet.
"""
self.requirements = OrderedDict()
self.require_hashes = require_hashes
self.check_supported_wheels = check_supported_wheels
# Mapping of alias: real_name
self.requirement_aliases = {}
@ -65,7 +66,7 @@ class RequirementSet(object):
# environment markers.
if install_req.link and install_req.link.is_wheel:
wheel = Wheel(install_req.link.filename)
if not wheel.supported():
if self.check_supported_wheels and not wheel.supported():
raise InstallationError(
"%s is not a supported wheel on this platform." %
wheel.filename

View File

@ -8,7 +8,7 @@ from os.path import curdir, join, pardir
import pytest
from pip._internal import pep425tags
from pip._internal.cli.status_codes import ERROR
from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.models.index import PyPI, TestPyPI
from pip._internal.utils.misc import rmtree
from tests.lib import (
@ -679,6 +679,66 @@ def test_install_package_with_target(script):
assert singlemodule_py in result.files_updated, str(result)
def test_install_nonlocal_compatible_wheel(script, data):
target_dir = script.scratch_path / 'target'
# Test install with --target
result = script.pip(
'install',
'-t', target_dir,
'--no-index', '--find-links', data.find_links,
'--only-binary=:all:',
'--python', '3',
'--platform', 'fakeplat',
'--abi', 'fakeabi',
'simplewheel',
)
assert result.returncode == SUCCESS
distinfo = Path('scratch') / 'target' / 'simplewheel-2.0-1.dist-info'
assert distinfo in result.files_created
# Test install without --target
result = script.pip(
'install',
'--no-index', '--find-links', data.find_links,
'--only-binary=:all:',
'--python', '3',
'--platform', 'fakeplat',
'--abi', 'fakeabi',
'simplewheel',
expect_error=True
)
assert result.returncode == ERROR
def test_install_nonlocal_compatible_wheel_path(script, data):
target_dir = script.scratch_path / 'target'
# Test a full path requirement
result = script.pip(
'install',
'-t', target_dir,
'--no-index',
'--only-binary=:all:',
Path(data.packages) / 'simplewheel-2.0-py3-fakeabi-fakeplat.whl'
)
assert result.returncode == SUCCESS
distinfo = Path('scratch') / 'target' / 'simplewheel-2.0.dist-info'
assert distinfo in result.files_created
# Test a full path requirement (without --target)
result = script.pip(
'install',
'--no-index',
'--only-binary=:all:',
Path(data.packages) / 'simplewheel-2.0-py3-fakeabi-fakeplat.whl',
expect_error=True
)
assert result.returncode == ERROR
def test_install_with_target_and_scripts_no_warning(script, common_wheels):
"""
Test that installing with --target does not trigger the "script not