mirror of https://github.com/pypa/pip
Issue #2675: Granular control over wheels/sdists
With wheel autobuilding in place a release blocker is some granular way to opt-out of wheels for known-bad packages. This patch introduces two new options: --no-binary and --only-binary to control what archives we are willing to use on both a global and per-package basis. This also closes #2084
This commit is contained in:
parent
31eb67d0dc
commit
6aec23cafe
|
@ -25,6 +25,9 @@
|
|||
* Build Wheels prior to installing from sdist, caching them in the pip cache
|
||||
directory to speed up subsequent installs. (:pull:`2618`)
|
||||
|
||||
* Allow fine grained control over the use of wheels and source builds.
|
||||
(:pull:`2699`)
|
||||
|
||||
**6.1.1 (2015-04-07)**
|
||||
|
||||
* No longer ignore dependencies which have been added to the standard library,
|
||||
|
|
|
@ -112,7 +112,8 @@ Additionally, the following Package Index Options are supported:
|
|||
* :ref:`--allow-external <--allow-external>`
|
||||
* :ref:`--allow-all-external <--allow-external>`
|
||||
* :ref:`--allow-unverified <--allow-unverified>`
|
||||
* :ref:`--no-use-wheel <install_--no-use-wheel>`
|
||||
* :ref:`--no-binary <install_--no-binary>`
|
||||
* :ref:`--only-binary <install_--only-binary>`
|
||||
|
||||
For example, to specify :ref:`--no-index <--no-index>` and 2 :ref:`--find-links <--find-links>` locations:
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ to building and installing from source archives. For more information, see the
|
|||
`PEP425 <http://www.python.org/dev/peps/pep-0425>`_
|
||||
|
||||
Pip prefers Wheels where they are available. To disable this, use the
|
||||
:ref:`--no-use-wheel <install_--no-use-wheel>` flag for :ref:`pip install`.
|
||||
:ref:`--no-binary <install_--no-binary>` flag for :ref:`pip install`.
|
||||
|
||||
If no satisfactory wheels are found, pip will default to finding source archives.
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ from __future__ import absolute_import
|
|||
|
||||
from functools import partial
|
||||
from optparse import OptionGroup, SUPPRESS_HELP, Option
|
||||
from pip.index import PyPI
|
||||
|
||||
from pip.index import (
|
||||
PyPI, FormatControl, fmt_ctl_handle_mutual_exclude, fmt_ctl_no_use_wheel)
|
||||
from pip.locations import CA_BUNDLE_PATH, USER_CACHE_DIR, src_prefix
|
||||
|
||||
|
||||
|
@ -27,6 +29,12 @@ def make_option_group(group, parser):
|
|||
return option_group
|
||||
|
||||
|
||||
def resolve_wheel_no_use_binary(options):
|
||||
if not options.use_wheel:
|
||||
control = options.format_control
|
||||
fmt_ctl_no_use_wheel(control)
|
||||
|
||||
|
||||
###########
|
||||
# options #
|
||||
###########
|
||||
|
@ -339,6 +347,7 @@ src = partial(
|
|||
'The default for global installs is "<current dir>/src".'
|
||||
)
|
||||
|
||||
# XXX: deprecated, remove in 9.0
|
||||
use_wheel = partial(
|
||||
Option,
|
||||
'--use-wheel',
|
||||
|
@ -354,9 +363,53 @@ no_use_wheel = partial(
|
|||
action='store_false',
|
||||
default=True,
|
||||
help=('Do not Find and prefer wheel archives when searching indexes and '
|
||||
'find-links locations.'),
|
||||
'find-links locations. DEPRECATED in favour of --no-binary.'),
|
||||
)
|
||||
|
||||
|
||||
def _get_format_control(values, option):
|
||||
"""Get a format_control object."""
|
||||
return getattr(values, option.dest)
|
||||
|
||||
|
||||
def _handle_no_binary(option, opt_str, value, parser):
|
||||
existing = getattr(parser.values, option.dest)
|
||||
fmt_ctl_handle_mutual_exclude(
|
||||
value, existing.no_binary, existing.only_binary)
|
||||
|
||||
|
||||
def _handle_only_binary(option, opt_str, value, parser):
|
||||
existing = getattr(parser.values, option.dest)
|
||||
fmt_ctl_handle_mutual_exclude(
|
||||
value, existing.only_binary, existing.no_binary)
|
||||
|
||||
|
||||
def no_binary():
|
||||
return Option(
|
||||
"--no-binary", dest="format_control", action="callback",
|
||||
callback=_handle_no_binary, type="str",
|
||||
default=FormatControl(set(), set()),
|
||||
help="Do not use binary packages. Can be supplied multiple times, and "
|
||||
"each time adds to the existing value. Accepts either :all: to "
|
||||
"disable all binary packages, :none: to empty the set, or one or "
|
||||
"more package names with commas between them. Note that some "
|
||||
"packages are tricky to compile and may fail to install when "
|
||||
"this option is used on them.")
|
||||
|
||||
|
||||
def only_binary():
|
||||
return Option(
|
||||
"--only-binary", dest="format_control", action="callback",
|
||||
callback=_handle_only_binary, type="str",
|
||||
default=FormatControl(set(), set()),
|
||||
help="Do not use source packages. Can be supplied multiple times, and "
|
||||
"each time adds to the existing value. Accepts either :all: to "
|
||||
"disable all source packages, :none: to empty the set, or one or "
|
||||
"more package names with commas between them. Packages without "
|
||||
"binary distributions will fail to install when this option is "
|
||||
"used on them.")
|
||||
|
||||
|
||||
cache_dir = partial(
|
||||
Option,
|
||||
"--cache-dir",
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import
|
|||
|
||||
import sys
|
||||
|
||||
import pip
|
||||
from pip.basecommand import Command
|
||||
from pip.operations.freeze import freeze
|
||||
from pip.wheel import WheelCache
|
||||
|
@ -55,7 +56,8 @@ class FreezeCommand(Command):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
format_control = pip.index.FormatControl(set(), set())
|
||||
wheel_cache = WheelCache(options.cache_dir, format_control)
|
||||
freeze_kwargs = dict(
|
||||
requirement=options.requirement,
|
||||
find_links=options.find_links,
|
||||
|
|
|
@ -153,6 +153,8 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
cmd_opts.add_option(cmdoptions.use_wheel())
|
||||
cmd_opts.add_option(cmdoptions.no_use_wheel())
|
||||
cmd_opts.add_option(cmdoptions.no_binary())
|
||||
cmd_opts.add_option(cmdoptions.only_binary())
|
||||
|
||||
cmd_opts.add_option(
|
||||
'--pre',
|
||||
|
@ -179,8 +181,8 @@ class InstallCommand(RequirementCommand):
|
|||
"""
|
||||
return PackageFinder(
|
||||
find_links=options.find_links,
|
||||
format_control=options.format_control,
|
||||
index_urls=index_urls,
|
||||
use_wheel=options.use_wheel,
|
||||
allow_external=options.allow_external,
|
||||
allow_unverified=options.allow_unverified,
|
||||
allow_all_external=options.allow_all_external,
|
||||
|
@ -191,6 +193,7 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
def run(self, options, args):
|
||||
cmdoptions.resolve_wheel_no_use_binary(options)
|
||||
|
||||
if options.download_dir:
|
||||
options.ignore_installed = True
|
||||
|
@ -239,7 +242,7 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
finder = self._build_package_finder(options, index_urls, session)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
with BuildDirectory(options.build_dir,
|
||||
delete=build_delete) as build_dir:
|
||||
requirement_set = RequirementSet(
|
||||
|
|
|
@ -6,7 +6,7 @@ from pip._vendor import pkg_resources
|
|||
|
||||
from pip.basecommand import Command
|
||||
from pip.exceptions import DistributionNotFound
|
||||
from pip.index import PackageFinder, Search
|
||||
from pip.index import FormatControl, fmt_ctl_formats, PackageFinder, Search
|
||||
from pip.req import InstallRequirement
|
||||
from pip.utils import get_installed_distributions, dist_is_editable
|
||||
from pip.wheel import WheelCache
|
||||
|
@ -131,7 +131,8 @@ class ListCommand(Command):
|
|||
user_only=options.user,
|
||||
include_editables=False,
|
||||
)
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
format_control = FormatControl(set(), set())
|
||||
wheel_cache = WheelCache(options.cache_dir, format_control)
|
||||
for dist in installed_packages:
|
||||
req = InstallRequirement.from_line(
|
||||
dist.key, None, isolated=options.isolated_mode,
|
||||
|
@ -148,10 +149,12 @@ class ListCommand(Command):
|
|||
except DistributionNotFound:
|
||||
continue
|
||||
else:
|
||||
canonical_name = pkg_resources.safe_name(req.name).lower()
|
||||
formats = fmt_ctl_formats(format_control, canonical_name)
|
||||
search = Search(
|
||||
req.name,
|
||||
pkg_resources.safe_name(req.name).lower(),
|
||||
["source", "binary"])
|
||||
canonical_name,
|
||||
formats)
|
||||
remote_version = finder._link_package_versions(
|
||||
link, search).version
|
||||
if link.is_wheel:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import pip
|
||||
from pip.wheel import WheelCache
|
||||
from pip.req import InstallRequirement, RequirementSet, parse_requirements
|
||||
from pip.basecommand import Command
|
||||
|
@ -43,7 +44,8 @@ class UninstallCommand(Command):
|
|||
|
||||
def run(self, options, args):
|
||||
with self._build_session(options) as session:
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
format_control = pip.index.FormatControl(set(), set())
|
||||
wheel_cache = WheelCache(options.cache_dir, format_control)
|
||||
requirement_set = RequirementSet(
|
||||
build_dir=None,
|
||||
src_dir=None,
|
||||
|
|
|
@ -61,6 +61,8 @@ class WheelCommand(RequirementCommand):
|
|||
)
|
||||
cmd_opts.add_option(cmdoptions.use_wheel())
|
||||
cmd_opts.add_option(cmdoptions.no_use_wheel())
|
||||
cmd_opts.add_option(cmdoptions.no_binary())
|
||||
cmd_opts.add_option(cmdoptions.only_binary())
|
||||
cmd_opts.add_option(
|
||||
'--build-option',
|
||||
dest='build_options',
|
||||
|
@ -122,6 +124,7 @@ class WheelCommand(RequirementCommand):
|
|||
|
||||
def run(self, options, args):
|
||||
self.check_required_packages()
|
||||
cmdoptions.resolve_wheel_no_use_binary(options)
|
||||
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index:
|
||||
|
@ -143,8 +146,8 @@ class WheelCommand(RequirementCommand):
|
|||
|
||||
finder = PackageFinder(
|
||||
find_links=options.find_links,
|
||||
format_control=options.format_control,
|
||||
index_urls=index_urls,
|
||||
use_wheel=options.use_wheel,
|
||||
allow_external=options.allow_external,
|
||||
allow_unverified=options.allow_unverified,
|
||||
allow_all_external=options.allow_all_external,
|
||||
|
@ -155,7 +158,7 @@ class WheelCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir)
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
with BuildDirectory(options.build_dir,
|
||||
delete=build_delete) as build_dir:
|
||||
requirement_set = RequirementSet(
|
||||
|
|
83
pip/index.py
83
pip/index.py
|
@ -34,7 +34,7 @@ from pip._vendor.packaging.version import parse as parse_version
|
|||
from pip._vendor.requests.exceptions import SSLError
|
||||
|
||||
|
||||
__all__ = ['PackageFinder']
|
||||
__all__ = ['FormatControl', 'fmt_ctl_handle_mutual_exclude', 'PackageFinder']
|
||||
|
||||
|
||||
# Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
|
||||
|
@ -97,14 +97,20 @@ class PackageFinder(object):
|
|||
"""This finds packages.
|
||||
|
||||
This is meant to match easy_install's technique for looking for
|
||||
packages, by reading pages and looking for appropriate links
|
||||
packages, by reading pages and looking for appropriate links.
|
||||
"""
|
||||
|
||||
def __init__(self, find_links, index_urls,
|
||||
use_wheel=True, allow_external=(), allow_unverified=(),
|
||||
allow_external=(), allow_unverified=(),
|
||||
allow_all_external=False, allow_all_prereleases=False,
|
||||
trusted_hosts=None, process_dependency_links=False,
|
||||
session=None):
|
||||
session=None, format_control=None):
|
||||
"""Create a PackageFinder.
|
||||
|
||||
:param format_control: A FormatControl object or None. Used to control
|
||||
the selection of source packages / binary packages when consulting
|
||||
the index and links.
|
||||
"""
|
||||
if session is None:
|
||||
raise TypeError(
|
||||
"PackageFinder() missing 1 required keyword argument: "
|
||||
|
@ -130,7 +136,7 @@ class PackageFinder(object):
|
|||
# These are boring links that have already been logged somehow:
|
||||
self.logged_links = set()
|
||||
|
||||
self.use_wheel = use_wheel
|
||||
self.format_control = format_control or FormatControl(set(), set())
|
||||
|
||||
# Do we allow (safe and verifiable) externally hosted files?
|
||||
self.allow_external = set(normalize_name(n) for n in allow_external)
|
||||
|
@ -413,13 +419,9 @@ class PackageFinder(object):
|
|||
for location in url_locations:
|
||||
logger.debug('* %s', location)
|
||||
|
||||
formats = set(["source"])
|
||||
if self.use_wheel:
|
||||
formats.add("binary")
|
||||
search = Search(
|
||||
project_name.lower(),
|
||||
pkg_resources.safe_name(project_name).lower(),
|
||||
frozenset(formats))
|
||||
canonical_name = pkg_resources.safe_name(project_name).lower()
|
||||
formats = fmt_ctl_formats(self.format_control, canonical_name)
|
||||
search = Search(project_name.lower(), canonical_name, formats)
|
||||
find_links_versions = self._package_versions(
|
||||
# We trust every directly linked archive in find_links
|
||||
(Link(url, '-f', trusted=True) for url in self.find_links),
|
||||
|
@ -686,6 +688,7 @@ class PackageFinder(object):
|
|||
version = None
|
||||
if link.egg_fragment:
|
||||
egg_info = link.egg_fragment
|
||||
ext = link.ext
|
||||
else:
|
||||
egg_info, ext = link.splitext()
|
||||
if not ext:
|
||||
|
@ -743,6 +746,12 @@ class PackageFinder(object):
|
|||
return
|
||||
version = wheel.version
|
||||
|
||||
# This should be up by the search.ok_binary check, but see issue 2700.
|
||||
if "source" not in search.formats and ext != wheel_ext:
|
||||
self._log_skipped_link(
|
||||
link, 'No sources permitted for %s' % search.supplied)
|
||||
return
|
||||
|
||||
if not version:
|
||||
version = egg_info_matches(egg_info, search.supplied, link)
|
||||
if version is None:
|
||||
|
@ -1192,6 +1201,56 @@ class Link(object):
|
|||
INSTALLED_VERSION = Link(Inf)
|
||||
|
||||
|
||||
FormatControl = namedtuple('FormatControl', 'no_binary only_binary')
|
||||
"""This object has two fields, no_binary and only_binary.
|
||||
|
||||
If a field is falsy, it isn't set. If it is {':all:'}, it should match all
|
||||
packages except those listed in the other field. Only one field can be set
|
||||
to {':all:'} at a time. The rest of the time exact package name matches
|
||||
are listed, with any given package only showing up in one field at a time.
|
||||
"""
|
||||
|
||||
|
||||
def fmt_ctl_handle_mutual_exclude(value, target, other):
|
||||
new = value.split(',')
|
||||
while ':all:' in new:
|
||||
other.clear()
|
||||
target.clear()
|
||||
target.add(':all:')
|
||||
del new[:new.index(':all:') + 1]
|
||||
if ':none:' not in new:
|
||||
# Without a none, we want to discard everything as :all: covers it
|
||||
return
|
||||
for name in new:
|
||||
if name == ':none:':
|
||||
target.clear()
|
||||
continue
|
||||
name = pkg_resources.safe_name(name).lower()
|
||||
other.discard(name)
|
||||
target.add(name)
|
||||
|
||||
|
||||
def fmt_ctl_formats(fmt_ctl, canonical_name):
|
||||
result = set(["binary", "source"])
|
||||
if canonical_name in fmt_ctl.only_binary:
|
||||
result.discard('source')
|
||||
elif canonical_name in fmt_ctl.no_binary:
|
||||
result.discard('binary')
|
||||
elif ':all:' in fmt_ctl.only_binary:
|
||||
result.discard('source')
|
||||
elif ':all:' in fmt_ctl.no_binary:
|
||||
result.discard('binary')
|
||||
return frozenset(result)
|
||||
|
||||
|
||||
def fmt_ctl_no_use_wheel(fmt_ctl):
|
||||
fmt_ctl_handle_mutual_exclude(
|
||||
':all:', fmt_ctl.no_binary, fmt_ctl.only_binary)
|
||||
warnings.warn(
|
||||
'--no-use-wheel is deprecated and will be removed in the future. '
|
||||
' Please use --no-binary :all: instead.')
|
||||
|
||||
|
||||
Search = namedtuple('Search', 'supplied canonical formats')
|
||||
"""Capture key aspects of a search.
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import optparse
|
|||
from pip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pip._vendor.six.moves import filterfalse
|
||||
|
||||
import pip
|
||||
from pip.download import get_file_content
|
||||
from pip.req.req_install import InstallRequirement
|
||||
from pip.exceptions import (RequirementsFileParseError,
|
||||
|
@ -45,6 +46,8 @@ SUPPORTED_OPTIONS = [
|
|||
SUPPORTED_OPTIONS_REQ = [
|
||||
cmdoptions.install_options,
|
||||
cmdoptions.global_options,
|
||||
cmdoptions.no_binary,
|
||||
cmdoptions.only_binary,
|
||||
]
|
||||
|
||||
# the 'dest' string values
|
||||
|
@ -91,12 +94,27 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
|||
Process a single requirements line; This can result in creating/yielding
|
||||
requirements, or updating the finder.
|
||||
"""
|
||||
|
||||
parser = build_parser()
|
||||
values = parser.get_default_values()
|
||||
if finder:
|
||||
values.format_control = finder.format_control
|
||||
else:
|
||||
# Undo the hack that removes defaults so that
|
||||
# this can be parsed correctly.
|
||||
values.format_control = pip.index.FormatControl(set(), set())
|
||||
orig_no_binary = frozenset(values.format_control.no_binary)
|
||||
orig_only_binary = frozenset(values.format_control.only_binary)
|
||||
args = shlex.split(line)
|
||||
opts, args = parser.parse_args(args)
|
||||
req = None
|
||||
opts, args = parser.parse_args(args, values)
|
||||
if opts.use_wheel is False and finder:
|
||||
pip.index.fmt_ctl_no_use_wheel(finder.format_control)
|
||||
setattr(values, 'use_wheel', None)
|
||||
if (orig_no_binary == opts.format_control.no_binary and
|
||||
orig_only_binary == opts.format_control.only_binary):
|
||||
# Make the per-requirement-line check work.
|
||||
setattr(values, 'format_control', None)
|
||||
|
||||
req = None
|
||||
if args:
|
||||
for key, value in opts.__dict__.items():
|
||||
# only certain options can be on req lines
|
||||
|
|
|
@ -257,7 +257,7 @@ class InstallRequirement(object):
|
|||
if self._wheel_cache is None:
|
||||
self._link = link
|
||||
else:
|
||||
self._link = self._wheel_cache.cached_wheel(link)
|
||||
self._link = self._wheel_cache.cached_wheel(link, self.name)
|
||||
|
||||
@property
|
||||
def specifier(self):
|
||||
|
|
25
pip/wheel.py
25
pip/wheel.py
|
@ -47,15 +47,19 @@ logger = logging.getLogger(__name__)
|
|||
class WheelCache(object):
|
||||
"""A cache of wheels for future installs."""
|
||||
|
||||
def __init__(self, cache_dir):
|
||||
def __init__(self, cache_dir, format_control):
|
||||
"""Create a wheel cache.
|
||||
|
||||
:param cache_dir: The root of the cache.
|
||||
:param format_control: A pip.index.FormatControl object to limit
|
||||
binaries being read from the cache.
|
||||
"""
|
||||
self._cache_dir = cache_dir
|
||||
self._format_control = format_control
|
||||
|
||||
def cached_wheel(self, link):
|
||||
return cached_wheel(self._cache_dir, link)
|
||||
def cached_wheel(self, link, package_name):
|
||||
return cached_wheel(
|
||||
self._cache_dir, link, self._format_control, package_name)
|
||||
|
||||
|
||||
def _cache_for_filename(cache_dir, sdistfilename):
|
||||
|
@ -78,13 +82,19 @@ def _cache_for_filename(cache_dir, sdistfilename):
|
|||
return os.path.join(cache_dir, 'wheels', sdistfilename)
|
||||
|
||||
|
||||
def cached_wheel(cache_dir, link):
|
||||
def cached_wheel(cache_dir, link, format_control, package_name):
|
||||
if not cache_dir:
|
||||
return link
|
||||
if not link:
|
||||
return link
|
||||
if link.is_wheel:
|
||||
return link
|
||||
if not package_name:
|
||||
return link
|
||||
canonical_name = pkg_resources.safe_name(package_name).lower()
|
||||
formats = pip.index.fmt_ctl_formats(format_control, canonical_name)
|
||||
if "binary" not in formats:
|
||||
return link
|
||||
root = _cache_for_filename(cache_dir, link.filename)
|
||||
try:
|
||||
wheel_names = os.listdir(root)
|
||||
|
@ -693,6 +703,13 @@ class WheelBuilder(object):
|
|||
# Doesn't look like a package - don't autobuild a wheel
|
||||
# because we'll have no way to lookup the result sanely
|
||||
continue
|
||||
if "binary" not in pip.index.fmt_ctl_formats(
|
||||
self.finder.format_control,
|
||||
pkg_resources.safe_name(req.name).lower()):
|
||||
logger.info(
|
||||
"Skipping bdist_wheel for %s, due to binaries "
|
||||
"being disabled for it.", req.name)
|
||||
continue
|
||||
buildset.append(req)
|
||||
|
||||
if not buildset:
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
supported_options.txt
|
||||
---------------------
|
||||
|
||||
Contains --no-use-wheel.
|
||||
|
||||
supported_options2.txt
|
||||
----------------------
|
||||
|
||||
Contains --no-binary and --only-binary options.
|
|
@ -0,0 +1,5 @@
|
|||
# default is no constraints
|
||||
# We're not testing the format control logic here, just that the options are
|
||||
# accepted
|
||||
--no-binary fred
|
||||
--only-binary wilma
|
|
@ -22,7 +22,7 @@ def test_without_setuptools(script, data):
|
|||
"'install', "
|
||||
"'INITools==0.2', "
|
||||
"'-f', '%s', "
|
||||
"'--no-use-wheel'])" % data.packages,
|
||||
"'--no-binary=:all:'])" % data.packages,
|
||||
expect_error=True,
|
||||
)
|
||||
assert (
|
||||
|
@ -605,7 +605,7 @@ def test_compiles_pyc(script):
|
|||
Test installing with --compile on
|
||||
"""
|
||||
del script.environ["PYTHONDONTWRITEBYTECODE"]
|
||||
script.pip("install", "--compile", "--no-use-wheel", "INITools==0.2")
|
||||
script.pip("install", "--compile", "--no-binary=:all:", "INITools==0.2")
|
||||
|
||||
# There are many locations for the __init__.pyc file so attempt to find
|
||||
# any of them
|
||||
|
@ -626,7 +626,7 @@ def test_no_compiles_pyc(script, data):
|
|||
Test installing from wheel with --compile on
|
||||
"""
|
||||
del script.environ["PYTHONDONTWRITEBYTECODE"]
|
||||
script.pip("install", "--no-compile", "--no-use-wheel", "INITools==0.2")
|
||||
script.pip("install", "--no-compile", "--no-binary=:all:", "INITools==0.2")
|
||||
|
||||
# There are many locations for the __init__.pyc file so attempt to find
|
||||
# any of them
|
||||
|
@ -714,3 +714,51 @@ def test_install_builds_wheels(script, data):
|
|||
assert "Running setup.py install for requires-wheel" in str(res), str(res)
|
||||
# wheelbroken has to run install
|
||||
assert "Running setup.py install for wheelb" in str(res), str(res)
|
||||
|
||||
|
||||
def test_install_no_binary_disables_building_wheels(script, data):
|
||||
script.pip('install', 'wheel')
|
||||
to_install = data.packages.join('requires_wheelbroken_upper')
|
||||
res = script.pip(
|
||||
'install', '--no-index', '--no-binary=upper', '-f', data.find_links,
|
||||
to_install, expect_stderr=True)
|
||||
expected = ("Successfully installed requires-wheelbroken-upper-0"
|
||||
" upper-2.0 wheelbroken-0.1")
|
||||
# Must have installed it all
|
||||
assert expected in str(res), str(res)
|
||||
root = appdirs.user_cache_dir('pip')
|
||||
wheels = []
|
||||
for top, dirs, files in os.walk(root):
|
||||
wheels.extend(files)
|
||||
# and built wheels for wheelbroken only
|
||||
assert "Running setup.py bdist_wheel for wheelb" in str(res), str(res)
|
||||
# But not requires_wheel... which is a local dir and thus uncachable.
|
||||
assert "Running setup.py bdist_wheel for requir" not in str(res), str(res)
|
||||
# Nor upper, which was blacklisted
|
||||
assert "Running setup.py bdist_wheel for upper" not in str(res), str(res)
|
||||
# wheelbroken has to run install
|
||||
# into the cache
|
||||
assert wheels != [], str(res)
|
||||
# the local tree can't build a wheel (because we can't assume that every
|
||||
# build will have a suitable unique key to cache on).
|
||||
assert "Running setup.py install for requires-wheel" in str(res), str(res)
|
||||
# And these two fell back to sdist based installed.
|
||||
assert "Running setup.py install for wheelb" in str(res), str(res)
|
||||
assert "Running setup.py install for upper" in str(res), str(res)
|
||||
|
||||
|
||||
def test_install_no_binary_disables_cached_wheels(script, data):
|
||||
script.pip('install', 'wheel')
|
||||
# Seed the cache
|
||||
script.pip(
|
||||
'install', '--no-index', '-f', data.find_links,
|
||||
'upper')
|
||||
script.pip('uninstall', 'upper', '-y')
|
||||
res = script.pip(
|
||||
'install', '--no-index', '--no-binary=:all:', '-f', data.find_links,
|
||||
'upper', expect_stderr=True)
|
||||
assert "Successfully installed upper-2.0" in str(res), str(res)
|
||||
# No wheel building for upper, which was blacklisted
|
||||
assert "Running setup.py bdist_wheel for upper" not in str(res), str(res)
|
||||
# Must have used source, not a cached wheel to install upper.
|
||||
assert "Running setup.py install for upper" in str(res), str(res)
|
||||
|
|
|
@ -61,7 +61,7 @@ def test_nonexistent_extra_warns_user_no_wheel(script, data):
|
|||
This exercises source installs.
|
||||
"""
|
||||
result = script.pip(
|
||||
'install', '--no-use-wheel', '--no-index',
|
||||
'install', '--no-binary=:all:', '--no-index',
|
||||
'--find-links=' + data.find_links,
|
||||
'simple[nonexistent]', expect_stderr=True,
|
||||
)
|
||||
|
|
|
@ -333,7 +333,7 @@ class TestUpgradeSetuptools(object):
|
|||
self, script, data, virtualenv):
|
||||
self.prep_ve(script, '1.9.1', virtualenv.pip_source_dir)
|
||||
result = self.script.run(
|
||||
self.ve_bin / 'pip', 'install', '--no-use-wheel', '--no-index',
|
||||
self.ve_bin / 'pip', 'install', '--no-binary=:all:', '--no-index',
|
||||
'--find-links=%s' % data.find_links, '-U', 'setuptools'
|
||||
)
|
||||
assert (
|
||||
|
|
|
@ -50,6 +50,15 @@ def test_pip_wheel_downloads_wheels(script, data):
|
|||
assert "Saved" in result.stdout, result.stdout
|
||||
|
||||
|
||||
@pytest.mark.network
|
||||
def test_pip_wheel_builds_when_no_binary_set(script, data):
|
||||
script.pip('install', 'wheel')
|
||||
res = script.pip(
|
||||
'wheel', '--no-index', '--no-binary', ':all:', '-f', data.find_links,
|
||||
'setuptools==0.9.8')
|
||||
assert "Running setup.py bdist_wheel for setuptools" in str(res), str(res)
|
||||
|
||||
|
||||
@pytest.mark.network
|
||||
def test_pip_wheel_builds_editable_deps(script, data):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import pip
|
||||
from pip.basecommand import Command
|
||||
from pip import cmdoptions
|
||||
|
||||
|
||||
class SimpleCommand(Command):
|
||||
name = 'fake'
|
||||
summary = name
|
||||
|
||||
def __init__(self):
|
||||
super(SimpleCommand, self).__init__()
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_wheel())
|
||||
self.cmd_opts.add_option(cmdoptions.no_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.only_binary())
|
||||
|
||||
def run(self, options, args):
|
||||
cmdoptions.resolve_wheel_no_use_binary(options)
|
||||
self.options = options
|
||||
|
||||
|
||||
def test_no_use_wheel_sets_no_binary_all():
|
||||
cmd = SimpleCommand()
|
||||
cmd.main(['fake', '--no-use-wheel'])
|
||||
expected = pip.index.FormatControl(set([':all:']), set([]))
|
||||
assert cmd.options.format_control == expected
|
||||
|
||||
|
||||
def test_no_binary_overrides():
|
||||
cmd = SimpleCommand()
|
||||
cmd.main(['fake', '--only-binary=:all:', '--no-binary=fred'])
|
||||
expected = pip.index.FormatControl(set(['fred']), set([':all:']))
|
||||
assert cmd.options.format_control == expected
|
||||
|
||||
|
||||
def test_only_binary_overrides():
|
||||
cmd = SimpleCommand()
|
||||
cmd.main(['fake', '--no-binary=:all:', '--only-binary=fred'])
|
||||
expected = pip.index.FormatControl(set([':all:']), set(['fred']))
|
||||
assert cmd.options.format_control == expected
|
||||
|
||||
|
||||
def test_none_resets():
|
||||
cmd = SimpleCommand()
|
||||
cmd.main(['fake', '--no-binary=:all:', '--no-binary=:none:'])
|
||||
expected = pip.index.FormatControl(set([]), set([]))
|
||||
assert cmd.options.format_control == expected
|
||||
|
||||
|
||||
def test_none_preserves_other_side():
|
||||
cmd = SimpleCommand()
|
||||
cmd.main(
|
||||
['fake', '--no-binary=:all:', '--only-binary=fred',
|
||||
'--no-binary=:none:'])
|
||||
expected = pip.index.FormatControl(set([]), set(['fred']))
|
||||
assert cmd.options.format_control == expected
|
||||
|
||||
|
||||
def test_comma_separated_values():
|
||||
cmd = SimpleCommand()
|
||||
cmd.main(['fake', '--no-binary=1,2,3'])
|
||||
expected = pip.index.FormatControl(set(['1', '2', '3']), set([]))
|
||||
assert cmd.options.format_control == expected
|
|
@ -5,7 +5,9 @@ import pip.pep425tags
|
|||
|
||||
from pkg_resources import parse_version, Distribution
|
||||
from pip.req import InstallRequirement
|
||||
from pip.index import InstallationCandidate, PackageFinder, Link
|
||||
from pip.index import (
|
||||
InstallationCandidate, PackageFinder, Link, FormatControl,
|
||||
fmt_ctl_formats)
|
||||
from pip.exceptions import (
|
||||
BestVersionAlreadyInstalled, DistributionNotFound, InstallationError,
|
||||
)
|
||||
|
@ -760,3 +762,16 @@ def test_find_all_versions_find_links_and_index(data):
|
|||
versions = finder._find_all_versions('simple')
|
||||
# first the find-links versions then the page versions
|
||||
assert [str(v.version) for v in versions] == ['3.0', '2.0', '1.0', '1.0']
|
||||
|
||||
|
||||
def test_fmt_ctl_matches():
|
||||
fmt = FormatControl(set(), set())
|
||||
assert fmt_ctl_formats(fmt, "fred") == frozenset(["source", "binary"])
|
||||
fmt = FormatControl(set(["fred"]), set())
|
||||
assert fmt_ctl_formats(fmt, "fred") == frozenset(["source"])
|
||||
fmt = FormatControl(set(["fred"]), set([":all:"]))
|
||||
assert fmt_ctl_formats(fmt, "fred") == frozenset(["source"])
|
||||
fmt = FormatControl(set(), set(["fred"]))
|
||||
assert fmt_ctl_formats(fmt, "fred") == frozenset(["binary"])
|
||||
fmt = FormatControl(set([":all:"]), set(["fred"]))
|
||||
assert fmt_ctl_formats(fmt, "fred") == frozenset(["binary"])
|
||||
|
|
|
@ -6,6 +6,7 @@ from mock import patch
|
|||
import pytest
|
||||
from pretend import stub
|
||||
|
||||
import pip
|
||||
from pip.exceptions import (RequirementsFileParseError,
|
||||
ReqFileOnleOneOptionPerLineError,
|
||||
ReqFileOptionNotAllowedWithReqError)
|
||||
|
@ -175,11 +176,13 @@ class TestProcessLine(object):
|
|||
|
||||
def test_set_finder_use_wheel(self, finder):
|
||||
list(process_line("--use-wheel", "file", 1, finder=finder))
|
||||
assert finder.use_wheel is True
|
||||
no_use_wheel_fmt = pip.index.FormatControl(set(), set())
|
||||
assert finder.format_control == no_use_wheel_fmt
|
||||
|
||||
def test_set_finder_no_use_wheel(self, finder):
|
||||
list(process_line("--no-use-wheel", "file", 1, finder=finder))
|
||||
assert finder.use_wheel is False
|
||||
no_use_wheel_fmt = pip.index.FormatControl(set([':all:']), set())
|
||||
assert finder.format_control == no_use_wheel_fmt
|
||||
|
||||
def test_noop_always_unzip(self, finder):
|
||||
# noop, but confirm it can be set
|
||||
|
@ -262,6 +265,14 @@ class TestParseRequirements(object):
|
|||
|
||||
assert finder.index_urls == ['url1', 'url2']
|
||||
|
||||
def test_req_file_parse_no_only_binary(self, data):
|
||||
finder = PackageFinder([], [], session=PipSession())
|
||||
list(parse_requirements(
|
||||
data.reqfiles.join("supported_options2.txt"), finder,
|
||||
session=PipSession()))
|
||||
expected = pip.index.FormatControl(set(['fred']), set(['wilma']))
|
||||
assert finder.format_control == expected
|
||||
|
||||
def test_req_file_parse_comment_start_of_line(self, tmpdir, finder):
|
||||
"""
|
||||
Test parsing comments in a requirements file
|
||||
|
|
Loading…
Reference in New Issue