Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open up plat/abi/impl options to install --target #5404

Merged
merged 1 commit into from
Aug 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/5355.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allows dist options (--abi, --python-version, --platform, --implementation) when installing with --target
95 changes: 95 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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,
)
Expand Down Expand Up @@ -60,6 +61,45 @@ def getname(n):
)


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 #
###########
Expand Down Expand Up @@ -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",
Expand Down
72 changes: 5 additions & 67 deletions src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

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
Expand Down Expand Up @@ -69,52 +67,10 @@ def __init__(self, *args, **kw):
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,
Expand All @@ -135,25 +91,7 @@ def run(self, options, args):
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)
Expand Down
23 changes: 21 additions & 2 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ def __init__(self, *args, **kw):
'<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',
Expand Down Expand Up @@ -204,14 +209,20 @@ def __init__(self, *args, **kw):

def run(self, options, args):
cmdoptions.check_install_build_global(options)

upgrade_strategy = "to-satisfy-only"
if options.upgrade:
upgrade_strategy = options.upgrade_strategy

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:
Expand Down Expand Up @@ -246,7 +257,14 @@ def run(self, options, args):
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)

Expand All @@ -266,6 +284,7 @@ def run(self, options, args):
) as directory:
requirement_set = RequirementSet(
require_hashes=options.require_hashes,
check_supported_wheels=not options.target_dir,
)

try:
Expand Down
5 changes: 3 additions & 2 deletions src/pip/_internal/req/req_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@

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 = {}
Expand Down Expand Up @@ -65,7 +66,7 @@ def add_requirement(self, install_req, parent_req_name=None,
# 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():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.check_supported_wheels can move to the outer conditional -- saves creating a Wheel instance. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check_supported_wheels boolean is the gate to whether or not we raise the incompatibility exception (based on whether --target was used during install). So I don't think it can move.

raise InstallationError(
"%s is not a supported wheel on this platform." %
wheel.filename
Expand Down
Binary file not shown.
62 changes: 61 additions & 1 deletion tests/functional/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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 (
Expand Down Expand Up @@ -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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

'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
Expand Down