1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00

updated distlib to 0.2.2

This fixes an issue with tox/virtualenv/pip where the pip and wheel
shebang lines were wrapped with a quote " that prevents the command
from executing.

66e4805271/distlib/scripts.py?at=default&fileviewer=file-view-default#scripts.py-66

I confirmed that with the updated distlib I was able to create a
virtualenv, and then install the updated `pip` into it.

```
virtualenv --no-wheel --no-pip --no-setuptools --python=jython jython.env
jython -c 'import sys, pip; sys.exit(pip.main(["install", "--ignore-installed"] + sys.argv[1:]))' pip
```

```
$ pip list
decorator (4.0.9)
ipython (4.1.1)
ipython-genutils (0.1.0)
path.py (8.1.2)
pexpect (4.0.1)
pickleshare (0.6)
pip (8.0.2)
ptyprocess (0.5.1)
py (1.4.31)
pytest (2.8.7)
requests (2.9.1)
setuptools (20.0)
simplegeneric (0.8.1)
simplejson (3.8.1)
six (1.10.0)
traitlets (4.1.0)
wheel (0.29.0)
```

Aside: The process for tracking and updating _vendor libs should be streamlined.
The Makefile blows away modified code, making it a manual process to update
vendored libs.
This commit is contained in:
seanj 2016-02-08 10:27:00 -06:00
parent 61f75c40ad
commit d7ee5a639f
11 changed files with 107 additions and 59 deletions

View file

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2014 Vinay Sajip.
# Copyright (C) 2012-2016 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
import logging
__version__ = '0.2.1'
__version__ = '0.2.2'
class DistlibException(Exception):
pass

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-2014 Vinay Sajip.
# Copyright (C) 2013-2016 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2014 The Python Software Foundation.
# Copyright (C) 2012-2016 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""PEP 376 implementation."""
@ -20,7 +20,7 @@ import zipimport
from . import DistlibException, resources
from .compat import StringIO
from .version import get_scheme, UnsupportedVersionError
from .metadata import Metadata, METADATA_FILENAME
from .metadata import Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME
from .util import (parse_requirement, cached_property, parse_name_and_version,
read_exports, write_exports, CSVReader, CSVWriter)
@ -132,13 +132,17 @@ class DistributionPath(object):
if not r or r.path in seen:
continue
if self._include_dist and entry.endswith(DISTINFO_EXT):
metadata_path = posixpath.join(entry, METADATA_FILENAME)
pydist = finder.find(metadata_path)
if not pydist:
possible_filenames = [METADATA_FILENAME, WHEEL_METADATA_FILENAME]
for metadata_filename in possible_filenames:
metadata_path = posixpath.join(entry, metadata_filename)
pydist = finder.find(metadata_path)
if pydist:
break
else:
continue
metadata = Metadata(fileobj=pydist.as_stream(),
scheme='legacy')
with contextlib.closing(pydist.as_stream()) as stream:
metadata = Metadata(fileobj=stream, scheme='legacy')
logger.debug('Found %s', r.path)
seen.add(r.path)
yield new_dist_class(r.path, metadata=metadata,
@ -532,6 +536,9 @@ class InstalledDistribution(BaseInstalledDistribution):
metadata = env._cache.path[path].metadata
elif metadata is None:
r = finder.find(METADATA_FILENAME)
# Temporary - for Wheel 0.23 support
if r is None:
r = finder.find(WHEEL_METADATA_FILENAME)
# Temporary - for legacy support
if r is None:
r = finder.find('METADATA')

View file

@ -14,7 +14,7 @@ import posixpath
import re
try:
import threading
except ImportError:
except ImportError: # pragma: no cover
import dummy_threading as threading
import zlib
@ -36,7 +36,7 @@ logger = logging.getLogger(__name__)
HASHER_HASH = re.compile('^(\w+)=([a-f0-9]+)')
CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I)
HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml')
DEFAULT_INDEX = 'http://python.org/pypi'
DEFAULT_INDEX = 'https://pypi.python.org/pypi'
def get_all_distribution_names(url=None):
"""
@ -354,7 +354,7 @@ class Locator(object):
else:
logger.debug('skipping pre-release '
'version %s of %s', k, matcher.name)
except Exception:
except Exception: # pragma: no cover
logger.warning('error matching %s with %r', matcher, k)
pass # slist.append(k)
if len(slist) > 1:
@ -763,18 +763,18 @@ class SimpleScrapingLocator(Locator):
encoding = m.group(1)
try:
data = data.decode(encoding)
except UnicodeError:
except UnicodeError: # pragma: no cover
data = data.decode('latin-1') # fallback
result = Page(data, final_url)
self._page_cache[final_url] = result
except HTTPError as e:
if e.code != 404:
logger.exception('Fetch failed: %s: %s', url, e)
except URLError as e:
except URLError as e: # pragma: no cover
logger.exception('Fetch failed: %s: %s', url, e)
with self._lock:
self._bad_hosts.add(host)
except Exception as e:
except Exception as e: # pragma: no cover
logger.exception('Fetch failed: %s: %s', url, e)
finally:
self._page_cache[url] = result # even if None (failure)
@ -812,7 +812,7 @@ class DirectoryLocator(Locator):
self.recursive = kwargs.pop('recursive', True)
super(DirectoryLocator, self).__init__(**kwargs)
path = os.path.abspath(path)
if not os.path.isdir(path):
if not os.path.isdir(path): # pragma: no cover
raise DistlibException('Not a directory: %r' % path)
self.base_dir = path
@ -1083,7 +1083,7 @@ class DependencyFinder(object):
"""
try:
matcher = self.scheme.matcher(reqt)
except UnsupportedVersionError:
except UnsupportedVersionError: # pragma: no cover
# XXX compat-mode if cannot read the version
name = reqt.split()[0]
matcher = self.scheme.matcher(name)

View file

@ -50,7 +50,8 @@ PKG_INFO_ENCODING = 'utf-8'
# to 1.2 once PEP 345 is supported everywhere
PKG_INFO_PREFERRED_VERSION = '1.1'
_LINE_PREFIX = re.compile('\n \|')
_LINE_PREFIX_1_2 = re.compile('\n \|')
_LINE_PREFIX_PRE_1_2 = re.compile('\n ')
_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
'Summary', 'Description',
'Keywords', 'Home-page', 'Author', 'Author-email',
@ -295,7 +296,10 @@ class LegacyMetadata(object):
return 'UNKNOWN'
def _remove_line_prefix(self, value):
return _LINE_PREFIX.sub('\n', value)
if self.metadata_version in ('1.0', '1.1'):
return _LINE_PREFIX_PRE_1_2.sub('\n', value)
else:
return _LINE_PREFIX_1_2.sub('\n', value)
def __getattr__(self, name):
if name in _ATTR2FIELD:
@ -374,7 +378,10 @@ class LegacyMetadata(object):
continue
if field not in _LISTFIELDS:
if field == 'Description':
values = values.replace('\n', '\n |')
if self.metadata_version in ('1.0', '1.1'):
values = values.replace('\n', '\n ')
else:
values = values.replace('\n', '\n |')
values = [values]
if field in _LISTTUPLEFIELDS:
@ -548,7 +555,7 @@ class LegacyMetadata(object):
('description', 'Description'),
('keywords', 'Keywords'),
('platform', 'Platform'),
('classifier', 'Classifier'),
('classifiers', 'Classifier'),
('download_url', 'Download-URL'),
)
@ -617,6 +624,7 @@ class LegacyMetadata(object):
METADATA_FILENAME = 'pydist.json'
WHEEL_METADATA_FILENAME = 'metadata.json'
class Metadata(object):

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Vinay Sajip.
# Copyright (C) 2013-2016 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#

View file

@ -63,6 +63,22 @@ if __name__ == '__main__':
'''
def _enquote_executable(executable):
if ' ' in executable:
# make sure we quote only the executable in case of env
# for example /usr/bin/env "/dir with spaces/bin/jython"
# instead of "/usr/bin/env /dir with spaces/bin/jython"
# otherwise whole
if executable.startswith('/usr/bin/env '):
env, _executable = executable.split(' ', 1)
if ' ' in _executable and not _executable.startswith('"'):
executable = '%s "%s"' % (env, _executable)
else:
if not executable.startswith('"'):
executable = '"%s"' % executable
return executable
class ScriptMaker(object):
"""
A class to copy or create scripts from source scripts or callable
@ -85,8 +101,11 @@ class ScriptMaker(object):
self.variants = set(('', 'X.Y'))
self._fileop = fileop or FileOperator(dry_run)
self._is_nt = os.name == 'nt' or (
os.name == 'java' and os._name == 'nt')
def _get_alternate_executable(self, executable, options):
if options.get('gui', False) and os.name == 'nt':
if options.get('gui', False) and self._is_nt: # pragma: no cover
dn, fn = os.path.split(executable)
fn = fn.replace('python', 'pythonw')
executable = os.path.join(dn, fn)
@ -124,10 +143,10 @@ class ScriptMaker(object):
enquote = False # assume this will be taken care of
elif not sysconfig.is_python_build():
executable = get_executable()
elif in_venv():
elif in_venv(): # pragma: no cover
executable = os.path.join(sysconfig.get_path('scripts'),
'python%s' % sysconfig.get_config_var('EXE'))
else:
else: # pragma: no cover
executable = os.path.join(
sysconfig.get_config_var('BINDIR'),
'python%s%s' % (sysconfig.get_config_var('VERSION'),
@ -141,14 +160,14 @@ class ScriptMaker(object):
executable = os.path.normcase(executable)
# If the user didn't specify an executable, it may be necessary to
# cater for executable paths with spaces (not uncommon on Windows)
if enquote and ' ' in executable:
executable = '"%s"' % executable
if enquote:
executable = _enquote_executable(executable)
# Issue #51: don't use fsencode, since we later try to
# check that the shebang is decodable using utf-8.
executable = executable.encode('utf-8')
# in case of IronPython, play safe and enable frames support
if (sys.platform == 'cli' and '-X:Frames' not in post_interp
and '-X:FullFrames' not in post_interp):
and '-X:FullFrames' not in post_interp): # pragma: no cover
post_interp += b' -X:Frames'
shebang = b'#!' + executable + post_interp + b'\n'
# Python parser starts to read a script using UTF-8 until
@ -158,7 +177,7 @@ class ScriptMaker(object):
# UTF-8.
try:
shebang.decode('utf-8')
except UnicodeDecodeError:
except UnicodeDecodeError: # pragma: no cover
raise ValueError(
'The shebang (%r) is not decodable from utf-8' % shebang)
# If the script is encoded to a custom encoding (use a
@ -167,7 +186,7 @@ class ScriptMaker(object):
if encoding != 'utf-8':
try:
shebang.decode(encoding)
except UnicodeDecodeError:
except UnicodeDecodeError: # pragma: no cover
raise ValueError(
'The shebang (%r) is not decodable '
'from the script encoding (%r)' % (shebang, encoding))
@ -184,11 +203,11 @@ class ScriptMaker(object):
return self.manifest % base
def _write_script(self, names, shebang, script_bytes, filenames, ext):
use_launcher = self.add_launchers and os.name == 'nt'
use_launcher = self.add_launchers and self._is_nt
linesep = os.linesep.encode('utf-8')
if not use_launcher:
script_bytes = shebang + linesep + script_bytes
else:
else: # pragma: no cover
if ext == 'py':
launcher = self._get_launcher('t')
else:
@ -200,7 +219,7 @@ class ScriptMaker(object):
script_bytes = launcher + shebang + linesep + zip_data
for name in names:
outname = os.path.join(self.target_dir, name)
if use_launcher:
if use_launcher: # pragma: no cover
n, e = os.path.splitext(outname)
if e.startswith('.py'):
outname = n
@ -223,7 +242,7 @@ class ScriptMaker(object):
except Exception:
pass # still in use - ignore error
else:
if os.name == 'nt' and not outname.endswith('.' + ext):
if self._is_nt and not outname.endswith('.' + ext): # pragma: no cover
outname = '%s.%s' % (outname, ext)
if os.path.exists(outname) and not self.clobber:
logger.warning('Skipping existing file %s', outname)
@ -269,15 +288,13 @@ class ScriptMaker(object):
# script.
try:
f = open(script, 'rb')
except IOError:
except IOError: # pragma: no cover
if not self.dry_run:
raise
f = None
else:
encoding, lines = detect_encoding(f.readline)
f.seek(0)
first_line = f.readline()
if not first_line:
if not first_line: # pragma: no cover
logger.warning('%s: %s is an empty file (skipping)',
self.get_command_name(), script)
return
@ -298,8 +315,10 @@ class ScriptMaker(object):
logger.info('copying and adjusting %s -> %s', script,
self.target_dir)
if not self._fileop.dry_run:
encoding, lines = detect_encoding(f.readline)
f.seek(0)
shebang = self._get_shebang(encoding, post_interp)
if b'pythonw' in first_line:
if b'pythonw' in first_line: # pragma: no cover
ext = 'pyw'
else:
ext = 'py'
@ -316,7 +335,7 @@ class ScriptMaker(object):
def dry_run(self, value):
self._fileop.dry_run = value
if os.name == 'nt':
if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'): # pragma: no cover
# Executable launcher support.
# Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/

View file

@ -1,5 +1,5 @@
#
# Copyright (C) 2012-2014 The Python Software Foundation.
# Copyright (C) 2012-2016 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
import codecs
@ -20,6 +20,8 @@ import subprocess
import sys
import tarfile
import tempfile
import textwrap
try:
import threading
except ImportError:
@ -28,8 +30,8 @@ import time
from . import DistlibException
from .compat import (string_types, text_type, shutil, raw_input, StringIO,
cache_from_source, urlopen, httplib, xmlrpclib, splittype,
HTTPHandler, HTTPSHandler as BaseHTTPSHandler,
cache_from_source, urlopen, urljoin, httplib, xmlrpclib,
splittype, HTTPHandler, HTTPSHandler as BaseHTTPSHandler,
BaseConfigurator, valid_ident, Container, configparser,
URLError, match_hostname, CertificateError, ZipFile)
@ -199,8 +201,8 @@ def read_exports(stream):
data = stream.read()
stream = StringIO(data)
try:
data = json.load(stream)
result = data['extensions']['python.exports']['exports']
jdata = json.load(stream)
result = jdata['extensions']['python.exports']['exports']
for group, entries in result.items():
for k, v in entries.items():
s = '%s = %s' % (k, v)
@ -210,11 +212,22 @@ def read_exports(stream):
return result
except Exception:
stream.seek(0, 0)
def read_stream(cp, stream):
if hasattr(cp, 'read_file'):
cp.read_file(stream)
else:
cp.readfp(stream)
cp = configparser.ConfigParser()
if hasattr(cp, 'read_file'):
cp.read_file(stream)
else:
cp.readfp(stream)
try:
read_stream(cp, stream)
except configparser.MissingSectionHeaderError:
stream.close()
data = textwrap.dedent(data)
stream = StringIO(data)
read_stream(cp, stream)
result = {}
for key in cp.sections():
result[key] = entries = {}
@ -758,16 +771,17 @@ def _get_external_data(url):
logger.exception('Failed to get external data for %s: %s', url, e)
return result
_external_data_base_url = 'https://www.red-dove.com/pypi/projects/'
def get_project_data(name):
url = ('https://www.red-dove.com/pypi/projects/'
'%s/%s/project.json' % (name[0].upper(), name))
url = '%s/%s/project.json' % (name[0].upper(), name)
url = urljoin(_external_data_base_url, url)
result = _get_external_data(url)
return result
def get_package_data(name, version):
url = ('https://www.red-dove.com/pypi/projects/'
'%s/%s/package-%s.json' % (name[0].upper(), name, version))
url = '%s/%s/package-%s.json' % (name[0].upper(), name, version)
url = urljoin(_external_data_base_url, url)
return _get_external_data(url)

View file

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2014 The Python Software Foundation.
# Copyright (C) 2012-2016 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""
Implementation of a flexible versioning scheme providing support for PEP-386,
distribute-compatible and semantic versioning.
Implementation of a flexible versioning scheme providing support for PEP-440,
setuptools-compatible and semantic versioning.
"""
import logging

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-2014 Vinay Sajip.
# Copyright (C) 2013-2016 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#

View file

@ -1,4 +1,4 @@
distlib==0.2.1
distlib==0.2.2
html5lib==1.0b8
six==1.10.0
colorama==0.3.6