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:
Robert Collins 2015-04-17 15:03:34 +12:00
parent 31eb67d0dc
commit 6aec23cafe
22 changed files with 365 additions and 42 deletions

View File

@ -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,

View File

@ -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:

View File

@ -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.

View File

@ -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",

View File

@ -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,

View File

@ -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(

View File

@ -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:

View File

@ -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,

View File

@ -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(

View File

@ -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.

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -0,0 +1,9 @@
supported_options.txt
---------------------
Contains --no-use-wheel.
supported_options2.txt
----------------------
Contains --no-binary and --only-binary options.

View File

@ -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

View File

@ -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)

View File

@ -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,
)

View File

@ -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 (

View File

@ -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):
"""

View File

@ -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

View File

@ -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"])

View File

@ -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