Browse files

Upgrade peep to support pip 7.

1 parent 5d8d5da commit f7aba88dca31c62b3e3d9324fc759bcb34419413 @mythmon mythmon committed Jan 5, 2016
Showing with 118 additions and 58 deletions.
  1. +0 −1 bin/vagrant_provision.sh
  2. +12 −5 peep.sh
  3. +105 −51 scripts/peep.py
  4. +1 −1 scripts/travis/install.sh
View
1 bin/vagrant_provision.sh
@@ -69,7 +69,6 @@ VENV=/home/vagrant/virtualenv
sudo -H -u vagrant -s -- <<EOF
virtualenv $VENV
source $VENV/bin/activate
-pip install -U "pip<7"
cd ~/kitsune
./peep.sh install -r requirements/default.txt
./peep.sh install -r requirements/dev.txt
View
17 peep.sh
@@ -16,13 +16,20 @@ case $PIPVER in
# we intentionally don't use the wheel packages, since otherwise each
# package in the requirements files would need multiple hashes.
echo "peep.sh: Wheel-using pip detected, so passing --no-use-wheel."
- ARGS="--no-use-wheel"
+ ARGS="$ARGS --no-use-wheel"
;;
7.*)
- # Pip 7.x is just not compatible with peep.
- echo 'peep.sh: Pip 7.x and above are not compatible with peep.'
- echo 'peep.sh: Please install an earlier version with the command'
- echo "peep.sh: \"pip install -U 'pip<7'\""
+ echo "peep.sh: Binary-using pip detected, so passing --no-binary=:all:"
+ ARGS="$ARGS --no-binary=:all:"
+ ;;
+ 1.*)
+ # A version of pip that won't automatically use wheels.
+ ;;
+ *)
+ echo "peep.sh: Unrecognized version of pip: $PIPVER".
+ echo "peep.sh: I don't know what to do about that."
+ echo "peep.sh: Maybe try to install pip 7?"
+ echo "peep.sh: \"pip install -U 'pip==7'\""
exit 1
;;
esac
View
156 scripts/peep.py
@@ -26,13 +26,13 @@
xrange = xrange
except NameError:
xrange = range
-from base64 import urlsafe_b64encode
+from base64 import urlsafe_b64encode, urlsafe_b64decode
+from binascii import hexlify
import cgi
from collections import defaultdict
from functools import wraps
from hashlib import sha256
-from itertools import chain
-from linecache import getline
+from itertools import chain, islice
import mimetypes
from optparse import OptionParser
from os.path import join, basename, splitext, isdir
@@ -104,8 +104,18 @@ def iter(self, ret, *args, **kwargs):
DownloadProgressBar = DownloadProgressSpinner = NullProgressBar
+__version__ = 2, 5, 0
-__version__ = 2, 4, 0
+try:
+ from pip.index import FormatControl # noqa
+ FORMAT_CONTROL_ARG = 'format_control'
+
+ # The line-numbering bug will be fixed in pip 8. All 7.x releases had it.
+ PIP_MAJOR_VERSION = int(pip.__version__.split('.')[0])
+ PIP_COUNTS_COMMENTS = PIP_MAJOR_VERSION >= 8
+except ImportError:
+ FORMAT_CONTROL_ARG = 'use_wheel' # pre-7
+ PIP_COUNTS_COMMENTS = True
ITS_FINE_ITS_FINE = 0
@@ -149,6 +159,45 @@ def encoded_hash(sha):
return urlsafe_b64encode(sha.digest()).decode('ascii').rstrip('=')
+def path_and_line(req):
+ """Return the path and line number of the file from which an
+ InstallRequirement came.
+
+ """
+ path, line = (re.match(r'-r (.*) \(line (\d+)\)$',
+ req.comes_from).groups())
+ return path, int(line)
+
+
+def hashes_above(path, line_number):
+ """Yield hashes from contiguous comment lines before line ``line_number``.
+
+ """
+ def hash_lists(path):
+ """Yield lists of hashes appearing between non-comment lines.
+
+ The lists will be in order of appearance and, for each non-empty
+ list, their place in the results will coincide with that of the
+ line number of the corresponding result from `parse_requirements`
+ (which changed in pip 7.0 to not count comments).
+
+ """
+ hashes = []
+ with open(path) as file:
+ for lineno, line in enumerate(file, 1):
+ match = HASH_COMMENT_RE.match(line)
+ if match: # Accumulate this hash.
+ hashes.append(match.groupdict()['hash'])
+ if not IGNORED_LINE_RE.match(line):
+ yield hashes # Report hashes seen so far.
+ hashes = []
+ elif PIP_COUNTS_COMMENTS:
+ # Comment: count as normal req but have no hashes.
+ yield []
+
+ return next(islice(hash_lists(path), line_number - 1, None))
+
+
def run_pip(initial_args):
"""Delegate to pip the given args (starting with the subcommand), and raise
``PipException`` if something goes wrong."""
@@ -217,6 +266,8 @@ def requirement_args(argv, want_paths=False, want_other=False):
if want_other:
yield arg
+# any line that is a comment or just whitespace
+IGNORED_LINE_RE = re.compile(r'^(\s*#.*)?\s*$')
HASH_COMMENT_RE = re.compile(
r"""
@@ -311,7 +362,7 @@ def package_finder(argv):
# Carry over PackageFinder kwargs that have [about] the same names as
# options attr names:
possible_options = [
- 'find_links', 'use_wheel', 'allow_external', 'allow_unverified',
+ 'find_links', FORMAT_CONTROL_ARG, 'allow_external', 'allow_unverified',
'allow_all_external', ('allow_all_prereleases', 'pre'),
'process_dependency_links']
kwargs = {}
@@ -355,7 +406,6 @@ def __init__(self, req, argv, finder):
self._argv = argv
self._finder = finder
-
# We use a separate temp dir for each requirement so requirements
# (from different indices) that happen to have the same archive names
# don't overwrite each other, leading to a security hole in which the
@@ -435,36 +485,10 @@ def _is_always_unsatisfied(self):
return True
return False
- def _path_and_line(self):
- """Return the path and line number of the file from which our
- InstallRequirement came.
-
- """
- path, line = (re.match(r'-r (.*) \(line (\d+)\)$',
- self._req.comes_from).groups())
- return path, int(line)
-
@memoize # Avoid hitting the file[cache] over and over.
def _expected_hashes(self):
"""Return a list of known-good hashes for this package."""
-
- def hashes_above(path, line_number):
- """Yield hashes from contiguous comment lines before line
- ``line_number``.
-
- """
- for line_number in xrange(line_number - 1, 0, -1):
- line = getline(path, line_number)
- match = HASH_COMMENT_RE.match(line)
- if match:
- yield match.groupdict()['hash']
- elif not line.lstrip().startswith('#'):
- # If we hit a non-comment line, abort
- break
-
- hashes = list(hashes_above(*self._path_and_line()))
- hashes.reverse() # because we read them backwards
- return hashes
+ return hashes_above(*path_and_line(self._req))
def _download(self, link):
"""Download a file, and return its name within my temp dir.
@@ -784,33 +808,35 @@ def first_every_last(iterable, first, every, last):
last(item)
-def downloaded_reqs_from_path(path, argv):
- """Return a list of DownloadedReqs representing the requirements parsed
- out of a given requirements file.
-
- :arg path: The path to the requirements file
- :arg argv: The commandline args, starting after the subcommand
-
- """
- finder = package_finder(argv)
-
- def downloaded_reqs(parsed_reqs):
- """Just avoid repeating this list comp."""
- return [DownloadedReq(req, argv, finder) for req in parsed_reqs]
-
+def _parse_requirements(path, finder):
try:
- return downloaded_reqs(parse_requirements(
+ # list() so the generator that is parse_requirements() actually runs
+ # far enough to report a TypeError
+ return list(parse_requirements(
path, options=EmptyOptions(), finder=finder))
except TypeError:
# session is a required kwarg as of pip 6.0 and will raise
# a TypeError if missing. It needs to be a PipSession instance,
# but in older versions we can't import it from pip.download
# (nor do we need it at all) so we only import it in this except block
from pip.download import PipSession
- return downloaded_reqs(parse_requirements(
+ return list(parse_requirements(
path, options=EmptyOptions(), session=PipSession(), finder=finder))
+def downloaded_reqs_from_path(path, argv):
+ """Return a list of DownloadedReqs representing the requirements parsed
+ out of a given requirements file.
+
+ :arg path: The path to the requirements file
+ :arg argv: The commandline args, starting after the subcommand
+
+ """
+ finder = package_finder(argv)
+ return [DownloadedReq(req, argv, finder) for req in
+ _parse_requirements(path, finder)]
+
+
def peep_install(argv):
"""Perform the ``peep install`` subcommand, returning a shell status code
or raising a PipException.
@@ -865,10 +891,38 @@ def peep_install(argv):
print(''.join(output))
+def peep_port(paths):
+ """Convert a peep requirements file to one compatble with pip-8 hashing.
+
+ Loses comments and tromps on URLs, so the result will need a little manual
+ massaging, but the hard part--the hash conversion--is done for you.
+
+ """
+ if not paths:
+ print('Please specify one or more requirements files so I have '
+ 'something to port.\n')
+ return COMMAND_LINE_ERROR
+ for req in chain.from_iterable(
+ _parse_requirements(path, package_finder(argv)) for path in paths):
+ hashes = [hexlify(urlsafe_b64decode((hash + '=').encode('ascii'))).decode('ascii')
+ for hash in hashes_above(*path_and_line(req))]
+ if not hashes:
+ print(req.req)
+ elif len(hashes) == 1:
+ print('%s --hash=sha256:%s' % (req.req, hashes[0]))
+ else:
+ print('%s' % req.req, end='')
+ for hash in hashes:
+ print(' \\')
+ print(' --hash=sha256:%s' % hash, end='')
+ print()
+
+
def main():
"""Be the top-level entrypoint. Return a shell status code."""
commands = {'hash': peep_hash,
- 'install': peep_install}
+ 'install': peep_install,
+ 'port': peep_port}
try:
if len(argv) >= 2 and argv[1] in commands:
return commands[argv[1]](argv[2:])
@@ -890,7 +944,7 @@ def exception_handler(exc_type, exc_value, exc_tb):
print('---')
print('peep:', repr(__version__))
print('python:', repr(sys.version))
- print('pip:', repr(pip.__version__))
+ print('pip:', repr(getattr(pip, '__version__', 'no __version__ attr')))
print('Command line: ', repr(sys.argv))
print(
''.join(traceback.format_exception(exc_type, exc_value, exc_tb)))
View
2 scripts/travis/install.sh
@@ -9,7 +9,7 @@ ln -sf /usr/lib/`uname -i`-linux-gnu/libz.so ~/virtualenv/python2.6/lib/
echo "Install Python dependencies"
./peep.sh install -r requirements/dev.txt
-./peep.sh install -r "requirements/default.txt"
+./peep.sh install -r requirements/default.txt
echo
# Installing dependencies for smoke tests

0 comments on commit f7aba88

Please sign in to comment.