mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Update to pip 10.0.1 and update imports
Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
Copyright (c) 2008-2016 The pip developers (see AUTHORS.txt file)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Copyright (c) 2008-2016 The pip developers (see AUTHORS.txt file)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,346 +1 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import absolute_import
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import optparse
|
||||
import warnings
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks,
|
||||
# but if invoked (i.e. imported), it will issue a warning to stderr if socks
|
||||
# isn't available. requests unconditionally imports urllib3's socks contrib
|
||||
# module, triggering this warning. The warning breaks DEP-8 tests (because of
|
||||
# the stderr output) and is just plain annoying in normal usage. I don't want
|
||||
# to add socks as yet another dependency for pip, nor do I want to allow-stder
|
||||
# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to
|
||||
# be done before the import of pip9.vcs.
|
||||
from pip9._vendor.urllib3.exceptions import DependencyWarning
|
||||
warnings.filterwarnings("ignore", category=DependencyWarning) # noqa
|
||||
|
||||
# We want to inject the use of SecureTransport as early as possible so that any
|
||||
# references or sessions or what have you are ensured to have it, however we
|
||||
# only want to do this in the case that we're running on macOS and the linked
|
||||
# OpenSSL is too old to handle TLSv1.2
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
if (sys.platform == "darwin" and
|
||||
getattr(ssl, "OPENSSL_VERSION_NUMBER", 0) < 0x1000100f): # OpenSSL 1.0.1
|
||||
try:
|
||||
from pip9._vendor.urllib3.contrib import securetransport
|
||||
except (ImportError, OSError):
|
||||
pass
|
||||
else:
|
||||
securetransport.inject_into_urllib3()
|
||||
|
||||
from pip9.exceptions import InstallationError, CommandError, PipError
|
||||
from pip9.utils import get_installed_distributions, get_prog
|
||||
from pip9.utils import deprecation, dist_is_editable
|
||||
from pip9.vcs import git, mercurial, subversion, bazaar # noqa
|
||||
from pip9.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
|
||||
from pip9.commands import get_summaries, get_similar_commands
|
||||
from pip9.commands import commands_dict
|
||||
from pip9._vendor.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
|
||||
# assignment for flake8 to be happy
|
||||
|
||||
# This fixes a peculiarity when importing via __import__ - as we are
|
||||
# initialising the pip module, "from pip9.import cmdoptions" is recursive
|
||||
# and appears not to work properly in that situation.
|
||||
import pip9.cmdoptions
|
||||
cmdoptions = pip9.cmdoptions
|
||||
|
||||
# The version as used in the setup.py and the docs conf.py
|
||||
__version__ = "9.0.3"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Hide the InsecureRequestWarning from urllib3
|
||||
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
|
||||
|
||||
|
||||
def autocomplete():
|
||||
"""Command and option completion for the main option parser (and options)
|
||||
and its subcommands (and options).
|
||||
|
||||
Enable by sourcing one of the completion shell scripts (bash, zsh or fish).
|
||||
"""
|
||||
# Don't complete if user hasn't sourced bash_completion file.
|
||||
if 'PIP_AUTO_COMPLETE' not in os.environ:
|
||||
return
|
||||
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
try:
|
||||
current = cwords[cword - 1]
|
||||
except IndexError:
|
||||
current = ''
|
||||
|
||||
subcommands = [cmd for cmd, summary in get_summaries()]
|
||||
options = []
|
||||
# subcommand
|
||||
try:
|
||||
subcommand_name = [w for w in cwords if w in subcommands][0]
|
||||
except IndexError:
|
||||
subcommand_name = None
|
||||
|
||||
parser = create_main_parser()
|
||||
# subcommand options
|
||||
if subcommand_name:
|
||||
# special case: 'help' subcommand has no options
|
||||
if subcommand_name == 'help':
|
||||
sys.exit(1)
|
||||
# special case: list locally installed dists for uninstall command
|
||||
if subcommand_name == 'uninstall' and not current.startswith('-'):
|
||||
installed = []
|
||||
lc = current.lower()
|
||||
for dist in get_installed_distributions(local_only=True):
|
||||
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
|
||||
installed.append(dist.key)
|
||||
# if there are no dists installed, fall back to option completion
|
||||
if installed:
|
||||
for dist in installed:
|
||||
print(dist)
|
||||
sys.exit(1)
|
||||
|
||||
subcommand = commands_dict[subcommand_name]()
|
||||
options += [(opt.get_opt_string(), opt.nargs)
|
||||
for opt in subcommand.parser.option_list_all
|
||||
if opt.help != optparse.SUPPRESS_HELP]
|
||||
|
||||
# filter out previously specified options from available options
|
||||
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
||||
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
||||
# filter options by current input
|
||||
options = [(k, v) for k, v in options if k.startswith(current)]
|
||||
for option in options:
|
||||
opt_label = option[0]
|
||||
# append '=' to options which require args
|
||||
if option[1]:
|
||||
opt_label += '='
|
||||
print(opt_label)
|
||||
else:
|
||||
# show main parser options only when necessary
|
||||
if current.startswith('-') or current.startswith('--'):
|
||||
opts = [i.option_list for i in parser.option_groups]
|
||||
opts.append(parser.option_list)
|
||||
opts = (o for it in opts for o in it)
|
||||
|
||||
subcommands += [i.get_opt_string() for i in opts
|
||||
if i.help != optparse.SUPPRESS_HELP]
|
||||
|
||||
print(' '.join([x for x in subcommands if x.startswith(current)]))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_main_parser():
|
||||
parser_kw = {
|
||||
'usage': '\n%prog <command> [options]',
|
||||
'add_help_option': False,
|
||||
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||
'name': 'global',
|
||||
'prog': get_prog(),
|
||||
}
|
||||
|
||||
parser = ConfigOptionParser(**parser_kw)
|
||||
parser.disable_interspersed_args()
|
||||
|
||||
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
parser.version = 'pip %s from %s (python %s)' % (
|
||||
__version__, pip_pkg_dir, sys.version[:3])
|
||||
|
||||
# add the general options
|
||||
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||||
parser.add_option_group(gen_opts)
|
||||
|
||||
parser.main = True # so the help formatter knows
|
||||
|
||||
# create command listing for description
|
||||
command_summaries = get_summaries()
|
||||
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
|
||||
parser.description = '\n'.join(description)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def parseopts(args):
|
||||
parser = create_main_parser()
|
||||
|
||||
# Note: parser calls disable_interspersed_args(), so the result of this
|
||||
# call is to split the initial args into the general options before the
|
||||
# subcommand and everything else.
|
||||
# For example:
|
||||
# args: ['--timeout=5', 'install', '--user', 'INITools']
|
||||
# general_options: ['--timeout==5']
|
||||
# args_else: ['install', '--user', 'INITools']
|
||||
general_options, args_else = parser.parse_args(args)
|
||||
|
||||
# --version
|
||||
if general_options.version:
|
||||
sys.stdout.write(parser.version)
|
||||
sys.stdout.write(os.linesep)
|
||||
sys.exit()
|
||||
|
||||
# pip || pip help -> print_help()
|
||||
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
# the subcommand name
|
||||
cmd_name = args_else[0]
|
||||
|
||||
if cmd_name not in commands_dict:
|
||||
guess = get_similar_commands(cmd_name)
|
||||
|
||||
msg = ['unknown command "%s"' % cmd_name]
|
||||
if guess:
|
||||
msg.append('maybe you meant "%s"' % guess)
|
||||
|
||||
raise CommandError(' - '.join(msg))
|
||||
|
||||
# all the args without the subcommand
|
||||
cmd_args = args[:]
|
||||
cmd_args.remove(cmd_name)
|
||||
|
||||
return cmd_name, cmd_args
|
||||
|
||||
|
||||
def check_isolated(args):
|
||||
isolated = False
|
||||
|
||||
if "--isolated" in args:
|
||||
isolated = True
|
||||
|
||||
return isolated
|
||||
|
||||
|
||||
def main(args=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
# Configure our deprecation warnings to be sent through loggers
|
||||
deprecation.install_warning_logger()
|
||||
|
||||
autocomplete()
|
||||
|
||||
try:
|
||||
cmd_name, cmd_args = parseopts(args)
|
||||
except PipError as exc:
|
||||
sys.stderr.write("ERROR: %s" % exc)
|
||||
sys.stderr.write(os.linesep)
|
||||
sys.exit(1)
|
||||
|
||||
# Needed for locale.getpreferredencoding(False) to work
|
||||
# in pip9.utils.encoding.auto_decode
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except locale.Error as e:
|
||||
# setlocale can apparently crash if locale are uninitialized
|
||||
logger.debug("Ignoring error %s when setting locale", e)
|
||||
command = commands_dict[cmd_name](isolated=check_isolated(cmd_args))
|
||||
return command.main(cmd_args)
|
||||
|
||||
|
||||
# ###########################################################
|
||||
# # Writing freeze files
|
||||
|
||||
class FrozenRequirement(object):
|
||||
|
||||
def __init__(self, name, req, editable, comments=()):
|
||||
self.name = name
|
||||
self.req = req
|
||||
self.editable = editable
|
||||
self.comments = comments
|
||||
|
||||
_rev_re = re.compile(r'-r(\d+)$')
|
||||
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist, dependency_links):
|
||||
location = os.path.normcase(os.path.abspath(dist.location))
|
||||
comments = []
|
||||
from pip9.vcs import vcs, get_src_requirement
|
||||
if dist_is_editable(dist) and vcs.get_backend_name(location):
|
||||
editable = True
|
||||
try:
|
||||
req = get_src_requirement(dist, location)
|
||||
except InstallationError as exc:
|
||||
logger.warning(
|
||||
"Error when trying to get requirement for VCS system %s, "
|
||||
"falling back to uneditable format", exc
|
||||
)
|
||||
req = None
|
||||
if req is None:
|
||||
logger.warning(
|
||||
'Could not determine repository location of %s', location
|
||||
)
|
||||
comments.append(
|
||||
'## !! Could not determine repository location'
|
||||
)
|
||||
req = dist.as_requirement()
|
||||
editable = False
|
||||
else:
|
||||
editable = False
|
||||
req = dist.as_requirement()
|
||||
specs = req.specs
|
||||
assert len(specs) == 1 and specs[0][0] in ["==", "==="], \
|
||||
'Expected 1 spec with == or ===; specs = %r; dist = %r' % \
|
||||
(specs, dist)
|
||||
version = specs[0][1]
|
||||
ver_match = cls._rev_re.search(version)
|
||||
date_match = cls._date_re.search(version)
|
||||
if ver_match or date_match:
|
||||
svn_backend = vcs.get_backend('svn')
|
||||
if svn_backend:
|
||||
svn_location = svn_backend().get_location(
|
||||
dist,
|
||||
dependency_links,
|
||||
)
|
||||
if not svn_location:
|
||||
logger.warning(
|
||||
'Warning: cannot find svn location for %s', req)
|
||||
comments.append(
|
||||
'## FIXME: could not find svn URL in dependency_links '
|
||||
'for this package:'
|
||||
)
|
||||
else:
|
||||
comments.append(
|
||||
'# Installing as editable to satisfy requirement %s:' %
|
||||
req
|
||||
)
|
||||
if ver_match:
|
||||
rev = ver_match.group(1)
|
||||
else:
|
||||
rev = '{%s}' % date_match.group(1)
|
||||
editable = True
|
||||
req = '%s@%s#egg=%s' % (
|
||||
svn_location,
|
||||
rev,
|
||||
cls.egg_name(dist)
|
||||
)
|
||||
return cls(dist.project_name, req, editable, comments)
|
||||
|
||||
@staticmethod
|
||||
def egg_name(dist):
|
||||
name = dist.egg_name()
|
||||
match = re.search(r'-py\d\.\d$', name)
|
||||
if match:
|
||||
name = name[:match.start()]
|
||||
return name
|
||||
|
||||
def __str__(self):
|
||||
req = self.req
|
||||
if self.editable:
|
||||
req = '-e %s' % req
|
||||
return '\n'.join(list(self.comments) + [str(req)]) + '\n'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
__version__ = "10.0.1"
|
||||
|
||||
@@ -9,11 +9,11 @@ if __package__ == '':
|
||||
# __file__ is pip-*.whl/pip/__main__.py
|
||||
# first dirname call strips of '/__main__.py', second strips off '/pip'
|
||||
# Resulting path is the name of the wheel itself
|
||||
# Add that to sys.path so we can import pip9
|
||||
# Add that to sys.path so we can import notpip
|
||||
path = os.path.dirname(os.path.dirname(__file__))
|
||||
sys.path.insert(0, path)
|
||||
|
||||
import pip9 # noqa
|
||||
from notpip._internal import main as _main # noqa
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(pip9.main())
|
||||
sys.exit(_main())
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import absolute_import
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import optparse
|
||||
import warnings
|
||||
|
||||
import sys
|
||||
|
||||
# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks,
|
||||
# but if invoked (i.e. imported), it will issue a warning to stderr if socks
|
||||
# isn't available. requests unconditionally imports urllib3's socks contrib
|
||||
# module, triggering this warning. The warning breaks DEP-8 tests (because of
|
||||
# the stderr output) and is just plain annoying in normal usage. I don't want
|
||||
# to add socks as yet another dependency for pip, nor do I want to allow-stder
|
||||
# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to
|
||||
# be done before the import of pip.vcs.
|
||||
from notpip._vendor.urllib3.exceptions import DependencyWarning
|
||||
warnings.filterwarnings("ignore", category=DependencyWarning) # noqa
|
||||
|
||||
# We want to inject the use of SecureTransport as early as possible so that any
|
||||
# references or sessions or what have you are ensured to have it, however we
|
||||
# only want to do this in the case that we're running on macOS and the linked
|
||||
# OpenSSL is too old to handle TLSv1.2
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
# Checks for OpenSSL 1.0.1 on MacOS
|
||||
if sys.platform == "darwin" and ssl.OPENSSL_VERSION_NUMBER < 0x1000100f:
|
||||
try:
|
||||
from notpip._vendor.urllib3.contrib import securetransport
|
||||
except (ImportError, OSError):
|
||||
pass
|
||||
else:
|
||||
securetransport.inject_into_urllib3()
|
||||
|
||||
from notpip import __version__
|
||||
from notpip._internal import cmdoptions
|
||||
from notpip._internal.exceptions import CommandError, PipError
|
||||
from notpip._internal.utils.misc import get_installed_distributions, get_prog
|
||||
from notpip._internal.utils import deprecation
|
||||
from notpip._internal.vcs import git, mercurial, subversion, bazaar # noqa
|
||||
from notpip._internal.baseparser import (
|
||||
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||
)
|
||||
from notpip._internal.commands import get_summaries, get_similar_commands
|
||||
from notpip._internal.commands import commands_dict
|
||||
from notpip._vendor.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Hide the InsecureRequestWarning from urllib3
|
||||
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
|
||||
|
||||
|
||||
def autocomplete():
|
||||
"""Command and option completion for the main option parser (and options)
|
||||
and its subcommands (and options).
|
||||
|
||||
Enable by sourcing one of the completion shell scripts (bash, zsh or fish).
|
||||
"""
|
||||
# Don't complete if user hasn't sourced bash_completion file.
|
||||
if 'PIP_AUTO_COMPLETE' not in os.environ:
|
||||
return
|
||||
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
try:
|
||||
current = cwords[cword - 1]
|
||||
except IndexError:
|
||||
current = ''
|
||||
|
||||
subcommands = [cmd for cmd, summary in get_summaries()]
|
||||
options = []
|
||||
# subcommand
|
||||
try:
|
||||
subcommand_name = [w for w in cwords if w in subcommands][0]
|
||||
except IndexError:
|
||||
subcommand_name = None
|
||||
|
||||
parser = create_main_parser()
|
||||
# subcommand options
|
||||
if subcommand_name:
|
||||
# special case: 'help' subcommand has no options
|
||||
if subcommand_name == 'help':
|
||||
sys.exit(1)
|
||||
# special case: list locally installed dists for show and uninstall
|
||||
should_list_installed = (
|
||||
subcommand_name in ['show', 'uninstall'] and
|
||||
not current.startswith('-')
|
||||
)
|
||||
if should_list_installed:
|
||||
installed = []
|
||||
lc = current.lower()
|
||||
for dist in get_installed_distributions(local_only=True):
|
||||
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
|
||||
installed.append(dist.key)
|
||||
# if there are no dists installed, fall back to option completion
|
||||
if installed:
|
||||
for dist in installed:
|
||||
print(dist)
|
||||
sys.exit(1)
|
||||
|
||||
subcommand = commands_dict[subcommand_name]()
|
||||
|
||||
for opt in subcommand.parser.option_list_all:
|
||||
if opt.help != optparse.SUPPRESS_HELP:
|
||||
for opt_str in opt._long_opts + opt._short_opts:
|
||||
options.append((opt_str, opt.nargs))
|
||||
|
||||
# filter out previously specified options from available options
|
||||
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
||||
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
||||
# filter options by current input
|
||||
options = [(k, v) for k, v in options if k.startswith(current)]
|
||||
for option in options:
|
||||
opt_label = option[0]
|
||||
# append '=' to options which require args
|
||||
if option[1] and option[0][:2] == "--":
|
||||
opt_label += '='
|
||||
print(opt_label)
|
||||
else:
|
||||
# show main parser options only when necessary
|
||||
if current.startswith('-') or current.startswith('--'):
|
||||
opts = [i.option_list for i in parser.option_groups]
|
||||
opts.append(parser.option_list)
|
||||
opts = (o for it in opts for o in it)
|
||||
|
||||
for opt in opts:
|
||||
if opt.help != optparse.SUPPRESS_HELP:
|
||||
subcommands += opt._long_opts + opt._short_opts
|
||||
|
||||
print(' '.join([x for x in subcommands if x.startswith(current)]))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_main_parser():
|
||||
parser_kw = {
|
||||
'usage': '\n%prog <command> [options]',
|
||||
'add_help_option': False,
|
||||
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||
'name': 'global',
|
||||
'prog': get_prog(),
|
||||
}
|
||||
|
||||
parser = ConfigOptionParser(**parser_kw)
|
||||
parser.disable_interspersed_args()
|
||||
|
||||
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
parser.version = 'pip %s from %s (python %s)' % (
|
||||
__version__, pip_pkg_dir, sys.version[:3],
|
||||
)
|
||||
|
||||
# add the general options
|
||||
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||||
parser.add_option_group(gen_opts)
|
||||
|
||||
parser.main = True # so the help formatter knows
|
||||
|
||||
# create command listing for description
|
||||
command_summaries = get_summaries()
|
||||
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
|
||||
parser.description = '\n'.join(description)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def parseopts(args):
|
||||
parser = create_main_parser()
|
||||
|
||||
# Note: parser calls disable_interspersed_args(), so the result of this
|
||||
# call is to split the initial args into the general options before the
|
||||
# subcommand and everything else.
|
||||
# For example:
|
||||
# args: ['--timeout=5', 'install', '--user', 'INITools']
|
||||
# general_options: ['--timeout==5']
|
||||
# args_else: ['install', '--user', 'INITools']
|
||||
general_options, args_else = parser.parse_args(args)
|
||||
|
||||
# --version
|
||||
if general_options.version:
|
||||
sys.stdout.write(parser.version)
|
||||
sys.stdout.write(os.linesep)
|
||||
sys.exit()
|
||||
|
||||
# pip || pip help -> print_help()
|
||||
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
# the subcommand name
|
||||
cmd_name = args_else[0]
|
||||
|
||||
if cmd_name not in commands_dict:
|
||||
guess = get_similar_commands(cmd_name)
|
||||
|
||||
msg = ['unknown command "%s"' % cmd_name]
|
||||
if guess:
|
||||
msg.append('maybe you meant "%s"' % guess)
|
||||
|
||||
raise CommandError(' - '.join(msg))
|
||||
|
||||
# all the args without the subcommand
|
||||
cmd_args = args[:]
|
||||
cmd_args.remove(cmd_name)
|
||||
|
||||
return cmd_name, cmd_args
|
||||
|
||||
|
||||
def check_isolated(args):
|
||||
isolated = False
|
||||
|
||||
if "--isolated" in args:
|
||||
isolated = True
|
||||
|
||||
return isolated
|
||||
|
||||
|
||||
def main(args=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
# Configure our deprecation warnings to be sent through loggers
|
||||
deprecation.install_warning_logger()
|
||||
|
||||
autocomplete()
|
||||
|
||||
try:
|
||||
cmd_name, cmd_args = parseopts(args)
|
||||
except PipError as exc:
|
||||
sys.stderr.write("ERROR: %s" % exc)
|
||||
sys.stderr.write(os.linesep)
|
||||
sys.exit(1)
|
||||
|
||||
# Needed for locale.getpreferredencoding(False) to work
|
||||
# in pip._internal.utils.encoding.auto_decode
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except locale.Error as e:
|
||||
# setlocale can apparently crash if locale are uninitialized
|
||||
logger.debug("Ignoring error %s when setting locale", e)
|
||||
command = commands_dict[cmd_name](isolated=check_isolated(cmd_args))
|
||||
return command.main(cmd_args)
|
||||
+117
-81
@@ -2,40 +2,49 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import logging.config
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import optparse
|
||||
import warnings
|
||||
|
||||
from pip9 import cmdoptions
|
||||
from pip9.index import PackageFinder
|
||||
from pip9.locations import running_under_virtualenv
|
||||
from pip9.download import pip9Session
|
||||
from pip9.exceptions import (BadCommand, InstallationError, UninstallationError,
|
||||
CommandError, PreviousBuildDirError)
|
||||
|
||||
from pip9.compat import logging_dictConfig
|
||||
from pip9.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
|
||||
from pip9.req import InstallRequirement, parse_requirements
|
||||
from pip9.status_codes import (
|
||||
SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND,
|
||||
PREVIOUS_BUILD_DIR_ERROR,
|
||||
from notpip._internal import cmdoptions
|
||||
from notpip._internal.baseparser import (
|
||||
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||
)
|
||||
from pip9.utils import deprecation, get_prog, normalize_path
|
||||
from pip9.utils.logging import IndentingFormatter
|
||||
from pip9.utils.outdated import pip9_version_check
|
||||
from notpip._internal.compat import WINDOWS
|
||||
from notpip._internal.download import PipSession
|
||||
from notpip._internal.exceptions import (
|
||||
BadCommand, CommandError, InstallationError, PreviousBuildDirError,
|
||||
UninstallationError,
|
||||
)
|
||||
from notpip._internal.index import PackageFinder
|
||||
from notpip._internal.locations import running_under_virtualenv
|
||||
from notpip._internal.req.req_file import parse_requirements
|
||||
from notpip._internal.req.req_install import InstallRequirement
|
||||
from notpip._internal.status_codes import (
|
||||
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
|
||||
VIRTUALENV_NOT_FOUND,
|
||||
)
|
||||
from notpip._internal.utils import deprecation
|
||||
from notpip._internal.utils.logging import IndentingFormatter
|
||||
from notpip._internal.utils.misc import get_prog, normalize_path
|
||||
from notpip._internal.utils.outdated import pip_version_check
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional
|
||||
|
||||
__all__ = ['Command']
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(object):
|
||||
name = None
|
||||
usage = None
|
||||
hidden = False
|
||||
name = None # type: Optional[str]
|
||||
usage = None # type: Optional[str]
|
||||
hidden = False # type: bool
|
||||
ignore_require_venv = False # type: bool
|
||||
log_streams = ("ext://sys.stdout", "ext://sys.stderr")
|
||||
|
||||
def __init__(self, isolated=False):
|
||||
@@ -105,15 +114,17 @@ class Command(object):
|
||||
def main(self, args):
|
||||
options, args = self.parse_args(args)
|
||||
|
||||
if options.quiet:
|
||||
if options.quiet == 1:
|
||||
level = "WARNING"
|
||||
if options.quiet == 2:
|
||||
level = "ERROR"
|
||||
else:
|
||||
level = "CRITICAL"
|
||||
elif options.verbose:
|
||||
# Set verbosity so that it can be used elsewhere.
|
||||
self.verbosity = options.verbose - options.quiet
|
||||
|
||||
if self.verbosity >= 1:
|
||||
level = "DEBUG"
|
||||
elif self.verbosity == -1:
|
||||
level = "WARNING"
|
||||
elif self.verbosity == -2:
|
||||
level = "ERROR"
|
||||
elif self.verbosity <= -3:
|
||||
level = "CRITICAL"
|
||||
else:
|
||||
level = "INFO"
|
||||
|
||||
@@ -123,12 +134,15 @@ class Command(object):
|
||||
if options.log:
|
||||
root_level = "DEBUG"
|
||||
|
||||
logging_dictConfig({
|
||||
logger_class = "pip._internal.utils.logging.ColorizedStreamHandler"
|
||||
handler_class = "pip._internal.utils.logging.BetterRotatingFileHandler"
|
||||
|
||||
logging.config.dictConfig({
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"filters": {
|
||||
"exclude_warnings": {
|
||||
"()": "pip9.utils.logging.MaxLevelFilter",
|
||||
"()": "pip._internal.utils.logging.MaxLevelFilter",
|
||||
"level": logging.WARNING,
|
||||
},
|
||||
},
|
||||
@@ -141,20 +155,22 @@ class Command(object):
|
||||
"handlers": {
|
||||
"console": {
|
||||
"level": level,
|
||||
"class": "pip9.utils.logging.ColorizedStreamHandler",
|
||||
"class": logger_class,
|
||||
"no_color": options.no_color,
|
||||
"stream": self.log_streams[0],
|
||||
"filters": ["exclude_warnings"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
"console_errors": {
|
||||
"level": "WARNING",
|
||||
"class": "pip9.utils.logging.ColorizedStreamHandler",
|
||||
"class": logger_class,
|
||||
"no_color": options.no_color,
|
||||
"stream": self.log_streams[1],
|
||||
"formatter": "indent",
|
||||
},
|
||||
"user_log": {
|
||||
"level": "DEBUG",
|
||||
"class": "pip9.utils.logging.BetterRotatingFileHandler",
|
||||
"class": handler_class,
|
||||
"filename": options.log or "/dev/null",
|
||||
"delay": True,
|
||||
"formatter": "indent",
|
||||
@@ -169,29 +185,24 @@ class Command(object):
|
||||
])),
|
||||
},
|
||||
# Disable any logging besides WARNING unless we have DEBUG level
|
||||
# logging enabled. These use both pip9._vendor and the bare names
|
||||
# logging enabled. These use both pip._vendor and the bare names
|
||||
# for the case where someone unbundles our libraries.
|
||||
"loggers": dict(
|
||||
(
|
||||
name,
|
||||
{
|
||||
"level": (
|
||||
"WARNING"
|
||||
if level in ["INFO", "ERROR"]
|
||||
else "DEBUG"
|
||||
),
|
||||
},
|
||||
)
|
||||
for name in ["pip9._vendor", "distlib", "requests", "urllib3"]
|
||||
),
|
||||
"loggers": {
|
||||
name: {
|
||||
"level": (
|
||||
"WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
|
||||
)
|
||||
} for name in [
|
||||
"pip._vendor", "distlib", "requests", "urllib3"
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
if sys.version_info[:2] == (2, 6):
|
||||
if sys.version_info[:2] == (3, 3):
|
||||
warnings.warn(
|
||||
"Python 2.6 is no longer supported by the Python core team, "
|
||||
"please upgrade your Python. A future version of pip will "
|
||||
"drop support for Python 2.6",
|
||||
deprecation.Python26DeprecationWarning
|
||||
"Python 3.3 supported has been deprecated and support for it "
|
||||
"will be dropped in the future. Please upgrade your Python.",
|
||||
deprecation.RemovedInPip11Warning,
|
||||
)
|
||||
|
||||
# TODO: try to get these passing down from the command?
|
||||
@@ -203,7 +214,7 @@ class Command(object):
|
||||
if options.exists_action:
|
||||
os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
|
||||
|
||||
if options.require_venv:
|
||||
if options.require_venv and not self.ignore_require_venv:
|
||||
# If a venv is required check if it can really be found
|
||||
if not running_under_virtualenv():
|
||||
logger.critical(
|
||||
@@ -211,6 +222,8 @@ class Command(object):
|
||||
)
|
||||
sys.exit(VIRTUALENV_NOT_FOUND)
|
||||
|
||||
original_root_handlers = set(logging.root.handlers)
|
||||
|
||||
try:
|
||||
status = self.run(options, args)
|
||||
# FIXME: all commands should return an exit status
|
||||
@@ -249,7 +262,11 @@ class Command(object):
|
||||
options,
|
||||
retries=0,
|
||||
timeout=min(5, options.timeout)) as session:
|
||||
pip_version_check(session)
|
||||
pip_version_check(session, options)
|
||||
# Avoid leaking loggers
|
||||
for handler in set(logging.root.handlers) - original_root_handlers:
|
||||
# this method benefit from the Logger class internal lock
|
||||
logging.root.removeHandler(handler)
|
||||
|
||||
return SUCCESS
|
||||
|
||||
@@ -262,54 +279,73 @@ class RequirementCommand(Command):
|
||||
"""
|
||||
Marshal cmd line args into a requirement set.
|
||||
"""
|
||||
# NOTE: As a side-effect, options.require_hashes and
|
||||
# requirement_set.require_hashes may be updated
|
||||
|
||||
for filename in options.constraints:
|
||||
for req in parse_requirements(
|
||||
for req_to_add in parse_requirements(
|
||||
filename,
|
||||
constraint=True, finder=finder, options=options,
|
||||
session=session, wheel_cache=wheel_cache):
|
||||
requirement_set.add_requirement(req)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for req in args:
|
||||
requirement_set.add_requirement(
|
||||
InstallRequirement.from_line(
|
||||
req, None, isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add = InstallRequirement.from_line(
|
||||
req, None, isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for req in options.editables:
|
||||
requirement_set.add_requirement(
|
||||
InstallRequirement.from_editable(
|
||||
req,
|
||||
default_vcs=options.default_vcs,
|
||||
isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add = InstallRequirement.from_editable(
|
||||
req,
|
||||
isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
found_req_in_file = False
|
||||
for filename in options.requirements:
|
||||
for req in parse_requirements(
|
||||
for req_to_add in parse_requirements(
|
||||
filename,
|
||||
finder=finder, options=options, session=session,
|
||||
wheel_cache=wheel_cache):
|
||||
found_req_in_file = True
|
||||
requirement_set.add_requirement(req)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
# If --require-hashes was a line in a requirements file, tell
|
||||
# RequirementSet about it:
|
||||
requirement_set.require_hashes = options.require_hashes
|
||||
|
||||
if not (args or options.editables or found_req_in_file):
|
||||
if not (args or options.editables or options.requirements):
|
||||
opts = {'name': name}
|
||||
if options.find_links:
|
||||
msg = ('You must give at least one requirement to '
|
||||
'%(name)s (maybe you meant "pip %(name)s '
|
||||
'%(links)s"?)' %
|
||||
dict(opts, links=' '.join(options.find_links)))
|
||||
raise CommandError(
|
||||
'You must give at least one requirement to %(name)s '
|
||||
'(maybe you meant "pip %(name)s %(links)s"?)' %
|
||||
dict(opts, links=' '.join(options.find_links)))
|
||||
else:
|
||||
msg = ('You must give at least one requirement '
|
||||
'to %(name)s (see "pip help %(name)s")' % opts)
|
||||
logger.warning(msg)
|
||||
raise CommandError(
|
||||
'You must give at least one requirement to %(name)s '
|
||||
'(see "pip help %(name)s")' % opts)
|
||||
|
||||
# On Windows, any operation modifying pip should be run as:
|
||||
# python -m pip ...
|
||||
# See https://github.com/pypa/pip/issues/1299 for more discussion
|
||||
should_show_use_python_msg = (
|
||||
WINDOWS and
|
||||
requirement_set.has_requirement("pip") and
|
||||
os.path.basename(sys.argv[0]).startswith("pip")
|
||||
)
|
||||
if should_show_use_python_msg:
|
||||
new_command = [
|
||||
sys.executable, "-m", "pip"
|
||||
] + sys.argv[1:]
|
||||
raise CommandError(
|
||||
'To modify pip, please run the following command:\n{}'
|
||||
.format(" ".join(new_command))
|
||||
)
|
||||
|
||||
def _build_package_finder(self, options, session,
|
||||
platform=None, python_versions=None,
|
||||
+44
-97
@@ -1,23 +1,18 @@
|
||||
"""Base option parser setup"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
from distutils.util import strtobool
|
||||
|
||||
from pip9._vendor.six import string_types
|
||||
from pip9._vendor.six.moves import configparser
|
||||
from pip9.locations import (
|
||||
legacy_config_file, config_basename, running_under_virtualenv,
|
||||
site_config_files
|
||||
)
|
||||
from pip9.utils import appdirs, get_terminal_size
|
||||
from notpip._vendor.six import string_types
|
||||
|
||||
from notpip._internal.compat import get_terminal_size
|
||||
from notpip._internal.configuration import Configuration, ConfigurationError
|
||||
|
||||
_environ_prefix_re = re.compile(r"^PIP_", re.I)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
||||
@@ -137,58 +132,15 @@ class ConfigOptionParser(CustomOptionParser):
|
||||
"""Custom option parser which updates its defaults by checking the
|
||||
configuration files and environmental variables"""
|
||||
|
||||
isolated = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.config = configparser.RawConfigParser()
|
||||
self.name = kwargs.pop('name')
|
||||
self.isolated = kwargs.pop("isolated", False)
|
||||
self.files = self.get_config_files()
|
||||
if self.files:
|
||||
self.config.read(self.files)
|
||||
|
||||
isolated = kwargs.pop("isolated", False)
|
||||
self.config = Configuration(isolated)
|
||||
|
||||
assert self.name
|
||||
optparse.OptionParser.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_config_files(self):
|
||||
# the files returned by this method will be parsed in order with the
|
||||
# first files listed being overridden by later files in standard
|
||||
# ConfigParser fashion
|
||||
config_file = os.environ.get('PIP_CONFIG_FILE', False)
|
||||
if config_file == os.devnull:
|
||||
return []
|
||||
|
||||
# at the base we have any site-wide configuration
|
||||
files = list(site_config_files)
|
||||
|
||||
# per-user configuration next
|
||||
if not self.isolated:
|
||||
if config_file and os.path.exists(config_file):
|
||||
files.append(config_file)
|
||||
else:
|
||||
# This is the legacy config file, we consider it to be a lower
|
||||
# priority than the new file location.
|
||||
files.append(legacy_config_file)
|
||||
|
||||
# This is the new config file, we consider it to be a higher
|
||||
# priority than the legacy file.
|
||||
files.append(
|
||||
os.path.join(
|
||||
appdirs.user_config_dir("pip"),
|
||||
config_basename,
|
||||
)
|
||||
)
|
||||
|
||||
# finally virtualenv configuration first trumping others
|
||||
if running_under_virtualenv():
|
||||
venv_config_file = os.path.join(
|
||||
sys.prefix,
|
||||
config_basename,
|
||||
)
|
||||
if os.path.exists(venv_config_file):
|
||||
files.append(venv_config_file)
|
||||
|
||||
return files
|
||||
|
||||
def check_default(self, option, key, val):
|
||||
try:
|
||||
return option.check_value(key, val)
|
||||
@@ -196,30 +148,43 @@ class ConfigOptionParser(CustomOptionParser):
|
||||
print("An error occurred during configuration: %s" % exc)
|
||||
sys.exit(3)
|
||||
|
||||
def _get_ordered_configuration_items(self):
|
||||
# Configuration gives keys in an unordered manner. Order them.
|
||||
override_order = ["global", self.name, ":env:"]
|
||||
|
||||
# Pool the options into different groups
|
||||
section_items = {name: [] for name in override_order}
|
||||
for section_key, val in self.config.items():
|
||||
# ignore empty values
|
||||
if not val:
|
||||
logger.debug(
|
||||
"Ignoring configuration key '%s' as it's value is empty.",
|
||||
section_key
|
||||
)
|
||||
continue
|
||||
|
||||
section, key = section_key.split(".", 1)
|
||||
if section in override_order:
|
||||
section_items[section].append((key, val))
|
||||
|
||||
# Yield each group in their override order
|
||||
for section in override_order:
|
||||
for key, val in section_items[section]:
|
||||
yield key, val
|
||||
|
||||
def _update_defaults(self, defaults):
|
||||
"""Updates the given defaults with values from the config files and
|
||||
the environ. Does a little special handling for certain types of
|
||||
options (lists)."""
|
||||
# Then go and look for the other sources of configuration:
|
||||
config = {}
|
||||
# 1. config files
|
||||
for section in ('global', self.name):
|
||||
config.update(
|
||||
self.normalize_keys(self.get_config_section(section))
|
||||
)
|
||||
# 2. environmental variables
|
||||
if not self.isolated:
|
||||
config.update(self.normalize_keys(self.get_environ_vars()))
|
||||
|
||||
# Accumulate complex default state.
|
||||
self.values = optparse.Values(self.defaults)
|
||||
late_eval = set()
|
||||
# Then set the options with those values
|
||||
for key, val in config.items():
|
||||
# ignore empty values
|
||||
if not val:
|
||||
continue
|
||||
for key, val in self._get_ordered_configuration_items():
|
||||
# '--' because configuration supports only long names
|
||||
option = self.get_option('--' + key)
|
||||
|
||||
option = self.get_option(key)
|
||||
# Ignore options not present in this parser. E.g. non-globals put
|
||||
# in [global] by users that want them to apply to all applicable
|
||||
# commands.
|
||||
@@ -249,30 +214,6 @@ class ConfigOptionParser(CustomOptionParser):
|
||||
self.values = None
|
||||
return defaults
|
||||
|
||||
def normalize_keys(self, items):
|
||||
"""Return a config dictionary with normalized keys regardless of
|
||||
whether the keys were specified in environment variables or in config
|
||||
files"""
|
||||
normalized = {}
|
||||
for key, val in items:
|
||||
key = key.replace('_', '-')
|
||||
if not key.startswith('--'):
|
||||
key = '--%s' % key # only prefer long opts
|
||||
normalized[key] = val
|
||||
return normalized
|
||||
|
||||
def get_config_section(self, name):
|
||||
"""Get a section of a configuration"""
|
||||
if self.config.has_section(name):
|
||||
return self.config.items(name)
|
||||
return []
|
||||
|
||||
def get_environ_vars(self):
|
||||
"""Returns a generator with all environmental vars with prefix PIP_"""
|
||||
for key, val in os.environ.items():
|
||||
if _environ_prefix_re.search(key):
|
||||
yield (_environ_prefix_re.sub("", key).lower(), val)
|
||||
|
||||
def get_default_values(self):
|
||||
"""Overriding to make updating the defaults after instantiation of
|
||||
the option parser possible, _update_defaults() does the dirty work."""
|
||||
@@ -280,6 +221,12 @@ class ConfigOptionParser(CustomOptionParser):
|
||||
# Old, pre-Optik 1.5 behaviour.
|
||||
return optparse.Values(self.defaults)
|
||||
|
||||
# Load the configuration, or error out in case of an error
|
||||
try:
|
||||
self.config.load()
|
||||
except ConfigurationError as err:
|
||||
self.exit(2, err.args[0])
|
||||
|
||||
defaults = self._update_defaults(self.defaults.copy()) # ours
|
||||
for option in self._get_all_options():
|
||||
default = defaults.get(option.dest)
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Build Environment used for isolation during sdist building
|
||||
"""
|
||||
|
||||
import os
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from sysconfig import get_paths
|
||||
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
|
||||
class BuildEnvironment(object):
|
||||
"""Creates and manages an isolated environment to install build deps
|
||||
"""
|
||||
|
||||
def __init__(self, no_clean):
|
||||
self._temp_dir = TempDirectory(kind="build-env")
|
||||
self._no_clean = no_clean
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._temp_dir.path
|
||||
|
||||
def __enter__(self):
|
||||
self._temp_dir.create()
|
||||
|
||||
self.save_path = os.environ.get('PATH', None)
|
||||
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
|
||||
self.save_nousersite = os.environ.get('PYTHONNOUSERSITE', None)
|
||||
|
||||
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
|
||||
install_dirs = get_paths(install_scheme, vars={
|
||||
'base': self.path,
|
||||
'platbase': self.path,
|
||||
})
|
||||
|
||||
scripts = install_dirs['scripts']
|
||||
if self.save_path:
|
||||
os.environ['PATH'] = scripts + os.pathsep + self.save_path
|
||||
else:
|
||||
os.environ['PATH'] = scripts + os.pathsep + os.defpath
|
||||
|
||||
# Note: prefer distutils' sysconfig to get the
|
||||
# library paths so PyPy is correctly supported.
|
||||
purelib = get_python_lib(plat_specific=0, prefix=self.path)
|
||||
platlib = get_python_lib(plat_specific=1, prefix=self.path)
|
||||
if purelib == platlib:
|
||||
lib_dirs = purelib
|
||||
else:
|
||||
lib_dirs = purelib + os.pathsep + platlib
|
||||
if self.save_pythonpath:
|
||||
os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
|
||||
self.save_pythonpath
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = lib_dirs
|
||||
|
||||
os.environ['PYTHONNOUSERSITE'] = '1'
|
||||
|
||||
return self.path
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if not self._no_clean:
|
||||
self._temp_dir.cleanup()
|
||||
|
||||
def restore_var(varname, old_value):
|
||||
if old_value is None:
|
||||
os.environ.pop(varname, None)
|
||||
else:
|
||||
os.environ[varname] = old_value
|
||||
|
||||
restore_var('PATH', self.save_path)
|
||||
restore_var('PYTHONPATH', self.save_pythonpath)
|
||||
restore_var('PYTHONNOUSERSITE', self.save_nousersite)
|
||||
|
||||
def cleanup(self):
|
||||
self._temp_dir.cleanup()
|
||||
|
||||
|
||||
class NoOpBuildEnvironment(BuildEnvironment):
|
||||
"""A no-op drop-in replacement for BuildEnvironment
|
||||
"""
|
||||
|
||||
def __init__(self, no_clean):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
@@ -0,0 +1,202 @@
|
||||
"""Cache Management
|
||||
"""
|
||||
|
||||
import errno
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from notpip._internal import index
|
||||
from notpip._internal.compat import expanduser
|
||||
from notpip._internal.download import path_to_url
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.wheel import InvalidWheelFilename, Wheel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""An abstract class - provides cache directories for data from links
|
||||
|
||||
|
||||
:param cache_dir: The root of the cache.
|
||||
:param format_control: A pip.index.FormatControl object to limit
|
||||
binaries being read from the cache.
|
||||
:param allowed_formats: which formats of files the cache should store.
|
||||
('binary' and 'source' are the only allowed values)
|
||||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control, allowed_formats):
|
||||
super(Cache, self).__init__()
|
||||
self.cache_dir = expanduser(cache_dir) if cache_dir else None
|
||||
self.format_control = format_control
|
||||
self.allowed_formats = allowed_formats
|
||||
|
||||
_valid_formats = {"source", "binary"}
|
||||
assert self.allowed_formats.union(_valid_formats) == _valid_formats
|
||||
|
||||
def _get_cache_path_parts(self, link):
|
||||
"""Get parts of part that must be os.path.joined with cache_dir
|
||||
"""
|
||||
|
||||
# We want to generate an url to use as our cache key, we don't want to
|
||||
# just re-use the URL because it might have other items in the fragment
|
||||
# and we don't care about those.
|
||||
key_parts = [link.url_without_fragment]
|
||||
if link.hash_name is not None and link.hash is not None:
|
||||
key_parts.append("=".join([link.hash_name, link.hash]))
|
||||
key_url = "#".join(key_parts)
|
||||
|
||||
# Encode our key url with sha224, we'll use this because it has similar
|
||||
# security properties to sha256, but with a shorter total output (and
|
||||
# thus less secure). However the differences don't make a lot of
|
||||
# difference for our use case here.
|
||||
hashed = hashlib.sha224(key_url.encode()).hexdigest()
|
||||
|
||||
# We want to nest the directories some to prevent having a ton of top
|
||||
# level directories where we might run out of sub directories on some
|
||||
# FS.
|
||||
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
|
||||
|
||||
return parts
|
||||
|
||||
def _get_candidates(self, link, package_name):
|
||||
can_not_cache = (
|
||||
not self.cache_dir or
|
||||
not package_name or
|
||||
not link
|
||||
)
|
||||
if can_not_cache:
|
||||
return []
|
||||
|
||||
canonical_name = canonicalize_name(package_name)
|
||||
formats = index.fmt_ctl_formats(
|
||||
self.format_control, canonical_name
|
||||
)
|
||||
if not self.allowed_formats.intersection(formats):
|
||||
return []
|
||||
|
||||
root = self.get_path_for_link(link)
|
||||
try:
|
||||
return os.listdir(root)
|
||||
except OSError as err:
|
||||
if err.errno in {errno.ENOENT, errno.ENOTDIR}:
|
||||
return []
|
||||
raise
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
"""Return a directory to store cached items in for link.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get(self, link, package_name):
|
||||
"""Returns a link to a cached item if it exists, otherwise returns the
|
||||
passed link.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _link_for_candidate(self, link, candidate):
|
||||
root = self.get_path_for_link(link)
|
||||
path = os.path.join(root, candidate)
|
||||
|
||||
return index.Link(path_to_url(path))
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
|
||||
|
||||
class SimpleWheelCache(Cache):
|
||||
"""A cache of wheels for future installs.
|
||||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control):
|
||||
super(SimpleWheelCache, self).__init__(
|
||||
cache_dir, format_control, {"binary"}
|
||||
)
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
"""Return a directory to store cached wheels for link
|
||||
|
||||
Because there are M wheels for any one sdist, we provide a directory
|
||||
to cache them in, and then consult that directory when looking up
|
||||
cache hits.
|
||||
|
||||
We only insert things into the cache if they have plausible version
|
||||
numbers, so that we don't contaminate the cache with things that were
|
||||
not unique. E.g. ./package might have dozens of installs done for it
|
||||
and build a version of 0.0...and if we built and cached a wheel, we'd
|
||||
end up using the same wheel even if the source has been edited.
|
||||
|
||||
:param link: The link of the sdist for which this will cache wheels.
|
||||
"""
|
||||
parts = self._get_cache_path_parts(link)
|
||||
|
||||
# Store wheels within the root cache_dir
|
||||
return os.path.join(self.cache_dir, "wheels", *parts)
|
||||
|
||||
def get(self, link, package_name):
|
||||
candidates = []
|
||||
|
||||
for wheel_name in self._get_candidates(link, package_name):
|
||||
try:
|
||||
wheel = Wheel(wheel_name)
|
||||
except InvalidWheelFilename:
|
||||
continue
|
||||
if not wheel.supported():
|
||||
# Built for a different python/arch/etc
|
||||
continue
|
||||
candidates.append((wheel.support_index_min(), wheel_name))
|
||||
|
||||
if not candidates:
|
||||
return link
|
||||
|
||||
return self._link_for_candidate(link, min(candidates)[1])
|
||||
|
||||
|
||||
class EphemWheelCache(SimpleWheelCache):
|
||||
"""A SimpleWheelCache that creates it's own temporary cache directory
|
||||
"""
|
||||
|
||||
def __init__(self, format_control):
|
||||
self._temp_dir = TempDirectory(kind="ephem-wheel-cache")
|
||||
self._temp_dir.create()
|
||||
|
||||
super(EphemWheelCache, self).__init__(
|
||||
self._temp_dir.path, format_control
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
self._temp_dir.cleanup()
|
||||
|
||||
|
||||
class WheelCache(Cache):
|
||||
"""Wraps EphemWheelCache and SimpleWheelCache into a single Cache
|
||||
|
||||
This Cache allows for gracefully degradation, using the ephem wheel cache
|
||||
when a certain link is not found in the simple wheel cache first.
|
||||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control):
|
||||
super(WheelCache, self).__init__(
|
||||
cache_dir, format_control, {'binary'}
|
||||
)
|
||||
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
|
||||
self._ephem_cache = EphemWheelCache(format_control)
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
return self._wheel_cache.get_path_for_link(link)
|
||||
|
||||
def get_ephem_path_for_link(self, link):
|
||||
return self._ephem_cache.get_path_for_link(link)
|
||||
|
||||
def get(self, link, package_name):
|
||||
retval = self._wheel_cache.get(link, package_name)
|
||||
if retval is link:
|
||||
retval = self._ephem_cache.get(link, package_name)
|
||||
return retval
|
||||
|
||||
def cleanup(self):
|
||||
self._wheel_cache.cleanup()
|
||||
self._ephem_cache.cleanup()
|
||||
+125
-149
@@ -9,16 +9,21 @@ pass on state. To be consistent, all options will follow this design.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from functools import partial
|
||||
from optparse import OptionGroup, SUPPRESS_HELP, Option
|
||||
import warnings
|
||||
from functools import partial
|
||||
from optparse import SUPPRESS_HELP, Option, OptionGroup
|
||||
|
||||
from pip9.index import (
|
||||
from notpip._internal.index import (
|
||||
FormatControl, fmt_ctl_handle_mutual_exclude, fmt_ctl_no_binary,
|
||||
fmt_ctl_no_use_wheel)
|
||||
from pip9.models import PyPI
|
||||
from pip9.locations import USER_CACHE_DIR, src_prefix
|
||||
from pip9.utils.hashes import STRONG_HASHES
|
||||
)
|
||||
from notpip._internal.locations import USER_CACHE_DIR, src_prefix
|
||||
from notpip._internal.models import PyPI
|
||||
from notpip._internal.utils.hashes import STRONG_HASHES
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from notpip._internal.utils.ui import BAR_TYPES
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
def make_option_group(group, parser):
|
||||
@@ -33,12 +38,6 @@ 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)
|
||||
|
||||
|
||||
def check_install_build_global(options, check_options=None):
|
||||
"""Disable wheels if per-setup.py call options are set.
|
||||
|
||||
@@ -57,7 +56,8 @@ def check_install_build_global(options, check_options=None):
|
||||
fmt_ctl_no_binary(control)
|
||||
warnings.warn(
|
||||
'Disabling all use of wheels due to the use of --build-options '
|
||||
'/ --global-options / --install-options.', stacklevel=2)
|
||||
'/ --global-options / --install-options.', stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
###########
|
||||
@@ -69,7 +69,8 @@ help_ = partial(
|
||||
'-h', '--help',
|
||||
dest='help',
|
||||
action='help',
|
||||
help='Show help.')
|
||||
help='Show help.',
|
||||
) # type: Any
|
||||
|
||||
isolated_mode = partial(
|
||||
Option,
|
||||
@@ -90,7 +91,8 @@ require_virtualenv = partial(
|
||||
dest='require_venv',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=SUPPRESS_HELP)
|
||||
help=SUPPRESS_HELP
|
||||
) # type: Any
|
||||
|
||||
verbose = partial(
|
||||
Option,
|
||||
@@ -101,12 +103,22 @@ verbose = partial(
|
||||
help='Give more output. Option is additive, and can be used up to 3 times.'
|
||||
)
|
||||
|
||||
no_color = partial(
|
||||
Option,
|
||||
'--no-color',
|
||||
dest='no_color',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Suppress colored output",
|
||||
)
|
||||
|
||||
version = partial(
|
||||
Option,
|
||||
'-V', '--version',
|
||||
dest='version',
|
||||
action='store_true',
|
||||
help='Show version and exit.')
|
||||
help='Show version and exit.',
|
||||
) # type: Any
|
||||
|
||||
quiet = partial(
|
||||
Option,
|
||||
@@ -114,10 +126,25 @@ quiet = partial(
|
||||
dest='quiet',
|
||||
action='count',
|
||||
default=0,
|
||||
help=('Give less output. Option is additive, and can be used up to 3'
|
||||
' times (corresponding to WARNING, ERROR, and CRITICAL logging'
|
||||
' levels).')
|
||||
)
|
||||
help=(
|
||||
'Give less output. Option is additive, and can be used up to 3'
|
||||
' times (corresponding to WARNING, ERROR, and CRITICAL logging'
|
||||
' levels).'
|
||||
),
|
||||
) # type: Any
|
||||
|
||||
progress_bar = partial(
|
||||
Option,
|
||||
'--progress-bar',
|
||||
dest='progress_bar',
|
||||
type='choice',
|
||||
choices=list(BAR_TYPES.keys()),
|
||||
default='on',
|
||||
help=(
|
||||
'Specify type of progress to be displayed [' +
|
||||
'|'.join(BAR_TYPES.keys()) + '] (default: %default)'
|
||||
),
|
||||
) # type: Any
|
||||
|
||||
log = partial(
|
||||
Option,
|
||||
@@ -125,7 +152,7 @@ log = partial(
|
||||
dest="log",
|
||||
metavar="path",
|
||||
help="Path to a verbose appending log."
|
||||
)
|
||||
) # type: Any
|
||||
|
||||
no_input = partial(
|
||||
Option,
|
||||
@@ -134,7 +161,8 @@ no_input = partial(
|
||||
dest='no_input',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=SUPPRESS_HELP)
|
||||
help=SUPPRESS_HELP
|
||||
) # type: Any
|
||||
|
||||
proxy = partial(
|
||||
Option,
|
||||
@@ -142,7 +170,8 @@ proxy = partial(
|
||||
dest='proxy',
|
||||
type='str',
|
||||
default='',
|
||||
help="Specify a proxy in the form [user:passwd@]proxy.server:port.")
|
||||
help="Specify a proxy in the form [user:passwd@]proxy.server:port."
|
||||
) # type: Any
|
||||
|
||||
retries = partial(
|
||||
Option,
|
||||
@@ -151,7 +180,8 @@ retries = partial(
|
||||
type='int',
|
||||
default=5,
|
||||
help="Maximum number of retries each connection should attempt "
|
||||
"(default %default times).")
|
||||
"(default %default times).",
|
||||
) # type: Any
|
||||
|
||||
timeout = partial(
|
||||
Option,
|
||||
@@ -160,16 +190,8 @@ timeout = partial(
|
||||
dest='timeout',
|
||||
type='float',
|
||||
default=15,
|
||||
help='Set the socket timeout (default %default seconds).')
|
||||
|
||||
default_vcs = partial(
|
||||
Option,
|
||||
# The default version control system for editables, e.g. 'svn'
|
||||
'--default-vcs',
|
||||
dest='default_vcs',
|
||||
type='str',
|
||||
default='',
|
||||
help=SUPPRESS_HELP)
|
||||
help='Set the socket timeout (default %default seconds).',
|
||||
) # type: Any
|
||||
|
||||
skip_requirements_regex = partial(
|
||||
Option,
|
||||
@@ -178,7 +200,8 @@ skip_requirements_regex = partial(
|
||||
dest='skip_requirements_regex',
|
||||
type='str',
|
||||
default='',
|
||||
help=SUPPRESS_HELP)
|
||||
help=SUPPRESS_HELP,
|
||||
) # type: Any
|
||||
|
||||
|
||||
def exists_action():
|
||||
@@ -192,7 +215,8 @@ def exists_action():
|
||||
action='append',
|
||||
metavar='action',
|
||||
help="Default action when a path already exists: "
|
||||
"(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.")
|
||||
"(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort).",
|
||||
)
|
||||
|
||||
|
||||
cert = partial(
|
||||
@@ -201,7 +225,8 @@ cert = partial(
|
||||
dest='cert',
|
||||
type='str',
|
||||
metavar='path',
|
||||
help="Path to alternate CA bundle.")
|
||||
help="Path to alternate CA bundle.",
|
||||
) # type: Any
|
||||
|
||||
client_cert = partial(
|
||||
Option,
|
||||
@@ -211,7 +236,8 @@ client_cert = partial(
|
||||
default=None,
|
||||
metavar='path',
|
||||
help="Path to SSL client certificate, a single file containing the "
|
||||
"private key and the certificate in PEM format.")
|
||||
"private key and the certificate in PEM format.",
|
||||
) # type: Any
|
||||
|
||||
index_url = partial(
|
||||
Option,
|
||||
@@ -222,7 +248,8 @@ index_url = partial(
|
||||
help="Base URL of Python Package Index (default %default). "
|
||||
"This should point to a repository compliant with PEP 503 "
|
||||
"(the simple repository API) or a local directory laid out "
|
||||
"in the same format.")
|
||||
"in the same format.",
|
||||
) # type: Any
|
||||
|
||||
|
||||
def extra_index_url():
|
||||
@@ -234,7 +261,7 @@ def extra_index_url():
|
||||
default=[],
|
||||
help="Extra URLs of package indexes to use in addition to "
|
||||
"--index-url. Should follow the same rules as "
|
||||
"--index-url."
|
||||
"--index-url.",
|
||||
)
|
||||
|
||||
|
||||
@@ -244,7 +271,8 @@ no_index = partial(
|
||||
dest='no_index',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Ignore package index (only looking at --find-links URLs instead).')
|
||||
help='Ignore package index (only looking at --find-links URLs instead).',
|
||||
) # type: Any
|
||||
|
||||
|
||||
def find_links():
|
||||
@@ -256,30 +284,10 @@ def find_links():
|
||||
metavar='url',
|
||||
help="If a url or path to an html file, then parse for links to "
|
||||
"archives. If a local path or file:// url that's a directory, "
|
||||
"then look for archives in the directory listing.")
|
||||
|
||||
|
||||
def allow_external():
|
||||
return Option(
|
||||
"--allow-external",
|
||||
dest="allow_external",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="PACKAGE",
|
||||
help=SUPPRESS_HELP,
|
||||
"then look for archives in the directory listing.",
|
||||
)
|
||||
|
||||
|
||||
allow_all_external = partial(
|
||||
Option,
|
||||
"--allow-all-external",
|
||||
dest="allow_all_external",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
|
||||
|
||||
def trusted_host():
|
||||
return Option(
|
||||
"--trusted-host",
|
||||
@@ -292,38 +300,6 @@ def trusted_host():
|
||||
)
|
||||
|
||||
|
||||
# Remove after 7.0
|
||||
no_allow_external = partial(
|
||||
Option,
|
||||
"--no-allow-external",
|
||||
dest="allow_all_external",
|
||||
action="store_false",
|
||||
default=False,
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
|
||||
|
||||
# Remove --allow-insecure after 7.0
|
||||
def allow_unsafe():
|
||||
return Option(
|
||||
"--allow-unverified", "--allow-insecure",
|
||||
dest="allow_unverified",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="PACKAGE",
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
|
||||
# Remove after 7.0
|
||||
no_allow_unsafe = partial(
|
||||
Option,
|
||||
"--no-allow-insecure",
|
||||
dest="allow_all_insecure",
|
||||
action="store_false",
|
||||
default=False,
|
||||
help=SUPPRESS_HELP
|
||||
)
|
||||
|
||||
# Remove after 1.5
|
||||
process_dependency_links = partial(
|
||||
Option,
|
||||
@@ -332,7 +308,7 @@ process_dependency_links = partial(
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Enable the processing of dependency links.",
|
||||
)
|
||||
) # type: Any
|
||||
|
||||
|
||||
def constraints():
|
||||
@@ -343,7 +319,8 @@ def constraints():
|
||||
default=[],
|
||||
metavar='file',
|
||||
help='Constrain versions using the given constraints file. '
|
||||
'This option can be used multiple times.')
|
||||
'This option can be used multiple times.'
|
||||
)
|
||||
|
||||
|
||||
def requirements():
|
||||
@@ -354,7 +331,8 @@ def requirements():
|
||||
default=[],
|
||||
metavar='file',
|
||||
help='Install from the given requirements file. '
|
||||
'This option can be used multiple times.')
|
||||
'This option can be used multiple times.'
|
||||
)
|
||||
|
||||
|
||||
def editable():
|
||||
@@ -368,6 +346,7 @@ def editable():
|
||||
'"develop mode") from a local project path or a VCS url.'),
|
||||
)
|
||||
|
||||
|
||||
src = partial(
|
||||
Option,
|
||||
'--src', '--source', '--source-dir', '--source-directory',
|
||||
@@ -377,28 +356,7 @@ src = partial(
|
||||
help='Directory to check out editable projects into. '
|
||||
'The default in a virtualenv is "<venv path>/src". '
|
||||
'The default for global installs is "<current dir>/src".'
|
||||
)
|
||||
|
||||
# XXX: deprecated, remove in 9.0
|
||||
use_wheel = partial(
|
||||
Option,
|
||||
'--use-wheel',
|
||||
dest='use_wheel',
|
||||
action='store_true',
|
||||
default=True,
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
|
||||
# XXX: deprecated, remove in 9.0
|
||||
no_use_wheel = partial(
|
||||
Option,
|
||||
'--no-use-wheel',
|
||||
dest='use_wheel',
|
||||
action='store_false',
|
||||
default=True,
|
||||
help=('Do not Find and prefer wheel archives when searching indexes and '
|
||||
'find-links locations. DEPRECATED in favour of --no-binary.'),
|
||||
)
|
||||
) # type: Any
|
||||
|
||||
|
||||
def _get_format_control(values, option):
|
||||
@@ -409,13 +367,15 @@ def _get_format_control(values, option):
|
||||
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)
|
||||
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)
|
||||
value, existing.only_binary, existing.no_binary,
|
||||
)
|
||||
|
||||
|
||||
def no_binary():
|
||||
@@ -428,7 +388,8 @@ def no_binary():
|
||||
"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.")
|
||||
"this option is used on them.",
|
||||
)
|
||||
|
||||
|
||||
def only_binary():
|
||||
@@ -441,7 +402,8 @@ def only_binary():
|
||||
"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.")
|
||||
"used on them.",
|
||||
)
|
||||
|
||||
|
||||
cache_dir = partial(
|
||||
@@ -467,22 +429,39 @@ no_deps = partial(
|
||||
dest='ignore_dependencies',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Don't install package dependencies.")
|
||||
help="Don't install package dependencies.",
|
||||
) # type: Any
|
||||
|
||||
build_dir = partial(
|
||||
Option,
|
||||
'-b', '--build', '--build-dir', '--build-directory',
|
||||
dest='build_dir',
|
||||
metavar='dir',
|
||||
help='Directory to unpack packages into and build in.'
|
||||
)
|
||||
help='Directory to unpack packages into and build in. Note that '
|
||||
'an initial build still takes place in a temporary directory. '
|
||||
'The location of temporary directories can be controlled by setting '
|
||||
'the TMPDIR environment variable (TEMP on Windows) appropriately. '
|
||||
'When passed, build directories are not cleaned in case of failures.'
|
||||
) # type: Any
|
||||
|
||||
ignore_requires_python = partial(
|
||||
Option,
|
||||
'--ignore-requires-python',
|
||||
dest='ignore_requires_python',
|
||||
action='store_true',
|
||||
help='Ignore the Requires-Python information.')
|
||||
help='Ignore the Requires-Python information.'
|
||||
) # type: Any
|
||||
|
||||
no_build_isolation = partial(
|
||||
Option,
|
||||
'--no-build-isolation',
|
||||
dest='build_isolation',
|
||||
action='store_false',
|
||||
default=True,
|
||||
help='Disable isolation when building a modern source distribution. '
|
||||
'Build dependencies specified by PEP 518 must be already installed '
|
||||
'if this option is used.'
|
||||
) # type: Any
|
||||
|
||||
install_options = partial(
|
||||
Option,
|
||||
@@ -494,7 +473,8 @@ install_options = partial(
|
||||
"command (use like --install-option=\"--install-scripts=/usr/local/"
|
||||
"bin\"). Use multiple --install-option options to pass multiple "
|
||||
"options to setup.py install. If you are using an option with a "
|
||||
"directory path, be sure to use absolute path.")
|
||||
"directory path, be sure to use absolute path.",
|
||||
) # type: Any
|
||||
|
||||
global_options = partial(
|
||||
Option,
|
||||
@@ -503,14 +483,16 @@ global_options = partial(
|
||||
action='append',
|
||||
metavar='options',
|
||||
help="Extra global options to be supplied to the setup.py "
|
||||
"call before the install command.")
|
||||
"call before the install command.",
|
||||
) # type: Any
|
||||
|
||||
no_clean = partial(
|
||||
Option,
|
||||
'--no-clean',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Don't clean up build directories.")
|
||||
help="Don't clean up build directories)."
|
||||
) # type: Any
|
||||
|
||||
pre = partial(
|
||||
Option,
|
||||
@@ -518,7 +500,8 @@ pre = partial(
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Include pre-release and development versions. By default, "
|
||||
"pip only finds stable versions.")
|
||||
"pip only finds stable versions.",
|
||||
) # type: Any
|
||||
|
||||
disable_pip_version_check = partial(
|
||||
Option,
|
||||
@@ -527,7 +510,9 @@ disable_pip_version_check = partial(
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Don't periodically check PyPI to determine whether a new version "
|
||||
"of pip is available for download. Implied with --no-index.")
|
||||
"of pip is available for download. Implied with --no-index.",
|
||||
) # type: Any
|
||||
|
||||
|
||||
# Deprecated, Remove later
|
||||
always_unzip = partial(
|
||||
@@ -536,7 +521,7 @@ always_unzip = partial(
|
||||
dest='always_unzip',
|
||||
action='store_true',
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
) # type: Any
|
||||
|
||||
|
||||
def _merge_hash(option, opt_str, value, parser):
|
||||
@@ -566,7 +551,8 @@ hash = partial(
|
||||
callback=_merge_hash,
|
||||
type='string',
|
||||
help="Verify that the package's archive matches this "
|
||||
'hash before installing. Example: --hash=sha256:abcdef...')
|
||||
'hash before installing. Example: --hash=sha256:abcdef...',
|
||||
) # type: Any
|
||||
|
||||
|
||||
require_hashes = partial(
|
||||
@@ -577,7 +563,8 @@ require_hashes = partial(
|
||||
default=False,
|
||||
help='Require a hash to check each requirement against, for '
|
||||
'repeatable installs. This option is implied when any package in a '
|
||||
'requirements file has a --hash option.')
|
||||
'requirements file has a --hash option.',
|
||||
) # type: Any
|
||||
|
||||
|
||||
##########
|
||||
@@ -598,7 +585,6 @@ general_group = {
|
||||
proxy,
|
||||
retries,
|
||||
timeout,
|
||||
default_vcs,
|
||||
skip_requirements_regex,
|
||||
exists_action,
|
||||
trusted_host,
|
||||
@@ -607,10 +593,11 @@ general_group = {
|
||||
cache_dir,
|
||||
no_cache,
|
||||
disable_pip_version_check,
|
||||
no_color,
|
||||
]
|
||||
}
|
||||
|
||||
non_deprecated_index_group = {
|
||||
index_group = {
|
||||
'name': 'Package Index Options',
|
||||
'options': [
|
||||
index_url,
|
||||
@@ -620,14 +607,3 @@ non_deprecated_index_group = {
|
||||
process_dependency_links,
|
||||
]
|
||||
}
|
||||
|
||||
index_group = {
|
||||
'name': 'Package Index Options (including deprecated options)',
|
||||
'options': non_deprecated_index_group['options'] + [
|
||||
allow_external,
|
||||
allow_all_external,
|
||||
no_allow_external,
|
||||
allow_unsafe,
|
||||
no_allow_unsafe,
|
||||
]
|
||||
}
|
||||
+21
-28
@@ -3,35 +3,25 @@ Package containing all pip commands
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pip9.commands.completion import CompletionCommand
|
||||
from pip9.commands.download import DownloadCommand
|
||||
from pip9.commands.freeze import FreezeCommand
|
||||
from pip9.commands.hash import HashCommand
|
||||
from pip9.commands.help import HelpCommand
|
||||
from pip9.commands.list import ListCommand
|
||||
from pip9.commands.check import CheckCommand
|
||||
from pip9.commands.search import SearchCommand
|
||||
from pip9.commands.show import ShowCommand
|
||||
from pip9.commands.install import InstallCommand
|
||||
from pip9.commands.uninstall import UninstallCommand
|
||||
from pip9.commands.wheel import WheelCommand
|
||||
from notpip._internal.commands.completion import CompletionCommand
|
||||
from notpip._internal.commands.configuration import ConfigurationCommand
|
||||
from notpip._internal.commands.download import DownloadCommand
|
||||
from notpip._internal.commands.freeze import FreezeCommand
|
||||
from notpip._internal.commands.hash import HashCommand
|
||||
from notpip._internal.commands.help import HelpCommand
|
||||
from notpip._internal.commands.list import ListCommand
|
||||
from notpip._internal.commands.check import CheckCommand
|
||||
from notpip._internal.commands.search import SearchCommand
|
||||
from notpip._internal.commands.show import ShowCommand
|
||||
from notpip._internal.commands.install import InstallCommand
|
||||
from notpip._internal.commands.uninstall import UninstallCommand
|
||||
from notpip._internal.commands.wheel import WheelCommand
|
||||
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
commands_dict = {
|
||||
CompletionCommand.name: CompletionCommand,
|
||||
FreezeCommand.name: FreezeCommand,
|
||||
HashCommand.name: HashCommand,
|
||||
HelpCommand.name: HelpCommand,
|
||||
SearchCommand.name: SearchCommand,
|
||||
ShowCommand.name: ShowCommand,
|
||||
InstallCommand.name: InstallCommand,
|
||||
UninstallCommand.name: UninstallCommand,
|
||||
DownloadCommand.name: DownloadCommand,
|
||||
ListCommand.name: ListCommand,
|
||||
CheckCommand.name: CheckCommand,
|
||||
WheelCommand.name: WheelCommand,
|
||||
}
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import List, Type
|
||||
from notpip._internal.basecommand import Command
|
||||
|
||||
commands_order = [
|
||||
InstallCommand,
|
||||
@@ -41,12 +31,15 @@ commands_order = [
|
||||
ListCommand,
|
||||
ShowCommand,
|
||||
CheckCommand,
|
||||
ConfigurationCommand,
|
||||
SearchCommand,
|
||||
WheelCommand,
|
||||
HashCommand,
|
||||
CompletionCommand,
|
||||
HelpCommand,
|
||||
]
|
||||
] # type: List[Type[Command]]
|
||||
|
||||
commands_dict = {c.name: c for c in commands_order}
|
||||
|
||||
|
||||
def get_summaries(ordered=True):
|
||||
@@ -0,0 +1,42 @@
|
||||
import logging
|
||||
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.operations.check import (
|
||||
check_package_set, create_package_set_from_installed,
|
||||
)
|
||||
from notpip._internal.utils.misc import get_installed_distributions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CheckCommand(Command):
|
||||
"""Verify installed packages have compatible dependencies."""
|
||||
name = 'check'
|
||||
usage = """
|
||||
%prog [options]"""
|
||||
summary = 'Verify installed packages have compatible dependencies.'
|
||||
|
||||
def run(self, options, args):
|
||||
package_set = create_package_set_from_installed()
|
||||
missing, conflicting = check_package_set(package_set)
|
||||
|
||||
for project_name in missing:
|
||||
version = package_set[project_name].version
|
||||
for dependency in missing[project_name]:
|
||||
logger.info(
|
||||
"%s %s requires %s, which is not installed.",
|
||||
project_name, version, dependency[0],
|
||||
)
|
||||
|
||||
for project_name in conflicting:
|
||||
version = package_set[project_name].version
|
||||
for dep_name, dep_version, req in conflicting[project_name]:
|
||||
logger.info(
|
||||
"%s %s has requirement %s, but you have %s %s.",
|
||||
project_name, version, req, dep_name, dep_version,
|
||||
)
|
||||
|
||||
if missing or conflicting:
|
||||
return 1
|
||||
else:
|
||||
logger.info("No broken requirements found.")
|
||||
+40
-27
@@ -1,7 +1,10 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
from pip9.basecommand import Command
|
||||
import textwrap
|
||||
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.utils.misc import get_prog
|
||||
|
||||
BASE_COMPLETION = """
|
||||
# pip %(shell)s completion start%(script)s# pip %(shell)s completion end
|
||||
@@ -9,38 +12,44 @@ BASE_COMPLETION = """
|
||||
|
||||
COMPLETION_SCRIPTS = {
|
||||
'bash': """
|
||||
_pip_completion()
|
||||
{
|
||||
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
PIP_AUTO_COMPLETE=1 $1 ) )
|
||||
_pip_completion()
|
||||
{
|
||||
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
PIP_AUTO_COMPLETE=1 $1 ) )
|
||||
}
|
||||
complete -o default -F _pip_completion %(prog)s
|
||||
""",
|
||||
'zsh': """
|
||||
function _pip_completion {
|
||||
local words cword
|
||||
read -Ac words
|
||||
read -cn cword
|
||||
reply=( $( COMP_WORDS="$words[*]" \\
|
||||
COMP_CWORD=$(( cword-1 )) \\
|
||||
PIP_AUTO_COMPLETE=1 $words[1] ) )
|
||||
}
|
||||
compctl -K _pip_completion %(prog)s
|
||||
""",
|
||||
'fish': """
|
||||
function __fish_complete_pip
|
||||
set -lx COMP_WORDS (commandline -o) ""
|
||||
set -lx COMP_CWORD ( \\
|
||||
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
|
||||
)
|
||||
set -lx PIP_AUTO_COMPLETE 1
|
||||
string split \\ -- (eval $COMP_WORDS[1])
|
||||
end
|
||||
complete -fa "(__fish_complete_pip)" -c %(prog)s
|
||||
""",
|
||||
}
|
||||
complete -o default -F _pip_completion pip
|
||||
""", 'zsh': """
|
||||
function _pip_completion {
|
||||
local words cword
|
||||
read -Ac words
|
||||
read -cn cword
|
||||
reply=( $( COMP_WORDS="$words[*]" \\
|
||||
COMP_CWORD=$(( cword-1 )) \\
|
||||
PIP_AUTO_COMPLETE=1 $words[1] ) )
|
||||
}
|
||||
compctl -K _pip_completion pip
|
||||
""", 'fish': """
|
||||
function __fish_complete_pip
|
||||
set -lx COMP_WORDS (commandline -o) ""
|
||||
set -lx COMP_CWORD (math (contains -i -- (commandline -t) $COMP_WORDS)-1)
|
||||
set -lx PIP_AUTO_COMPLETE 1
|
||||
string split \ -- (eval $COMP_WORDS[1])
|
||||
end
|
||||
complete -fa "(__fish_complete_pip)" -c pip
|
||||
"""}
|
||||
|
||||
|
||||
class CompletionCommand(Command):
|
||||
"""A helper command to be used for command completion."""
|
||||
name = 'completion'
|
||||
summary = 'A helper command used for command completion.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(CompletionCommand, self).__init__(*args, **kw)
|
||||
@@ -73,7 +82,11 @@ class CompletionCommand(Command):
|
||||
shells = COMPLETION_SCRIPTS.keys()
|
||||
shell_options = ['--' + shell for shell in sorted(shells)]
|
||||
if options.shell in shells:
|
||||
script = COMPLETION_SCRIPTS.get(options.shell, '')
|
||||
script = textwrap.dedent(
|
||||
COMPLETION_SCRIPTS.get(options.shell, '') % {
|
||||
'prog': get_prog(),
|
||||
}
|
||||
)
|
||||
print(BASE_COMPLETION % {'script': script, 'shell': options.shell})
|
||||
else:
|
||||
sys.stderr.write(
|
||||
@@ -0,0 +1,227 @@
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.configuration import Configuration, kinds
|
||||
from notpip._internal.exceptions import PipError
|
||||
from notpip._internal.locations import venv_config_file
|
||||
from notpip._internal.status_codes import ERROR, SUCCESS
|
||||
from notpip._internal.utils.misc import get_prog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigurationCommand(Command):
|
||||
"""Manage local and global configuration.
|
||||
|
||||
Subcommands:
|
||||
|
||||
list: List the active configuration (or from the file specified)
|
||||
edit: Edit the configuration file in an editor
|
||||
get: Get the value associated with name
|
||||
set: Set the name=value
|
||||
unset: Unset the value associated with name
|
||||
|
||||
If none of --user, --global and --venv are passed, a virtual
|
||||
environment configuration file is used if one is active and the file
|
||||
exists. Otherwise, all modifications happen on the to the user file by
|
||||
default.
|
||||
"""
|
||||
|
||||
name = 'config'
|
||||
usage = """
|
||||
%prog [<file-option>] list
|
||||
%prog [<file-option>] [--editor <editor-path>] edit
|
||||
|
||||
%prog [<file-option>] get name
|
||||
%prog [<file-option>] set name value
|
||||
%prog [<file-option>] unset name
|
||||
"""
|
||||
|
||||
summary = "Manage local and global configuration."
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ConfigurationCommand, self).__init__(*args, **kwargs)
|
||||
|
||||
self.configuration = None
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
'--editor',
|
||||
dest='editor',
|
||||
action='store',
|
||||
default=None,
|
||||
help=(
|
||||
'Editor to use to edit the file. Uses VISUAL or EDITOR '
|
||||
'environment variables if not provided.'
|
||||
)
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
'--global',
|
||||
dest='global_file',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use the system-wide configuration file only'
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
'--user',
|
||||
dest='user_file',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use the user configuration file only'
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
'--venv',
|
||||
dest='venv_file',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use the virtualenv configuration file only'
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
handlers = {
|
||||
"list": self.list_values,
|
||||
"edit": self.open_in_editor,
|
||||
"get": self.get_name,
|
||||
"set": self.set_name_value,
|
||||
"unset": self.unset_name
|
||||
}
|
||||
|
||||
# Determine action
|
||||
if not args or args[0] not in handlers:
|
||||
logger.error("Need an action ({}) to perform.".format(
|
||||
", ".join(sorted(handlers)))
|
||||
)
|
||||
return ERROR
|
||||
|
||||
action = args[0]
|
||||
|
||||
# Determine which configuration files are to be loaded
|
||||
# Depends on whether the command is modifying.
|
||||
try:
|
||||
load_only = self._determine_file(
|
||||
options, need_value=(action in ["get", "set", "unset", "edit"])
|
||||
)
|
||||
except PipError as e:
|
||||
logger.error(e.args[0])
|
||||
return ERROR
|
||||
|
||||
# Load a new configuration
|
||||
self.configuration = Configuration(
|
||||
isolated=options.isolated_mode, load_only=load_only
|
||||
)
|
||||
self.configuration.load()
|
||||
|
||||
# Error handling happens here, not in the action-handlers.
|
||||
try:
|
||||
handlers[action](options, args[1:])
|
||||
except PipError as e:
|
||||
logger.error(e.args[0])
|
||||
return ERROR
|
||||
|
||||
return SUCCESS
|
||||
|
||||
def _determine_file(self, options, need_value):
|
||||
file_options = {
|
||||
kinds.USER: options.user_file,
|
||||
kinds.GLOBAL: options.global_file,
|
||||
kinds.VENV: options.venv_file
|
||||
}
|
||||
|
||||
if sum(file_options.values()) == 0:
|
||||
if not need_value:
|
||||
return None
|
||||
# Default to user, unless there's a virtualenv file.
|
||||
elif os.path.exists(venv_config_file):
|
||||
return kinds.VENV
|
||||
else:
|
||||
return kinds.USER
|
||||
elif sum(file_options.values()) == 1:
|
||||
# There's probably a better expression for this.
|
||||
return [key for key in file_options if file_options[key]][0]
|
||||
|
||||
raise PipError(
|
||||
"Need exactly one file to operate upon "
|
||||
"(--user, --venv, --global) to perform."
|
||||
)
|
||||
|
||||
def list_values(self, options, args):
|
||||
self._get_n_args(args, "list", n=0)
|
||||
|
||||
for key, value in sorted(self.configuration.items()):
|
||||
logger.info("%s=%r", key, value)
|
||||
|
||||
def get_name(self, options, args):
|
||||
key = self._get_n_args(args, "get [name]", n=1)
|
||||
value = self.configuration.get_value(key)
|
||||
|
||||
logger.info("%s", value)
|
||||
|
||||
def set_name_value(self, options, args):
|
||||
key, value = self._get_n_args(args, "set [name] [value]", n=2)
|
||||
self.configuration.set_value(key, value)
|
||||
|
||||
self._save_configuration()
|
||||
|
||||
def unset_name(self, options, args):
|
||||
key = self._get_n_args(args, "unset [name]", n=1)
|
||||
self.configuration.unset_value(key)
|
||||
|
||||
self._save_configuration()
|
||||
|
||||
def open_in_editor(self, options, args):
|
||||
editor = self._determine_editor(options)
|
||||
|
||||
fname = self.configuration.get_file_to_edit()
|
||||
if fname is None:
|
||||
raise PipError("Could not determine appropriate file.")
|
||||
|
||||
try:
|
||||
subprocess.check_call([editor, fname])
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise PipError(
|
||||
"Editor Subprocess exited with exit code {}"
|
||||
.format(e.returncode)
|
||||
)
|
||||
|
||||
def _get_n_args(self, args, example, n):
|
||||
"""Helper to make sure the command got the right number of arguments
|
||||
"""
|
||||
if len(args) != n:
|
||||
msg = (
|
||||
'Got unexpected number of arguments, expected {}. '
|
||||
'(example: "{} config {}")'
|
||||
).format(n, get_prog(), example)
|
||||
raise PipError(msg)
|
||||
|
||||
if n == 1:
|
||||
return args[0]
|
||||
else:
|
||||
return args
|
||||
|
||||
def _save_configuration(self):
|
||||
# We successfully ran a modifying command. Need to save the
|
||||
# configuration.
|
||||
try:
|
||||
self.configuration.save()
|
||||
except Exception:
|
||||
logger.error(
|
||||
"Unable to save configuration. Please report this as a bug.",
|
||||
exc_info=1
|
||||
)
|
||||
raise PipError("Internal Error.")
|
||||
|
||||
def _determine_editor(self, options):
|
||||
if options.editor is not None:
|
||||
return options.editor
|
||||
elif "VISUAL" in os.environ:
|
||||
return os.environ["VISUAL"]
|
||||
elif "EDITOR" in os.environ:
|
||||
return os.environ["EDITOR"]
|
||||
else:
|
||||
raise PipError("Could not determine editor to use.")
|
||||
+56
-35
@@ -3,15 +3,16 @@ from __future__ import absolute_import
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pip9.exceptions import CommandError
|
||||
from pip9.index import FormatControl
|
||||
from pip9.req import RequirementSet
|
||||
from pip9.basecommand import RequirementCommand
|
||||
from pip9 import cmdoptions
|
||||
from pip9.utils import ensure_dir, normalize_path
|
||||
from pip9.utils.build import BuildDirectory
|
||||
from pip9.utils.filesystem import check_path_owner
|
||||
|
||||
from notpip._internal import cmdoptions
|
||||
from notpip._internal.basecommand import RequirementCommand
|
||||
from notpip._internal.exceptions import CommandError
|
||||
from notpip._internal.index import FormatControl
|
||||
from notpip._internal.operations.prepare import RequirementPreparer
|
||||
from notpip._internal.req import RequirementSet
|
||||
from notpip._internal.resolve import Resolver
|
||||
from notpip._internal.utils.filesystem import check_path_owner
|
||||
from notpip._internal.utils.misc import ensure_dir, normalize_path
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -33,8 +34,8 @@ class DownloadCommand(RequirementCommand):
|
||||
usage = """
|
||||
%prog [options] <requirement specifier> [package-index-options] ...
|
||||
%prog [options] -r <requirements file> [package-index-options] ...
|
||||
%prog [options] [-e] <vcs project url> ...
|
||||
%prog [options] [-e] <local project path> ...
|
||||
%prog [options] <vcs project url> ...
|
||||
%prog [options] <local project path> ...
|
||||
%prog [options] <archive url/path> ..."""
|
||||
|
||||
summary = 'Download packages.'
|
||||
@@ -45,7 +46,6 @@ class DownloadCommand(RequirementCommand):
|
||||
cmd_opts = self.cmd_opts
|
||||
|
||||
cmd_opts.add_option(cmdoptions.constraints())
|
||||
cmd_opts.add_option(cmdoptions.editable())
|
||||
cmd_opts.add_option(cmdoptions.requirements())
|
||||
cmd_opts.add_option(cmdoptions.build_dir())
|
||||
cmd_opts.add_option(cmdoptions.no_deps())
|
||||
@@ -56,6 +56,8 @@ class DownloadCommand(RequirementCommand):
|
||||
cmd_opts.add_option(cmdoptions.pre())
|
||||
cmd_opts.add_option(cmdoptions.no_clean())
|
||||
cmd_opts.add_option(cmdoptions.require_hashes())
|
||||
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
|
||||
cmd_opts.add_option(
|
||||
'-d', '--dest', '--destination-dir', '--destination-directory',
|
||||
@@ -113,7 +115,7 @@ class DownloadCommand(RequirementCommand):
|
||||
)
|
||||
|
||||
index_opts = cmdoptions.make_option_group(
|
||||
cmdoptions.non_deprecated_index_group,
|
||||
cmdoptions.index_group,
|
||||
self.parser,
|
||||
)
|
||||
|
||||
@@ -122,6 +124,9 @@ class DownloadCommand(RequirementCommand):
|
||||
|
||||
def run(self, options, args):
|
||||
options.ignore_installed = True
|
||||
# editable doesn't really make sense for `pip download`, but the bowels
|
||||
# of the RequirementSet code require that property.
|
||||
options.editables = []
|
||||
|
||||
if options.python_version:
|
||||
python_versions = [options.python_version]
|
||||
@@ -134,13 +139,18 @@ class DownloadCommand(RequirementCommand):
|
||||
options.abi,
|
||||
options.implementation,
|
||||
])
|
||||
binary_only = FormatControl(set(), set([':all:']))
|
||||
if dist_restriction_set and options.format_control != binary_only:
|
||||
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(
|
||||
"--only-binary=:all: must be set and --no-binary must not "
|
||||
"be set (or must be set to :none:) when restricting platform "
|
||||
"and interpreter constraints using --python-version, "
|
||||
"--platform, --abi, or --implementation."
|
||||
"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:)."
|
||||
)
|
||||
|
||||
options.src_dir = os.path.abspath(options.src_dir)
|
||||
@@ -169,18 +179,12 @@ class DownloadCommand(RequirementCommand):
|
||||
)
|
||||
options.cache_dir = None
|
||||
|
||||
with BuildDirectory(options.build_dir,
|
||||
delete=build_delete) as build_dir:
|
||||
with TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="download"
|
||||
) as directory:
|
||||
|
||||
requirement_set = RequirementSet(
|
||||
build_dir=build_dir,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=options.download_dir,
|
||||
ignore_installed=True,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
session=session,
|
||||
isolated=options.isolated_mode,
|
||||
require_hashes=options.require_hashes
|
||||
require_hashes=options.require_hashes,
|
||||
)
|
||||
self.populate_requirement_set(
|
||||
requirement_set,
|
||||
@@ -192,18 +196,35 @@ class DownloadCommand(RequirementCommand):
|
||||
None
|
||||
)
|
||||
|
||||
if not requirement_set.has_requirements:
|
||||
return
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=options.download_dir,
|
||||
wheel_download_dir=None,
|
||||
progress_bar=options.progress_bar,
|
||||
build_isolation=options.build_isolation,
|
||||
)
|
||||
|
||||
requirement_set.prepare_files(finder)
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=None,
|
||||
use_user_site=False,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=False,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=False,
|
||||
ignore_installed=True,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
downloaded = ' '.join([
|
||||
req.name for req in requirement_set.successfully_downloaded
|
||||
])
|
||||
if downloaded:
|
||||
logger.info(
|
||||
'Successfully downloaded %s', downloaded
|
||||
)
|
||||
logger.info('Successfully downloaded %s', downloaded)
|
||||
|
||||
# Clean up
|
||||
if not options.no_clean:
|
||||
+20
-11
@@ -2,14 +2,13 @@ from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
import pip9
|
||||
from pip9.compat import stdlib_pkgs
|
||||
from pip9.basecommand import Command
|
||||
from pip9.operations.freeze import freeze
|
||||
from pip9.wheel import WheelCache
|
||||
from notpip._internal import index
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.cache import WheelCache
|
||||
from notpip._internal.compat import stdlib_pkgs
|
||||
from notpip._internal.operations.freeze import freeze
|
||||
|
||||
|
||||
DEV_PKGS = ('pip', 'setuptools', 'distribute', 'wheel')
|
||||
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
|
||||
|
||||
|
||||
class FreezeCommand(Command):
|
||||
@@ -63,11 +62,16 @@ class FreezeCommand(Command):
|
||||
action='store_true',
|
||||
help='Do not skip these packages in the output:'
|
||||
' %s' % ', '.join(DEV_PKGS))
|
||||
self.cmd_opts.add_option(
|
||||
'--exclude-editable',
|
||||
dest='exclude_editable',
|
||||
action='store_true',
|
||||
help='Exclude editable package from output.')
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
format_control = pip9.index.FormatControl(set(), set())
|
||||
format_control = index.FormatControl(set(), set())
|
||||
wheel_cache = WheelCache(options.cache_dir, format_control)
|
||||
skip = set(stdlib_pkgs)
|
||||
if not options.freeze_all:
|
||||
@@ -81,7 +85,12 @@ class FreezeCommand(Command):
|
||||
skip_regex=options.skip_requirements_regex,
|
||||
isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache,
|
||||
skip=skip)
|
||||
skip=skip,
|
||||
exclude_editable=options.exclude_editable,
|
||||
)
|
||||
|
||||
for line in freeze(**freeze_kwargs):
|
||||
sys.stdout.write(line + '\n')
|
||||
try:
|
||||
for line in freeze(**freeze_kwargs):
|
||||
sys.stdout.write(line + '\n')
|
||||
finally:
|
||||
wheel_cache.cleanup()
|
||||
+5
-5
@@ -4,11 +4,10 @@ import hashlib
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from pip9.basecommand import Command
|
||||
from pip9.status_codes import ERROR
|
||||
from pip9.utils import read_chunks
|
||||
from pip9.utils.hashes import FAVORITE_HASH, STRONG_HASHES
|
||||
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.status_codes import ERROR
|
||||
from notpip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
|
||||
from notpip._internal.utils.misc import read_chunks
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,6 +23,7 @@ class HashCommand(Command):
|
||||
name = 'hash'
|
||||
usage = '%prog [options] <file> ...'
|
||||
summary = 'Compute hashes of package archives.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(HashCommand, self).__init__(*args, **kw)
|
||||
+5
-4
@@ -1,7 +1,7 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pip9.basecommand import Command, SUCCESS
|
||||
from pip9.exceptions import CommandError
|
||||
from notpip._internal.basecommand import SUCCESS, Command
|
||||
from notpip._internal.exceptions import CommandError
|
||||
|
||||
|
||||
class HelpCommand(Command):
|
||||
@@ -10,12 +10,13 @@ class HelpCommand(Command):
|
||||
usage = """
|
||||
%prog <command>"""
|
||||
summary = 'Show help for commands.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def run(self, options, args):
|
||||
from pip9.commands import commands_dict, get_similar_commands
|
||||
from notpip._internal.commands import commands_dict, get_similar_commands
|
||||
|
||||
try:
|
||||
# 'pip help' with no args is handled by pip9.__init__.parseopt()
|
||||
# 'pip help' with no args is handled by pip.__init__.parseopt()
|
||||
cmd_name = args[0] # the command we need help for
|
||||
except IndexError:
|
||||
return SUCCESS
|
||||
@@ -0,0 +1,502 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import operator
|
||||
import os
|
||||
import shutil
|
||||
from optparse import SUPPRESS_HELP
|
||||
|
||||
from notpip._internal import cmdoptions
|
||||
from notpip._internal.basecommand import RequirementCommand
|
||||
from notpip._internal.cache import WheelCache
|
||||
from notpip._internal.exceptions import (
|
||||
CommandError, InstallationError, PreviousBuildDirError,
|
||||
)
|
||||
from notpip._internal.locations import distutils_scheme, virtualenv_no_global
|
||||
from notpip._internal.operations.check import check_install_conflicts
|
||||
from notpip._internal.operations.prepare import RequirementPreparer
|
||||
from notpip._internal.req import RequirementSet, install_given_reqs
|
||||
from notpip._internal.resolve import Resolver
|
||||
from notpip._internal.status_codes import ERROR
|
||||
from notpip._internal.utils.filesystem import check_path_owner
|
||||
from notpip._internal.utils.misc import ensure_dir, get_installed_version
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.wheel import WheelBuilder
|
||||
|
||||
try:
|
||||
import wheel
|
||||
except ImportError:
|
||||
wheel = None
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InstallCommand(RequirementCommand):
|
||||
"""
|
||||
Install packages from:
|
||||
|
||||
- PyPI (and other indexes) using requirement specifiers.
|
||||
- VCS project urls.
|
||||
- Local project directories.
|
||||
- Local or remote source archives.
|
||||
|
||||
pip also supports installing from "requirements files", which provide
|
||||
an easy way to specify a whole environment to be installed.
|
||||
"""
|
||||
name = 'install'
|
||||
|
||||
usage = """
|
||||
%prog [options] <requirement specifier> [package-index-options] ...
|
||||
%prog [options] -r <requirements file> [package-index-options] ...
|
||||
%prog [options] [-e] <vcs project url> ...
|
||||
%prog [options] [-e] <local project path> ...
|
||||
%prog [options] <archive url/path> ..."""
|
||||
|
||||
summary = 'Install packages.'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(InstallCommand, self).__init__(*args, **kw)
|
||||
|
||||
cmd_opts = self.cmd_opts
|
||||
|
||||
cmd_opts.add_option(cmdoptions.requirements())
|
||||
cmd_opts.add_option(cmdoptions.constraints())
|
||||
cmd_opts.add_option(cmdoptions.no_deps())
|
||||
cmd_opts.add_option(cmdoptions.pre())
|
||||
|
||||
cmd_opts.add_option(cmdoptions.editable())
|
||||
cmd_opts.add_option(
|
||||
'-t', '--target',
|
||||
dest='target_dir',
|
||||
metavar='dir',
|
||||
default=None,
|
||||
help='Install packages into <dir>. '
|
||||
'By default this will not replace existing files/folders in '
|
||||
'<dir>. Use --upgrade to replace existing packages in <dir> '
|
||||
'with new versions.'
|
||||
)
|
||||
cmd_opts.add_option(
|
||||
'--user',
|
||||
dest='use_user_site',
|
||||
action='store_true',
|
||||
help="Install to the Python user install directory for your "
|
||||
"platform. Typically ~/.local/, or %APPDATA%\\Python on "
|
||||
"Windows. (See the Python documentation for site.USER_BASE "
|
||||
"for full details.)")
|
||||
cmd_opts.add_option(
|
||||
'--no-user',
|
||||
dest='use_user_site',
|
||||
action='store_false',
|
||||
help=SUPPRESS_HELP)
|
||||
cmd_opts.add_option(
|
||||
'--root',
|
||||
dest='root_path',
|
||||
metavar='dir',
|
||||
default=None,
|
||||
help="Install everything relative to this alternate root "
|
||||
"directory.")
|
||||
cmd_opts.add_option(
|
||||
'--prefix',
|
||||
dest='prefix_path',
|
||||
metavar='dir',
|
||||
default=None,
|
||||
help="Installation prefix where lib, bin and other top-level "
|
||||
"folders are placed")
|
||||
|
||||
cmd_opts.add_option(cmdoptions.build_dir())
|
||||
|
||||
cmd_opts.add_option(cmdoptions.src())
|
||||
|
||||
cmd_opts.add_option(
|
||||
'-U', '--upgrade',
|
||||
dest='upgrade',
|
||||
action='store_true',
|
||||
help='Upgrade all specified packages to the newest available '
|
||||
'version. The handling of dependencies depends on the '
|
||||
'upgrade-strategy used.'
|
||||
)
|
||||
|
||||
cmd_opts.add_option(
|
||||
'--upgrade-strategy',
|
||||
dest='upgrade_strategy',
|
||||
default='only-if-needed',
|
||||
choices=['only-if-needed', 'eager'],
|
||||
help='Determines how dependency upgrading should be handled '
|
||||
'[default: %default]. '
|
||||
'"eager" - dependencies are upgraded regardless of '
|
||||
'whether the currently installed version satisfies the '
|
||||
'requirements of the upgraded package(s). '
|
||||
'"only-if-needed" - are upgraded only when they do not '
|
||||
'satisfy the requirements of the upgraded package(s).'
|
||||
)
|
||||
|
||||
cmd_opts.add_option(
|
||||
'--force-reinstall',
|
||||
dest='force_reinstall',
|
||||
action='store_true',
|
||||
help='Reinstall all packages even if they are already '
|
||||
'up-to-date.')
|
||||
|
||||
cmd_opts.add_option(
|
||||
'-I', '--ignore-installed',
|
||||
dest='ignore_installed',
|
||||
action='store_true',
|
||||
help='Ignore the installed packages (reinstalling instead).')
|
||||
|
||||
cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
|
||||
cmd_opts.add_option(cmdoptions.install_options())
|
||||
cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
cmd_opts.add_option(
|
||||
"--compile",
|
||||
action="store_true",
|
||||
dest="compile",
|
||||
default=True,
|
||||
help="Compile Python source files to bytecode",
|
||||
)
|
||||
|
||||
cmd_opts.add_option(
|
||||
"--no-compile",
|
||||
action="store_false",
|
||||
dest="compile",
|
||||
help="Do not compile Python source files to bytecode",
|
||||
)
|
||||
|
||||
cmd_opts.add_option(
|
||||
"--no-warn-script-location",
|
||||
action="store_false",
|
||||
dest="warn_script_location",
|
||||
default=True,
|
||||
help="Do not warn when installing scripts outside PATH",
|
||||
)
|
||||
cmd_opts.add_option(
|
||||
"--no-warn-conflicts",
|
||||
action="store_false",
|
||||
dest="warn_about_conflicts",
|
||||
default=True,
|
||||
help="Do not warn about broken dependencies",
|
||||
)
|
||||
|
||||
cmd_opts.add_option(cmdoptions.no_binary())
|
||||
cmd_opts.add_option(cmdoptions.only_binary())
|
||||
cmd_opts.add_option(cmdoptions.no_clean())
|
||||
cmd_opts.add_option(cmdoptions.require_hashes())
|
||||
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||
|
||||
index_opts = cmdoptions.make_option_group(
|
||||
cmdoptions.index_group,
|
||||
self.parser,
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, index_opts)
|
||||
self.parser.insert_option_group(0, cmd_opts)
|
||||
|
||||
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)
|
||||
|
||||
options.src_dir = os.path.abspath(options.src_dir)
|
||||
install_options = options.install_options or []
|
||||
if options.use_user_site:
|
||||
if options.prefix_path:
|
||||
raise CommandError(
|
||||
"Can not combine '--user' and '--prefix' as they imply "
|
||||
"different installation locations"
|
||||
)
|
||||
if virtualenv_no_global():
|
||||
raise InstallationError(
|
||||
"Can not perform a '--user' install. User site-packages "
|
||||
"are not visible in this virtualenv."
|
||||
)
|
||||
install_options.append('--user')
|
||||
install_options.append('--prefix=')
|
||||
|
||||
target_temp_dir = TempDirectory(kind="target")
|
||||
if options.target_dir:
|
||||
options.ignore_installed = True
|
||||
options.target_dir = os.path.abspath(options.target_dir)
|
||||
if (os.path.exists(options.target_dir) and not
|
||||
os.path.isdir(options.target_dir)):
|
||||
raise CommandError(
|
||||
"Target path exists but is not a directory, will not "
|
||||
"continue."
|
||||
)
|
||||
|
||||
# Create a target directory for using with the target option
|
||||
target_temp_dir.create()
|
||||
install_options.append('--home=' + target_temp_dir.path)
|
||||
|
||||
global_options = options.global_options or []
|
||||
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(options, session)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
|
||||
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned "
|
||||
"by the current user and caching wheels has been "
|
||||
"disabled. check the permissions and owner of that "
|
||||
"directory. If executing pip with sudo, you may want "
|
||||
"sudo's -H flag.",
|
||||
options.cache_dir,
|
||||
)
|
||||
options.cache_dir = None
|
||||
|
||||
with TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="install"
|
||||
) as directory:
|
||||
requirement_set = RequirementSet(
|
||||
require_hashes=options.require_hashes,
|
||||
)
|
||||
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
self.name, wheel_cache
|
||||
)
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=None,
|
||||
progress_bar=options.progress_bar,
|
||||
build_isolation=options.build_isolation,
|
||||
)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=options.use_user_site,
|
||||
upgrade_strategy=upgrade_strategy,
|
||||
force_reinstall=options.force_reinstall,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
ignore_installed=options.ignore_installed,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
# If caching is disabled or wheel is not installed don't
|
||||
# try to build wheels.
|
||||
if wheel and options.cache_dir:
|
||||
# build wheels before install.
|
||||
wb = WheelBuilder(
|
||||
finder, preparer, wheel_cache,
|
||||
build_options=[], global_options=[],
|
||||
)
|
||||
# Ignore the result: a failed wheel will be
|
||||
# installed from the sdist/vcs whatever.
|
||||
wb.build(
|
||||
requirement_set.requirements.values(),
|
||||
session=session, autobuilding=True
|
||||
)
|
||||
|
||||
to_install = resolver.get_installation_order(
|
||||
requirement_set
|
||||
)
|
||||
|
||||
# Consistency Checking of the package set we're installing.
|
||||
should_warn_about_conflicts = (
|
||||
not options.ignore_dependencies and
|
||||
options.warn_about_conflicts
|
||||
)
|
||||
if should_warn_about_conflicts:
|
||||
self._warn_about_conflicts(to_install)
|
||||
|
||||
# Don't warn about script install locations if
|
||||
# --target has been specified
|
||||
warn_script_location = options.warn_script_location
|
||||
if options.target_dir:
|
||||
warn_script_location = False
|
||||
|
||||
installed = install_given_reqs(
|
||||
to_install,
|
||||
install_options,
|
||||
global_options,
|
||||
root=options.root_path,
|
||||
home=target_temp_dir.path,
|
||||
prefix=options.prefix_path,
|
||||
pycompile=options.compile,
|
||||
warn_script_location=warn_script_location,
|
||||
use_user_site=options.use_user_site,
|
||||
)
|
||||
|
||||
possible_lib_locations = get_lib_location_guesses(
|
||||
user=options.use_user_site,
|
||||
home=target_temp_dir.path,
|
||||
root=options.root_path,
|
||||
prefix=options.prefix_path,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
reqs = sorted(installed, key=operator.attrgetter('name'))
|
||||
items = []
|
||||
for req in reqs:
|
||||
item = req.name
|
||||
try:
|
||||
installed_version = get_installed_version(
|
||||
req.name, possible_lib_locations
|
||||
)
|
||||
if installed_version:
|
||||
item += '-' + installed_version
|
||||
except Exception:
|
||||
pass
|
||||
items.append(item)
|
||||
installed = ' '.join(items)
|
||||
if installed:
|
||||
logger.info('Successfully installed %s', installed)
|
||||
except EnvironmentError as error:
|
||||
show_traceback = (self.verbosity >= 1)
|
||||
|
||||
message = create_env_error_message(
|
||||
error, show_traceback, options.use_user_site,
|
||||
)
|
||||
logger.error(message, exc_info=show_traceback)
|
||||
|
||||
return ERROR
|
||||
except PreviousBuildDirError:
|
||||
options.no_clean = True
|
||||
raise
|
||||
finally:
|
||||
# Clean up
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
|
||||
if options.target_dir:
|
||||
self._handle_target_dir(
|
||||
options.target_dir, target_temp_dir, options.upgrade
|
||||
)
|
||||
return requirement_set
|
||||
|
||||
def _handle_target_dir(self, target_dir, target_temp_dir, upgrade):
|
||||
ensure_dir(target_dir)
|
||||
|
||||
# Checking both purelib and platlib directories for installed
|
||||
# packages to be moved to target directory
|
||||
lib_dir_list = []
|
||||
|
||||
with target_temp_dir:
|
||||
# Checking both purelib and platlib directories for installed
|
||||
# packages to be moved to target directory
|
||||
scheme = distutils_scheme('', home=target_temp_dir.path)
|
||||
purelib_dir = scheme['purelib']
|
||||
platlib_dir = scheme['platlib']
|
||||
data_dir = scheme['data']
|
||||
|
||||
if os.path.exists(purelib_dir):
|
||||
lib_dir_list.append(purelib_dir)
|
||||
if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
|
||||
lib_dir_list.append(platlib_dir)
|
||||
if os.path.exists(data_dir):
|
||||
lib_dir_list.append(data_dir)
|
||||
|
||||
for lib_dir in lib_dir_list:
|
||||
for item in os.listdir(lib_dir):
|
||||
if lib_dir == data_dir:
|
||||
ddir = os.path.join(data_dir, item)
|
||||
if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
|
||||
continue
|
||||
target_item_dir = os.path.join(target_dir, item)
|
||||
if os.path.exists(target_item_dir):
|
||||
if not upgrade:
|
||||
logger.warning(
|
||||
'Target directory %s already exists. Specify '
|
||||
'--upgrade to force replacement.',
|
||||
target_item_dir
|
||||
)
|
||||
continue
|
||||
if os.path.islink(target_item_dir):
|
||||
logger.warning(
|
||||
'Target directory %s already exists and is '
|
||||
'a link. Pip will not automatically replace '
|
||||
'links, please remove if replacement is '
|
||||
'desired.',
|
||||
target_item_dir
|
||||
)
|
||||
continue
|
||||
if os.path.isdir(target_item_dir):
|
||||
shutil.rmtree(target_item_dir)
|
||||
else:
|
||||
os.remove(target_item_dir)
|
||||
|
||||
shutil.move(
|
||||
os.path.join(lib_dir, item),
|
||||
target_item_dir
|
||||
)
|
||||
|
||||
def _warn_about_conflicts(self, to_install):
|
||||
package_set, _dep_info = check_install_conflicts(to_install)
|
||||
missing, conflicting = _dep_info
|
||||
|
||||
# NOTE: There is some duplication here from notpip check
|
||||
for project_name in missing:
|
||||
version = package_set[project_name][0]
|
||||
for dependency in missing[project_name]:
|
||||
logger.critical(
|
||||
"%s %s requires %s, which is not installed.",
|
||||
project_name, version, dependency[1],
|
||||
)
|
||||
|
||||
for project_name in conflicting:
|
||||
version = package_set[project_name][0]
|
||||
for dep_name, dep_version, req in conflicting[project_name]:
|
||||
logger.critical(
|
||||
"%s %s has requirement %s, but you'll have %s %s which is "
|
||||
"incompatible.",
|
||||
project_name, version, req, dep_name, dep_version,
|
||||
)
|
||||
|
||||
|
||||
def get_lib_location_guesses(*args, **kwargs):
|
||||
scheme = distutils_scheme('', *args, **kwargs)
|
||||
return [scheme['purelib'], scheme['platlib']]
|
||||
|
||||
|
||||
def create_env_error_message(error, show_traceback, using_user_site):
|
||||
"""Format an error message for an EnvironmentError
|
||||
|
||||
It may occur anytime during the execution of the install command.
|
||||
"""
|
||||
parts = []
|
||||
|
||||
# Mention the error if we are not going to show a traceback
|
||||
parts.append("Could not install packages due to an EnvironmentError")
|
||||
if not show_traceback:
|
||||
parts.append(": ")
|
||||
parts.append(str(error))
|
||||
else:
|
||||
parts.append(".")
|
||||
|
||||
# Spilt the error indication from a helper message (if any)
|
||||
parts[-1] += "\n"
|
||||
|
||||
# Suggest useful actions to the user:
|
||||
# (1) using user site-packages or (2) verifying the permissions
|
||||
if error.errno == errno.EACCES:
|
||||
user_option_part = "Consider using the `--user` option"
|
||||
permissions_part = "Check the permissions"
|
||||
|
||||
if not using_user_site:
|
||||
parts.extend([
|
||||
user_option_part, " or ",
|
||||
permissions_part.lower(),
|
||||
])
|
||||
else:
|
||||
parts.append(permissions_part)
|
||||
parts.append(".\n")
|
||||
|
||||
return "".join(parts).strip() + "\n"
|
||||
+61
-55
@@ -3,20 +3,19 @@ from __future__ import absolute_import
|
||||
import json
|
||||
import logging
|
||||
import warnings
|
||||
try:
|
||||
from itertools import zip_longest
|
||||
except ImportError:
|
||||
from itertools import izip_longest as zip_longest
|
||||
|
||||
from pip9._vendor import six
|
||||
from notpip._vendor import six
|
||||
from notpip._vendor.six.moves import zip_longest
|
||||
|
||||
from pip9.basecommand import Command
|
||||
from pip9.exceptions import CommandError
|
||||
from pip9.index import PackageFinder
|
||||
from pip9.utils import (
|
||||
get_installed_distributions, dist_is_editable)
|
||||
from pip9.utils.deprecation import RemovedInPip10Warning
|
||||
from pip9.cmdoptions import make_option_group, index_group
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.cmdoptions import index_group, make_option_group
|
||||
from notpip._internal.exceptions import CommandError
|
||||
from notpip._internal.index import PackageFinder
|
||||
from notpip._internal.utils.deprecation import RemovedInPip11Warning
|
||||
from notpip._internal.utils.misc import (
|
||||
dist_is_editable, get_installed_distributions,
|
||||
)
|
||||
from notpip._internal.utils.packaging import get_installer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -78,9 +77,10 @@ class ListCommand(Command):
|
||||
'--format',
|
||||
action='store',
|
||||
dest='list_format',
|
||||
default="columns",
|
||||
choices=('legacy', 'columns', 'freeze', 'json'),
|
||||
help="Select the output format among: legacy (default), columns, "
|
||||
"freeze or json.",
|
||||
help="Select the output format among: columns (default), freeze, "
|
||||
"json, or legacy.",
|
||||
)
|
||||
|
||||
cmd_opts.add_option(
|
||||
@@ -91,6 +91,19 @@ class ListCommand(Command):
|
||||
"installed packages.",
|
||||
)
|
||||
|
||||
cmd_opts.add_option(
|
||||
'--exclude-editable',
|
||||
action='store_false',
|
||||
dest='include_editable',
|
||||
help='Exclude editable package from output.',
|
||||
)
|
||||
cmd_opts.add_option(
|
||||
'--include-editable',
|
||||
action='store_true',
|
||||
dest='include_editable',
|
||||
help='Include editable package from output.',
|
||||
default=True,
|
||||
)
|
||||
index_opts = make_option_group(index_group, self.parser)
|
||||
|
||||
self.parser.insert_option_group(0, index_opts)
|
||||
@@ -110,37 +123,11 @@ class ListCommand(Command):
|
||||
)
|
||||
|
||||
def run(self, options, args):
|
||||
if options.allow_external:
|
||||
if options.list_format == "legacy":
|
||||
warnings.warn(
|
||||
"--allow-external has been deprecated and will be removed in "
|
||||
"the future. Due to changes in the repository protocol, it no "
|
||||
"longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if options.allow_all_external:
|
||||
warnings.warn(
|
||||
"--allow-all-external has been deprecated and will be removed "
|
||||
"in the future. Due to changes in the repository protocol, it "
|
||||
"no longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if options.allow_unverified:
|
||||
warnings.warn(
|
||||
"--allow-unverified has been deprecated and will be removed "
|
||||
"in the future. Due to changes in the repository protocol, it "
|
||||
"no longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if options.list_format is None:
|
||||
warnings.warn(
|
||||
"The default format will switch to columns in the future. "
|
||||
"You can use --format=(legacy|columns) (or define a "
|
||||
"format=(legacy|columns) in your pip9.conf under the [list] "
|
||||
"section) to disable this warning.",
|
||||
RemovedInPip10Warning,
|
||||
"The legacy format has been deprecated and will be removed "
|
||||
"in the future.",
|
||||
RemovedInPip11Warning,
|
||||
)
|
||||
|
||||
if options.outdated and options.uptodate:
|
||||
@@ -151,6 +138,7 @@ class ListCommand(Command):
|
||||
local_only=options.local,
|
||||
user_only=options.user,
|
||||
editables_only=options.editable,
|
||||
include_editables=options.include_editable,
|
||||
)
|
||||
|
||||
if options.outdated:
|
||||
@@ -179,7 +167,7 @@ class ListCommand(Command):
|
||||
dep_keys = set()
|
||||
for dist in packages:
|
||||
dep_keys.update(requirement.key for requirement in dist.requires())
|
||||
return set(pkg for pkg in packages if pkg.key not in dep_keys)
|
||||
return {pkg for pkg in packages if pkg.key not in dep_keys}
|
||||
|
||||
def iter_packages_latest_infos(self, packages, options):
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
@@ -220,8 +208,15 @@ class ListCommand(Command):
|
||||
dist.latest_filetype = typ
|
||||
yield dist
|
||||
|
||||
def output_legacy(self, dist):
|
||||
if dist_is_editable(dist):
|
||||
def output_legacy(self, dist, options):
|
||||
if options.verbose >= 1:
|
||||
return '%s (%s, %s, %s)' % (
|
||||
dist.project_name,
|
||||
dist.version,
|
||||
dist.location,
|
||||
get_installer(dist),
|
||||
)
|
||||
elif dist_is_editable(dist):
|
||||
return '%s (%s, %s)' % (
|
||||
dist.project_name,
|
||||
dist.version,
|
||||
@@ -230,9 +225,9 @@ class ListCommand(Command):
|
||||
else:
|
||||
return '%s (%s)' % (dist.project_name, dist.version)
|
||||
|
||||
def output_legacy_latest(self, dist):
|
||||
def output_legacy_latest(self, dist, options):
|
||||
return '%s - Latest: %s [%s]' % (
|
||||
self.output_legacy(dist),
|
||||
self.output_legacy(dist, options),
|
||||
dist.latest_version,
|
||||
dist.latest_filetype,
|
||||
)
|
||||
@@ -247,15 +242,19 @@ class ListCommand(Command):
|
||||
self.output_package_listing_columns(data, header)
|
||||
elif options.list_format == 'freeze':
|
||||
for dist in packages:
|
||||
logger.info("%s==%s", dist.project_name, dist.version)
|
||||
if options.verbose >= 1:
|
||||
logger.info("%s==%s (%s)", dist.project_name,
|
||||
dist.version, dist.location)
|
||||
else:
|
||||
logger.info("%s==%s", dist.project_name, dist.version)
|
||||
elif options.list_format == 'json':
|
||||
logger.info(format_for_json(packages, options))
|
||||
else: # legacy
|
||||
elif options.list_format == "legacy":
|
||||
for dist in packages:
|
||||
if options.outdated:
|
||||
logger.info(self.output_legacy_latest(dist))
|
||||
logger.info(self.output_legacy_latest(dist, options))
|
||||
else:
|
||||
logger.info(self.output_legacy(dist))
|
||||
logger.info(self.output_legacy(dist, options))
|
||||
|
||||
def output_package_listing_columns(self, data, header):
|
||||
# insert the header first: we need to know the size of column names
|
||||
@@ -303,8 +302,10 @@ def format_for_columns(pkgs, options):
|
||||
header = ["Package", "Version"]
|
||||
|
||||
data = []
|
||||
if any(dist_is_editable(x) for x in pkgs):
|
||||
if options.verbose >= 1 or any(dist_is_editable(x) for x in pkgs):
|
||||
header.append("Location")
|
||||
if options.verbose >= 1:
|
||||
header.append("Installer")
|
||||
|
||||
for proj in pkgs:
|
||||
# if we're working on the 'outdated' list, separate out the
|
||||
@@ -315,8 +316,10 @@ def format_for_columns(pkgs, options):
|
||||
row.append(proj.latest_version)
|
||||
row.append(proj.latest_filetype)
|
||||
|
||||
if dist_is_editable(proj):
|
||||
if options.verbose >= 1 or dist_is_editable(proj):
|
||||
row.append(proj.location)
|
||||
if options.verbose >= 1:
|
||||
row.append(get_installer(proj))
|
||||
|
||||
data.append(row)
|
||||
|
||||
@@ -330,6 +333,9 @@ def format_for_json(packages, options):
|
||||
'name': dist.project_name,
|
||||
'version': six.text_type(dist.version),
|
||||
}
|
||||
if options.verbose >= 1:
|
||||
info['location'] = dist.location
|
||||
info['installer'] = get_installer(dist)
|
||||
if options.outdated:
|
||||
info['latest_version'] = six.text_type(dist.latest_version)
|
||||
info['latest_filetype'] = dist.latest_filetype
|
||||
+17
-15
@@ -3,19 +3,21 @@ from __future__ import absolute_import
|
||||
import logging
|
||||
import sys
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
|
||||
from pip9.basecommand import Command, SUCCESS
|
||||
from pip9.compat import OrderedDict
|
||||
from pip9.download import pip9XmlrpcTransport
|
||||
from pip9.models import PyPI
|
||||
from pip9.utils import get_terminal_size
|
||||
from pip9.utils.logging import indent_log
|
||||
from pip9.exceptions import CommandError
|
||||
from pip9.status_codes import NO_MATCHES_FOUND
|
||||
from pip9._vendor.packaging.version import parse as parse_version
|
||||
from pip9._vendor import pkg_resources
|
||||
from pip9._vendor.six.moves import xmlrpc_client
|
||||
from notpip._vendor import pkg_resources
|
||||
from notpip._vendor.packaging.version import parse as parse_version
|
||||
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
|
||||
# why we ignore the type on this import
|
||||
from notpip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||
|
||||
from notpip._internal.basecommand import SUCCESS, Command
|
||||
from notpip._internal.compat import get_terminal_size
|
||||
from notpip._internal.download import PipXmlrpcTransport
|
||||
from notpip._internal.exceptions import CommandError
|
||||
from notpip._internal.models import PyPI
|
||||
from notpip._internal.status_codes import NO_MATCHES_FOUND
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,6 +28,7 @@ class SearchCommand(Command):
|
||||
usage = """
|
||||
%prog [options] <query>"""
|
||||
summary = 'Search PyPI for packages.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(SearchCommand, self).__init__(*args, **kw)
|
||||
@@ -96,7 +99,7 @@ def print_results(hits, name_column_width=None, terminal_width=None):
|
||||
return
|
||||
if name_column_width is None:
|
||||
name_column_width = max([
|
||||
len(hit['name']) + len(hit.get('versions', ['-'])[-1])
|
||||
len(hit['name']) + len(highest_version(hit.get('versions', ['-'])))
|
||||
for hit in hits
|
||||
]) + 4
|
||||
|
||||
@@ -104,7 +107,7 @@ def print_results(hits, name_column_width=None, terminal_width=None):
|
||||
for hit in hits:
|
||||
name = hit['name']
|
||||
summary = hit['summary'] or ''
|
||||
version = hit.get('versions', ['-'])[-1]
|
||||
latest = highest_version(hit.get('versions', ['-']))
|
||||
if terminal_width is not None:
|
||||
target_width = terminal_width - name_column_width - 5
|
||||
if target_width > 10:
|
||||
@@ -113,13 +116,12 @@ def print_results(hits, name_column_width=None, terminal_width=None):
|
||||
summary = ('\n' + ' ' * (name_column_width + 3)).join(summary)
|
||||
|
||||
line = '%-*s - %s' % (name_column_width,
|
||||
'%s (%s)' % (name, version), summary)
|
||||
'%s (%s)' % (name, latest), summary)
|
||||
try:
|
||||
logger.info(line)
|
||||
if name in installed_packages:
|
||||
dist = pkg_resources.get_distribution(name)
|
||||
with indent_log():
|
||||
latest = highest_version(hit['versions'])
|
||||
if dist.version == latest:
|
||||
logger.info('INSTALLED: %s (latest)', dist.version)
|
||||
else:
|
||||
+16
-6
@@ -1,14 +1,14 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from email.parser import FeedParser
|
||||
import logging
|
||||
import os
|
||||
from email.parser import FeedParser # type: ignore
|
||||
|
||||
from pip9.basecommand import Command
|
||||
from pip9.status_codes import SUCCESS, ERROR
|
||||
from pip9._vendor import pkg_resources
|
||||
from pip9._vendor.packaging.utils import canonicalize_name
|
||||
from notpip._vendor import pkg_resources
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.status_codes import ERROR, SUCCESS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,6 +19,7 @@ class ShowCommand(Command):
|
||||
usage = """
|
||||
%prog [options] <package> ..."""
|
||||
summary = 'Show information about installed packages.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(ShowCommand, self).__init__(*args, **kw)
|
||||
@@ -126,7 +127,14 @@ def print_results(distributions, list_files=False, verbose=False):
|
||||
results_printed = True
|
||||
if i > 0:
|
||||
logger.info("---")
|
||||
logger.info("Name: %s", dist.get('name', ''))
|
||||
|
||||
name = dist.get('name', '')
|
||||
required_by = [
|
||||
pkg.project_name for pkg in pkg_resources.working_set
|
||||
if name in [required.name for required in pkg.requires()]
|
||||
]
|
||||
|
||||
logger.info("Name: %s", name)
|
||||
logger.info("Version: %s", dist.get('version', ''))
|
||||
logger.info("Summary: %s", dist.get('summary', ''))
|
||||
logger.info("Home-page: %s", dist.get('home-page', ''))
|
||||
@@ -135,6 +143,8 @@ def print_results(distributions, list_files=False, verbose=False):
|
||||
logger.info("License: %s", dist.get('license', ''))
|
||||
logger.info("Location: %s", dist.get('location', ''))
|
||||
logger.info("Requires: %s", ', '.join(dist.get('requires', [])))
|
||||
logger.info("Required-by: %s", ', '.join(required_by))
|
||||
|
||||
if verbose:
|
||||
logger.info("Metadata-Version: %s",
|
||||
dist.get('metadata-version', ''))
|
||||
+20
-25
@@ -1,10 +1,10 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pip9
|
||||
from pip9.wheel import WheelCache
|
||||
from pip9.req import InstallRequirement, RequirementSet, parse_requirements
|
||||
from pip9.basecommand import Command
|
||||
from pip9.exceptions import InstallationError
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from notpip._internal.basecommand import Command
|
||||
from notpip._internal.exceptions import InstallationError
|
||||
from notpip._internal.req import InstallRequirement, parse_requirements
|
||||
|
||||
|
||||
class UninstallCommand(Command):
|
||||
@@ -44,33 +44,28 @@ class UninstallCommand(Command):
|
||||
|
||||
def run(self, options, args):
|
||||
with self._build_session(options) as session:
|
||||
format_control = pip9.index.FormatControl(set(), set())
|
||||
wheel_cache = WheelCache(options.cache_dir, format_control)
|
||||
requirement_set = RequirementSet(
|
||||
build_dir=None,
|
||||
src_dir=None,
|
||||
download_dir=None,
|
||||
isolated=options.isolated_mode,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
)
|
||||
reqs_to_uninstall = {}
|
||||
for name in args:
|
||||
requirement_set.add_requirement(
|
||||
InstallRequirement.from_line(
|
||||
name, isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req = InstallRequirement.from_line(
|
||||
name, isolated=options.isolated_mode,
|
||||
)
|
||||
if req.name:
|
||||
reqs_to_uninstall[canonicalize_name(req.name)] = req
|
||||
for filename in options.requirements:
|
||||
for req in parse_requirements(
|
||||
filename,
|
||||
options=options,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache):
|
||||
requirement_set.add_requirement(req)
|
||||
if not requirement_set.has_requirements:
|
||||
session=session):
|
||||
if req.name:
|
||||
reqs_to_uninstall[canonicalize_name(req.name)] = req
|
||||
if not reqs_to_uninstall:
|
||||
raise InstallationError(
|
||||
'You must give at least one requirement to %(name)s (see '
|
||||
'"pip help %(name)s")' % dict(name=self.name)
|
||||
)
|
||||
requirement_set.uninstall(auto_confirm=options.yes)
|
||||
for req in reqs_to_uninstall.values():
|
||||
uninstall_pathset = req.uninstall(
|
||||
auto_confirm=options.yes, verbose=self.verbosity > 0,
|
||||
)
|
||||
if uninstall_pathset:
|
||||
uninstall_pathset.commit()
|
||||
+54
-83
@@ -3,17 +3,16 @@ from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from pip9.basecommand import RequirementCommand
|
||||
from pip9.exceptions import CommandError, PreviousBuildDirError
|
||||
from pip9.req import RequirementSet
|
||||
from pip9.utils import import_or_raise
|
||||
from pip9.utils.build import BuildDirectory
|
||||
from pip9.utils.deprecation import RemovedInPip10Warning
|
||||
from pip9.wheel import WheelCache, WheelBuilder
|
||||
from pip9 import cmdoptions
|
||||
|
||||
from notpip._internal import cmdoptions
|
||||
from notpip._internal.basecommand import RequirementCommand
|
||||
from notpip._internal.cache import WheelCache
|
||||
from notpip._internal.exceptions import CommandError, PreviousBuildDirError
|
||||
from notpip._internal.operations.prepare import RequirementPreparer
|
||||
from notpip._internal.req import RequirementSet
|
||||
from notpip._internal.resolve import Resolver
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.wheel import WheelBuilder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -56,8 +55,6 @@ class WheelCommand(RequirementCommand):
|
||||
help=("Build wheels into <dir>, where the default is the "
|
||||
"current working directory."),
|
||||
)
|
||||
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(
|
||||
@@ -65,7 +62,9 @@ class WheelCommand(RequirementCommand):
|
||||
dest='build_options',
|
||||
metavar='options',
|
||||
action='append',
|
||||
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.")
|
||||
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
|
||||
)
|
||||
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
cmd_opts.add_option(cmdoptions.constraints())
|
||||
cmd_opts.add_option(cmdoptions.editable())
|
||||
cmd_opts.add_option(cmdoptions.requirements())
|
||||
@@ -73,6 +72,7 @@ class WheelCommand(RequirementCommand):
|
||||
cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
cmd_opts.add_option(cmdoptions.no_deps())
|
||||
cmd_opts.add_option(cmdoptions.build_dir())
|
||||
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||
|
||||
cmd_opts.add_option(
|
||||
'--global-option',
|
||||
@@ -101,55 +101,9 @@ class WheelCommand(RequirementCommand):
|
||||
self.parser.insert_option_group(0, index_opts)
|
||||
self.parser.insert_option_group(0, cmd_opts)
|
||||
|
||||
def check_required_packages(self):
|
||||
import_or_raise(
|
||||
'wheel.bdist_wheel',
|
||||
CommandError,
|
||||
"'pip wheel' requires the 'wheel' package. To fix this, run: "
|
||||
"pip install wheel"
|
||||
)
|
||||
pkg_resources = import_or_raise(
|
||||
'pkg_resources',
|
||||
CommandError,
|
||||
"'pip wheel' requires setuptools >= 0.8 for dist-info support."
|
||||
" To fix this, run: pip install --upgrade setuptools"
|
||||
)
|
||||
if not hasattr(pkg_resources, 'DistInfoDistribution'):
|
||||
raise CommandError(
|
||||
"'pip wheel' requires setuptools >= 0.8 for dist-info "
|
||||
"support. To fix this, run: pip install --upgrade "
|
||||
"setuptools"
|
||||
)
|
||||
|
||||
def run(self, options, args):
|
||||
self.check_required_packages()
|
||||
cmdoptions.resolve_wheel_no_use_binary(options)
|
||||
cmdoptions.check_install_build_global(options)
|
||||
|
||||
if options.allow_external:
|
||||
warnings.warn(
|
||||
"--allow-external has been deprecated and will be removed in "
|
||||
"the future. Due to changes in the repository protocol, it no "
|
||||
"longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if options.allow_all_external:
|
||||
warnings.warn(
|
||||
"--allow-all-external has been deprecated and will be removed "
|
||||
"in the future. Due to changes in the repository protocol, it "
|
||||
"no longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if options.allow_unverified:
|
||||
warnings.warn(
|
||||
"--allow-unverified has been deprecated and will be removed "
|
||||
"in the future. Due to changes in the repository protocol, it "
|
||||
"no longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index:
|
||||
logger.debug('Ignoring indexes: %s', ','.join(index_urls))
|
||||
@@ -164,39 +118,55 @@ class WheelCommand(RequirementCommand):
|
||||
finder = self._build_package_finder(options, session)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
with BuildDirectory(options.build_dir,
|
||||
delete=build_delete) as build_dir:
|
||||
|
||||
with TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="wheel"
|
||||
) as directory:
|
||||
requirement_set = RequirementSet(
|
||||
build_dir=build_dir,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_installed=True,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
isolated=options.isolated_mode,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
wheel_download_dir=options.wheel_dir,
|
||||
require_hashes=options.require_hashes
|
||||
require_hashes=options.require_hashes,
|
||||
)
|
||||
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session, self.name,
|
||||
wheel_cache
|
||||
)
|
||||
|
||||
if not requirement_set.has_requirements:
|
||||
return
|
||||
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
self.name, wheel_cache
|
||||
)
|
||||
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=options.wheel_dir,
|
||||
progress_bar=options.progress_bar,
|
||||
build_isolation=options.build_isolation,
|
||||
)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=False,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=False,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
ignore_installed=True,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
# build wheels
|
||||
wb = WheelBuilder(
|
||||
requirement_set,
|
||||
finder,
|
||||
finder, preparer, wheel_cache,
|
||||
build_options=options.build_options or [],
|
||||
global_options=options.global_options or [],
|
||||
no_clean=options.no_clean,
|
||||
)
|
||||
if not wb.build():
|
||||
wheels_built_successfully = wb.build(
|
||||
requirement_set.requirements.values(), session=session,
|
||||
)
|
||||
if not wheels_built_successfully:
|
||||
raise CommandError(
|
||||
"Failed to build one or more wheels"
|
||||
)
|
||||
@@ -206,3 +176,4 @@ class WheelCommand(RequirementCommand):
|
||||
finally:
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
@@ -0,0 +1,235 @@
|
||||
"""Stuff that differs in different Python versions and platform
|
||||
distributions."""
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import codecs
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from notpip._vendor.six import text_type
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
try:
|
||||
from notpip._vendor import ipaddress # type: ignore
|
||||
except ImportError:
|
||||
import ipaddr as ipaddress # type: ignore
|
||||
ipaddress.ip_address = ipaddress.IPAddress
|
||||
ipaddress.ip_network = ipaddress.IPNetwork
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ipaddress", "uses_pycache", "console_to_str", "native_str",
|
||||
"get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", "get_terminal_size",
|
||||
]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if sys.version_info >= (3, 4):
|
||||
uses_pycache = True
|
||||
from importlib.util import cache_from_source
|
||||
else:
|
||||
import imp
|
||||
|
||||
try:
|
||||
cache_from_source = imp.cache_from_source # type: ignore
|
||||
except AttributeError:
|
||||
# does not use __pycache__
|
||||
cache_from_source = None
|
||||
|
||||
uses_pycache = cache_from_source is not None
|
||||
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
backslashreplace_decode = "backslashreplace"
|
||||
else:
|
||||
# In version 3.4 and older, backslashreplace exists
|
||||
# but does not support use for decoding.
|
||||
# We implement our own replace handler for this
|
||||
# situation, so that we can consistently use
|
||||
# backslash replacement for all versions.
|
||||
def backslashreplace_decode_fn(err):
|
||||
raw_bytes = (err.object[i] for i in range(err.start, err.end))
|
||||
if sys.version_info[0] == 2:
|
||||
# Python 2 gave us characters - convert to numeric bytes
|
||||
raw_bytes = (ord(b) for b in raw_bytes)
|
||||
return u"".join(u"\\x%x" % c for c in raw_bytes), err.end
|
||||
codecs.register_error(
|
||||
"backslashreplace_decode",
|
||||
backslashreplace_decode_fn,
|
||||
)
|
||||
backslashreplace_decode = "backslashreplace_decode"
|
||||
|
||||
|
||||
def console_to_str(data):
|
||||
"""Return a string, safe for output, of subprocess output.
|
||||
|
||||
We assume the data is in the locale preferred encoding.
|
||||
If it won't decode properly, we warn the user but decode as
|
||||
best we can.
|
||||
|
||||
We also ensure that the output can be safely written to
|
||||
standard output without encoding errors.
|
||||
"""
|
||||
|
||||
# First, get the encoding we assume. This is the preferred
|
||||
# encoding for the locale, unless that is not found, or
|
||||
# it is ASCII, in which case assume UTF-8
|
||||
encoding = locale.getpreferredencoding()
|
||||
if (not encoding) or codecs.lookup(encoding).name == "ascii":
|
||||
encoding = "utf-8"
|
||||
|
||||
# Now try to decode the data - if we fail, warn the user and
|
||||
# decode with replacement.
|
||||
try:
|
||||
s = data.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning(
|
||||
"Subprocess output does not appear to be encoded as %s",
|
||||
encoding,
|
||||
)
|
||||
s = data.decode(encoding, errors=backslashreplace_decode)
|
||||
|
||||
# Make sure we can print the output, by encoding it to the output
|
||||
# encoding with replacement of unencodable characters, and then
|
||||
# decoding again.
|
||||
# We use stderr's encoding because it's less likely to be
|
||||
# redirected and if we don't find an encoding we skip this
|
||||
# step (on the assumption that output is wrapped by something
|
||||
# that won't fail).
|
||||
# The double getattr is to deal with the possibility that we're
|
||||
# being called in a situation where sys.__stderr__ doesn't exist,
|
||||
# or doesn't have an encoding attribute. Neither of these cases
|
||||
# should occur in normal pip use, but there's no harm in checking
|
||||
# in case people use pip in (unsupported) unusual situations.
|
||||
output_encoding = getattr(getattr(sys, "__stderr__", None),
|
||||
"encoding", None)
|
||||
|
||||
if output_encoding:
|
||||
s = s.encode(output_encoding, errors="backslashreplace")
|
||||
s = s.decode(output_encoding)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
def native_str(s, replace=False):
|
||||
if isinstance(s, bytes):
|
||||
return s.decode('utf-8', 'replace' if replace else 'strict')
|
||||
return s
|
||||
|
||||
else:
|
||||
def native_str(s, replace=False):
|
||||
# Replace is ignored -- unicode to UTF-8 can't fail
|
||||
if isinstance(s, text_type):
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
|
||||
def get_path_uid(path):
|
||||
"""
|
||||
Return path's uid.
|
||||
|
||||
Does not follow symlinks:
|
||||
https://github.com/pypa/pip/pull/935#discussion_r5307003
|
||||
|
||||
Placed this function in compat due to differences on AIX and
|
||||
Jython, that should eventually go away.
|
||||
|
||||
:raises OSError: When path is a symlink or can't be read.
|
||||
"""
|
||||
if hasattr(os, 'O_NOFOLLOW'):
|
||||
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
||||
file_uid = os.fstat(fd).st_uid
|
||||
os.close(fd)
|
||||
else: # AIX and Jython
|
||||
# WARNING: time of check vulnerability, but best we can do w/o NOFOLLOW
|
||||
if not os.path.islink(path):
|
||||
# older versions of Jython don't have `os.fstat`
|
||||
file_uid = os.stat(path).st_uid
|
||||
else:
|
||||
# raise OSError for parity with os.O_NOFOLLOW above
|
||||
raise OSError(
|
||||
"%s is a symlink; Will not return uid for symlinks" % path
|
||||
)
|
||||
return file_uid
|
||||
|
||||
|
||||
def expanduser(path):
|
||||
"""
|
||||
Expand ~ and ~user constructions.
|
||||
|
||||
Includes a workaround for http://bugs.python.org/issue14768
|
||||
"""
|
||||
expanded = os.path.expanduser(path)
|
||||
if path.startswith('~/') and expanded.startswith('//'):
|
||||
expanded = expanded[1:]
|
||||
return expanded
|
||||
|
||||
|
||||
# packages in the stdlib that may have installation metadata, but should not be
|
||||
# considered 'installed'. this theoretically could be determined based on
|
||||
# dist.location (py27:`sysconfig.get_paths()['stdlib']`,
|
||||
# py26:sysconfig.get_config_vars('LIBDEST')), but fear platform variation may
|
||||
# make this ineffective, so hard-coding
|
||||
stdlib_pkgs = {"python", "wsgiref", "argparse"}
|
||||
|
||||
|
||||
# windows detection, covers cpython and ironpython
|
||||
WINDOWS = (sys.platform.startswith("win") or
|
||||
(sys.platform == 'cli' and os.name == 'nt'))
|
||||
|
||||
|
||||
def samefile(file1, file2):
|
||||
"""Provide an alternative for os.path.samefile on Windows/Python2"""
|
||||
if hasattr(os.path, 'samefile'):
|
||||
return os.path.samefile(file1, file2)
|
||||
else:
|
||||
path1 = os.path.normcase(os.path.abspath(file1))
|
||||
path2 = os.path.normcase(os.path.abspath(file2))
|
||||
return path1 == path2
|
||||
|
||||
|
||||
if hasattr(shutil, 'get_terminal_size'):
|
||||
def get_terminal_size():
|
||||
"""
|
||||
Returns a tuple (x, y) representing the width(x) and the height(y)
|
||||
in characters of the terminal window.
|
||||
"""
|
||||
return tuple(shutil.get_terminal_size())
|
||||
else:
|
||||
def get_terminal_size():
|
||||
"""
|
||||
Returns a tuple (x, y) representing the width(x) and the height(y)
|
||||
in characters of the terminal window.
|
||||
"""
|
||||
def ioctl_GWINSZ(fd):
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
import struct
|
||||
cr = struct.unpack_from(
|
||||
'hh',
|
||||
fcntl.ioctl(fd, termios.TIOCGWINSZ, '12345678')
|
||||
)
|
||||
except:
|
||||
return None
|
||||
if cr == (0, 0):
|
||||
return None
|
||||
return cr
|
||||
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
||||
if not cr:
|
||||
try:
|
||||
fd = os.open(os.ctermid(), os.O_RDONLY)
|
||||
cr = ioctl_GWINSZ(fd)
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
if not cr:
|
||||
cr = (os.environ.get('LINES', 25), os.environ.get('COLUMNS', 80))
|
||||
return int(cr[1]), int(cr[0])
|
||||
@@ -0,0 +1,378 @@
|
||||
"""Configuration management setup
|
||||
|
||||
Some terminology:
|
||||
- name
|
||||
As written in config files.
|
||||
- value
|
||||
Value associated with a name
|
||||
- key
|
||||
Name combined with it's section (section.name)
|
||||
- variant
|
||||
A single word describing where the configuration key-value pair came from
|
||||
"""
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
|
||||
from notpip._vendor import six
|
||||
from notpip._vendor.six.moves import configparser
|
||||
|
||||
from notpip._internal.exceptions import ConfigurationError
|
||||
from notpip._internal.locations import (
|
||||
legacy_config_file, new_config_file, running_under_virtualenv,
|
||||
site_config_files, venv_config_file,
|
||||
)
|
||||
from notpip._internal.utils.misc import ensure_dir, enum
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple
|
||||
|
||||
RawConfigParser = configparser.RawConfigParser # Shorthand
|
||||
Kind = NewType("Kind", str)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# NOTE: Maybe use the optionx attribute to normalize keynames.
|
||||
def _normalize_name(name):
|
||||
# type: (str) -> str
|
||||
"""Make a name consistent regardless of source (environment or file)
|
||||
"""
|
||||
name = name.lower().replace('_', '-')
|
||||
if name.startswith('--'):
|
||||
name = name[2:] # only prefer long opts
|
||||
return name
|
||||
|
||||
|
||||
def _disassemble_key(name):
|
||||
# type: (str) -> List[str]
|
||||
return name.split(".", 1)
|
||||
|
||||
|
||||
# The kinds of configurations there are.
|
||||
kinds = enum(
|
||||
USER="user", # User Specific
|
||||
GLOBAL="global", # System Wide
|
||||
VENV="venv", # Virtual Environment Specific
|
||||
ENV="env", # from PIP_CONFIG_FILE
|
||||
ENV_VAR="env-var", # from Environment Variables
|
||||
)
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
"""Handles management of configuration.
|
||||
|
||||
Provides an interface to accessing and managing configuration files.
|
||||
|
||||
This class converts provides an API that takes "section.key-name" style
|
||||
keys and stores the value associated with it as "key-name" under the
|
||||
section "section".
|
||||
|
||||
This allows for a clean interface wherein the both the section and the
|
||||
key-name are preserved in an easy to manage form in the configuration files
|
||||
and the data stored is also nice.
|
||||
"""
|
||||
|
||||
def __init__(self, isolated, load_only=None):
|
||||
# type: (bool, Kind) -> None
|
||||
super(Configuration, self).__init__()
|
||||
|
||||
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.VENV, None]
|
||||
if load_only not in _valid_load_only:
|
||||
raise ConfigurationError(
|
||||
"Got invalid value for load_only - should be one of {}".format(
|
||||
", ".join(map(repr, _valid_load_only[:-1]))
|
||||
)
|
||||
)
|
||||
self.isolated = isolated # type: bool
|
||||
self.load_only = load_only # type: Optional[Kind]
|
||||
|
||||
# The order here determines the override order.
|
||||
self._override_order = [
|
||||
kinds.GLOBAL, kinds.USER, kinds.VENV, kinds.ENV, kinds.ENV_VAR
|
||||
]
|
||||
|
||||
self._ignore_env_names = ["version", "help"]
|
||||
|
||||
# Because we keep track of where we got the data from
|
||||
self._parsers = {
|
||||
variant: [] for variant in self._override_order
|
||||
} # type: Dict[Kind, List[Tuple[str, RawConfigParser]]]
|
||||
self._config = {
|
||||
variant: {} for variant in self._override_order
|
||||
} # type: Dict[Kind, Dict[str, Any]]
|
||||
self._modified_parsers = [] # type: List[Tuple[str, RawConfigParser]]
|
||||
|
||||
def load(self):
|
||||
# type: () -> None
|
||||
"""Loads configuration from configuration files and environment
|
||||
"""
|
||||
self._load_config_files()
|
||||
if not self.isolated:
|
||||
self._load_environment_vars()
|
||||
|
||||
def get_file_to_edit(self):
|
||||
# type: () -> Optional[str]
|
||||
"""Returns the file with highest priority in configuration
|
||||
"""
|
||||
assert self.load_only is not None, \
|
||||
"Need to be specified a file to be editing"
|
||||
|
||||
try:
|
||||
return self._get_parser_to_modify()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def items(self):
|
||||
# type: () -> Iterable[Tuple[str, Any]]
|
||||
"""Returns key-value pairs like dict.items() representing the loaded
|
||||
configuration
|
||||
"""
|
||||
return self._dictionary.items()
|
||||
|
||||
def get_value(self, key):
|
||||
# type: (str) -> Any
|
||||
"""Get a value from the configuration.
|
||||
"""
|
||||
try:
|
||||
return self._dictionary[key]
|
||||
except KeyError:
|
||||
raise ConfigurationError("No such key - {}".format(key))
|
||||
|
||||
def set_value(self, key, value):
|
||||
# type: (str, Any) -> None
|
||||
"""Modify a value in the configuration.
|
||||
"""
|
||||
self._ensure_have_load_only()
|
||||
|
||||
fname, parser = self._get_parser_to_modify()
|
||||
|
||||
if parser is not None:
|
||||
section, name = _disassemble_key(key)
|
||||
|
||||
# Modify the parser and the configuration
|
||||
if not parser.has_section(section):
|
||||
parser.add_section(section)
|
||||
parser.set(section, name, value)
|
||||
|
||||
self._config[self.load_only][key] = value
|
||||
self._mark_as_modified(fname, parser)
|
||||
|
||||
def unset_value(self, key):
|
||||
# type: (str) -> None
|
||||
"""Unset a value in the configuration.
|
||||
"""
|
||||
self._ensure_have_load_only()
|
||||
|
||||
if key not in self._config[self.load_only]:
|
||||
raise ConfigurationError("No such key - {}".format(key))
|
||||
|
||||
fname, parser = self._get_parser_to_modify()
|
||||
|
||||
if parser is not None:
|
||||
section, name = _disassemble_key(key)
|
||||
|
||||
# Remove the key in the parser
|
||||
modified_something = False
|
||||
if parser.has_section(section):
|
||||
# Returns whether the option was removed or not
|
||||
modified_something = parser.remove_option(section, name)
|
||||
|
||||
if modified_something:
|
||||
# name removed from parser, section may now be empty
|
||||
section_iter = iter(parser.items(section))
|
||||
try:
|
||||
val = six.next(section_iter)
|
||||
except StopIteration:
|
||||
val = None
|
||||
|
||||
if val is None:
|
||||
parser.remove_section(section)
|
||||
|
||||
self._mark_as_modified(fname, parser)
|
||||
else:
|
||||
raise ConfigurationError(
|
||||
"Fatal Internal error [id=1]. Please report as a bug."
|
||||
)
|
||||
|
||||
del self._config[self.load_only][key]
|
||||
|
||||
def save(self):
|
||||
# type: () -> None
|
||||
"""Save the currentin-memory state.
|
||||
"""
|
||||
self._ensure_have_load_only()
|
||||
|
||||
for fname, parser in self._modified_parsers:
|
||||
logger.info("Writing to %s", fname)
|
||||
|
||||
# Ensure directory exists.
|
||||
ensure_dir(os.path.dirname(fname))
|
||||
|
||||
with open(fname, "w") as f:
|
||||
parser.write(f) # type: ignore
|
||||
|
||||
#
|
||||
# Private routines
|
||||
#
|
||||
|
||||
def _ensure_have_load_only(self):
|
||||
# type: () -> None
|
||||
if self.load_only is None:
|
||||
raise ConfigurationError("Needed a specific file to be modifying.")
|
||||
logger.debug("Will be working with %s variant only", self.load_only)
|
||||
|
||||
@property
|
||||
def _dictionary(self):
|
||||
# type: () -> Dict[str, Any]
|
||||
"""A dictionary representing the loaded configuration.
|
||||
"""
|
||||
# NOTE: Dictionaries are not populated if not loaded. So, conditionals
|
||||
# are not needed here.
|
||||
retval = {}
|
||||
|
||||
for variant in self._override_order:
|
||||
retval.update(self._config[variant])
|
||||
|
||||
return retval
|
||||
|
||||
def _load_config_files(self):
|
||||
# type: () -> None
|
||||
"""Loads configuration from configuration files
|
||||
"""
|
||||
config_files = dict(self._iter_config_files())
|
||||
if config_files[kinds.ENV][0:1] == [os.devnull]:
|
||||
logger.debug(
|
||||
"Skipping loading configuration files due to "
|
||||
"environment's PIP_CONFIG_FILE being os.devnull"
|
||||
)
|
||||
return
|
||||
|
||||
for variant, files in config_files.items():
|
||||
for fname in files:
|
||||
# If there's specific variant set in `load_only`, load only
|
||||
# that variant, not the others.
|
||||
if self.load_only is not None and variant != self.load_only:
|
||||
logger.debug(
|
||||
"Skipping file '%s' (variant: %s)", fname, variant
|
||||
)
|
||||
continue
|
||||
|
||||
parser = self._load_file(variant, fname)
|
||||
|
||||
# Keeping track of the parsers used
|
||||
self._parsers[variant].append((fname, parser))
|
||||
|
||||
def _load_file(self, variant, fname):
|
||||
# type: (Kind, str) -> RawConfigParser
|
||||
logger.debug("For variant '%s', will try loading '%s'", variant, fname)
|
||||
parser = self._construct_parser(fname)
|
||||
|
||||
for section in parser.sections():
|
||||
items = parser.items(section)
|
||||
self._config[variant].update(self._normalized_keys(section, items))
|
||||
|
||||
return parser
|
||||
|
||||
def _construct_parser(self, fname):
|
||||
# type: (str) -> RawConfigParser
|
||||
parser = configparser.RawConfigParser()
|
||||
# If there is no such file, don't bother reading it but create the
|
||||
# parser anyway, to hold the data.
|
||||
# Doing this is useful when modifying and saving files, where we don't
|
||||
# need to construct a parser.
|
||||
if os.path.exists(fname):
|
||||
try:
|
||||
parser.read(fname)
|
||||
except UnicodeDecodeError:
|
||||
raise ConfigurationError((
|
||||
"ERROR: "
|
||||
"Configuration file contains invalid %s characters.\n"
|
||||
"Please fix your configuration, located at %s\n"
|
||||
) % (locale.getpreferredencoding(False), fname))
|
||||
return parser
|
||||
|
||||
def _load_environment_vars(self):
|
||||
# type: () -> None
|
||||
"""Loads configuration from environment variables
|
||||
"""
|
||||
self._config[kinds.ENV_VAR].update(
|
||||
self._normalized_keys(":env:", self._get_environ_vars())
|
||||
)
|
||||
|
||||
def _normalized_keys(self, section, items):
|
||||
# type: (str, Iterable[Tuple[str, Any]]) -> Dict[str, Any]
|
||||
"""Normalizes items to construct a dictionary with normalized keys.
|
||||
|
||||
This routine is where the names become keys and are made the same
|
||||
regardless of source - configuration files or environment.
|
||||
"""
|
||||
normalized = {}
|
||||
for name, val in items:
|
||||
key = section + "." + _normalize_name(name)
|
||||
normalized[key] = val
|
||||
return normalized
|
||||
|
||||
def _get_environ_vars(self):
|
||||
# type: () -> Iterable[Tuple[str, str]]
|
||||
"""Returns a generator with all environmental vars with prefix PIP_"""
|
||||
for key, val in os.environ.items():
|
||||
should_be_yielded = (
|
||||
key.startswith("PIP_") and
|
||||
key[4:].lower() not in self._ignore_env_names
|
||||
)
|
||||
if should_be_yielded:
|
||||
yield key[4:].lower(), val
|
||||
|
||||
# XXX: This is patched in the tests.
|
||||
def _iter_config_files(self):
|
||||
# type: () -> Iterable[Tuple[Kind, List[str]]]
|
||||
"""Yields variant and configuration files associated with it.
|
||||
|
||||
This should be treated like items of a dictionary.
|
||||
"""
|
||||
# SMELL: Move the conditions out of this function
|
||||
|
||||
# environment variables have the lowest priority
|
||||
config_file = os.environ.get('PIP_CONFIG_FILE', None)
|
||||
if config_file is not None:
|
||||
yield kinds.ENV, [config_file]
|
||||
else:
|
||||
yield kinds.ENV, []
|
||||
|
||||
# at the base we have any global configuration
|
||||
yield kinds.GLOBAL, list(site_config_files)
|
||||
|
||||
# per-user configuration next
|
||||
should_load_user_config = not self.isolated and not (
|
||||
config_file and os.path.exists(config_file)
|
||||
)
|
||||
if should_load_user_config:
|
||||
# The legacy config file is overridden by the new config file
|
||||
yield kinds.USER, [legacy_config_file, new_config_file]
|
||||
|
||||
# finally virtualenv configuration first trumping others
|
||||
if running_under_virtualenv():
|
||||
yield kinds.VENV, [venv_config_file]
|
||||
|
||||
def _get_parser_to_modify(self):
|
||||
# type: () -> Tuple[str, RawConfigParser]
|
||||
# Determine which parser to modify
|
||||
parsers = self._parsers[self.load_only]
|
||||
if not parsers:
|
||||
# This should not happen if everything works correctly.
|
||||
raise ConfigurationError(
|
||||
"Fatal Internal error [id=2]. Please report as a bug."
|
||||
)
|
||||
|
||||
# Use the highest priority parser.
|
||||
return parsers[-1]
|
||||
|
||||
# XXX: This is patched in the tests.
|
||||
def _mark_as_modified(self, fname, parser):
|
||||
# type: (str, RawConfigParser) -> None
|
||||
file_parser_tuple = (fname, parser)
|
||||
if file_parser_tuple not in self._modified_parsers:
|
||||
self._modified_parsers.append(file_parser_tuple)
|
||||
@@ -11,44 +11,49 @@ import platform
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from notpip._vendor import requests, six, urllib3
|
||||
from notpip._vendor.cachecontrol import CacheControlAdapter
|
||||
from notpip._vendor.cachecontrol.caches import FileCache
|
||||
from notpip._vendor.lockfile import LockError
|
||||
from notpip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
|
||||
from notpip._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
||||
from notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
from notpip._vendor.requests.structures import CaseInsensitiveDict
|
||||
from notpip._vendor.requests.utils import get_netrc_auth
|
||||
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
|
||||
# why we ignore the type on this import
|
||||
from notpip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from notpip._vendor.six.moves.urllib import request as urllib_request
|
||||
from notpip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
|
||||
from notpip._vendor.urllib3.util import IS_PYOPENSSL
|
||||
|
||||
import notpip
|
||||
from notpip._internal.compat import WINDOWS
|
||||
from notpip._internal.exceptions import HashMismatch, InstallationError
|
||||
from notpip._internal.locations import write_delete_marker_file
|
||||
from notpip._internal.models import PyPI
|
||||
from notpip._internal.utils.encoding import auto_decode
|
||||
from notpip._internal.utils.filesystem import check_path_owner
|
||||
from notpip._internal.utils.glibc import libc_ver
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import (
|
||||
ARCHIVE_EXTENSIONS, ask_path_exists, backup_dir, call_subprocess, consume,
|
||||
display_path, format_size, get_installed_version, rmtree, splitext,
|
||||
unpack_file,
|
||||
)
|
||||
from notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.utils.ui import DownloadProgressProvider
|
||||
from notpip._internal.vcs import vcs
|
||||
|
||||
try:
|
||||
import ssl # noqa
|
||||
HAS_TLS = True
|
||||
except ImportError:
|
||||
HAS_TLS = False
|
||||
|
||||
from pip9._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pip9._vendor.six.moves.urllib import request as urllib_request
|
||||
|
||||
import pip9
|
||||
|
||||
from pip9.exceptions import InstallationError, HashMismatch
|
||||
from pip9.models import PyPI
|
||||
from pip9.utils import (splitext, rmtree, format_size, display_path,
|
||||
backup_dir, ask_path_exists, unpack_file,
|
||||
ARCHIVE_EXTENSIONS, consume, call_subprocess)
|
||||
from pip9.utils.encoding import auto_decode
|
||||
from pip9.utils.filesystem import check_path_owner
|
||||
from pip9.utils.logging import indent_log
|
||||
from pip9.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||
from pip9.utils.glibc import libc_ver
|
||||
from pip9.utils.ui import DownloadProgressBar, DownloadProgressSpinner
|
||||
from pip9.locations import write_delete_marker_file
|
||||
from pip9.vcs import vcs
|
||||
from pip9._vendor import requests, six
|
||||
from pip9._vendor.requests.adapters import BaseAdapter, HTTPAdapter
|
||||
from pip9._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
||||
from pip9._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
from pip9._vendor.requests.utils import get_netrc_auth
|
||||
from pip9._vendor.requests.structures import CaseInsensitiveDict
|
||||
from pip9._vendor import urllib3
|
||||
from pip9._vendor.cachecontrol import CacheControlAdapter
|
||||
from pip9._vendor.cachecontrol.caches import FileCache
|
||||
from pip9._vendor.lockfile import LockError
|
||||
from pip9._vendor.six.moves import xmlrpc_client
|
||||
ssl = None
|
||||
|
||||
HAS_TLS = (ssl is not None) or IS_PYOPENSSL
|
||||
|
||||
__all__ = ['get_file_content',
|
||||
'is_url', 'url_to_path', 'path_to_url',
|
||||
@@ -65,7 +70,7 @@ def user_agent():
|
||||
Return a string representing the user agent.
|
||||
"""
|
||||
data = {
|
||||
"installer": {"name": "pip", "version": pip9.__version__},
|
||||
"installer": {"name": "pip", "version": pip.__version__},
|
||||
"python": platform.python_version(),
|
||||
"implementation": {
|
||||
"name": platform.python_implementation(),
|
||||
@@ -90,7 +95,7 @@ def user_agent():
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
from pip9._vendor import distro
|
||||
from notpip._vendor import distro
|
||||
distro_infos = dict(filter(
|
||||
lambda x: x[1],
|
||||
zip(["name", "version", "id"], distro.linux_distribution()),
|
||||
@@ -116,10 +121,13 @@ def user_agent():
|
||||
if platform.machine():
|
||||
data["cpu"] = platform.machine()
|
||||
|
||||
# Python 2.6 doesn't have ssl.OPENSSL_VERSION.
|
||||
if HAS_TLS and sys.version_info[:2] > (2, 6):
|
||||
if HAS_TLS:
|
||||
data["openssl_version"] = ssl.OPENSSL_VERSION
|
||||
|
||||
setuptools_version = get_installed_version("setuptools")
|
||||
if setuptools_version is not None:
|
||||
data["setuptools_version"] = setuptools_version
|
||||
|
||||
return "{data[installer][name]}/{data[installer][version]} {json}".format(
|
||||
data=data,
|
||||
json=json.dumps(data, separators=(",", ":"), sort_keys=True),
|
||||
@@ -203,8 +211,9 @@ class MultiDomainBasicAuth(AuthBase):
|
||||
if "@" in netloc:
|
||||
userinfo = netloc.rsplit("@", 1)[0]
|
||||
if ":" in userinfo:
|
||||
return userinfo.split(":", 1)
|
||||
return userinfo, None
|
||||
user, pwd = userinfo.split(":", 1)
|
||||
return (urllib_unquote(user), urllib_unquote(pwd))
|
||||
return urllib_unquote(userinfo), None
|
||||
return None, None
|
||||
|
||||
|
||||
@@ -251,7 +260,7 @@ class SafeFileCache(FileCache):
|
||||
super(SafeFileCache, self).__init__(*args, **kwargs)
|
||||
|
||||
# Check to ensure that the directory containing our cache directory
|
||||
# is owned by the user current executing pip9. If it does not exist
|
||||
# is owned by the user current executing pip. If it does not exist
|
||||
# we will check the parent directory until we find one that does exist.
|
||||
# If it is not owned by the user executing pip then we will disable
|
||||
# the cache and log a warning.
|
||||
@@ -342,7 +351,9 @@ class PipSession(requests.Session):
|
||||
# connection got interrupted in some way. A 503 error in general
|
||||
# is typically considered a transient error so we'll go ahead and
|
||||
# retry it.
|
||||
status_forcelist=[503],
|
||||
# A 500 may indicate transient error in Amazon S3
|
||||
# A 520 or 527 - may indicate transient error in CloudFlare
|
||||
status_forcelist=[500, 503, 520, 527],
|
||||
|
||||
# Add a small amount of back off between failed requests in
|
||||
# order to prevent hammering the service.
|
||||
@@ -376,7 +387,7 @@ class PipSession(requests.Session):
|
||||
# We want to use a non-validating adapter for any requests which are
|
||||
# deemed insecure.
|
||||
for host in insecure_hosts:
|
||||
self.mount("https://{0}/".format(host), insecure_adapter)
|
||||
self.mount("https://{}/".format(host), insecure_adapter)
|
||||
|
||||
def request(self, method, url, *args, **kwargs):
|
||||
# Allow setting a default timeout on a session
|
||||
@@ -388,7 +399,12 @@ class PipSession(requests.Session):
|
||||
|
||||
def get_file_content(url, comes_from=None, session=None):
|
||||
"""Gets the content of a file; it may be a filename, file: URL, or
|
||||
http: URL. Returns (location, content). Content is unicode."""
|
||||
http: URL. Returns (location, content). Content is unicode.
|
||||
|
||||
:param url: File path or url.
|
||||
:param comes_from: Origin description of requirements.
|
||||
:param session: Instance of pip.download.PipSession.
|
||||
"""
|
||||
if session is None:
|
||||
raise TypeError(
|
||||
"get_file_content() missing 1 required keyword argument: 'session'"
|
||||
@@ -509,14 +525,13 @@ def _progress_indicator(iterable, *args, **kwargs):
|
||||
return iterable
|
||||
|
||||
|
||||
def _download_url(resp, link, content_file, hashes):
|
||||
def _download_url(resp, link, content_file, hashes, progress_bar):
|
||||
try:
|
||||
total_length = int(resp.headers['content-length'])
|
||||
except (ValueError, KeyError, TypeError):
|
||||
total_length = 0
|
||||
|
||||
cached_resp = getattr(resp, "from_cache", False)
|
||||
|
||||
if logger.getEffectiveLevel() > logging.INFO:
|
||||
show_progress = False
|
||||
elif cached_resp:
|
||||
@@ -580,12 +595,12 @@ def _download_url(resp, link, content_file, hashes):
|
||||
url = link.url_without_fragment
|
||||
|
||||
if show_progress: # We don't show progress on cached responses
|
||||
progress_indicator = DownloadProgressProvider(progress_bar,
|
||||
max=total_length)
|
||||
if total_length:
|
||||
logger.info("Downloading %s (%s)", url, format_size(total_length))
|
||||
progress_indicator = DownloadProgressBar(max=total_length).iter
|
||||
else:
|
||||
logger.info("Downloading %s", url)
|
||||
progress_indicator = DownloadProgressSpinner().iter
|
||||
elif cached_resp:
|
||||
logger.info("Using cached %s", url)
|
||||
else:
|
||||
@@ -633,42 +648,41 @@ def _copy_file(filename, location, link):
|
||||
|
||||
|
||||
def unpack_http_url(link, location, download_dir=None,
|
||||
session=None, hashes=None):
|
||||
session=None, hashes=None, progress_bar="on"):
|
||||
if session is None:
|
||||
raise TypeError(
|
||||
"unpack_http_url() missing 1 required keyword argument: 'session'"
|
||||
)
|
||||
|
||||
temp_dir = tempfile.mkdtemp('-unpack', 'pip-')
|
||||
with TempDirectory(kind="unpack") as temp_dir:
|
||||
# If a download dir is specified, is the file already downloaded there?
|
||||
already_downloaded_path = None
|
||||
if download_dir:
|
||||
already_downloaded_path = _check_download_dir(link,
|
||||
download_dir,
|
||||
hashes)
|
||||
|
||||
# If a download dir is specified, is the file already downloaded there?
|
||||
already_downloaded_path = None
|
||||
if download_dir:
|
||||
already_downloaded_path = _check_download_dir(link,
|
||||
download_dir,
|
||||
hashes)
|
||||
if already_downloaded_path:
|
||||
from_path = already_downloaded_path
|
||||
content_type = mimetypes.guess_type(from_path)[0]
|
||||
else:
|
||||
# let's download to a tmp dir
|
||||
from_path, content_type = _download_http_url(link,
|
||||
session,
|
||||
temp_dir.path,
|
||||
hashes,
|
||||
progress_bar)
|
||||
|
||||
if already_downloaded_path:
|
||||
from_path = already_downloaded_path
|
||||
content_type = mimetypes.guess_type(from_path)[0]
|
||||
else:
|
||||
# let's download to a tmp dir
|
||||
from_path, content_type = _download_http_url(link,
|
||||
session,
|
||||
temp_dir,
|
||||
hashes)
|
||||
# unpack the archive to the build dir location. even when only
|
||||
# downloading archives, they have to be unpacked to parse dependencies
|
||||
unpack_file(from_path, location, content_type, link)
|
||||
|
||||
# unpack the archive to the build dir location. even when only downloading
|
||||
# archives, they have to be unpacked to parse dependencies
|
||||
unpack_file(from_path, location, content_type, link)
|
||||
# a download dir is specified; let's copy the archive there
|
||||
if download_dir and not already_downloaded_path:
|
||||
_copy_file(from_path, download_dir, link)
|
||||
|
||||
# a download dir is specified; let's copy the archive there
|
||||
if download_dir and not already_downloaded_path:
|
||||
_copy_file(from_path, download_dir, link)
|
||||
|
||||
if not already_downloaded_path:
|
||||
os.unlink(from_path)
|
||||
rmtree(temp_dir)
|
||||
if not already_downloaded_path:
|
||||
os.unlink(from_path)
|
||||
|
||||
|
||||
def unpack_file_url(link, location, download_dir=None, hashes=None):
|
||||
@@ -739,7 +753,7 @@ def _copy_dist_from_dir(link_path, location):
|
||||
|
||||
# build an sdist
|
||||
setup_py = 'setup.py'
|
||||
sdist_args = [os.environ['PIP_PYTHON_PATH']]
|
||||
sdist_args = [os.environ.get('PIP_PYTHON_PATH', sys.executable)]
|
||||
sdist_args.append('-c')
|
||||
sdist_args.append(SETUPTOOLS_SHIM % setup_py)
|
||||
sdist_args.append('sdist')
|
||||
@@ -785,7 +799,8 @@ class PipXmlrpcTransport(xmlrpc_client.Transport):
|
||||
|
||||
|
||||
def unpack_url(link, location, download_dir=None,
|
||||
only_download=False, session=None, hashes=None):
|
||||
only_download=False, session=None, hashes=None,
|
||||
progress_bar="on"):
|
||||
"""Unpack link.
|
||||
If link is a VCS link:
|
||||
if only_download, export into download_dir and ignore location
|
||||
@@ -818,13 +833,14 @@ def unpack_url(link, location, download_dir=None,
|
||||
location,
|
||||
download_dir,
|
||||
session,
|
||||
hashes=hashes
|
||||
hashes=hashes,
|
||||
progress_bar=progress_bar
|
||||
)
|
||||
if only_download:
|
||||
write_delete_marker_file(location)
|
||||
|
||||
|
||||
def _download_http_url(link, session, temp_dir, hashes):
|
||||
def _download_http_url(link, session, temp_dir, hashes, progress_bar):
|
||||
"""Download link url into temp_dir using provided session"""
|
||||
target_url = link.url.split('#', 1)[0]
|
||||
try:
|
||||
@@ -879,7 +895,7 @@ def _download_http_url(link, session, temp_dir, hashes):
|
||||
filename += ext
|
||||
file_path = os.path.join(temp_dir, filename)
|
||||
with open(file_path, 'wb') as content_file:
|
||||
_download_url(resp, link, content_file, hashes)
|
||||
_download_url(resp, link, content_file, hashes, progress_bar)
|
||||
return file_path, content_type
|
||||
|
||||
|
||||
@@ -3,13 +3,17 @@ from __future__ import absolute_import
|
||||
|
||||
from itertools import chain, groupby, repeat
|
||||
|
||||
from pip9._vendor.six import iteritems
|
||||
from notpip._vendor.six import iteritems
|
||||
|
||||
|
||||
class PipError(Exception):
|
||||
"""Base pip exception"""
|
||||
|
||||
|
||||
class ConfigurationError(PipError):
|
||||
"""General exception in configuration"""
|
||||
|
||||
|
||||
class InstallationError(PipError):
|
||||
"""General exception during installation"""
|
||||
|
||||
@@ -158,7 +162,8 @@ class HashMissing(HashError):
|
||||
self.gotten_hash = gotten_hash
|
||||
|
||||
def body(self):
|
||||
from pip9.utils.hashes import FAVORITE_HASH # Dodge circular import.
|
||||
# Dodge circular import.
|
||||
from notpip._internal.utils.hashes import FAVORITE_HASH
|
||||
|
||||
package = None
|
||||
if self.req:
|
||||
@@ -1,42 +1,42 @@
|
||||
"""Routines related to PyPI, indexes"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import cgi
|
||||
from collections import namedtuple
|
||||
import itertools
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
|
||||
from pip9._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pip9._vendor.six.moves.urllib import request as urllib_request
|
||||
from notpip._vendor import html5lib, requests, six
|
||||
from notpip._vendor.distlib.compat import unescape
|
||||
from notpip._vendor.packaging import specifiers
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
from notpip._vendor.packaging.version import parse as parse_version
|
||||
from notpip._vendor.requests.exceptions import SSLError
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from notpip._vendor.six.moves.urllib import request as urllib_request
|
||||
|
||||
from pip9.compat import ipaddress
|
||||
from pip9.utils import (
|
||||
cached_property, splitext, normalize_path,
|
||||
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS,
|
||||
)
|
||||
from pip9.utils.deprecation import RemovedInPip10Warning
|
||||
from pip9.utils.logging import indent_log
|
||||
from notpip.utils.packaging import check_requires_python
|
||||
from pip9.exceptions import (
|
||||
DistributionNotFound, BestVersionAlreadyInstalled, InvalidWheelFilename,
|
||||
from notpip._internal.compat import ipaddress
|
||||
from notpip._internal.download import HAS_TLS, is_url, path_to_url, url_to_path
|
||||
from notpip._internal.exceptions import (
|
||||
BestVersionAlreadyInstalled, DistributionNotFound, InvalidWheelFilename,
|
||||
UnsupportedWheel,
|
||||
)
|
||||
from pip9.download import HAS_TLS, is_url, path_to_url, url_to_path
|
||||
from notpip.wheel import Wheel, wheel_ext
|
||||
from pip9.pep425tags import get_supported
|
||||
from notpip._vendor import html5lib, requests, six
|
||||
from pip9._vendor.packaging.version import parse as parse_version
|
||||
from pip9._vendor.packaging.utils import canonicalize_name
|
||||
from notpip._vendor.packaging import specifiers
|
||||
from pip9._vendor.requests.exceptions import SSLError, HTTPError
|
||||
from pip9._vendor.distlib.compat import unescape
|
||||
|
||||
from notpip._internal.models import PyPI
|
||||
from notpip._internal.pep425tags import get_supported
|
||||
from notpip._internal.utils.deprecation import RemovedInPip11Warning
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import (
|
||||
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, cached_property, normalize_path,
|
||||
splitext,
|
||||
)
|
||||
from notpip._internal.utils.packaging import check_requires_python
|
||||
from notpip._internal.wheel import Wheel, wheel_ext
|
||||
|
||||
__all__ = ['FormatControl', 'fmt_ctl_handle_mutual_exclude', 'PackageFinder']
|
||||
|
||||
@@ -67,8 +67,8 @@ class InstallationCandidate(object):
|
||||
self.requires_python = requires_python
|
||||
|
||||
def __repr__(self):
|
||||
return "<InstallationCandidate({0!r}, {1!r}, {2!r})>".format(
|
||||
self.project, self.version, self.location
|
||||
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
|
||||
self.project, self.version, self.location,
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
@@ -193,6 +193,18 @@ class PackageFinder(object):
|
||||
)
|
||||
break
|
||||
|
||||
def get_formatted_locations(self):
|
||||
lines = []
|
||||
if self.index_urls and self.index_urls != [PyPI.simple_url]:
|
||||
lines.append(
|
||||
"Looking in indexes: {}".format(", ".join(self.index_urls))
|
||||
)
|
||||
if self.find_links:
|
||||
lines.append(
|
||||
"Looking in links: {}".format(", ".join(self.find_links))
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
def add_dependency_links(self, links):
|
||||
# # FIXME: this shouldn't be global list this, it should only
|
||||
# # apply to requirements of the package that specifies the
|
||||
@@ -202,7 +214,7 @@ class PackageFinder(object):
|
||||
warnings.warn(
|
||||
"Dependency Links processing has been deprecated and will be "
|
||||
"removed in a future release.",
|
||||
RemovedInPip10Warning,
|
||||
RemovedInPip11Warning,
|
||||
)
|
||||
self.dependency_links.extend(links)
|
||||
|
||||
@@ -224,8 +236,6 @@ class PackageFinder(object):
|
||||
|
||||
return extras
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _sort_locations(locations, expand_dir=False):
|
||||
"""
|
||||
@@ -265,14 +275,16 @@ class PackageFinder(object):
|
||||
else:
|
||||
logger.warning(
|
||||
"Url '%s' is ignored: it is neither a file "
|
||||
"nor a directory.", url)
|
||||
"nor a directory.", url,
|
||||
)
|
||||
elif is_url(url):
|
||||
# Only add url with clear scheme
|
||||
urls.append(url)
|
||||
else:
|
||||
logger.warning(
|
||||
"Url '%s' is ignored. It is either a non-existing "
|
||||
"path or lacks a specific scheme.", url)
|
||||
"path or lacks a specific scheme.", url,
|
||||
)
|
||||
|
||||
return files, urls
|
||||
|
||||
@@ -290,6 +302,7 @@ class PackageFinder(object):
|
||||
with the same version, would have to be considered equal
|
||||
"""
|
||||
support_num = len(self.valid_tags)
|
||||
build_tag = tuple()
|
||||
if candidate.location.is_wheel:
|
||||
# can raise InvalidWheelFilename
|
||||
wheel = Wheel(candidate.location.filename)
|
||||
@@ -304,9 +317,14 @@ class PackageFinder(object):
|
||||
pri = -(wheel.support_index_min(tags=tags))
|
||||
except TypeError:
|
||||
pri = -(support_num)
|
||||
|
||||
if wheel.build_tag is not None:
|
||||
match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
|
||||
build_tag_groups = match.groups()
|
||||
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
|
||||
else: # sdist
|
||||
pri = -(support_num)
|
||||
return (candidate.version, pri)
|
||||
return (candidate.version, build_tag, pri)
|
||||
|
||||
def _validate_secure_origin(self, logger, location):
|
||||
# Determine if this url used a secure transport mechanism
|
||||
@@ -370,9 +388,9 @@ class PackageFinder(object):
|
||||
# log a warning that we are ignoring it.
|
||||
logger.warning(
|
||||
"The repository located at %s is not a trusted or secure host and "
|
||||
"is being ignored. If this repository is available via HTTPS it "
|
||||
"is recommended to use HTTPS instead, otherwise you may silence "
|
||||
"this warning and allow it anyways with '--trusted-host %s'.",
|
||||
"is being ignored. If this repository is available via HTTPS we "
|
||||
"recommend you use HTTPS instead, otherwise you may silence "
|
||||
"this warning and allow it anyway with '--trusted-host %s'.",
|
||||
parsed.hostname,
|
||||
parsed.hostname,
|
||||
)
|
||||
@@ -412,13 +430,13 @@ class PackageFinder(object):
|
||||
index_locations = self._get_index_urls_locations(project_name)
|
||||
index_file_loc, index_url_loc = self._sort_locations(index_locations)
|
||||
fl_file_loc, fl_url_loc = self._sort_locations(
|
||||
self.find_links, expand_dir=True)
|
||||
self.find_links, expand_dir=True,
|
||||
)
|
||||
dep_file_loc, dep_url_loc = self._sort_locations(self.dependency_links)
|
||||
|
||||
file_locations = (
|
||||
Link(url) for url in itertools.chain(
|
||||
index_file_loc, fl_file_loc, dep_file_loc)
|
||||
)
|
||||
file_locations = (Link(url) for url in itertools.chain(
|
||||
index_file_loc, fl_file_loc, dep_file_loc,
|
||||
))
|
||||
|
||||
# We trust every url that the user has given us whether it was given
|
||||
# via --index-url or --find-links
|
||||
@@ -513,7 +531,6 @@ class PackageFinder(object):
|
||||
)
|
||||
else:
|
||||
compatible_versions = [str(c.version) for c in all_candidates]
|
||||
|
||||
applicable_candidates = [
|
||||
# Again, converting to str to deal with debundling.
|
||||
c for c in all_candidates if str(c.version) in compatible_versions
|
||||
@@ -537,7 +554,7 @@ class PackageFinder(object):
|
||||
req,
|
||||
', '.join(
|
||||
sorted(
|
||||
set(str(c.version) for c in all_candidates),
|
||||
{str(c.version) for c in all_candidates},
|
||||
key=parse_version,
|
||||
)
|
||||
)
|
||||
@@ -600,7 +617,7 @@ class PackageFinder(object):
|
||||
|
||||
try:
|
||||
page = self._get_page(location)
|
||||
except HTTPError as e:
|
||||
except requests.HTTPError as e:
|
||||
page = None
|
||||
if page is None:
|
||||
continue
|
||||
@@ -652,11 +669,13 @@ class PackageFinder(object):
|
||||
# Always ignore unsupported extensions even when we ignore compatibility
|
||||
if ext not in SUPPORTED_EXTENSIONS:
|
||||
self._log_skipped_link(
|
||||
link, 'unsupported archive format: %s' % ext)
|
||||
link, 'unsupported archive format: %s' % ext,
|
||||
)
|
||||
return
|
||||
if "binary" not in search.formats and ext == wheel_ext and not ignore_compatibility:
|
||||
self._log_skipped_link(
|
||||
link, 'No binaries permitted for %s' % search.supplied)
|
||||
link, 'No binaries permitted for %s' % search.supplied,
|
||||
)
|
||||
return
|
||||
if "macosx10" in link.path and ext == '.zip' and not ignore_compatibility:
|
||||
self._log_skipped_link(link, 'macosx10 one')
|
||||
@@ -682,7 +701,8 @@ class PackageFinder(object):
|
||||
# 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)
|
||||
link, 'No sources permitted for %s' % search.supplied,
|
||||
)
|
||||
return
|
||||
|
||||
if not version:
|
||||
@@ -713,6 +733,7 @@ class PackageFinder(object):
|
||||
link, link.requires_python)
|
||||
return
|
||||
logger.debug('Found link %s, version: %s', link, version)
|
||||
|
||||
return InstallationCandidate(search.supplied, version, link, link.requires_python)
|
||||
|
||||
def _get_page(self, link):
|
||||
@@ -783,7 +804,7 @@ class HTMLPage(object):
|
||||
url = url.split('#', 1)[0]
|
||||
|
||||
# Check for VCS schemes that do not support lookup as web pages.
|
||||
from pip9.vcs import VcsSupport
|
||||
from notpip._internal.vcs import VcsSupport
|
||||
for scheme in VcsSupport.schemes:
|
||||
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
|
||||
logger.debug('Cannot look at %s URL %s', scheme, link)
|
||||
@@ -848,8 +869,8 @@ class HTMLPage(object):
|
||||
except requests.HTTPError as exc:
|
||||
cls._handle_fail(link, exc, url)
|
||||
except SSLError as exc:
|
||||
reason = ("There was a problem confirming the ssl certificate: "
|
||||
"%s" % exc)
|
||||
reason = "There was a problem confirming the ssl certificate: "
|
||||
reason += str(exc)
|
||||
cls._handle_fail(link, reason, url, meth=logger.info)
|
||||
except requests.ConnectionError as exc:
|
||||
cls._handle_fail(link, "connection error: %s" % exc, url)
|
||||
@@ -869,7 +890,7 @@ class HTMLPage(object):
|
||||
def _get_content_type(url, session):
|
||||
"""Get the Content-Type of the given url, using a HEAD request"""
|
||||
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
|
||||
if scheme not in ('http', 'https'):
|
||||
if scheme not in {'http', 'https'}:
|
||||
# FIXME: some warning or something?
|
||||
# assertion error?
|
||||
return ''
|
||||
@@ -1065,7 +1086,7 @@ class Link(object):
|
||||
Determines if this points to an actual artifact (e.g. a tarball) or if
|
||||
it points to an "abstract" thing like a path or a VCS location.
|
||||
"""
|
||||
from pip9.vcs import vcs
|
||||
from notpip._internal.vcs import vcs
|
||||
|
||||
if self.scheme in vcs.all_schemes:
|
||||
return False
|
||||
@@ -1103,7 +1124,7 @@ def fmt_ctl_handle_mutual_exclude(value, target, other):
|
||||
|
||||
|
||||
def fmt_ctl_formats(fmt_ctl, canonical_name):
|
||||
result = set(["binary", "source"])
|
||||
result = {"binary", "source"}
|
||||
if canonical_name in fmt_ctl.only_binary:
|
||||
result.discard('source')
|
||||
elif canonical_name in fmt_ctl.no_binary:
|
||||
@@ -1117,15 +1138,8 @@ def fmt_ctl_formats(fmt_ctl, canonical_name):
|
||||
|
||||
def fmt_ctl_no_binary(fmt_ctl):
|
||||
fmt_ctl_handle_mutual_exclude(
|
||||
':all:', fmt_ctl.no_binary, fmt_ctl.only_binary)
|
||||
|
||||
|
||||
def fmt_ctl_no_use_wheel(fmt_ctl):
|
||||
fmt_ctl_no_binary(fmt_ctl)
|
||||
warnings.warn(
|
||||
'--no-use-wheel is deprecated and will be removed in the future. '
|
||||
' Please use --no-binary :all: instead.', RemovedInPip10Warning,
|
||||
stacklevel=2)
|
||||
':all:', fmt_ctl.no_binary, fmt_ctl.only_binary,
|
||||
)
|
||||
|
||||
|
||||
Search = namedtuple('Search', 'supplied canonical formats')
|
||||
@@ -3,15 +3,15 @@ from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import platform
|
||||
import site
|
||||
import sys
|
||||
import sysconfig
|
||||
from distutils import sysconfig as distutils_sysconfig
|
||||
from distutils.command.install import SCHEME_KEYS, install # type: ignore
|
||||
|
||||
from distutils import sysconfig
|
||||
from distutils.command.install import install, SCHEME_KEYS # noqa
|
||||
|
||||
from pip9.compat import WINDOWS, expanduser
|
||||
from pip9.utils import appdirs
|
||||
|
||||
from notpip._internal.compat import WINDOWS, expanduser
|
||||
from notpip._internal.utils import appdirs
|
||||
|
||||
# Application Directories
|
||||
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
|
||||
@@ -19,7 +19,7 @@ USER_CACHE_DIR = appdirs.user_cache_dir("pip")
|
||||
|
||||
DELETE_MARKER_MESSAGE = '''\
|
||||
This file is placed here by pip to indicate the source was put
|
||||
here by pip9.
|
||||
here by pip.
|
||||
|
||||
Once this package is successfully installed this source code will be
|
||||
deleted (unless you remove this file).
|
||||
@@ -80,8 +80,18 @@ src_prefix = os.path.abspath(src_prefix)
|
||||
|
||||
# FIXME doesn't account for venv linked to global site-packages
|
||||
|
||||
site_packages = sysconfig.get_python_lib()
|
||||
user_site = site.USER_SITE
|
||||
site_packages = sysconfig.get_path("purelib")
|
||||
# This is because of a bug in PyPy's sysconfig module, see
|
||||
# https://bitbucket.org/pypy/pypy/issues/2506/sysconfig-returns-incorrect-paths
|
||||
# for more information.
|
||||
if platform.python_implementation().lower() == "pypy":
|
||||
site_packages = distutils_sysconfig.get_python_lib()
|
||||
try:
|
||||
# Use getusersitepackages if this is present, as it ensures that the
|
||||
# value is initialised properly.
|
||||
user_site = site.getusersitepackages()
|
||||
except AttributeError:
|
||||
user_site = site.USER_SITE
|
||||
user_dir = expanduser('~')
|
||||
if WINDOWS:
|
||||
bin_py = os.path.join(sys.prefix, 'Scripts')
|
||||
@@ -91,7 +101,7 @@ if WINDOWS:
|
||||
bin_py = os.path.join(sys.prefix, 'bin')
|
||||
bin_user = os.path.join(user_site, 'bin')
|
||||
|
||||
config_basename = 'pip9.ini'
|
||||
config_basename = 'pip.ini'
|
||||
|
||||
legacy_storage_dir = os.path.join(user_dir, 'pip')
|
||||
legacy_config_file = os.path.join(
|
||||
@@ -102,14 +112,13 @@ else:
|
||||
bin_py = os.path.join(sys.prefix, 'bin')
|
||||
bin_user = os.path.join(user_site, 'bin')
|
||||
|
||||
config_basename = 'pip9.conf'
|
||||
config_basename = 'pip.conf'
|
||||
|
||||
legacy_storage_dir = os.path.join(user_dir, '.pip')
|
||||
legacy_config_file = os.path.join(
|
||||
legacy_storage_dir,
|
||||
config_basename,
|
||||
)
|
||||
|
||||
# Forcing to use /usr/local/bin for standard macOS framework installs
|
||||
# Also log to ~/Library/Logs/ for use with the Console.app log viewer
|
||||
if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/':
|
||||
@@ -120,6 +129,9 @@ site_config_files = [
|
||||
for path in appdirs.site_config_dirs('pip')
|
||||
]
|
||||
|
||||
venv_config_file = os.path.join(sys.prefix, config_basename)
|
||||
new_config_file = os.path.join(appdirs.user_config_dir("pip"), config_basename)
|
||||
|
||||
|
||||
def distutils_scheme(dist_name, user=False, home=None, root=None,
|
||||
isolated=False, prefix=None):
|
||||
@@ -143,7 +155,7 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
|
||||
# NOTE: setting user or home has the side-effect of creating the home dir
|
||||
# or user base for installations during finalize_options()
|
||||
# ideally, we'd prefer a scheme class that has no side-effects.
|
||||
assert not (user and prefix), "user={0} prefix={1}".format(user, prefix)
|
||||
assert not (user and prefix), "user={} prefix={}".format(user, prefix)
|
||||
i.user = user or i.user
|
||||
if user:
|
||||
i.prefix = ""
|
||||
@@ -0,0 +1,4 @@
|
||||
from notpip._internal.models.index import Index, PyPI
|
||||
|
||||
|
||||
__all__ = ["Index", "PyPI"]
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
from pip9._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
|
||||
class Index(object):
|
||||
@@ -7,7 +7,6 @@ class Index(object):
|
||||
self.netloc = urllib_parse.urlsplit(url).netloc
|
||||
self.simple_url = self.url_to_path('simple')
|
||||
self.pypi_url = self.url_to_path('pypi')
|
||||
self.pip_json_url = self.url_to_path('pypi/pip/json')
|
||||
|
||||
def url_to_path(self, path):
|
||||
return urllib_parse.urljoin(self.url, path)
|
||||
@@ -0,0 +1,106 @@
|
||||
"""Validation of dependencies of packages
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from notpip._internal.operations.prepare import make_abstract_dist
|
||||
|
||||
from notpip._internal.utils.misc import get_installed_distributions
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from notpip._internal.req.req_install import InstallRequirement
|
||||
from typing import Any, Dict, Iterator, Set, Tuple, List
|
||||
|
||||
# Shorthands
|
||||
PackageSet = Dict[str, 'PackageDetails']
|
||||
Missing = Tuple[str, Any]
|
||||
Conflicting = Tuple[str, str, Any]
|
||||
|
||||
MissingDict = Dict[str, List[Missing]]
|
||||
ConflictingDict = Dict[str, List[Conflicting]]
|
||||
CheckResult = Tuple[MissingDict, ConflictingDict]
|
||||
|
||||
PackageDetails = namedtuple('PackageDetails', ['version', 'requires'])
|
||||
|
||||
|
||||
def create_package_set_from_installed(**kwargs):
|
||||
# type: (**Any) -> PackageSet
|
||||
"""Converts a list of distributions into a PackageSet.
|
||||
"""
|
||||
# Default to using all packages installed on the system
|
||||
if kwargs == {}:
|
||||
kwargs = {"local_only": False, "skip": ()}
|
||||
retval = {}
|
||||
for dist in get_installed_distributions(**kwargs):
|
||||
name = canonicalize_name(dist.project_name)
|
||||
retval[name] = PackageDetails(dist.version, dist.requires())
|
||||
return retval
|
||||
|
||||
|
||||
def check_package_set(package_set):
|
||||
# type: (PackageSet) -> CheckResult
|
||||
"""Check if a package set is consistent
|
||||
"""
|
||||
missing = dict()
|
||||
conflicting = dict()
|
||||
|
||||
for package_name in package_set:
|
||||
# Info about dependencies of package_name
|
||||
missing_deps = set() # type: Set[Missing]
|
||||
conflicting_deps = set() # type: Set[Conflicting]
|
||||
|
||||
for req in package_set[package_name].requires:
|
||||
name = canonicalize_name(req.project_name) # type: str
|
||||
|
||||
# Check if it's missing
|
||||
if name not in package_set:
|
||||
missed = True
|
||||
if req.marker is not None:
|
||||
missed = req.marker.evaluate()
|
||||
if missed:
|
||||
missing_deps.add((name, req))
|
||||
continue
|
||||
|
||||
# Check if there's a conflict
|
||||
version = package_set[name].version # type: str
|
||||
if not req.specifier.contains(version, prereleases=True):
|
||||
conflicting_deps.add((name, version, req))
|
||||
|
||||
def str_key(x):
|
||||
return str(x)
|
||||
|
||||
if missing_deps:
|
||||
missing[package_name] = sorted(missing_deps, key=str_key)
|
||||
if conflicting_deps:
|
||||
conflicting[package_name] = sorted(conflicting_deps, key=str_key)
|
||||
|
||||
return missing, conflicting
|
||||
|
||||
|
||||
def check_install_conflicts(to_install):
|
||||
# type: (List[InstallRequirement]) -> Tuple[PackageSet, CheckResult]
|
||||
"""For checking if the dependency graph would be consistent after \
|
||||
installing given requirements
|
||||
"""
|
||||
# Start from the current state
|
||||
state = create_package_set_from_installed()
|
||||
_simulate_installation_of(to_install, state)
|
||||
return state, check_package_set(state)
|
||||
|
||||
|
||||
# NOTE from @pradyunsg
|
||||
# This required a minor update in dependency link handling logic over at
|
||||
# operations.prepare.IsSDist.dist() to get it working
|
||||
def _simulate_installation_of(to_install, state):
|
||||
# type: (List[InstallRequirement], PackageSet) -> None
|
||||
"""Computes the version of packages after installing to_install.
|
||||
"""
|
||||
|
||||
# Modify it as installing requirement_set would (assuming no errors)
|
||||
for inst_req in to_install:
|
||||
dist = make_abstract_dist(inst_req).dist(finder=None)
|
||||
name = canonicalize_name(dist.key)
|
||||
state[name] = PackageDetails(dist.version, dist.requires())
|
||||
@@ -0,0 +1,252 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from notpip._vendor import pkg_resources, six
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
from notpip._vendor.pkg_resources import RequirementParseError
|
||||
|
||||
from notpip._internal.exceptions import InstallationError
|
||||
from notpip._internal.req import InstallRequirement
|
||||
from notpip._internal.req.req_file import COMMENT_RE
|
||||
from notpip._internal.utils.deprecation import RemovedInPip11Warning
|
||||
from notpip._internal.utils.misc import (
|
||||
dist_is_editable, get_installed_distributions,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def freeze(
|
||||
requirement=None,
|
||||
find_links=None, local_only=None, user_only=None, skip_regex=None,
|
||||
isolated=False,
|
||||
wheel_cache=None,
|
||||
exclude_editable=False,
|
||||
skip=()):
|
||||
find_links = find_links or []
|
||||
skip_match = None
|
||||
|
||||
if skip_regex:
|
||||
skip_match = re.compile(skip_regex).search
|
||||
|
||||
dependency_links = []
|
||||
|
||||
for dist in pkg_resources.working_set:
|
||||
if dist.has_metadata('dependency_links.txt'):
|
||||
dependency_links.extend(
|
||||
dist.get_metadata_lines('dependency_links.txt')
|
||||
)
|
||||
for link in find_links:
|
||||
if '#egg=' in link:
|
||||
dependency_links.append(link)
|
||||
for link in find_links:
|
||||
yield '-f %s' % link
|
||||
installations = {}
|
||||
for dist in get_installed_distributions(local_only=local_only,
|
||||
skip=(),
|
||||
user_only=user_only):
|
||||
try:
|
||||
req = FrozenRequirement.from_dist(
|
||||
dist,
|
||||
dependency_links
|
||||
)
|
||||
except RequirementParseError:
|
||||
logger.warning(
|
||||
"Could not parse requirement: %s",
|
||||
dist.project_name
|
||||
)
|
||||
continue
|
||||
if exclude_editable and req.editable:
|
||||
continue
|
||||
installations[req.name] = req
|
||||
|
||||
if requirement:
|
||||
# the options that don't get turned into an InstallRequirement
|
||||
# should only be emitted once, even if the same option is in multiple
|
||||
# requirements files, so we need to keep track of what has been emitted
|
||||
# so that we don't emit it again if it's seen again
|
||||
emitted_options = set()
|
||||
# keep track of which files a requirement is in so that we can
|
||||
# give an accurate warning if a requirement appears multiple times.
|
||||
req_files = collections.defaultdict(list)
|
||||
for req_file_path in requirement:
|
||||
with open(req_file_path) as req_file:
|
||||
for line in req_file:
|
||||
if (not line.strip() or
|
||||
line.strip().startswith('#') or
|
||||
(skip_match and skip_match(line)) or
|
||||
line.startswith((
|
||||
'-r', '--requirement',
|
||||
'-Z', '--always-unzip',
|
||||
'-f', '--find-links',
|
||||
'-i', '--index-url',
|
||||
'--pre',
|
||||
'--trusted-host',
|
||||
'--process-dependency-links',
|
||||
'--extra-index-url'))):
|
||||
line = line.rstrip()
|
||||
if line not in emitted_options:
|
||||
emitted_options.add(line)
|
||||
yield line
|
||||
continue
|
||||
|
||||
if line.startswith('-e') or line.startswith('--editable'):
|
||||
if line.startswith('-e'):
|
||||
line = line[2:].strip()
|
||||
else:
|
||||
line = line[len('--editable'):].strip().lstrip('=')
|
||||
line_req = InstallRequirement.from_editable(
|
||||
line,
|
||||
isolated=isolated,
|
||||
wheel_cache=wheel_cache,
|
||||
)
|
||||
else:
|
||||
line_req = InstallRequirement.from_line(
|
||||
COMMENT_RE.sub('', line).strip(),
|
||||
isolated=isolated,
|
||||
wheel_cache=wheel_cache,
|
||||
)
|
||||
|
||||
if not line_req.name:
|
||||
logger.info(
|
||||
"Skipping line in requirement file [%s] because "
|
||||
"it's not clear what it would install: %s",
|
||||
req_file_path, line.strip(),
|
||||
)
|
||||
logger.info(
|
||||
" (add #egg=PackageName to the URL to avoid"
|
||||
" this warning)"
|
||||
)
|
||||
elif line_req.name not in installations:
|
||||
# either it's not installed, or it is installed
|
||||
# but has been processed already
|
||||
if not req_files[line_req.name]:
|
||||
logger.warning(
|
||||
"Requirement file [%s] contains %s, but that "
|
||||
"package is not installed",
|
||||
req_file_path,
|
||||
COMMENT_RE.sub('', line).strip(),
|
||||
)
|
||||
else:
|
||||
req_files[line_req.name].append(req_file_path)
|
||||
else:
|
||||
yield str(installations[line_req.name]).rstrip()
|
||||
del installations[line_req.name]
|
||||
req_files[line_req.name].append(req_file_path)
|
||||
|
||||
# Warn about requirements that were included multiple times (in a
|
||||
# single requirements file or in different requirements files).
|
||||
for name, files in six.iteritems(req_files):
|
||||
if len(files) > 1:
|
||||
logger.warning("Requirement %s included multiple times [%s]",
|
||||
name, ', '.join(sorted(set(files))))
|
||||
|
||||
yield(
|
||||
'## The following requirements were added by '
|
||||
'pip freeze:'
|
||||
)
|
||||
for installation in sorted(
|
||||
installations.values(), key=lambda x: x.name.lower()):
|
||||
if canonicalize_name(installation.name) not in skip:
|
||||
yield str(installation).rstrip()
|
||||
|
||||
|
||||
class FrozenRequirement(object):
|
||||
def __init__(self, name, req, editable, comments=()):
|
||||
self.name = name
|
||||
self.req = req
|
||||
self.editable = editable
|
||||
self.comments = comments
|
||||
|
||||
_rev_re = re.compile(r'-r(\d+)$')
|
||||
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist, dependency_links):
|
||||
location = os.path.normcase(os.path.abspath(dist.location))
|
||||
comments = []
|
||||
from notpip._internal.vcs import vcs, get_src_requirement
|
||||
if dist_is_editable(dist) and vcs.get_backend_name(location):
|
||||
editable = True
|
||||
try:
|
||||
req = get_src_requirement(dist, location)
|
||||
except InstallationError as exc:
|
||||
logger.warning(
|
||||
"Error when trying to get requirement for VCS system %s, "
|
||||
"falling back to uneditable format", exc
|
||||
)
|
||||
req = None
|
||||
if req is None:
|
||||
logger.warning(
|
||||
'Could not determine repository location of %s', location
|
||||
)
|
||||
comments.append(
|
||||
'## !! Could not determine repository location'
|
||||
)
|
||||
req = dist.as_requirement()
|
||||
editable = False
|
||||
else:
|
||||
editable = False
|
||||
req = dist.as_requirement()
|
||||
specs = req.specs
|
||||
assert len(specs) == 1 and specs[0][0] in ["==", "==="], \
|
||||
'Expected 1 spec with == or ===; specs = %r; dist = %r' % \
|
||||
(specs, dist)
|
||||
version = specs[0][1]
|
||||
ver_match = cls._rev_re.search(version)
|
||||
date_match = cls._date_re.search(version)
|
||||
if ver_match or date_match:
|
||||
svn_backend = vcs.get_backend('svn')
|
||||
if svn_backend:
|
||||
svn_location = svn_backend().get_location(
|
||||
dist,
|
||||
dependency_links,
|
||||
)
|
||||
if not svn_location:
|
||||
logger.warning(
|
||||
'Warning: cannot find svn location for %s', req,
|
||||
)
|
||||
comments.append(
|
||||
'## FIXME: could not find svn URL in dependency_links '
|
||||
'for this package:'
|
||||
)
|
||||
else:
|
||||
warnings.warn(
|
||||
"SVN editable detection based on dependency links "
|
||||
"will be dropped in the future.",
|
||||
RemovedInPip11Warning,
|
||||
)
|
||||
comments.append(
|
||||
'# Installing as editable to satisfy requirement %s:' %
|
||||
req
|
||||
)
|
||||
if ver_match:
|
||||
rev = ver_match.group(1)
|
||||
else:
|
||||
rev = '{%s}' % date_match.group(1)
|
||||
editable = True
|
||||
req = '%s@%s#egg=%s' % (
|
||||
svn_location,
|
||||
rev,
|
||||
cls.egg_name(dist)
|
||||
)
|
||||
return cls(dist.project_name, req, editable, comments)
|
||||
|
||||
@staticmethod
|
||||
def egg_name(dist):
|
||||
name = dist.egg_name()
|
||||
match = re.search(r'-py\d\.\d$', name)
|
||||
if match:
|
||||
name = name[:match.start()]
|
||||
return name
|
||||
|
||||
def __str__(self):
|
||||
req = self.req
|
||||
if self.editable:
|
||||
req = '-e %s' % req
|
||||
return '\n'.join(list(self.comments) + [str(req)]) + '\n'
|
||||
@@ -0,0 +1,380 @@
|
||||
"""Prepares a distribution for installation
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from copy import copy
|
||||
|
||||
from notpip._vendor import pkg_resources, requests
|
||||
|
||||
from notpip._internal.build_env import NoOpBuildEnvironment
|
||||
from notpip._internal.compat import expanduser
|
||||
from notpip._internal.download import (
|
||||
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
|
||||
)
|
||||
from notpip._internal.exceptions import (
|
||||
DirectoryUrlHashUnsupported, HashUnpinned, InstallationError,
|
||||
PreviousBuildDirError, VcsHashUnsupported,
|
||||
)
|
||||
from notpip._internal.index import FormatControl
|
||||
from notpip._internal.req.req_install import InstallRequirement
|
||||
from notpip._internal.utils.hashes import MissingHashes
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import (
|
||||
call_subprocess, display_path, normalize_path,
|
||||
)
|
||||
from notpip._internal.utils.ui import open_spinner
|
||||
from notpip._internal.vcs import vcs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_abstract_dist(req):
|
||||
"""Factory to make an abstract dist object.
|
||||
|
||||
Preconditions: Either an editable req with a source_dir, or satisfied_by or
|
||||
a wheel link, or a non-editable req with a source_dir.
|
||||
|
||||
:return: A concrete DistAbstraction.
|
||||
"""
|
||||
if req.editable:
|
||||
return IsSDist(req)
|
||||
elif req.link and req.link.is_wheel:
|
||||
return IsWheel(req)
|
||||
else:
|
||||
return IsSDist(req)
|
||||
|
||||
|
||||
def _install_build_reqs(finder, prefix, build_requirements):
|
||||
# NOTE: What follows is not a very good thing.
|
||||
# Eventually, this should move into the BuildEnvironment class and
|
||||
# that should handle all the isolation and sub-process invocation.
|
||||
finder = copy(finder)
|
||||
finder.format_control = FormatControl(set(), set([":all:"]))
|
||||
urls = [
|
||||
finder.find_requirement(
|
||||
InstallRequirement.from_line(r), upgrade=False).url
|
||||
for r in build_requirements
|
||||
]
|
||||
args = [
|
||||
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
|
||||
'--no-user', '--prefix', prefix,
|
||||
] + list(urls)
|
||||
|
||||
with open_spinner("Installing build dependencies") as spinner:
|
||||
call_subprocess(args, show_stdout=False, spinner=spinner)
|
||||
|
||||
|
||||
class DistAbstraction(object):
|
||||
"""Abstracts out the wheel vs non-wheel Resolver.resolve() logic.
|
||||
|
||||
The requirements for anything installable are as follows:
|
||||
- we must be able to determine the requirement name
|
||||
(or we can't correctly handle the non-upgrade case).
|
||||
- we must be able to generate a list of run-time dependencies
|
||||
without installing any additional packages (or we would
|
||||
have to either burn time by doing temporary isolated installs
|
||||
or alternatively violate pips 'don't start installing unless
|
||||
all requirements are available' rule - neither of which are
|
||||
desirable).
|
||||
- for packages with setup requirements, we must also be able
|
||||
to determine their requirements without installing additional
|
||||
packages (for the same reason as run-time dependencies)
|
||||
- we must be able to create a Distribution object exposing the
|
||||
above metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, req):
|
||||
self.req = req
|
||||
|
||||
def dist(self, finder):
|
||||
"""Return a setuptools Dist object."""
|
||||
raise NotImplementedError(self.dist)
|
||||
|
||||
def prep_for_dist(self, finder):
|
||||
"""Ensure that we can get a Dist for this requirement."""
|
||||
raise NotImplementedError(self.dist)
|
||||
|
||||
|
||||
class IsWheel(DistAbstraction):
|
||||
|
||||
def dist(self, finder):
|
||||
return list(pkg_resources.find_distributions(
|
||||
self.req.source_dir))[0]
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# FIXME:https://github.com/pypa/pip/issues/1112
|
||||
pass
|
||||
|
||||
|
||||
class IsSDist(DistAbstraction):
|
||||
|
||||
def dist(self, finder):
|
||||
dist = self.req.get_dist()
|
||||
# FIXME: shouldn't be globally added.
|
||||
if finder and dist.has_metadata('dependency_links.txt'):
|
||||
finder.add_dependency_links(
|
||||
dist.get_metadata_lines('dependency_links.txt')
|
||||
)
|
||||
return dist
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# Before calling "setup.py egg_info", we need to set-up the build
|
||||
# environment.
|
||||
build_requirements, isolate = self.req.get_pep_518_info()
|
||||
should_isolate = build_isolation and isolate
|
||||
|
||||
minimum_requirements = ('setuptools', 'wheel')
|
||||
missing_requirements = set(minimum_requirements) - set(
|
||||
pkg_resources.Requirement(r).key
|
||||
for r in build_requirements
|
||||
)
|
||||
if missing_requirements:
|
||||
def format_reqs(rs):
|
||||
return ' and '.join(map(repr, sorted(rs)))
|
||||
logger.warning(
|
||||
"Missing build time requirements in pyproject.toml for %s: "
|
||||
"%s.", self.req, format_reqs(missing_requirements)
|
||||
)
|
||||
logger.warning(
|
||||
"This version of pip does not implement PEP 517 so it cannot "
|
||||
"build a wheel without %s.", format_reqs(minimum_requirements)
|
||||
)
|
||||
|
||||
if should_isolate:
|
||||
with self.req.build_env:
|
||||
pass
|
||||
_install_build_reqs(finder, self.req.build_env.path,
|
||||
build_requirements)
|
||||
else:
|
||||
self.req.build_env = NoOpBuildEnvironment(no_clean=False)
|
||||
|
||||
self.req.run_egg_info()
|
||||
self.req.assert_source_matches_version()
|
||||
|
||||
|
||||
class Installed(DistAbstraction):
|
||||
|
||||
def dist(self, finder):
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prep_for_dist(self, finder):
|
||||
pass
|
||||
|
||||
|
||||
class RequirementPreparer(object):
|
||||
"""Prepares a Requirement
|
||||
"""
|
||||
|
||||
def __init__(self, build_dir, download_dir, src_dir, wheel_download_dir,
|
||||
progress_bar, build_isolation):
|
||||
super(RequirementPreparer, self).__init__()
|
||||
|
||||
self.src_dir = src_dir
|
||||
self.build_dir = build_dir
|
||||
|
||||
# Where still packed archives should be written to. If None, they are
|
||||
# not saved, and are deleted immediately after unpacking.
|
||||
self.download_dir = download_dir
|
||||
|
||||
# Where still-packed .whl files should be written to. If None, they are
|
||||
# written to the download_dir parameter. Separate to download_dir to
|
||||
# permit only keeping wheel archives for pip wheel.
|
||||
if wheel_download_dir:
|
||||
wheel_download_dir = normalize_path(wheel_download_dir)
|
||||
self.wheel_download_dir = wheel_download_dir
|
||||
|
||||
# NOTE
|
||||
# download_dir and wheel_download_dir overlap semantically and may
|
||||
# be combined if we're willing to have non-wheel archives present in
|
||||
# the wheelhouse output by 'pip wheel'.
|
||||
|
||||
self.progress_bar = progress_bar
|
||||
|
||||
# Is build isolation allowed?
|
||||
self.build_isolation = build_isolation
|
||||
|
||||
@property
|
||||
def _download_should_save(self):
|
||||
# TODO: Modify to reduce indentation needed
|
||||
if self.download_dir:
|
||||
self.download_dir = expanduser(self.download_dir)
|
||||
if os.path.exists(self.download_dir):
|
||||
return True
|
||||
else:
|
||||
logger.critical('Could not find download directory')
|
||||
raise InstallationError(
|
||||
"Could not find or access download directory '%s'"
|
||||
% display_path(self.download_dir))
|
||||
return False
|
||||
|
||||
def prepare_linked_requirement(self, req, session, finder,
|
||||
upgrade_allowed, require_hashes):
|
||||
"""Prepare a requirement that would be obtained from req.link
|
||||
"""
|
||||
# TODO: Breakup into smaller functions
|
||||
if req.link and req.link.scheme == 'file':
|
||||
path = url_to_path(req.link.url)
|
||||
logger.info('Processing %s', display_path(path))
|
||||
else:
|
||||
logger.info('Collecting %s', req)
|
||||
|
||||
with indent_log():
|
||||
# @@ if filesystem packages are not marked
|
||||
# editable in a req, a non deterministic error
|
||||
# occurs when the script attempts to unpack the
|
||||
# build directory
|
||||
req.ensure_has_source_dir(self.build_dir)
|
||||
# If a checkout exists, it's unwise to keep going. version
|
||||
# inconsistencies are logged later, but do not fail the
|
||||
# installation.
|
||||
# FIXME: this won't upgrade when there's an existing
|
||||
# package unpacked in `req.source_dir`
|
||||
# package unpacked in `req.source_dir`
|
||||
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
|
||||
raise PreviousBuildDirError(
|
||||
"pip can't proceed with requirements '%s' due to a"
|
||||
" pre-existing build directory (%s). This is "
|
||||
"likely due to a previous installation that failed"
|
||||
". pip is being responsible and not assuming it "
|
||||
"can delete this. Please delete it and try again."
|
||||
% (req, req.source_dir)
|
||||
)
|
||||
req.populate_link(finder, upgrade_allowed, require_hashes)
|
||||
|
||||
# We can't hit this spot and have populate_link return None.
|
||||
# req.satisfied_by is None here (because we're
|
||||
# guarded) and upgrade has no impact except when satisfied_by
|
||||
# is not None.
|
||||
# Then inside find_requirement existing_applicable -> False
|
||||
# If no new versions are found, DistributionNotFound is raised,
|
||||
# otherwise a result is guaranteed.
|
||||
assert req.link
|
||||
link = req.link
|
||||
|
||||
# Now that we have the real link, we can tell what kind of
|
||||
# requirements we have and raise some more informative errors
|
||||
# than otherwise. (For example, we can raise VcsHashUnsupported
|
||||
# for a VCS URL rather than HashMissing.)
|
||||
if require_hashes:
|
||||
# We could check these first 2 conditions inside
|
||||
# unpack_url and save repetition of conditions, but then
|
||||
# we would report less-useful error messages for
|
||||
# unhashable requirements, complaining that there's no
|
||||
# hash provided.
|
||||
if is_vcs_url(link):
|
||||
raise VcsHashUnsupported()
|
||||
elif is_file_url(link) and is_dir_url(link):
|
||||
raise DirectoryUrlHashUnsupported()
|
||||
if not req.original_link and not req.is_pinned:
|
||||
# Unpinned packages are asking for trouble when a new
|
||||
# version is uploaded. This isn't a security check, but
|
||||
# it saves users a surprising hash mismatch in the
|
||||
# future.
|
||||
#
|
||||
# file:/// URLs aren't pinnable, so don't complain
|
||||
# about them not being pinned.
|
||||
raise HashUnpinned()
|
||||
|
||||
hashes = req.hashes(trust_internet=not require_hashes)
|
||||
if require_hashes and not hashes:
|
||||
# Known-good hashes are missing for this requirement, so
|
||||
# shim it with a facade object that will provoke hash
|
||||
# computation and then raise a HashMissing exception
|
||||
# showing the user what the hash should be.
|
||||
hashes = MissingHashes()
|
||||
|
||||
try:
|
||||
download_dir = self.download_dir
|
||||
# We always delete unpacked sdists after pip ran.
|
||||
autodelete_unpacked = True
|
||||
if req.link.is_wheel and self.wheel_download_dir:
|
||||
# when doing 'pip wheel` we download wheels to a
|
||||
# dedicated dir.
|
||||
download_dir = self.wheel_download_dir
|
||||
if req.link.is_wheel:
|
||||
if download_dir:
|
||||
# When downloading, we only unpack wheels to get
|
||||
# metadata.
|
||||
autodelete_unpacked = True
|
||||
else:
|
||||
# When installing a wheel, we use the unpacked
|
||||
# wheel.
|
||||
autodelete_unpacked = False
|
||||
unpack_url(
|
||||
req.link, req.source_dir,
|
||||
download_dir, autodelete_unpacked,
|
||||
session=session, hashes=hashes,
|
||||
progress_bar=self.progress_bar
|
||||
)
|
||||
except requests.HTTPError as exc:
|
||||
logger.critical(
|
||||
'Could not install requirement %s because of error %s',
|
||||
req,
|
||||
exc,
|
||||
)
|
||||
raise InstallationError(
|
||||
'Could not install requirement %s because of HTTP '
|
||||
'error %s for URL %s' %
|
||||
(req, exc, req.link)
|
||||
)
|
||||
abstract_dist = make_abstract_dist(req)
|
||||
abstract_dist.prep_for_dist(finder, self.build_isolation)
|
||||
if self._download_should_save:
|
||||
# Make a .zip of the source_dir we already created.
|
||||
if req.link.scheme in vcs.all_schemes:
|
||||
req.archive(self.download_dir)
|
||||
return abstract_dist
|
||||
|
||||
def prepare_editable_requirement(self, req, require_hashes, use_user_site,
|
||||
finder):
|
||||
"""Prepare an editable requirement
|
||||
"""
|
||||
assert req.editable, "cannot prepare a non-editable req as editable"
|
||||
|
||||
logger.info('Obtaining %s', req)
|
||||
|
||||
with indent_log():
|
||||
if require_hashes:
|
||||
raise InstallationError(
|
||||
'The editable requirement %s cannot be installed when '
|
||||
'requiring hashes, because there is no single file to '
|
||||
'hash.' % req
|
||||
)
|
||||
req.ensure_has_source_dir(self.src_dir)
|
||||
req.update_editable(not self._download_should_save)
|
||||
|
||||
abstract_dist = make_abstract_dist(req)
|
||||
abstract_dist.prep_for_dist(finder, self.build_isolation)
|
||||
|
||||
if self._download_should_save:
|
||||
req.archive(self.download_dir)
|
||||
req.check_if_exists(use_user_site)
|
||||
|
||||
return abstract_dist
|
||||
|
||||
def prepare_installed_requirement(self, req, require_hashes, skip_reason):
|
||||
"""Prepare an already-installed requirement
|
||||
"""
|
||||
assert req.satisfied_by, "req should have been satisfied but isn't"
|
||||
assert skip_reason is not None, (
|
||||
"did not get skip reason skipped but req.satisfied_by "
|
||||
"is set to %r" % (req.satisfied_by,)
|
||||
)
|
||||
logger.info(
|
||||
'Requirement %s: %s (%s)',
|
||||
skip_reason, req, req.satisfied_by.version
|
||||
)
|
||||
with indent_log():
|
||||
if require_hashes:
|
||||
logger.debug(
|
||||
'Since it is already installed, we are trusting this '
|
||||
'package without checking its hash. To ensure a '
|
||||
'completely repeatable environment, install into an '
|
||||
'empty virtualenv.'
|
||||
)
|
||||
abstract_dist = Installed(req)
|
||||
|
||||
return abstract_dist
|
||||
+14
-21
@@ -1,21 +1,16 @@
|
||||
"""Generate and work with PEP 425 Compatibility Tags."""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import distutils.util
|
||||
import logging
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import sysconfig
|
||||
import warnings
|
||||
import platform
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
try:
|
||||
import sysconfig
|
||||
except ImportError: # pragma nocover
|
||||
# Python < 2.7
|
||||
import distutils.sysconfig as sysconfig
|
||||
import distutils.util
|
||||
|
||||
from pip9.compat import OrderedDict
|
||||
import pip9.utils.glibc
|
||||
import notpip._internal.utils.glibc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,7 +21,7 @@ def get_config_var(var):
|
||||
try:
|
||||
return sysconfig.get_config_var(var)
|
||||
except IOError as e: # Issue #1074
|
||||
warnings.warn("{0}".format(e), RuntimeWarning)
|
||||
warnings.warn("{}".format(e), RuntimeWarning)
|
||||
return None
|
||||
|
||||
|
||||
@@ -66,7 +61,7 @@ def get_impl_tag():
|
||||
"""
|
||||
Returns the Tag for this specific implementation.
|
||||
"""
|
||||
return "{0}{1}".format(get_abbr_impl(), get_impl_ver())
|
||||
return "{}{}".format(get_abbr_impl(), get_impl_ver())
|
||||
|
||||
|
||||
def get_flag(var, fallback, expected=True, warn=True):
|
||||
@@ -86,7 +81,7 @@ def get_abi_tag():
|
||||
(CPython 2, PyPy)."""
|
||||
soabi = get_config_var('SOABI')
|
||||
impl = get_abbr_impl()
|
||||
if not soabi and impl in ('cp', 'pp') and hasattr(sys, 'maxunicode'):
|
||||
if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
|
||||
d = ''
|
||||
m = ''
|
||||
u = ''
|
||||
@@ -133,7 +128,7 @@ def get_platform():
|
||||
elif machine == "ppc64" and _is_running_32bit():
|
||||
machine = "ppc"
|
||||
|
||||
return 'macosx_{0}_{1}_{2}'.format(split_ver[0], split_ver[1], machine)
|
||||
return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine)
|
||||
|
||||
# XXX remove distutils dependency
|
||||
result = distutils.util.get_platform().replace('.', '_').replace('-', '_')
|
||||
@@ -147,7 +142,7 @@ def get_platform():
|
||||
|
||||
def is_manylinux1_compatible():
|
||||
# Only Linux, and only x86-64 / i686
|
||||
if get_platform() not in ("linux_x86_64", "linux_i686"):
|
||||
if get_platform() not in {"linux_x86_64", "linux_i686"}:
|
||||
return False
|
||||
|
||||
# Check for presence of _manylinux module
|
||||
@@ -159,7 +154,7 @@ def is_manylinux1_compatible():
|
||||
pass
|
||||
|
||||
# Check glibc version. CentOS 5 uses glibc 2.5.
|
||||
return pip9.utils.glibc.have_compatible_glibc(2, 5)
|
||||
return pip._internal.utils.glibc.have_compatible_glibc(2, 5)
|
||||
|
||||
|
||||
def get_darwin_arches(major, minor, machine):
|
||||
@@ -273,7 +268,7 @@ def get_supported(versions=None, noarch=False, platform=None,
|
||||
match = _osx_arch_pat.match(arch)
|
||||
if match:
|
||||
name, major, minor, actual_arch = match.groups()
|
||||
tpl = '{0}_{1}_%i_%s'.format(name, major)
|
||||
tpl = '{}_{}_%i_%s'.format(name, major)
|
||||
arches = []
|
||||
for m in reversed(range(int(minor) + 1)):
|
||||
for a in get_darwin_arches(int(major), m, actual_arch):
|
||||
@@ -294,7 +289,7 @@ def get_supported(versions=None, noarch=False, platform=None,
|
||||
# abi3 modules compatible with older version of Python
|
||||
for version in versions[1:]:
|
||||
# abi3 was introduced in Python 3.2
|
||||
if version in ('31', '30'):
|
||||
if version in {'31', '30'}:
|
||||
break
|
||||
for abi in abi3s: # empty set if not Python 3
|
||||
for arch in arches:
|
||||
@@ -318,7 +313,5 @@ def get_supported(versions=None, noarch=False, platform=None,
|
||||
|
||||
return supported
|
||||
|
||||
supported_tags = get_supported()
|
||||
supported_tags_noarch = get_supported(noarch=True)
|
||||
|
||||
implementation_tag = get_impl_tag()
|
||||
@@ -0,0 +1,69 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from .req_install import InstallRequirement
|
||||
from .req_set import RequirementSet
|
||||
from .req_file import parse_requirements
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
|
||||
|
||||
__all__ = [
|
||||
"RequirementSet", "InstallRequirement",
|
||||
"parse_requirements", "install_given_reqs",
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def install_given_reqs(to_install, install_options, global_options=(),
|
||||
*args, **kwargs):
|
||||
"""
|
||||
Install everything in the given list.
|
||||
|
||||
(to be called after having downloaded and unpacked the packages)
|
||||
"""
|
||||
|
||||
if to_install:
|
||||
logger.info(
|
||||
'Installing collected packages: %s',
|
||||
', '.join([req.name for req in to_install]),
|
||||
)
|
||||
|
||||
with indent_log():
|
||||
for requirement in to_install:
|
||||
if requirement.conflicts_with:
|
||||
logger.info(
|
||||
'Found existing installation: %s',
|
||||
requirement.conflicts_with,
|
||||
)
|
||||
with indent_log():
|
||||
uninstalled_pathset = requirement.uninstall(
|
||||
auto_confirm=True
|
||||
)
|
||||
try:
|
||||
requirement.install(
|
||||
install_options,
|
||||
global_options,
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
except:
|
||||
should_rollback = (
|
||||
requirement.conflicts_with and
|
||||
not requirement.install_succeeded
|
||||
)
|
||||
# if install did not succeed, rollback previous uninstall
|
||||
if should_rollback:
|
||||
uninstalled_pathset.rollback()
|
||||
raise
|
||||
else:
|
||||
should_commit = (
|
||||
requirement.conflicts_with and
|
||||
requirement.install_succeeded
|
||||
)
|
||||
if should_commit:
|
||||
uninstalled_pathset.commit()
|
||||
requirement.remove_temporary_source()
|
||||
|
||||
return to_install
|
||||
+52
-56
@@ -4,28 +4,31 @@ Requirements file parsing
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import optparse
|
||||
import warnings
|
||||
|
||||
from pip9._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pip9._vendor.six.moves import filterfalse
|
||||
from notpip._vendor.six.moves import filterfalse
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
import pip9
|
||||
from pip9.download import get_file_content
|
||||
from pip9.req.req_install import InstallRequirement
|
||||
from pip9.exceptions import (RequirementsFileParseError)
|
||||
from pip9.utils.deprecation import RemovedInPip10Warning
|
||||
from pip9 import cmdoptions
|
||||
from notpip._internal import cmdoptions
|
||||
from notpip._internal.download import get_file_content
|
||||
from notpip._internal.exceptions import RequirementsFileParseError
|
||||
from notpip._internal.req.req_install import InstallRequirement
|
||||
|
||||
__all__ = ['parse_requirements']
|
||||
|
||||
SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
|
||||
COMMENT_RE = re.compile(r'(^|\s)+#.*$')
|
||||
|
||||
# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
|
||||
# variable name consisting of only uppercase letters, digits or the '_'
|
||||
# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
|
||||
# 2013 Edition.
|
||||
ENV_VAR_RE = re.compile(r'(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})')
|
||||
|
||||
SUPPORTED_OPTIONS = [
|
||||
cmdoptions.constraints,
|
||||
cmdoptions.editable,
|
||||
@@ -34,13 +37,6 @@ SUPPORTED_OPTIONS = [
|
||||
cmdoptions.index_url,
|
||||
cmdoptions.find_links,
|
||||
cmdoptions.extra_index_url,
|
||||
cmdoptions.allow_external,
|
||||
cmdoptions.allow_all_external,
|
||||
cmdoptions.no_allow_external,
|
||||
cmdoptions.allow_unsafe,
|
||||
cmdoptions.no_allow_unsafe,
|
||||
cmdoptions.use_wheel,
|
||||
cmdoptions.no_use_wheel,
|
||||
cmdoptions.always_unzip,
|
||||
cmdoptions.no_binary,
|
||||
cmdoptions.only_binary,
|
||||
@@ -66,13 +62,13 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None,
|
||||
"""Parse a requirements file and yield InstallRequirement instances.
|
||||
|
||||
:param filename: Path or url of requirements file.
|
||||
:param finder: Instance of pip9.index.PackageFinder.
|
||||
:param finder: Instance of pip.index.PackageFinder.
|
||||
:param comes_from: Origin description of requirements.
|
||||
:param options: cli options.
|
||||
:param session: Instance of pip9.download.PipSession.
|
||||
:param session: Instance of pip.download.PipSession.
|
||||
:param constraint: If true, parsing a constraint file rather than
|
||||
requirements file.
|
||||
:param wheel_cache: Instance of pip9.wheel.WheelCache
|
||||
:param wheel_cache: Instance of pip.wheel.WheelCache
|
||||
"""
|
||||
if session is None:
|
||||
raise TypeError(
|
||||
@@ -104,6 +100,7 @@ def preprocess(content, options):
|
||||
lines_enum = join_lines(lines_enum)
|
||||
lines_enum = ignore_comments(lines_enum)
|
||||
lines_enum = skip_regex(lines_enum, options)
|
||||
lines_enum = expand_env_variables(lines_enum)
|
||||
return lines_enum
|
||||
|
||||
|
||||
@@ -127,7 +124,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||
:param constraint: If True, parsing a constraints file.
|
||||
:param options: OptionParser options that we may update
|
||||
"""
|
||||
parser = build_parser()
|
||||
parser = build_parser(line)
|
||||
defaults = parser.get_default_values()
|
||||
defaults.index_url = None
|
||||
if finder:
|
||||
@@ -141,7 +138,8 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||
|
||||
# preserve for the nested code path
|
||||
line_comes_from = '%s %s (line %s)' % (
|
||||
'-c' if constraint else '-r', filename, line_number)
|
||||
'-c' if constraint else '-r', filename, line_number,
|
||||
)
|
||||
|
||||
# yield a line requirement
|
||||
if args_str:
|
||||
@@ -161,11 +159,9 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||
# yield an editable requirement
|
||||
elif opts.editables:
|
||||
isolated = options.isolated_mode if options else False
|
||||
default_vcs = options.default_vcs if options else None
|
||||
yield InstallRequirement.from_editable(
|
||||
opts.editables[0], comes_from=line_comes_from,
|
||||
constraint=constraint, default_vcs=default_vcs, isolated=isolated,
|
||||
wheel_cache=wheel_cache
|
||||
constraint=constraint, isolated=isolated, wheel_cache=wheel_cache
|
||||
)
|
||||
|
||||
# parse a nested requirements file
|
||||
@@ -198,35 +194,8 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||
|
||||
# set finder options
|
||||
elif finder:
|
||||
if opts.allow_external:
|
||||
warnings.warn(
|
||||
"--allow-external has been deprecated and will be removed in "
|
||||
"the future. Due to changes in the repository protocol, it no "
|
||||
"longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if opts.allow_all_external:
|
||||
warnings.warn(
|
||||
"--allow-all-external has been deprecated and will be removed "
|
||||
"in the future. Due to changes in the repository protocol, it "
|
||||
"no longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if opts.allow_unverified:
|
||||
warnings.warn(
|
||||
"--allow-unverified has been deprecated and will be removed "
|
||||
"in the future. Due to changes in the repository protocol, it "
|
||||
"no longer has any effect.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
|
||||
if opts.index_url:
|
||||
finder.index_urls = [opts.index_url]
|
||||
if opts.use_wheel is False:
|
||||
finder.use_wheel = False
|
||||
pip9.index.fmt_ctl_no_use_wheel(finder.format_control)
|
||||
if opts.no_index is True:
|
||||
finder.index_urls = []
|
||||
if opts.extra_index_urls:
|
||||
@@ -267,7 +236,7 @@ def break_args_options(line):
|
||||
return ' '.join(args), ' '.join(options)
|
||||
|
||||
|
||||
def build_parser():
|
||||
def build_parser(line):
|
||||
"""
|
||||
Return a parser for parsing requirement lines
|
||||
"""
|
||||
@@ -281,6 +250,8 @@ def build_parser():
|
||||
# By default optparse sys.exits on parsing errors. We want to wrap
|
||||
# that in our own exception.
|
||||
def parser_exit(self, msg):
|
||||
# add offending line
|
||||
msg = 'Invalid requirement: %s\n%s' % (line, msg)
|
||||
raise RequirementsFileParseError(msg)
|
||||
parser.exit = parser_exit
|
||||
|
||||
@@ -336,7 +307,32 @@ def skip_regex(lines_enum, options):
|
||||
skip_regex = options.skip_requirements_regex if options else None
|
||||
if skip_regex:
|
||||
pattern = re.compile(skip_regex)
|
||||
lines_enum = filterfalse(
|
||||
lambda e: pattern.search(e[1]),
|
||||
lines_enum)
|
||||
lines_enum = filterfalse(lambda e: pattern.search(e[1]), lines_enum)
|
||||
return lines_enum
|
||||
|
||||
|
||||
def expand_env_variables(lines_enum):
|
||||
"""Replace all environment variables that can be retrieved via `os.getenv`.
|
||||
|
||||
The only allowed format for environment variables defined in the
|
||||
requirement file is `${MY_VARIABLE_1}` to ensure two things:
|
||||
|
||||
1. Strings that contain a `$` aren't accidentally (partially) expanded.
|
||||
2. Ensure consistency across platforms for requirement files.
|
||||
|
||||
These points are the result of a discusssion on the `github pull
|
||||
request #3514 <https://github.com/pypa/pip/pull/3514>`_.
|
||||
|
||||
Valid characters in variable names follow the `POSIX standard
|
||||
<http://pubs.opengroup.org/onlinepubs/9699919799/>`_ and are limited
|
||||
to uppercase letter, digits and the `_` (underscore).
|
||||
"""
|
||||
for line_number, line in lines_enum:
|
||||
for env_var, var_name in ENV_VAR_RE.findall(line):
|
||||
value = os.getenv(var_name)
|
||||
if not value:
|
||||
continue
|
||||
|
||||
line = line.replace(env_var, value)
|
||||
|
||||
yield line_number, line
|
||||
+291
-379
@@ -5,54 +5,52 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import sysconfig
|
||||
import traceback
|
||||
import warnings
|
||||
import zipfile
|
||||
|
||||
from distutils import sysconfig
|
||||
from distutils.util import change_root
|
||||
from email.parser import FeedParser
|
||||
from email.parser import FeedParser # type: ignore
|
||||
|
||||
from notpip._vendor import pkg_resources, six
|
||||
from notpip._vendor import pkg_resources, pytoml, six
|
||||
from notpip._vendor.packaging import specifiers
|
||||
from notpip._vendor.packaging.markers import Marker
|
||||
from pip9._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
from pip9._vendor.packaging.utils import canonicalize_name
|
||||
from pip9._vendor.packaging.version import Version, parse as parse_version
|
||||
from pip9._vendor.six.moves import configparser
|
||||
from notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
from notpip._vendor.packaging.version import parse as parse_version
|
||||
from notpip._vendor.packaging.version import Version
|
||||
from notpip._vendor.pkg_resources import RequirementParseError, parse_requirements
|
||||
|
||||
import pip9.wheel
|
||||
|
||||
from pip9.compat import native_str, get_stdlib, WINDOWS
|
||||
from pip9.download import is_url, url_to_path, path_to_url, is_archive_file
|
||||
from pip9.exceptions import (
|
||||
InstallationError, UninstallationError,
|
||||
from notpip._internal import wheel
|
||||
from notpip._internal.build_env import BuildEnvironment
|
||||
from notpip._internal.compat import native_str
|
||||
from notpip._internal.download import (
|
||||
is_archive_file, is_url, path_to_url, url_to_path,
|
||||
)
|
||||
from pip9.locations import (
|
||||
bin_py, running_under_virtualenv, PIP_DELETE_MARKER_FILENAME, bin_user,
|
||||
from notpip._internal.exceptions import InstallationError, UninstallationError
|
||||
from notpip._internal.locations import (
|
||||
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
|
||||
)
|
||||
from pip9.utils import (
|
||||
display_path, rmtree, ask_path_exists, backup_dir, is_installable_dir,
|
||||
dist_in_usersite, dist_in_site_packages, egg_link_path,
|
||||
call_subprocess, read_text_file, FakeFile, _make_build_dir, ensure_dir,
|
||||
get_installed_version, normalize_path, dist_is_local,
|
||||
from notpip._internal.req.req_uninstall import UninstallPathSet
|
||||
from notpip._internal.utils.deprecation import RemovedInPip11Warning
|
||||
from notpip._internal.utils.hashes import Hashes
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import (
|
||||
_make_build_dir, ask_path_exists, backup_dir, call_subprocess,
|
||||
display_path, dist_in_site_packages, dist_in_usersite, ensure_dir,
|
||||
get_installed_version, is_installable_dir, read_text_file, rmtree,
|
||||
)
|
||||
|
||||
from pip9.utils.hashes import Hashes
|
||||
from pip9.utils.deprecation import RemovedInPip10Warning
|
||||
from pip9.utils.logging import indent_log
|
||||
from pip9.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||
from pip9.utils.ui import open_spinner
|
||||
from pip9.req.req_uninstall import UninstallPathSet
|
||||
from pip9.vcs import vcs
|
||||
from pip9.wheel import move_wheel_files, Wheel
|
||||
|
||||
from notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.utils.ui import open_spinner
|
||||
from notpip._internal.vcs import vcs
|
||||
from notpip._internal.wheel import Wheel, move_wheel_files
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
operators = specifiers.Specifier._operators.keys()
|
||||
|
||||
|
||||
def _strip_extras(path):
|
||||
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
|
||||
extras = None
|
||||
@@ -65,40 +63,42 @@ def _strip_extras(path):
|
||||
return path_no_extras, extras
|
||||
|
||||
|
||||
def _safe_extras(extras):
|
||||
return set(pkg_resources.safe_extra(extra) for extra in extras)
|
||||
|
||||
|
||||
class InstallRequirement(object):
|
||||
"""
|
||||
Represents something that may be installed later on, may have information
|
||||
about where to fetch the relavant requirement and also contains logic for
|
||||
installing the said requirement.
|
||||
"""
|
||||
|
||||
def __init__(self, req, comes_from, source_dir=None, editable=False,
|
||||
link=None, as_egg=False, update=True,
|
||||
pycompile=True, markers=None, isolated=False, options=None,
|
||||
wheel_cache=None, constraint=False):
|
||||
self.extras = ()
|
||||
if isinstance(req, six.string_types):
|
||||
try:
|
||||
req = Requirement(req)
|
||||
except InvalidRequirement:
|
||||
if os.path.sep in req:
|
||||
add_msg = "It looks like a path. Does it exist ?"
|
||||
elif '=' in req and not any(op in req for op in operators):
|
||||
add_msg = "= is not a valid operator. Did you mean == ?"
|
||||
else:
|
||||
add_msg = traceback.format_exc()
|
||||
raise InstallationError(
|
||||
"Invalid requirement: '%s'\n%s" % (req, add_msg))
|
||||
self.extras = _safe_extras(req.extras)
|
||||
|
||||
link=None, update=True, markers=None,
|
||||
isolated=False, options=None, wheel_cache=None,
|
||||
constraint=False, extras=()):
|
||||
assert req is None or isinstance(req, Requirement), req
|
||||
self.req = req
|
||||
self.comes_from = comes_from
|
||||
self.constraint = constraint
|
||||
self.source_dir = source_dir
|
||||
if source_dir is not None:
|
||||
self.source_dir = os.path.normpath(os.path.abspath(source_dir))
|
||||
else:
|
||||
self.source_dir = None
|
||||
self.editable = editable
|
||||
|
||||
self._wheel_cache = wheel_cache
|
||||
self.link = self.original_link = link
|
||||
self.as_egg = as_egg
|
||||
if link is not None:
|
||||
self.link = self.original_link = link
|
||||
else:
|
||||
from notpip._internal.index import Link
|
||||
self.link = self.original_link = req and req.url and Link(req.url)
|
||||
|
||||
if extras:
|
||||
self.extras = extras
|
||||
elif req:
|
||||
self.extras = {
|
||||
pkg_resources.safe_extra(extra) for extra in req.extras
|
||||
}
|
||||
else:
|
||||
self.extras = set()
|
||||
if markers is not None:
|
||||
self.markers = markers
|
||||
else:
|
||||
@@ -111,7 +111,7 @@ class InstallRequirement(object):
|
||||
# conflicts with another installed distribution:
|
||||
self.conflicts_with = None
|
||||
# Temporary build location
|
||||
self._temp_build_dir = None
|
||||
self._temp_build_dir = TempDirectory(kind="req-build")
|
||||
# Used to store the global directory where the _temp_build_dir should
|
||||
# have been created. Cf _correct_build_location method.
|
||||
self._ideal_build_dir = None
|
||||
@@ -120,44 +120,56 @@ class InstallRequirement(object):
|
||||
# Set to True after successful installation
|
||||
self.install_succeeded = None
|
||||
# UninstallPathSet of uninstalled distribution (for possible rollback)
|
||||
self.uninstalled = None
|
||||
# Set True if a legitimate do-nothing-on-uninstall has happened - e.g.
|
||||
# system site packages, stdlib packages.
|
||||
self.nothing_to_uninstall = False
|
||||
self.use_user_site = False
|
||||
self.target_dir = None
|
||||
self.uninstalled_pathset = None
|
||||
self.options = options if options else {}
|
||||
self.pycompile = pycompile
|
||||
# Set to True after successful preparation of this requirement
|
||||
self.prepared = False
|
||||
self.is_direct = False
|
||||
|
||||
self.isolated = isolated
|
||||
self.build_env = BuildEnvironment(no_clean=True)
|
||||
|
||||
@classmethod
|
||||
def from_editable(cls, editable_req, comes_from=None, default_vcs=None,
|
||||
isolated=False, options=None, wheel_cache=None,
|
||||
constraint=False):
|
||||
from pip9.index import Link
|
||||
def from_editable(cls, editable_req, comes_from=None, isolated=False,
|
||||
options=None, wheel_cache=None, constraint=False):
|
||||
from notpip._internal.index import Link
|
||||
|
||||
name, url, extras_override = parse_editable(
|
||||
editable_req, default_vcs)
|
||||
name, url, extras_override = parse_editable(editable_req)
|
||||
if url.startswith('file:'):
|
||||
source_dir = url_to_path(url)
|
||||
else:
|
||||
source_dir = None
|
||||
|
||||
res = cls(name, comes_from, source_dir=source_dir,
|
||||
editable=True,
|
||||
link=Link(url),
|
||||
constraint=constraint,
|
||||
isolated=isolated,
|
||||
options=options if options else {},
|
||||
wheel_cache=wheel_cache)
|
||||
if name is not None:
|
||||
try:
|
||||
req = Requirement(name)
|
||||
except InvalidRequirement:
|
||||
raise InstallationError("Invalid requirement: '%s'" % name)
|
||||
else:
|
||||
req = None
|
||||
return cls(
|
||||
req, comes_from, source_dir=source_dir,
|
||||
editable=True,
|
||||
link=Link(url),
|
||||
constraint=constraint,
|
||||
isolated=isolated,
|
||||
options=options if options else {},
|
||||
wheel_cache=wheel_cache,
|
||||
extras=extras_override or (),
|
||||
)
|
||||
|
||||
if extras_override is not None:
|
||||
res.extras = _safe_extras(extras_override)
|
||||
|
||||
return res
|
||||
@classmethod
|
||||
def from_req(cls, req, comes_from=None, isolated=False, wheel_cache=None):
|
||||
try:
|
||||
req = Requirement(req)
|
||||
except InvalidRequirement:
|
||||
raise InstallationError("Invalid requirement: '%s'" % req)
|
||||
if req.url:
|
||||
raise InstallationError(
|
||||
"Direct url requirement (like %s) are not allowed for "
|
||||
"dependencies" % req
|
||||
)
|
||||
return cls(req, comes_from, isolated=isolated, wheel_cache=wheel_cache)
|
||||
|
||||
@classmethod
|
||||
def from_line(
|
||||
@@ -166,7 +178,7 @@ class InstallRequirement(object):
|
||||
"""Creates an InstallRequirement from a name, which might be a
|
||||
requirement, directory containing 'setup.py', filename, or URL.
|
||||
"""
|
||||
from pip9.index import Link
|
||||
from notpip._internal.index import Link
|
||||
|
||||
if is_url(name):
|
||||
marker_sep = '; '
|
||||
@@ -191,9 +203,12 @@ class InstallRequirement(object):
|
||||
link = Link(name)
|
||||
else:
|
||||
p, extras = _strip_extras(path)
|
||||
if (os.path.isdir(p) and
|
||||
(os.path.sep in name or name.startswith('.'))):
|
||||
|
||||
looks_like_dir = os.path.isdir(p) and (
|
||||
os.path.sep in name or
|
||||
(os.path.altsep is not None and os.path.altsep in name) or
|
||||
name.startswith('.')
|
||||
)
|
||||
if looks_like_dir:
|
||||
if not is_installable_dir(p):
|
||||
raise InstallationError(
|
||||
"Directory %r is not installable. File 'setup.py' "
|
||||
@@ -228,16 +243,31 @@ class InstallRequirement(object):
|
||||
else:
|
||||
req = name
|
||||
|
||||
options = options if options else {}
|
||||
res = cls(req, comes_from, link=link, markers=markers,
|
||||
isolated=isolated, options=options,
|
||||
wheel_cache=wheel_cache, constraint=constraint)
|
||||
|
||||
if extras:
|
||||
res.extras = _safe_extras(
|
||||
Requirement('placeholder' + extras).extras)
|
||||
|
||||
return res
|
||||
extras = Requirement("placeholder" + extras.lower()).extras
|
||||
else:
|
||||
extras = ()
|
||||
if req is not None:
|
||||
try:
|
||||
req = Requirement(req)
|
||||
except InvalidRequirement:
|
||||
if os.path.sep in req:
|
||||
add_msg = "It looks like a path."
|
||||
add_msg += deduce_helpful_msg(req)
|
||||
elif '=' in req and not any(op in req for op in operators):
|
||||
add_msg = "= is not a valid operator. Did you mean == ?"
|
||||
else:
|
||||
add_msg = traceback.format_exc()
|
||||
raise InstallationError(
|
||||
"Invalid requirement: '%s'\n%s" % (req, add_msg))
|
||||
return cls(
|
||||
req, comes_from, link=link, markers=markers,
|
||||
isolated=isolated,
|
||||
options=options if options else {},
|
||||
wheel_cache=wheel_cache,
|
||||
constraint=constraint,
|
||||
extras=extras,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
if self.req:
|
||||
@@ -277,7 +307,7 @@ class InstallRequirement(object):
|
||||
self.link = finder.find_requirement(self, upgrade)
|
||||
if self._wheel_cache is not None and not require_hashes:
|
||||
old_link = self.link
|
||||
self.link = self._wheel_cache.cached_wheel(self.link, self.name)
|
||||
self.link = self._wheel_cache.get(self.link, self.name)
|
||||
if old_link != self.link:
|
||||
logger.debug('Using cached wheel link: %s', self.link)
|
||||
|
||||
@@ -293,7 +323,7 @@ class InstallRequirement(object):
|
||||
"""
|
||||
specifiers = self.specifier
|
||||
return (len(specifiers) == 1 and
|
||||
next(iter(specifiers)).operator in ('==', '==='))
|
||||
next(iter(specifiers)).operator in {'==', '==='})
|
||||
|
||||
def from_path(self):
|
||||
if self.req is None:
|
||||
@@ -309,8 +339,9 @@ class InstallRequirement(object):
|
||||
return s
|
||||
|
||||
def build_location(self, build_dir):
|
||||
if self._temp_build_dir is not None:
|
||||
return self._temp_build_dir
|
||||
assert build_dir is not None
|
||||
if self._temp_build_dir.path is not None:
|
||||
return self._temp_build_dir.path
|
||||
if self.req is None:
|
||||
# for requirement via a path to a directory: the name of the
|
||||
# package is not available yet so we create a temp directory
|
||||
@@ -319,11 +350,10 @@ class InstallRequirement(object):
|
||||
# Some systems have /tmp as a symlink which confuses custom
|
||||
# builds (such as numpy). Thus, we ensure that the real path
|
||||
# is returned.
|
||||
self._temp_build_dir = os.path.realpath(
|
||||
tempfile.mkdtemp('-build', 'pip-')
|
||||
)
|
||||
self._temp_build_dir.create()
|
||||
self._ideal_build_dir = build_dir
|
||||
return self._temp_build_dir
|
||||
|
||||
return self._temp_build_dir.path
|
||||
if self.editable:
|
||||
name = self.name.lower()
|
||||
else:
|
||||
@@ -348,10 +378,11 @@ class InstallRequirement(object):
|
||||
if self.source_dir is not None:
|
||||
return
|
||||
assert self.req is not None
|
||||
assert self._temp_build_dir
|
||||
assert self._ideal_build_dir
|
||||
old_location = self._temp_build_dir
|
||||
self._temp_build_dir = None
|
||||
assert self._temp_build_dir.path
|
||||
assert self._ideal_build_dir.path
|
||||
old_location = self._temp_build_dir.path
|
||||
self._temp_build_dir.path = None
|
||||
|
||||
new_location = self.build_location(self._ideal_build_dir)
|
||||
if os.path.exists(new_location):
|
||||
raise InstallationError(
|
||||
@@ -362,9 +393,9 @@ class InstallRequirement(object):
|
||||
self, display_path(old_location), display_path(new_location),
|
||||
)
|
||||
shutil.move(old_location, new_location)
|
||||
self._temp_build_dir = new_location
|
||||
self._temp_build_dir.path = new_location
|
||||
self._ideal_build_dir = None
|
||||
self.source_dir = new_location
|
||||
self.source_dir = os.path.normpath(os.path.abspath(new_location))
|
||||
self._egg_info_path = None
|
||||
|
||||
@property
|
||||
@@ -382,18 +413,6 @@ class InstallRequirement(object):
|
||||
@property
|
||||
def setup_py(self):
|
||||
assert self.source_dir, "No source dir for %s" % self
|
||||
try:
|
||||
import setuptools # noqa
|
||||
except ImportError:
|
||||
if get_installed_version('setuptools') is None:
|
||||
add_msg = "Please install setuptools."
|
||||
else:
|
||||
add_msg = traceback.format_exc()
|
||||
# Setuptools is not available
|
||||
raise InstallationError(
|
||||
"Could not import setuptools which is required to "
|
||||
"install from a source distribution.\n%s" % add_msg
|
||||
)
|
||||
|
||||
setup_py = os.path.join(self.setup_py_dir, 'setup.py')
|
||||
|
||||
@@ -403,6 +422,34 @@ class InstallRequirement(object):
|
||||
|
||||
return setup_py
|
||||
|
||||
@property
|
||||
def pyproject_toml(self):
|
||||
assert self.source_dir, "No source dir for %s" % self
|
||||
|
||||
pp_toml = os.path.join(self.setup_py_dir, 'pyproject.toml')
|
||||
|
||||
# Python2 __file__ should not be unicode
|
||||
if six.PY2 and isinstance(pp_toml, six.text_type):
|
||||
pp_toml = pp_toml.encode(sys.getfilesystemencoding())
|
||||
|
||||
return pp_toml
|
||||
|
||||
def get_pep_518_info(self):
|
||||
"""Get a list of the packages required to build the project, if any,
|
||||
and a flag indicating whether pyproject.toml is present, indicating
|
||||
that the build should be isolated.
|
||||
|
||||
Build requirements can be specified in a pyproject.toml, as described
|
||||
in PEP 518. If this file exists but doesn't specify build
|
||||
requirements, pip will default to installing setuptools and wheel.
|
||||
"""
|
||||
if os.path.isfile(self.pyproject_toml):
|
||||
with open(self.pyproject_toml) as f:
|
||||
pp_toml = pytoml.load(f)
|
||||
build_sys = pp_toml.get('build-system', {})
|
||||
return (build_sys.get('requires', ['setuptools', 'wheel']), True)
|
||||
return (['setuptools', 'wheel'], False)
|
||||
|
||||
def run_egg_info(self):
|
||||
assert self.source_dir
|
||||
if self.name:
|
||||
@@ -418,7 +465,7 @@ class InstallRequirement(object):
|
||||
|
||||
with indent_log():
|
||||
script = SETUPTOOLS_SHIM % self.setup_py
|
||||
base_cmd = [os.environ['PIP_PYTHON_PATH'], '-c', script]
|
||||
base_cmd = [os.environ.get('PIP_PYTHON_PATH', sys.executable), '-c', script]
|
||||
if self.isolated:
|
||||
base_cmd += ["--no-user-cfg"]
|
||||
egg_info_cmd = base_cmd + ['egg_info']
|
||||
@@ -431,11 +478,12 @@ class InstallRequirement(object):
|
||||
egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info')
|
||||
ensure_dir(egg_info_dir)
|
||||
egg_base_option = ['--egg-base', 'pip-egg-info']
|
||||
call_subprocess(
|
||||
egg_info_cmd + egg_base_option,
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
command_desc='python setup.py egg_info')
|
||||
with self.build_env:
|
||||
call_subprocess(
|
||||
egg_info_cmd + egg_base_option,
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
command_desc='python setup.py egg_info')
|
||||
|
||||
if not self.req:
|
||||
if isinstance(parse_version(self.pkg_info()["Version"]), Version):
|
||||
@@ -506,7 +554,7 @@ class InstallRequirement(object):
|
||||
elif dir == 'test' or dir == 'tests':
|
||||
dirs.remove(dir)
|
||||
filenames.extend([os.path.join(root, dir)
|
||||
for dir in dirs])
|
||||
for dir in dirs])
|
||||
filenames = [f for f in filenames if f.endswith('.egg-info')]
|
||||
|
||||
if not filenames:
|
||||
@@ -551,7 +599,7 @@ class InstallRequirement(object):
|
||||
logger.warning(
|
||||
'Requested %s, but installing version %s',
|
||||
self,
|
||||
self.installed_version,
|
||||
version,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
@@ -590,7 +638,8 @@ class InstallRequirement(object):
|
||||
'Unexpected version control type (in %s): %s'
|
||||
% (self.link, vc_type))
|
||||
|
||||
def uninstall(self, auto_confirm=False):
|
||||
def uninstall(self, auto_confirm=False, verbose=False,
|
||||
use_user_site=False):
|
||||
"""
|
||||
Uninstall the distribution currently satisfying this requirement.
|
||||
|
||||
@@ -603,171 +652,14 @@ class InstallRequirement(object):
|
||||
linked to global site-packages.
|
||||
|
||||
"""
|
||||
if not self.check_if_exists():
|
||||
raise UninstallationError(
|
||||
"Cannot uninstall requirement %s, not installed" % (self.name,)
|
||||
)
|
||||
if not self.check_if_exists(use_user_site):
|
||||
logger.warning("Skipping %s as it is not installed.", self.name)
|
||||
return
|
||||
dist = self.satisfied_by or self.conflicts_with
|
||||
|
||||
dist_path = normalize_path(dist.location)
|
||||
if not dist_is_local(dist):
|
||||
logger.info(
|
||||
"Not uninstalling %s at %s, outside environment %s",
|
||||
dist.key,
|
||||
dist_path,
|
||||
sys.prefix,
|
||||
)
|
||||
self.nothing_to_uninstall = True
|
||||
return
|
||||
|
||||
if dist_path in get_stdlib():
|
||||
logger.info(
|
||||
"Not uninstalling %s at %s, as it is in the standard library.",
|
||||
dist.key,
|
||||
dist_path,
|
||||
)
|
||||
self.nothing_to_uninstall = True
|
||||
return
|
||||
|
||||
paths_to_remove = UninstallPathSet(dist)
|
||||
develop_egg_link = egg_link_path(dist)
|
||||
develop_egg_link_egg_info = '{0}.egg-info'.format(
|
||||
pkg_resources.to_filename(dist.project_name))
|
||||
egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info)
|
||||
# Special case for distutils installed package
|
||||
distutils_egg_info = getattr(dist._provider, 'path', None)
|
||||
|
||||
# Uninstall cases order do matter as in the case of 2 installs of the
|
||||
# same package, pip needs to uninstall the currently detected version
|
||||
if (egg_info_exists and dist.egg_info.endswith('.egg-info') and
|
||||
not dist.egg_info.endswith(develop_egg_link_egg_info)):
|
||||
# if dist.egg_info.endswith(develop_egg_link_egg_info), we
|
||||
# are in fact in the develop_egg_link case
|
||||
paths_to_remove.add(dist.egg_info)
|
||||
if dist.has_metadata('installed-files.txt'):
|
||||
for installed_file in dist.get_metadata(
|
||||
'installed-files.txt').splitlines():
|
||||
path = os.path.normpath(
|
||||
os.path.join(dist.egg_info, installed_file)
|
||||
)
|
||||
paths_to_remove.add(path)
|
||||
# FIXME: need a test for this elif block
|
||||
# occurs with --single-version-externally-managed/--record outside
|
||||
# of pip
|
||||
elif dist.has_metadata('top_level.txt'):
|
||||
if dist.has_metadata('namespace_packages.txt'):
|
||||
namespaces = dist.get_metadata('namespace_packages.txt')
|
||||
else:
|
||||
namespaces = []
|
||||
for top_level_pkg in [
|
||||
p for p
|
||||
in dist.get_metadata('top_level.txt').splitlines()
|
||||
if p and p not in namespaces]:
|
||||
path = os.path.join(dist.location, top_level_pkg)
|
||||
paths_to_remove.add(path)
|
||||
paths_to_remove.add(path + '.py')
|
||||
paths_to_remove.add(path + '.pyc')
|
||||
paths_to_remove.add(path + '.pyo')
|
||||
|
||||
elif distutils_egg_info:
|
||||
warnings.warn(
|
||||
"Uninstalling a distutils installed project ({0}) has been "
|
||||
"deprecated and will be removed in a future version. This is "
|
||||
"due to the fact that uninstalling a distutils project will "
|
||||
"only partially uninstall the project.".format(self.name),
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
paths_to_remove.add(distutils_egg_info)
|
||||
|
||||
elif dist.location.endswith('.egg'):
|
||||
# package installed by easy_install
|
||||
# We cannot match on dist.egg_name because it can slightly vary
|
||||
# i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
|
||||
paths_to_remove.add(dist.location)
|
||||
easy_install_egg = os.path.split(dist.location)[1]
|
||||
easy_install_pth = os.path.join(os.path.dirname(dist.location),
|
||||
'easy-install.pth')
|
||||
paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)
|
||||
|
||||
elif egg_info_exists and dist.egg_info.endswith('.dist-info'):
|
||||
for path in pip9.wheel.uninstallation_paths(dist):
|
||||
paths_to_remove.add(path)
|
||||
|
||||
elif develop_egg_link:
|
||||
# develop egg
|
||||
with open(develop_egg_link, 'r') as fh:
|
||||
link_pointer = os.path.normcase(fh.readline().strip())
|
||||
assert (link_pointer == dist.location), (
|
||||
'Egg-link %s does not match installed location of %s '
|
||||
'(at %s)' % (link_pointer, self.name, dist.location)
|
||||
)
|
||||
paths_to_remove.add(develop_egg_link)
|
||||
easy_install_pth = os.path.join(os.path.dirname(develop_egg_link),
|
||||
'easy-install.pth')
|
||||
paths_to_remove.add_pth(easy_install_pth, dist.location)
|
||||
|
||||
else:
|
||||
logger.debug(
|
||||
'Not sure how to uninstall: %s - Check: %s',
|
||||
dist, dist.location)
|
||||
|
||||
# find distutils scripts= scripts
|
||||
if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'):
|
||||
for script in dist.metadata_listdir('scripts'):
|
||||
if dist_in_usersite(dist):
|
||||
bin_dir = bin_user
|
||||
else:
|
||||
bin_dir = bin_py
|
||||
paths_to_remove.add(os.path.join(bin_dir, script))
|
||||
if WINDOWS:
|
||||
paths_to_remove.add(os.path.join(bin_dir, script) + '.bat')
|
||||
|
||||
# find console_scripts
|
||||
if dist.has_metadata('entry_points.txt'):
|
||||
if six.PY2:
|
||||
options = {}
|
||||
else:
|
||||
options = {"delimiters": ('=', )}
|
||||
config = configparser.SafeConfigParser(**options)
|
||||
config.readfp(
|
||||
FakeFile(dist.get_metadata_lines('entry_points.txt'))
|
||||
)
|
||||
if config.has_section('console_scripts'):
|
||||
for name, value in config.items('console_scripts'):
|
||||
if dist_in_usersite(dist):
|
||||
bin_dir = bin_user
|
||||
else:
|
||||
bin_dir = bin_py
|
||||
paths_to_remove.add(os.path.join(bin_dir, name))
|
||||
if WINDOWS:
|
||||
paths_to_remove.add(
|
||||
os.path.join(bin_dir, name) + '.exe'
|
||||
)
|
||||
paths_to_remove.add(
|
||||
os.path.join(bin_dir, name) + '.exe.manifest'
|
||||
)
|
||||
paths_to_remove.add(
|
||||
os.path.join(bin_dir, name) + '-script.py'
|
||||
)
|
||||
|
||||
paths_to_remove.remove(auto_confirm)
|
||||
self.uninstalled = paths_to_remove
|
||||
|
||||
def rollback_uninstall(self):
|
||||
if self.uninstalled:
|
||||
self.uninstalled.rollback()
|
||||
else:
|
||||
logger.error(
|
||||
"Can't rollback %s, nothing uninstalled.", self.name,
|
||||
)
|
||||
|
||||
def commit_uninstall(self):
|
||||
if self.uninstalled:
|
||||
self.uninstalled.commit()
|
||||
elif not self.nothing_to_uninstall:
|
||||
logger.error(
|
||||
"Can't commit %s, nothing uninstalled.", self.name,
|
||||
)
|
||||
uninstalled_pathset = UninstallPathSet.from_dist(dist)
|
||||
uninstalled_pathset.remove(auto_confirm, verbose)
|
||||
return uninstalled_pathset
|
||||
|
||||
def archive(self, build_dir):
|
||||
assert self.source_dir
|
||||
@@ -837,17 +729,24 @@ class InstallRequirement(object):
|
||||
else:
|
||||
return True
|
||||
|
||||
def install(self, install_options, global_options=[], root=None,
|
||||
prefix=None):
|
||||
def install(self, install_options, global_options=None, root=None,
|
||||
home=None, prefix=None, warn_script_location=True,
|
||||
use_user_site=False, pycompile=True):
|
||||
global_options = global_options if global_options is not None else []
|
||||
if self.editable:
|
||||
self.install_editable(
|
||||
install_options, global_options, prefix=prefix)
|
||||
install_options, global_options, prefix=prefix,
|
||||
)
|
||||
return
|
||||
if self.is_wheel:
|
||||
version = pip9.wheel.wheel_version(self.source_dir)
|
||||
pip9.wheel.check_compatibility(version, self.name)
|
||||
version = wheel.wheel_version(self.source_dir)
|
||||
wheel.check_compatibility(version, self.name)
|
||||
|
||||
self.move_wheel_files(self.source_dir, root=root, prefix=prefix)
|
||||
self.move_wheel_files(
|
||||
self.source_dir, root=root, prefix=prefix, home=home,
|
||||
warn_script_location=warn_script_location,
|
||||
use_user_site=use_user_site, pycompile=pycompile,
|
||||
)
|
||||
self.install_succeeded = True
|
||||
return
|
||||
|
||||
@@ -856,35 +755,34 @@ class InstallRequirement(object):
|
||||
# Options specified in requirements file override those
|
||||
# specified on the command line, since the last option given
|
||||
# to setup.py is the one that is used.
|
||||
global_options += self.options.get('global_options', [])
|
||||
install_options += self.options.get('install_options', [])
|
||||
global_options = list(global_options) + \
|
||||
self.options.get('global_options', [])
|
||||
install_options = list(install_options) + \
|
||||
self.options.get('install_options', [])
|
||||
|
||||
if self.isolated:
|
||||
global_options = list(global_options) + ["--no-user-cfg"]
|
||||
global_options = global_options + ["--no-user-cfg"]
|
||||
|
||||
temp_location = tempfile.mkdtemp('-record', 'pip-')
|
||||
record_filename = os.path.join(temp_location, 'install-record.txt')
|
||||
try:
|
||||
with TempDirectory(kind="record") as temp_dir:
|
||||
record_filename = os.path.join(temp_dir.path, 'install-record.txt')
|
||||
install_args = self.get_install_args(
|
||||
global_options, record_filename, root, prefix)
|
||||
global_options, record_filename, root, prefix, pycompile,
|
||||
)
|
||||
msg = 'Running setup.py install for %s' % (self.name,)
|
||||
with open_spinner(msg) as spinner:
|
||||
with indent_log():
|
||||
call_subprocess(
|
||||
install_args + install_options,
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
spinner=spinner,
|
||||
)
|
||||
with self.build_env:
|
||||
call_subprocess(
|
||||
install_args + install_options,
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
spinner=spinner,
|
||||
)
|
||||
|
||||
if not os.path.exists(record_filename):
|
||||
logger.debug('Record file %s not found', record_filename)
|
||||
return
|
||||
self.install_succeeded = True
|
||||
if self.as_egg:
|
||||
# there's no --always-unzip option we can pass to install
|
||||
# command so we unable to save the installed-files.txt
|
||||
return
|
||||
|
||||
def prepend_root(path):
|
||||
if root is None or not os.path.isabs(path):
|
||||
@@ -914,16 +812,13 @@ class InstallRequirement(object):
|
||||
if os.path.isdir(filename):
|
||||
filename += os.path.sep
|
||||
new_lines.append(
|
||||
os.path.relpath(
|
||||
prepend_root(filename), egg_info_dir)
|
||||
os.path.relpath(prepend_root(filename), egg_info_dir)
|
||||
)
|
||||
new_lines.sort()
|
||||
ensure_dir(egg_info_dir)
|
||||
inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
|
||||
with open(inst_files_path, 'w') as f:
|
||||
f.write('\n'.join(new_lines) + '\n')
|
||||
finally:
|
||||
if os.path.exists(record_filename):
|
||||
os.remove(record_filename)
|
||||
rmtree(temp_location)
|
||||
|
||||
def ensure_has_source_dir(self, parent_dir):
|
||||
"""Ensure that a source_dir is set.
|
||||
@@ -939,22 +834,21 @@ class InstallRequirement(object):
|
||||
self.source_dir = self.build_location(parent_dir)
|
||||
return self.source_dir
|
||||
|
||||
def get_install_args(self, global_options, record_filename, root, prefix):
|
||||
install_args = [os.environ['PIP_PYTHON_PATH'], "-u"]
|
||||
def get_install_args(self, global_options, record_filename, root, prefix,
|
||||
pycompile):
|
||||
install_args = [os.environ.get('PIP_PYTHON_PATH', sys.executable), "-u"]
|
||||
install_args.append('-c')
|
||||
install_args.append(SETUPTOOLS_SHIM % self.setup_py)
|
||||
install_args += list(global_options) + \
|
||||
['install', '--record', record_filename]
|
||||
|
||||
if not self.as_egg:
|
||||
install_args += ['--single-version-externally-managed']
|
||||
install_args += ['--single-version-externally-managed']
|
||||
|
||||
if root is not None:
|
||||
install_args += ['--root', root]
|
||||
if prefix is not None:
|
||||
install_args += ['--prefix', prefix]
|
||||
|
||||
if self.pycompile:
|
||||
if pycompile:
|
||||
install_args += ["--compile"]
|
||||
else:
|
||||
install_args += ["--no-compile"]
|
||||
@@ -975,9 +869,8 @@ class InstallRequirement(object):
|
||||
logger.debug('Removing source in %s', self.source_dir)
|
||||
rmtree(self.source_dir)
|
||||
self.source_dir = None
|
||||
if self._temp_build_dir and os.path.exists(self._temp_build_dir):
|
||||
rmtree(self._temp_build_dir)
|
||||
self._temp_build_dir = None
|
||||
self._temp_build_dir.cleanup()
|
||||
self.build_env.cleanup()
|
||||
|
||||
def install_editable(self, install_options,
|
||||
global_options=(), prefix=None):
|
||||
@@ -987,27 +880,29 @@ class InstallRequirement(object):
|
||||
global_options = list(global_options) + ["--no-user-cfg"]
|
||||
|
||||
if prefix:
|
||||
prefix_param = ['--prefix={0}'.format(prefix)]
|
||||
prefix_param = ['--prefix={}'.format(prefix)]
|
||||
install_options = list(install_options) + prefix_param
|
||||
|
||||
with indent_log():
|
||||
# FIXME: should we do --install-headers here too?
|
||||
call_subprocess(
|
||||
[
|
||||
os.environ['PIP_PYTHON_PATH'],
|
||||
'-c',
|
||||
SETUPTOOLS_SHIM % self.setup_py
|
||||
] +
|
||||
list(global_options) +
|
||||
['develop', '--no-deps'] +
|
||||
list(install_options),
|
||||
with self.build_env:
|
||||
call_subprocess(
|
||||
[
|
||||
os.environ.get('PIP_PYTHON_PATH', sys.executable),
|
||||
'-c',
|
||||
SETUPTOOLS_SHIM % self.setup_py
|
||||
] +
|
||||
list(global_options) +
|
||||
['develop', '--no-deps'] +
|
||||
list(install_options),
|
||||
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False)
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
)
|
||||
|
||||
self.install_succeeded = True
|
||||
|
||||
def check_if_exists(self):
|
||||
def check_if_exists(self, use_user_site):
|
||||
"""Find an installed distribution that satisfies or conflicts
|
||||
with this requirement, and set self.satisfied_by or
|
||||
self.conflicts_with appropriately.
|
||||
@@ -1034,7 +929,7 @@ class InstallRequirement(object):
|
||||
existing_dist = pkg_resources.get_distribution(
|
||||
self.req.name
|
||||
)
|
||||
if self.use_user_site:
|
||||
if use_user_site:
|
||||
if dist_in_usersite(existing_dist):
|
||||
self.conflicts_with = existing_dist
|
||||
elif (running_under_virtualenv() and
|
||||
@@ -1052,27 +947,31 @@ class InstallRequirement(object):
|
||||
def is_wheel(self):
|
||||
return self.link and self.link.is_wheel
|
||||
|
||||
def move_wheel_files(self, wheeldir, root=None, prefix=None):
|
||||
def move_wheel_files(self, wheeldir, root=None, home=None, prefix=None,
|
||||
warn_script_location=True, use_user_site=False,
|
||||
pycompile=True):
|
||||
move_wheel_files(
|
||||
self.name, self.req, wheeldir,
|
||||
user=self.use_user_site,
|
||||
home=self.target_dir,
|
||||
user=use_user_site,
|
||||
home=home,
|
||||
root=root,
|
||||
prefix=prefix,
|
||||
pycompile=self.pycompile,
|
||||
pycompile=pycompile,
|
||||
isolated=self.isolated,
|
||||
warn_script_location=warn_script_location,
|
||||
)
|
||||
|
||||
def get_dist(self):
|
||||
"""Return a pkg_resources.Distribution built from self.egg_info_path"""
|
||||
egg_info = self.egg_info_path('').rstrip('/')
|
||||
egg_info = self.egg_info_path('').rstrip(os.path.sep)
|
||||
base_dir = os.path.dirname(egg_info)
|
||||
metadata = pkg_resources.PathMetadata(base_dir, egg_info)
|
||||
dist_name = os.path.splitext(os.path.basename(egg_info))[0]
|
||||
return pkg_resources.Distribution(
|
||||
os.path.dirname(egg_info),
|
||||
project_name=dist_name,
|
||||
metadata=metadata)
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
@property
|
||||
def has_hash_options(self):
|
||||
@@ -1114,11 +1013,15 @@ def _strip_postfix(req):
|
||||
match = re.search(r'^(.*?)(?:-dev|-\d.*)$', req)
|
||||
if match:
|
||||
# Strip off -dev, -0.2, etc.
|
||||
warnings.warn(
|
||||
"#egg cleanup for editable urls will be dropped in the future",
|
||||
RemovedInPip11Warning,
|
||||
)
|
||||
req = match.group(1)
|
||||
return req
|
||||
|
||||
|
||||
def parse_editable(editable_req, default_vcs=None):
|
||||
def parse_editable(editable_req):
|
||||
"""Parses an editable requirement into:
|
||||
- a requirement name
|
||||
- an URL
|
||||
@@ -1129,18 +1032,12 @@ def parse_editable(editable_req, default_vcs=None):
|
||||
.[some_extra]
|
||||
"""
|
||||
|
||||
from pip9.index import Link
|
||||
from notpip._internal.index import Link
|
||||
|
||||
url = editable_req
|
||||
extras = None
|
||||
|
||||
# If a file path is specified with extras, strip off the extras.
|
||||
m = re.match(r'^(.+)(\[[^\]]+\])$', url)
|
||||
if m:
|
||||
url_no_extras = m.group(1)
|
||||
extras = m.group(2)
|
||||
else:
|
||||
url_no_extras = url
|
||||
url_no_extras, extras = _strip_extras(url)
|
||||
|
||||
if os.path.isdir(url_no_extras):
|
||||
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
|
||||
@@ -1168,19 +1065,11 @@ def parse_editable(editable_req, default_vcs=None):
|
||||
break
|
||||
|
||||
if '+' not in url:
|
||||
if default_vcs:
|
||||
warnings.warn(
|
||||
"--default-vcs has been deprecated and will be removed in "
|
||||
"the future.",
|
||||
RemovedInPip10Warning,
|
||||
)
|
||||
url = default_vcs + '+' + url
|
||||
else:
|
||||
raise InstallationError(
|
||||
'%s should either be a path to a local project or a VCS url '
|
||||
'beginning with svn+, git+, hg+, or bzr+' %
|
||||
editable_req
|
||||
)
|
||||
raise InstallationError(
|
||||
'%s should either be a path to a local project or a VCS url '
|
||||
'beginning with svn+, git+, hg+, or bzr+' %
|
||||
editable_req
|
||||
)
|
||||
|
||||
vc_type = url.split('+', 1)[0].lower()
|
||||
|
||||
@@ -1193,11 +1082,34 @@ def parse_editable(editable_req, default_vcs=None):
|
||||
package_name = Link(url).egg_fragment
|
||||
if not package_name:
|
||||
raise InstallationError(
|
||||
"Could not detect requirement name, please specify one with #egg="
|
||||
)
|
||||
if not package_name:
|
||||
raise InstallationError(
|
||||
'--editable=%s is not the right format; it must have '
|
||||
'#egg=Package' % editable_req
|
||||
"Could not detect requirement name for '%s', please specify one "
|
||||
"with #egg=your_package_name" % editable_req
|
||||
)
|
||||
return _strip_postfix(package_name), url, None
|
||||
|
||||
|
||||
def deduce_helpful_msg(req):
|
||||
"""Returns helpful msg in case requirements file does not exist,
|
||||
or cannot be parsed.
|
||||
|
||||
:params req: Requirements file path
|
||||
"""
|
||||
msg = ""
|
||||
if os.path.exists(req):
|
||||
msg = " It does exist."
|
||||
# Try to parse and check if it is a requirements file.
|
||||
try:
|
||||
with open(req, 'r') as fp:
|
||||
# parse first line only
|
||||
next(parse_requirements(fp.read()))
|
||||
msg += " The argument you provided " + \
|
||||
"(%s) appears to be a" % (req) + \
|
||||
" requirements file. If that is the" + \
|
||||
" case, use the '-r' flag to install" + \
|
||||
" the packages specified within it."
|
||||
except RequirementParseError:
|
||||
logger.debug("Cannot parse '%s' as requirements \
|
||||
file" % (req), exc_info=1)
|
||||
else:
|
||||
msg += " File '%s' does not exist." % (req)
|
||||
return msg
|
||||
@@ -0,0 +1,165 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from notpip._internal.exceptions import InstallationError
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.wheel import Wheel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RequirementSet(object):
|
||||
|
||||
def __init__(self, require_hashes=False, ignore_compatibility=True):
|
||||
"""Create a RequirementSet.
|
||||
|
||||
:param wheel_cache: The pip wheel cache, for passing to
|
||||
InstallRequirement.
|
||||
"""
|
||||
|
||||
self.requirements = OrderedDict()
|
||||
self.require_hashes = require_hashes
|
||||
|
||||
# Mapping of alias: real_name
|
||||
self.requirement_aliases = {}
|
||||
self.unnamed_requirements = []
|
||||
self.successfully_downloaded = []
|
||||
self.reqs_to_cleanup = []
|
||||
self.ignore_compatibility = ignore_compatibility
|
||||
|
||||
def __str__(self):
|
||||
reqs = [req for req in self.requirements.values()
|
||||
if not req.comes_from]
|
||||
reqs.sort(key=lambda req: req.name.lower())
|
||||
return ' '.join([str(req.req) for req in reqs])
|
||||
|
||||
def __repr__(self):
|
||||
reqs = [req for req in self.requirements.values()]
|
||||
reqs.sort(key=lambda req: req.name.lower())
|
||||
reqs_str = ', '.join([str(req.req) for req in reqs])
|
||||
return ('<%s object; %d requirement(s): %s>'
|
||||
% (self.__class__.__name__, len(reqs), reqs_str))
|
||||
|
||||
def add_requirement(self, install_req, parent_req_name=None,
|
||||
extras_requested=None):
|
||||
"""Add install_req as a requirement to install.
|
||||
|
||||
:param parent_req_name: The name of the requirement that needed this
|
||||
added. The name is used because when multiple unnamed requirements
|
||||
resolve to the same name, we could otherwise end up with dependency
|
||||
links that point outside the Requirements set. parent_req must
|
||||
already be added. Note that None implies that this is a user
|
||||
supplied requirement, vs an inferred one.
|
||||
:param extras_requested: an iterable of extras used to evaluate the
|
||||
environment markers.
|
||||
:return: Additional requirements to scan. That is either [] if
|
||||
the requirement is not applicable, or [install_req] if the
|
||||
requirement is applicable and has just been added.
|
||||
"""
|
||||
name = install_req.name
|
||||
if not install_req.match_markers(extras_requested):
|
||||
logger.info("Ignoring %s: markers '%s' don't match your "
|
||||
"environment", install_req.name,
|
||||
install_req.markers)
|
||||
return [], None
|
||||
|
||||
# This check has to come after we filter requirements with the
|
||||
# environment markers.
|
||||
if install_req.link and install_req.link.is_wheel:
|
||||
wheel = Wheel(install_req.link.filename)
|
||||
if not wheel.supported() and not self.ignore_compatibility:
|
||||
raise InstallationError(
|
||||
"%s is not a supported wheel on this platform." %
|
||||
wheel.filename
|
||||
)
|
||||
|
||||
# This next bit is really a sanity check.
|
||||
assert install_req.is_direct == (parent_req_name is None), (
|
||||
"a direct req shouldn't have a parent and also, "
|
||||
"a non direct req should have a parent"
|
||||
)
|
||||
|
||||
if not name:
|
||||
# url or path requirement w/o an egg fragment
|
||||
self.unnamed_requirements.append(install_req)
|
||||
return [install_req], None
|
||||
else:
|
||||
try:
|
||||
existing_req = self.get_requirement(name)
|
||||
except KeyError:
|
||||
existing_req = None
|
||||
if (parent_req_name is None and existing_req and not
|
||||
existing_req.constraint and
|
||||
existing_req.extras == install_req.extras and not
|
||||
existing_req.req.specifier == install_req.req.specifier):
|
||||
raise InstallationError(
|
||||
'Double requirement given: %s (already in %s, name=%r)'
|
||||
% (install_req, existing_req, name))
|
||||
if not existing_req:
|
||||
# Add requirement
|
||||
self.requirements[name] = install_req
|
||||
# FIXME: what about other normalizations? E.g., _ vs. -?
|
||||
if name.lower() != name:
|
||||
self.requirement_aliases[name.lower()] = name
|
||||
result = [install_req]
|
||||
else:
|
||||
# Assume there's no need to scan, and that we've already
|
||||
# encountered this for scanning.
|
||||
result = []
|
||||
if not install_req.constraint and existing_req.constraint:
|
||||
if (install_req.link and not (existing_req.link and
|
||||
install_req.link.path == existing_req.link.path)):
|
||||
self.reqs_to_cleanup.append(install_req)
|
||||
raise InstallationError(
|
||||
"Could not satisfy constraints for '%s': "
|
||||
"installation from path or url cannot be "
|
||||
"constrained to a version" % name,
|
||||
)
|
||||
# If we're now installing a constraint, mark the existing
|
||||
# object for real installation.
|
||||
existing_req.constraint = False
|
||||
existing_req.extras = tuple(
|
||||
sorted(set(existing_req.extras).union(
|
||||
set(install_req.extras))))
|
||||
logger.debug("Setting %s extras to: %s",
|
||||
existing_req, existing_req.extras)
|
||||
# And now we need to scan this.
|
||||
result = [existing_req]
|
||||
# Canonicalise to the already-added object for the backref
|
||||
# check below.
|
||||
install_req = existing_req
|
||||
|
||||
# We return install_req here to allow for the caller to add it to
|
||||
# the dependency information for the parent package.
|
||||
return result, install_req
|
||||
|
||||
def has_requirement(self, project_name):
|
||||
name = project_name.lower()
|
||||
if (name in self.requirements and
|
||||
not self.requirements[name].constraint or
|
||||
name in self.requirement_aliases and
|
||||
not self.requirements[self.requirement_aliases[name]].constraint):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def has_requirements(self):
|
||||
return list(req for req in self.requirements.values() if not
|
||||
req.constraint) or self.unnamed_requirements
|
||||
|
||||
def get_requirement(self, project_name):
|
||||
for name in project_name, project_name.lower():
|
||||
if name in self.requirements:
|
||||
return self.requirements[name]
|
||||
if name in self.requirement_aliases:
|
||||
return self.requirements[self.requirement_aliases[name]]
|
||||
# raise KeyError("No project with the name %r" % project_name)
|
||||
|
||||
def cleanup_files(self):
|
||||
"""Clean up files, remove builds."""
|
||||
logger.debug('Cleaning up...')
|
||||
with indent_log():
|
||||
for req in self.reqs_to_cleanup:
|
||||
req.remove_temporary_source()
|
||||
@@ -0,0 +1,455 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import csv
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
from notpip._vendor import pkg_resources
|
||||
|
||||
from notpip._internal.compat import WINDOWS, cache_from_source, uses_pycache
|
||||
from notpip._internal.exceptions import UninstallationError
|
||||
from notpip._internal.locations import bin_py, bin_user
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import (
|
||||
FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local,
|
||||
normalize_path, renames,
|
||||
)
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _script_names(dist, script_name, is_gui):
|
||||
"""Create the fully qualified name of the files created by
|
||||
{console,gui}_scripts for the given ``dist``.
|
||||
Returns the list of file names
|
||||
"""
|
||||
if dist_in_usersite(dist):
|
||||
bin_dir = bin_user
|
||||
else:
|
||||
bin_dir = bin_py
|
||||
exe_name = os.path.join(bin_dir, script_name)
|
||||
paths_to_remove = [exe_name]
|
||||
if WINDOWS:
|
||||
paths_to_remove.append(exe_name + '.exe')
|
||||
paths_to_remove.append(exe_name + '.exe.manifest')
|
||||
if is_gui:
|
||||
paths_to_remove.append(exe_name + '-script.pyw')
|
||||
else:
|
||||
paths_to_remove.append(exe_name + '-script.py')
|
||||
return paths_to_remove
|
||||
|
||||
|
||||
def _unique(fn):
|
||||
@functools.wraps(fn)
|
||||
def unique(*args, **kw):
|
||||
seen = set()
|
||||
for item in fn(*args, **kw):
|
||||
if item not in seen:
|
||||
seen.add(item)
|
||||
yield item
|
||||
return unique
|
||||
|
||||
|
||||
@_unique
|
||||
def uninstallation_paths(dist):
|
||||
"""
|
||||
Yield all the uninstallation paths for dist based on RECORD-without-.pyc
|
||||
|
||||
Yield paths to all the files in RECORD. For each .py file in RECORD, add
|
||||
the .pyc in the same directory.
|
||||
|
||||
UninstallPathSet.add() takes care of the __pycache__ .pyc.
|
||||
"""
|
||||
r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD')))
|
||||
for row in r:
|
||||
path = os.path.join(dist.location, row[0])
|
||||
yield path
|
||||
if path.endswith('.py'):
|
||||
dn, fn = os.path.split(path)
|
||||
base = fn[:-3]
|
||||
path = os.path.join(dn, base + '.pyc')
|
||||
yield path
|
||||
|
||||
|
||||
def compact(paths):
|
||||
"""Compact a path set to contain the minimal number of paths
|
||||
necessary to contain all paths in the set. If /a/path/ and
|
||||
/a/path/to/a/file.txt are both in the set, leave only the
|
||||
shorter path."""
|
||||
|
||||
sep = os.path.sep
|
||||
short_paths = set()
|
||||
for path in sorted(paths, key=len):
|
||||
should_add = any(
|
||||
path.startswith(shortpath.rstrip("*")) and
|
||||
path[len(shortpath.rstrip("*").rstrip(sep))] == sep
|
||||
for shortpath in short_paths
|
||||
)
|
||||
if not should_add:
|
||||
short_paths.add(path)
|
||||
return short_paths
|
||||
|
||||
|
||||
def compress_for_output_listing(paths):
|
||||
"""Returns a tuple of 2 sets of which paths to display to user
|
||||
|
||||
The first set contains paths that would be deleted. Files of a package
|
||||
are not added and the top-level directory of the package has a '*' added
|
||||
at the end - to signify that all it's contents are removed.
|
||||
|
||||
The second set contains files that would have been skipped in the above
|
||||
folders.
|
||||
"""
|
||||
|
||||
will_remove = list(paths)
|
||||
will_skip = set()
|
||||
|
||||
# Determine folders and files
|
||||
folders = set()
|
||||
files = set()
|
||||
for path in will_remove:
|
||||
if path.endswith(".pyc"):
|
||||
continue
|
||||
if path.endswith("__init__.py") or ".dist-info" in path:
|
||||
folders.add(os.path.dirname(path))
|
||||
files.add(path)
|
||||
|
||||
folders = compact(folders)
|
||||
|
||||
# This walks the tree using os.walk to not miss extra folders
|
||||
# that might get added.
|
||||
for folder in folders:
|
||||
for dirpath, _, dirfiles in os.walk(folder):
|
||||
for fname in dirfiles:
|
||||
if fname.endswith(".pyc"):
|
||||
continue
|
||||
|
||||
file_ = os.path.normcase(os.path.join(dirpath, fname))
|
||||
if os.path.isfile(file_) and file_ not in files:
|
||||
# We are skipping this file. Add it to the set.
|
||||
will_skip.add(file_)
|
||||
|
||||
will_remove = files | {
|
||||
os.path.join(folder, "*") for folder in folders
|
||||
}
|
||||
|
||||
return will_remove, will_skip
|
||||
|
||||
|
||||
class UninstallPathSet(object):
|
||||
"""A set of file paths to be removed in the uninstallation of a
|
||||
requirement."""
|
||||
def __init__(self, dist):
|
||||
self.paths = set()
|
||||
self._refuse = set()
|
||||
self.pth = {}
|
||||
self.dist = dist
|
||||
self.save_dir = TempDirectory(kind="uninstall")
|
||||
self._moved_paths = []
|
||||
|
||||
def _permitted(self, path):
|
||||
"""
|
||||
Return True if the given path is one we are permitted to
|
||||
remove/modify, False otherwise.
|
||||
|
||||
"""
|
||||
return is_local(path)
|
||||
|
||||
def add(self, path):
|
||||
head, tail = os.path.split(path)
|
||||
|
||||
# we normalize the head to resolve parent directory symlinks, but not
|
||||
# the tail, since we only want to uninstall symlinks, not their targets
|
||||
path = os.path.join(normalize_path(head), os.path.normcase(tail))
|
||||
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
if self._permitted(path):
|
||||
self.paths.add(path)
|
||||
else:
|
||||
self._refuse.add(path)
|
||||
|
||||
# __pycache__ files can show up after 'installed-files.txt' is created,
|
||||
# due to imports
|
||||
if os.path.splitext(path)[1] == '.py' and uses_pycache:
|
||||
self.add(cache_from_source(path))
|
||||
|
||||
def add_pth(self, pth_file, entry):
|
||||
pth_file = normalize_path(pth_file)
|
||||
if self._permitted(pth_file):
|
||||
if pth_file not in self.pth:
|
||||
self.pth[pth_file] = UninstallPthEntries(pth_file)
|
||||
self.pth[pth_file].add(entry)
|
||||
else:
|
||||
self._refuse.add(pth_file)
|
||||
|
||||
def _stash(self, path):
|
||||
return os.path.join(
|
||||
self.save_dir.path, os.path.splitdrive(path)[1].lstrip(os.path.sep)
|
||||
)
|
||||
|
||||
def remove(self, auto_confirm=False, verbose=False):
|
||||
"""Remove paths in ``self.paths`` with confirmation (unless
|
||||
``auto_confirm`` is True)."""
|
||||
|
||||
if not self.paths:
|
||||
logger.info(
|
||||
"Can't uninstall '%s'. No files were found to uninstall.",
|
||||
self.dist.project_name,
|
||||
)
|
||||
return
|
||||
|
||||
dist_name_version = (
|
||||
self.dist.project_name + "-" + self.dist.version
|
||||
)
|
||||
logger.info('Uninstalling %s:', dist_name_version)
|
||||
|
||||
with indent_log():
|
||||
if auto_confirm or self._allowed_to_proceed(verbose):
|
||||
self.save_dir.create()
|
||||
|
||||
for path in sorted(compact(self.paths)):
|
||||
new_path = self._stash(path)
|
||||
logger.debug('Removing file or directory %s', path)
|
||||
self._moved_paths.append(path)
|
||||
renames(path, new_path)
|
||||
for pth in self.pth.values():
|
||||
pth.remove()
|
||||
|
||||
logger.info('Successfully uninstalled %s', dist_name_version)
|
||||
|
||||
def _allowed_to_proceed(self, verbose):
|
||||
"""Display which files would be deleted and prompt for confirmation
|
||||
"""
|
||||
|
||||
def _display(msg, paths):
|
||||
if not paths:
|
||||
return
|
||||
|
||||
logger.info(msg)
|
||||
with indent_log():
|
||||
for path in sorted(compact(paths)):
|
||||
logger.info(path)
|
||||
|
||||
if not verbose:
|
||||
will_remove, will_skip = compress_for_output_listing(self.paths)
|
||||
else:
|
||||
# In verbose mode, display all the files that are going to be
|
||||
# deleted.
|
||||
will_remove = list(self.paths)
|
||||
will_skip = set()
|
||||
|
||||
_display('Would remove:', will_remove)
|
||||
_display('Would not remove (might be manually added):', will_skip)
|
||||
_display('Would not remove (outside of prefix):', self._refuse)
|
||||
|
||||
return ask('Proceed (y/n)? ', ('y', 'n')) == 'y'
|
||||
|
||||
def rollback(self):
|
||||
"""Rollback the changes previously made by remove()."""
|
||||
if self.save_dir.path is None:
|
||||
logger.error(
|
||||
"Can't roll back %s; was not uninstalled",
|
||||
self.dist.project_name,
|
||||
)
|
||||
return False
|
||||
logger.info('Rolling back uninstall of %s', self.dist.project_name)
|
||||
for path in self._moved_paths:
|
||||
tmp_path = self._stash(path)
|
||||
logger.debug('Replacing %s', path)
|
||||
renames(tmp_path, path)
|
||||
for pth in self.pth.values():
|
||||
pth.rollback()
|
||||
|
||||
def commit(self):
|
||||
"""Remove temporary save dir: rollback will no longer be possible."""
|
||||
self.save_dir.cleanup()
|
||||
self._moved_paths = []
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist):
|
||||
dist_path = normalize_path(dist.location)
|
||||
if not dist_is_local(dist):
|
||||
logger.info(
|
||||
"Not uninstalling %s at %s, outside environment %s",
|
||||
dist.key,
|
||||
dist_path,
|
||||
sys.prefix,
|
||||
)
|
||||
return cls(dist)
|
||||
|
||||
if dist_path in {p for p in {sysconfig.get_path("stdlib"),
|
||||
sysconfig.get_path("platstdlib")}
|
||||
if p}:
|
||||
logger.info(
|
||||
"Not uninstalling %s at %s, as it is in the standard library.",
|
||||
dist.key,
|
||||
dist_path,
|
||||
)
|
||||
return cls(dist)
|
||||
|
||||
paths_to_remove = cls(dist)
|
||||
develop_egg_link = egg_link_path(dist)
|
||||
develop_egg_link_egg_info = '{}.egg-info'.format(
|
||||
pkg_resources.to_filename(dist.project_name))
|
||||
egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info)
|
||||
# Special case for distutils installed package
|
||||
distutils_egg_info = getattr(dist._provider, 'path', None)
|
||||
|
||||
# Uninstall cases order do matter as in the case of 2 installs of the
|
||||
# same package, pip needs to uninstall the currently detected version
|
||||
if (egg_info_exists and dist.egg_info.endswith('.egg-info') and
|
||||
not dist.egg_info.endswith(develop_egg_link_egg_info)):
|
||||
# if dist.egg_info.endswith(develop_egg_link_egg_info), we
|
||||
# are in fact in the develop_egg_link case
|
||||
paths_to_remove.add(dist.egg_info)
|
||||
if dist.has_metadata('installed-files.txt'):
|
||||
for installed_file in dist.get_metadata(
|
||||
'installed-files.txt').splitlines():
|
||||
path = os.path.normpath(
|
||||
os.path.join(dist.egg_info, installed_file)
|
||||
)
|
||||
paths_to_remove.add(path)
|
||||
# FIXME: need a test for this elif block
|
||||
# occurs with --single-version-externally-managed/--record outside
|
||||
# of pip
|
||||
elif dist.has_metadata('top_level.txt'):
|
||||
if dist.has_metadata('namespace_packages.txt'):
|
||||
namespaces = dist.get_metadata('namespace_packages.txt')
|
||||
else:
|
||||
namespaces = []
|
||||
for top_level_pkg in [
|
||||
p for p
|
||||
in dist.get_metadata('top_level.txt').splitlines()
|
||||
if p and p not in namespaces]:
|
||||
path = os.path.join(dist.location, top_level_pkg)
|
||||
paths_to_remove.add(path)
|
||||
paths_to_remove.add(path + '.py')
|
||||
paths_to_remove.add(path + '.pyc')
|
||||
paths_to_remove.add(path + '.pyo')
|
||||
|
||||
elif distutils_egg_info:
|
||||
raise UninstallationError(
|
||||
"Cannot uninstall {!r}. It is a distutils installed project "
|
||||
"and thus we cannot accurately determine which files belong "
|
||||
"to it which would lead to only a partial uninstall.".format(
|
||||
dist.project_name,
|
||||
)
|
||||
)
|
||||
|
||||
elif dist.location.endswith('.egg'):
|
||||
# package installed by easy_install
|
||||
# We cannot match on dist.egg_name because it can slightly vary
|
||||
# i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
|
||||
paths_to_remove.add(dist.location)
|
||||
easy_install_egg = os.path.split(dist.location)[1]
|
||||
easy_install_pth = os.path.join(os.path.dirname(dist.location),
|
||||
'easy-install.pth')
|
||||
paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)
|
||||
|
||||
elif egg_info_exists and dist.egg_info.endswith('.dist-info'):
|
||||
for path in uninstallation_paths(dist):
|
||||
paths_to_remove.add(path)
|
||||
|
||||
elif develop_egg_link:
|
||||
# develop egg
|
||||
with open(develop_egg_link, 'r') as fh:
|
||||
link_pointer = os.path.normcase(fh.readline().strip())
|
||||
assert (link_pointer == dist.location), (
|
||||
'Egg-link %s does not match installed location of %s '
|
||||
'(at %s)' % (link_pointer, dist.project_name, dist.location)
|
||||
)
|
||||
paths_to_remove.add(develop_egg_link)
|
||||
easy_install_pth = os.path.join(os.path.dirname(develop_egg_link),
|
||||
'easy-install.pth')
|
||||
paths_to_remove.add_pth(easy_install_pth, dist.location)
|
||||
|
||||
else:
|
||||
logger.debug(
|
||||
'Not sure how to uninstall: %s - Check: %s',
|
||||
dist, dist.location,
|
||||
)
|
||||
|
||||
# find distutils scripts= scripts
|
||||
if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'):
|
||||
for script in dist.metadata_listdir('scripts'):
|
||||
if dist_in_usersite(dist):
|
||||
bin_dir = bin_user
|
||||
else:
|
||||
bin_dir = bin_py
|
||||
paths_to_remove.add(os.path.join(bin_dir, script))
|
||||
if WINDOWS:
|
||||
paths_to_remove.add(os.path.join(bin_dir, script) + '.bat')
|
||||
|
||||
# find console_scripts
|
||||
_scripts_to_remove = []
|
||||
console_scripts = dist.get_entry_map(group='console_scripts')
|
||||
for name in console_scripts.keys():
|
||||
_scripts_to_remove.extend(_script_names(dist, name, False))
|
||||
# find gui_scripts
|
||||
gui_scripts = dist.get_entry_map(group='gui_scripts')
|
||||
for name in gui_scripts.keys():
|
||||
_scripts_to_remove.extend(_script_names(dist, name, True))
|
||||
|
||||
for s in _scripts_to_remove:
|
||||
paths_to_remove.add(s)
|
||||
|
||||
return paths_to_remove
|
||||
|
||||
|
||||
class UninstallPthEntries(object):
|
||||
def __init__(self, pth_file):
|
||||
if not os.path.isfile(pth_file):
|
||||
raise UninstallationError(
|
||||
"Cannot remove entries from nonexistent file %s" % pth_file
|
||||
)
|
||||
self.file = pth_file
|
||||
self.entries = set()
|
||||
self._saved_lines = None
|
||||
|
||||
def add(self, entry):
|
||||
entry = os.path.normcase(entry)
|
||||
# On Windows, os.path.normcase converts the entry to use
|
||||
# backslashes. This is correct for entries that describe absolute
|
||||
# paths outside of site-packages, but all the others use forward
|
||||
# slashes.
|
||||
if WINDOWS and not os.path.splitdrive(entry)[0]:
|
||||
entry = entry.replace('\\', '/')
|
||||
self.entries.add(entry)
|
||||
|
||||
def remove(self):
|
||||
logger.debug('Removing pth entries from %s:', self.file)
|
||||
with open(self.file, 'rb') as fh:
|
||||
# windows uses '\r\n' with py3k, but uses '\n' with py2.x
|
||||
lines = fh.readlines()
|
||||
self._saved_lines = lines
|
||||
if any(b'\r\n' in line for line in lines):
|
||||
endline = '\r\n'
|
||||
else:
|
||||
endline = '\n'
|
||||
# handle missing trailing newline
|
||||
if lines and not lines[-1].endswith(endline.encode("utf-8")):
|
||||
lines[-1] = lines[-1] + endline.encode("utf-8")
|
||||
for entry in self.entries:
|
||||
try:
|
||||
logger.debug('Removing entry: %s', entry)
|
||||
lines.remove((entry + endline).encode("utf-8"))
|
||||
except ValueError:
|
||||
pass
|
||||
with open(self.file, 'wb') as fh:
|
||||
fh.writelines(lines)
|
||||
|
||||
def rollback(self):
|
||||
if self._saved_lines is None:
|
||||
logger.error(
|
||||
'Cannot roll back changes to %s, none were made', self.file
|
||||
)
|
||||
return False
|
||||
logger.debug('Rolling %s back to previous state', self.file)
|
||||
with open(self.file, 'wb') as fh:
|
||||
fh.writelines(self._saved_lines)
|
||||
return True
|
||||
@@ -0,0 +1,381 @@
|
||||
"""Dependency Resolution
|
||||
|
||||
The dependency resolution in pip is performed as follows:
|
||||
|
||||
for top-level requirements:
|
||||
a. only one spec allowed per project, regardless of conflicts or not.
|
||||
otherwise a "double requirement" exception is raised
|
||||
b. they override sub-dependency requirements.
|
||||
for sub-dependencies
|
||||
a. "first found, wins" (where the order is breadth first)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
|
||||
from notpip._internal.exceptions import (
|
||||
BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors,
|
||||
UnsupportedPythonVersion,
|
||||
)
|
||||
|
||||
from notpip._internal.req.req_install import InstallRequirement
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import dist_in_usersite, ensure_dir
|
||||
from notpip._internal.utils.packaging import check_dist_requires_python
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Resolver(object):
|
||||
"""Resolves which packages need to be installed/uninstalled to perform \
|
||||
the requested operation without breaking the requirements of any package.
|
||||
"""
|
||||
|
||||
_allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
|
||||
|
||||
def __init__(self, preparer, session, finder, wheel_cache, use_user_site,
|
||||
ignore_dependencies, ignore_installed, ignore_requires_python,
|
||||
force_reinstall, isolated, upgrade_strategy):
|
||||
super(Resolver, self).__init__()
|
||||
assert upgrade_strategy in self._allowed_strategies
|
||||
|
||||
self.preparer = preparer
|
||||
self.finder = finder
|
||||
self.session = session
|
||||
|
||||
# NOTE: This would eventually be replaced with a cache that can give
|
||||
# information about both sdist and wheels transparently.
|
||||
self.wheel_cache = wheel_cache
|
||||
|
||||
self.require_hashes = None # This is set in resolve
|
||||
|
||||
self.upgrade_strategy = upgrade_strategy
|
||||
self.force_reinstall = force_reinstall
|
||||
self.isolated = isolated
|
||||
self.ignore_dependencies = ignore_dependencies
|
||||
self.ignore_installed = ignore_installed
|
||||
self.ignore_requires_python = ignore_requires_python
|
||||
self.ignore_compatibility = ignore_requires_python
|
||||
self.use_user_site = use_user_site
|
||||
|
||||
self._discovered_dependencies = defaultdict(list)
|
||||
|
||||
def resolve(self, requirement_set):
|
||||
"""Resolve what operations need to be done
|
||||
|
||||
As a side-effect of this method, the packages (and their dependencies)
|
||||
are downloaded, unpacked and prepared for installation. This
|
||||
preparation is done by ``pip.operations.prepare``.
|
||||
|
||||
Once PyPI has static dependency metadata available, it would be
|
||||
possible to move the preparation to become a step separated from
|
||||
dependency resolution.
|
||||
"""
|
||||
# make the wheelhouse
|
||||
if self.preparer.wheel_download_dir:
|
||||
ensure_dir(self.preparer.wheel_download_dir)
|
||||
|
||||
# If any top-level requirement has a hash specified, enter
|
||||
# hash-checking mode, which requires hashes from all.
|
||||
root_reqs = (
|
||||
requirement_set.unnamed_requirements +
|
||||
list(requirement_set.requirements.values())
|
||||
)
|
||||
self.require_hashes = (
|
||||
requirement_set.require_hashes or
|
||||
any(req.has_hash_options for req in root_reqs)
|
||||
)
|
||||
|
||||
# Display where finder is looking for packages
|
||||
locations = self.finder.get_formatted_locations()
|
||||
if locations:
|
||||
logger.info(locations)
|
||||
|
||||
# Actually prepare the files, and collect any exceptions. Most hash
|
||||
# exceptions cannot be checked ahead of time, because
|
||||
# req.populate_link() needs to be called before we can make decisions
|
||||
# based on link type.
|
||||
discovered_reqs = []
|
||||
hash_errors = HashErrors()
|
||||
for req in chain(root_reqs, discovered_reqs):
|
||||
try:
|
||||
discovered_reqs.extend(
|
||||
self._resolve_one(requirement_set, req)
|
||||
)
|
||||
except HashError as exc:
|
||||
exc.req = req
|
||||
hash_errors.append(exc)
|
||||
|
||||
if hash_errors:
|
||||
raise hash_errors
|
||||
|
||||
def _is_upgrade_allowed(self, req):
|
||||
if self.upgrade_strategy == "to-satisfy-only":
|
||||
return False
|
||||
elif self.upgrade_strategy == "eager":
|
||||
return True
|
||||
else:
|
||||
assert self.upgrade_strategy == "only-if-needed"
|
||||
return req.is_direct
|
||||
|
||||
def _set_req_to_reinstall(self, req):
|
||||
"""
|
||||
Set a requirement to be installed.
|
||||
"""
|
||||
# Don't uninstall the conflict if doing a user install and the
|
||||
# conflict is not a user install.
|
||||
if not self.use_user_site or dist_in_usersite(req.satisfied_by):
|
||||
req.conflicts_with = req.satisfied_by
|
||||
req.satisfied_by = None
|
||||
|
||||
# XXX: Stop passing requirement_set for options
|
||||
def _check_skip_installed(self, req_to_install):
|
||||
"""Check if req_to_install should be skipped.
|
||||
|
||||
This will check if the req is installed, and whether we should upgrade
|
||||
or reinstall it, taking into account all the relevant user options.
|
||||
|
||||
After calling this req_to_install will only have satisfied_by set to
|
||||
None if the req_to_install is to be upgraded/reinstalled etc. Any
|
||||
other value will be a dist recording the current thing installed that
|
||||
satisfies the requirement.
|
||||
|
||||
Note that for vcs urls and the like we can't assess skipping in this
|
||||
routine - we simply identify that we need to pull the thing down,
|
||||
then later on it is pulled down and introspected to assess upgrade/
|
||||
reinstalls etc.
|
||||
|
||||
:return: A text reason for why it was skipped, or None.
|
||||
"""
|
||||
if self.ignore_installed:
|
||||
return None
|
||||
|
||||
req_to_install.check_if_exists(self.use_user_site)
|
||||
if not req_to_install.satisfied_by:
|
||||
return None
|
||||
|
||||
if self.force_reinstall:
|
||||
self._set_req_to_reinstall(req_to_install)
|
||||
return None
|
||||
|
||||
if not self._is_upgrade_allowed(req_to_install):
|
||||
if self.upgrade_strategy == "only-if-needed":
|
||||
return 'not upgraded as not directly required'
|
||||
return 'already satisfied'
|
||||
|
||||
# Check for the possibility of an upgrade. For link-based
|
||||
# requirements we have to pull the tree down and inspect to assess
|
||||
# the version #, so it's handled way down.
|
||||
if not req_to_install.link:
|
||||
try:
|
||||
self.finder.find_requirement(req_to_install, upgrade=True)
|
||||
except BestVersionAlreadyInstalled:
|
||||
# Then the best version is installed.
|
||||
return 'already up-to-date'
|
||||
except DistributionNotFound:
|
||||
# No distribution found, so we squash the error. It will
|
||||
# be raised later when we re-try later to do the install.
|
||||
# Why don't we just raise here?
|
||||
pass
|
||||
|
||||
self._set_req_to_reinstall(req_to_install)
|
||||
return None
|
||||
|
||||
def _get_abstract_dist_for(self, req):
|
||||
"""Takes a InstallRequirement and returns a single AbstractDist \
|
||||
representing a prepared variant of the same.
|
||||
"""
|
||||
assert self.require_hashes is not None, (
|
||||
"require_hashes should have been set in Resolver.resolve()"
|
||||
)
|
||||
|
||||
if req.editable:
|
||||
return self.preparer.prepare_editable_requirement(
|
||||
req, self.require_hashes, self.use_user_site, self.finder,
|
||||
)
|
||||
|
||||
# satisfied_by is only evaluated by calling _check_skip_installed,
|
||||
# so it must be None here.
|
||||
assert req.satisfied_by is None
|
||||
skip_reason = self._check_skip_installed(req)
|
||||
|
||||
if req.satisfied_by:
|
||||
return self.preparer.prepare_installed_requirement(
|
||||
req, self.require_hashes, skip_reason
|
||||
)
|
||||
|
||||
upgrade_allowed = self._is_upgrade_allowed(req)
|
||||
abstract_dist = self.preparer.prepare_linked_requirement(
|
||||
req, self.session, self.finder, upgrade_allowed,
|
||||
self.require_hashes
|
||||
)
|
||||
|
||||
# NOTE
|
||||
# The following portion is for determining if a certain package is
|
||||
# going to be re-installed/upgraded or not and reporting to the user.
|
||||
# This should probably get cleaned up in a future refactor.
|
||||
|
||||
# req.req is only avail after unpack for URL
|
||||
# pkgs repeat check_if_exists to uninstall-on-upgrade
|
||||
# (#14)
|
||||
if not self.ignore_installed:
|
||||
req.check_if_exists(self.use_user_site)
|
||||
|
||||
if req.satisfied_by:
|
||||
should_modify = (
|
||||
self.upgrade_strategy != "to-satisfy-only" or
|
||||
self.force_reinstall or
|
||||
self.ignore_installed or
|
||||
req.link.scheme == 'file'
|
||||
)
|
||||
if should_modify:
|
||||
self._set_req_to_reinstall(req)
|
||||
else:
|
||||
logger.info(
|
||||
'Requirement already satisfied (use --upgrade to upgrade):'
|
||||
' %s', req,
|
||||
)
|
||||
|
||||
return abstract_dist
|
||||
|
||||
def _resolve_one(self, requirement_set, req_to_install, ignore_requires_python=False):
|
||||
"""Prepare a single requirements file.
|
||||
|
||||
:return: A list of additional InstallRequirements to also install.
|
||||
"""
|
||||
# Tell user what we are doing for this requirement:
|
||||
# obtain (editable), skipping, processing (local url), collecting
|
||||
# (remote url or package name)
|
||||
if ignore_requires_python or self.ignore_requires_python:
|
||||
self.ignore_compatibility = True
|
||||
|
||||
if req_to_install.constraint or req_to_install.prepared:
|
||||
return []
|
||||
|
||||
req_to_install.prepared = True
|
||||
|
||||
# register tmp src for cleanup in case something goes wrong
|
||||
requirement_set.reqs_to_cleanup.append(req_to_install)
|
||||
|
||||
abstract_dist = self._get_abstract_dist_for(req_to_install)
|
||||
|
||||
# Parse and return dependencies
|
||||
dist = abstract_dist.dist(self.finder)
|
||||
try:
|
||||
check_dist_requires_python(dist)
|
||||
except UnsupportedPythonVersion as err:
|
||||
if self.ignore_compatibility:
|
||||
logger.warning(err.args[0])
|
||||
else:
|
||||
raise
|
||||
|
||||
# A huge hack, by Kenneth Reitz.
|
||||
try:
|
||||
self.requires_python = check_dist_requires_python(dist, absorb=False)
|
||||
except TypeError:
|
||||
self.requires_python = None
|
||||
|
||||
more_reqs = []
|
||||
|
||||
def add_req(subreq, extras_requested):
|
||||
sub_install_req = InstallRequirement.from_req(
|
||||
str(subreq),
|
||||
req_to_install,
|
||||
isolated=self.isolated,
|
||||
wheel_cache=self.wheel_cache,
|
||||
)
|
||||
parent_req_name = req_to_install.name
|
||||
to_scan_again, add_to_parent = requirement_set.add_requirement(
|
||||
sub_install_req,
|
||||
parent_req_name=parent_req_name,
|
||||
extras_requested=extras_requested,
|
||||
)
|
||||
if parent_req_name and add_to_parent:
|
||||
self._discovered_dependencies[parent_req_name].append(
|
||||
add_to_parent
|
||||
)
|
||||
more_reqs.extend(to_scan_again)
|
||||
|
||||
with indent_log():
|
||||
# We add req_to_install before its dependencies, so that we
|
||||
# can refer to it when adding dependencies.
|
||||
if not requirement_set.has_requirement(req_to_install.name):
|
||||
available_requested = sorted(
|
||||
set(dist.extras) & set(req_to_install.extras)
|
||||
)
|
||||
# 'unnamed' requirements will get added here
|
||||
req_to_install.is_direct = True
|
||||
requirement_set.add_requirement(
|
||||
req_to_install, parent_req_name=None,
|
||||
extras_requested=available_requested,
|
||||
)
|
||||
|
||||
if not self.ignore_dependencies:
|
||||
if req_to_install.extras:
|
||||
logger.debug(
|
||||
"Installing extra requirements: %r",
|
||||
','.join(req_to_install.extras),
|
||||
)
|
||||
missing_requested = sorted(
|
||||
set(req_to_install.extras) - set(dist.extras)
|
||||
)
|
||||
for missing in missing_requested:
|
||||
logger.warning(
|
||||
'%s does not provide the extra \'%s\'',
|
||||
dist, missing
|
||||
)
|
||||
|
||||
available_requested = sorted(
|
||||
set(dist.extras) & set(req_to_install.extras)
|
||||
)
|
||||
for subreq in dist.requires(available_requested):
|
||||
add_req(subreq, extras_requested=available_requested)
|
||||
|
||||
# Hack for deep-resolving extras.
|
||||
for available in available_requested:
|
||||
if hasattr(dist, '_DistInfoDistribution__dep_map'):
|
||||
for req in dist._DistInfoDistribution__dep_map[available]:
|
||||
req = InstallRequirement.from_req(
|
||||
str(req),
|
||||
req_to_install,
|
||||
isolated=self.isolated,
|
||||
wheel_cache=self.wheel_cache,
|
||||
)
|
||||
|
||||
more_reqs.append(req)
|
||||
|
||||
if not req_to_install.editable and not req_to_install.satisfied_by:
|
||||
# XXX: --no-install leads this to report 'Successfully
|
||||
# downloaded' for only non-editable reqs, even though we took
|
||||
# action on them.
|
||||
requirement_set.successfully_downloaded.append(req_to_install)
|
||||
|
||||
return more_reqs
|
||||
|
||||
def get_installation_order(self, req_set):
|
||||
"""Create the installation order.
|
||||
|
||||
The installation order is topological - requirements are installed
|
||||
before the requiring thing. We break cycles at an arbitrary point,
|
||||
and make no other guarantees.
|
||||
"""
|
||||
# The current implementation, which we may change at any point
|
||||
# installs the user specified things in the order given, except when
|
||||
# dependencies must come earlier to achieve topological order.
|
||||
order = []
|
||||
ordered_reqs = set()
|
||||
|
||||
def schedule(req):
|
||||
if req.satisfied_by or req in ordered_reqs:
|
||||
return
|
||||
if req.constraint:
|
||||
return
|
||||
ordered_reqs.add(req)
|
||||
for dep in self._discovered_dependencies[req.name]:
|
||||
schedule(dep)
|
||||
order.append(req)
|
||||
|
||||
for install_req in req_set.requirements.values():
|
||||
schedule(install_req)
|
||||
return order
|
||||
+14
-4
@@ -7,8 +7,9 @@ from __future__ import absolute_import
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pip9.compat import WINDOWS, expanduser
|
||||
from pip9._vendor.six import PY2, text_type
|
||||
from notpip._vendor.six import PY2, text_type
|
||||
|
||||
from notpip._internal.compat import WINDOWS, expanduser
|
||||
|
||||
|
||||
def user_cache_dir(appname):
|
||||
@@ -60,7 +61,7 @@ def user_cache_dir(appname):
|
||||
|
||||
|
||||
def user_data_dir(appname, roaming=False):
|
||||
"""
|
||||
r"""
|
||||
Return full path to the user-specific data dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
@@ -74,6 +75,7 @@ def user_data_dir(appname, roaming=False):
|
||||
|
||||
Typical user data directories are:
|
||||
macOS: ~/Library/Application Support/<AppName>
|
||||
if it exists, else ~/.config/<AppName>
|
||||
Unix: ~/.local/share/<AppName> # or in
|
||||
$XDG_DATA_HOME, if defined
|
||||
Win XP (not roaming): C:\Documents and Settings\<username>\ ...
|
||||
@@ -93,6 +95,13 @@ def user_data_dir(appname, roaming=False):
|
||||
path = os.path.join(
|
||||
expanduser('~/Library/Application Support/'),
|
||||
appname,
|
||||
) if os.path.isdir(os.path.join(
|
||||
expanduser('~/Library/Application Support/'),
|
||||
appname,
|
||||
)
|
||||
) else os.path.join(
|
||||
expanduser('~/.config/'),
|
||||
appname,
|
||||
)
|
||||
else:
|
||||
path = os.path.join(
|
||||
@@ -137,7 +146,7 @@ def user_config_dir(appname, roaming=True):
|
||||
# for the discussion regarding site_config_dirs locations
|
||||
# see <https://github.com/pypa/pip/issues/1733>
|
||||
def site_config_dirs(appname):
|
||||
"""Return a list of potential user-shared config dirs for this application.
|
||||
r"""Return a list of potential user-shared config dirs for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
|
||||
@@ -222,6 +231,7 @@ def _get_win_folder_with_ctypes(csidl_name):
|
||||
|
||||
return buf.value
|
||||
|
||||
|
||||
if WINDOWS:
|
||||
try:
|
||||
import ctypes
|
||||
+11
-10
@@ -6,6 +6,11 @@ from __future__ import absolute_import
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class PipDeprecationWarning(Warning):
|
||||
pass
|
||||
@@ -15,22 +20,18 @@ class Pending(object):
|
||||
pass
|
||||
|
||||
|
||||
class RemovedInPip10Warning(PipDeprecationWarning):
|
||||
class RemovedInPip11Warning(PipDeprecationWarning):
|
||||
pass
|
||||
|
||||
|
||||
class RemovedInPip11Warning(PipDeprecationWarning, Pending):
|
||||
pass
|
||||
|
||||
|
||||
class Python26DeprecationWarning(PipDeprecationWarning):
|
||||
class RemovedInPip12Warning(PipDeprecationWarning, Pending):
|
||||
pass
|
||||
|
||||
|
||||
# Warnings <-> Logging Integration
|
||||
|
||||
|
||||
_warnings_showwarning = None
|
||||
_warnings_showwarning = None # type: Any
|
||||
|
||||
|
||||
def _showwarning(message, category, filename, lineno, file=None, line=None):
|
||||
@@ -42,8 +43,8 @@ def _showwarning(message, category, filename, lineno, file=None, line=None):
|
||||
else:
|
||||
if issubclass(category, PipDeprecationWarning):
|
||||
# We use a specially named logger which will handle all of the
|
||||
# deprecation messages for pip9.
|
||||
logger = logging.getLogger("pip9.deprecations")
|
||||
# deprecation messages for pip.
|
||||
logger = logging.getLogger("pip._internal.deprecations")
|
||||
|
||||
# This is purposely using the % formatter here instead of letting
|
||||
# the logging module handle the interpolation. This is because we
|
||||
@@ -53,7 +54,7 @@ def _showwarning(message, category, filename, lineno, file=None, line=None):
|
||||
# PipDeprecationWarnings that are Pending still have at least 2
|
||||
# versions to go until they are removed so they can just be
|
||||
# warnings. Otherwise, they will be removed in the very next
|
||||
# version of pip9. We want these to be more obvious so we use the
|
||||
# version of pip. We want these to be more obvious so we use the
|
||||
# ERROR logging level.
|
||||
if issubclass(category, Pending):
|
||||
logger.warning(log_message)
|
||||
+5
-3
@@ -1,7 +1,7 @@
|
||||
import codecs
|
||||
import locale
|
||||
import re
|
||||
|
||||
import sys
|
||||
|
||||
BOMS = [
|
||||
(codecs.BOM_UTF8, 'utf8'),
|
||||
@@ -13,7 +13,7 @@ BOMS = [
|
||||
(codecs.BOM_UTF32_LE, 'utf32-le'),
|
||||
]
|
||||
|
||||
ENCODING_RE = re.compile(b'coding[:=]\s*([-\w.]+)')
|
||||
ENCODING_RE = re.compile(br'coding[:=]\s*([-\w.]+)')
|
||||
|
||||
|
||||
def auto_decode(data):
|
||||
@@ -28,4 +28,6 @@ def auto_decode(data):
|
||||
if line[0:1] == b'#' and ENCODING_RE.search(line):
|
||||
encoding = ENCODING_RE.search(line).groups()[0].decode('ascii')
|
||||
return data.decode(encoding)
|
||||
return data.decode(locale.getpreferredencoding(False))
|
||||
return data.decode(
|
||||
locale.getpreferredencoding(False) or sys.getdefaultencoding(),
|
||||
)
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from pip9.compat import get_path_uid
|
||||
from notpip._internal.compat import get_path_uid
|
||||
|
||||
|
||||
def check_path_owner(path):
|
||||
+7
-4
@@ -1,8 +1,7 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
import ctypes
|
||||
import platform
|
||||
import re
|
||||
import warnings
|
||||
|
||||
|
||||
@@ -73,9 +72,13 @@ def have_compatible_glibc(required_major, minimum_minor):
|
||||
# misleading. Solution: instead of using platform, use our code that actually
|
||||
# works.
|
||||
def libc_ver():
|
||||
"""Try to determine the glibc version
|
||||
|
||||
Returns a tuple of strings (lib, version) which default to empty strings
|
||||
in case the lookup fails.
|
||||
"""
|
||||
glibc_version = glibc_version_string()
|
||||
if glibc_version is None:
|
||||
# For non-glibc platforms, fall back on platform.libc_ver
|
||||
return platform.libc_ver()
|
||||
return ("", "")
|
||||
else:
|
||||
return ("glibc", glibc_version)
|
||||
+5
-3
@@ -2,10 +2,12 @@ from __future__ import absolute_import
|
||||
|
||||
import hashlib
|
||||
|
||||
from pip9.exceptions import HashMismatch, HashMissing, InstallationError
|
||||
from pip9.utils import read_chunks
|
||||
from pip9._vendor.six import iteritems, iterkeys, itervalues
|
||||
from notpip._vendor.six import iteritems, iterkeys, itervalues
|
||||
|
||||
from notpip._internal.exceptions import (
|
||||
HashMismatch, HashMissing, InstallationError,
|
||||
)
|
||||
from notpip._internal.utils.misc import read_chunks
|
||||
|
||||
# The recommended hash algo of the moment. Change this whenever the state of
|
||||
# the art changes; it won't hurt backward compatibility.
|
||||
+9
-7
@@ -5,16 +5,17 @@ import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
|
||||
from notpip._internal.compat import WINDOWS
|
||||
from notpip._internal.utils.misc import ensure_dir
|
||||
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
import dummy_threading as threading
|
||||
import dummy_threading as threading # type: ignore
|
||||
|
||||
from pip9.compat import WINDOWS
|
||||
from pip9.utils import ensure_dir
|
||||
|
||||
try:
|
||||
from pip9._vendor import colorama
|
||||
from notpip._vendor import colorama
|
||||
# Lots of different errors can come from this, including SystemError and
|
||||
# ImportError.
|
||||
except Exception:
|
||||
@@ -75,15 +76,16 @@ class ColorizedStreamHandler(logging.StreamHandler):
|
||||
else:
|
||||
COLORS = []
|
||||
|
||||
def __init__(self, stream=None):
|
||||
def __init__(self, stream=None, no_color=None):
|
||||
logging.StreamHandler.__init__(self, stream)
|
||||
self._no_color = no_color
|
||||
|
||||
if WINDOWS and colorama:
|
||||
self.stream = colorama.AnsiToWin32(self.stream)
|
||||
|
||||
def should_color(self):
|
||||
# Don't colorize things if we do not have colorama
|
||||
if not colorama:
|
||||
# Don't colorize things if we do not have colorama or if told not to
|
||||
if not colorama or self._no_color:
|
||||
return False
|
||||
|
||||
real_stream = (
|
||||
+50
-51
@@ -1,6 +1,5 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import deque
|
||||
import contextlib
|
||||
import errno
|
||||
import io
|
||||
@@ -8,26 +7,30 @@ import locale
|
||||
# we have a submodule named 'logging' which would shadow this if we used the
|
||||
# regular name:
|
||||
import logging as std_logging
|
||||
import re
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import zipfile
|
||||
from collections import deque
|
||||
|
||||
from pip9.exceptions import InstallationError
|
||||
from pip9.compat import console_to_str, expanduser, stdlib_pkgs
|
||||
from pip9.locations import (
|
||||
site_packages, user_site, running_under_virtualenv, virtualenv_no_global,
|
||||
from notpip._vendor import pkg_resources
|
||||
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
|
||||
# why we ignore the type on this import.
|
||||
from notpip._vendor.retrying import retry # type: ignore
|
||||
from notpip._vendor.six import PY2
|
||||
from notpip._vendor.six.moves import input
|
||||
|
||||
from notpip._internal.compat import console_to_str, expanduser, stdlib_pkgs
|
||||
from notpip._internal.exceptions import InstallationError
|
||||
from notpip._internal.locations import (
|
||||
running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
|
||||
write_delete_marker_file,
|
||||
)
|
||||
from pip9._vendor import pkg_resources
|
||||
from pip9._vendor.six.moves import input
|
||||
from pip9._vendor.six import PY2
|
||||
from pip9._vendor.retrying import retry
|
||||
|
||||
if PY2:
|
||||
from io import BytesIO as StringIO
|
||||
@@ -40,7 +43,7 @@ __all__ = ['rmtree', 'display_path', 'backup_dir',
|
||||
'is_svn_page', 'file_contents',
|
||||
'split_leading_dir', 'has_leading_dir',
|
||||
'normalize_path',
|
||||
'renames', 'get_terminal_size', 'get_prog',
|
||||
'renames', 'get_prog',
|
||||
'unzip_file', 'untar_file', 'unpack_file', 'call_subprocess',
|
||||
'captured_stdout', 'ensure_dir',
|
||||
'ARCHIVE_EXTENSIONS', 'SUPPORTED_EXTENSIONS',
|
||||
@@ -88,8 +91,11 @@ def ensure_dir(path):
|
||||
|
||||
def get_prog():
|
||||
try:
|
||||
if os.path.basename(sys.argv[0]) in ('__main__.py', '-c'):
|
||||
return "%s -m pip" % os.environ['PIP_PYTHON_PATH']
|
||||
prog = os.path.basename(sys.argv[0])
|
||||
if prog in ('__main__.py', '-c'):
|
||||
return "%s -m pip" % os.environ.get('PIP_PYTHON_PATH', sys.executable)
|
||||
else:
|
||||
return prog
|
||||
except (AttributeError, TypeError, IndexError):
|
||||
pass
|
||||
return 'pip'
|
||||
@@ -308,7 +314,7 @@ def dist_in_usersite(dist):
|
||||
def dist_in_site_packages(dist):
|
||||
"""
|
||||
Return True if given Distribution is installed in
|
||||
distutils.sysconfig.get_python_lib().
|
||||
sysconfig.get_python_lib().
|
||||
"""
|
||||
return normalize_path(
|
||||
dist_location(dist)
|
||||
@@ -338,7 +344,7 @@ def get_installed_distributions(local_only=True,
|
||||
``skip`` argument is an iterable of lower-case project names to
|
||||
ignore; defaults to stdlib_pkgs
|
||||
|
||||
If ``editables`` is False, don't report editables.
|
||||
If ``include_editables`` is False, don't report editables.
|
||||
|
||||
If ``editables_only`` is True , only report editables.
|
||||
|
||||
@@ -432,36 +438,6 @@ def dist_location(dist):
|
||||
return dist.location
|
||||
|
||||
|
||||
def get_terminal_size():
|
||||
"""Returns a tuple (x, y) representing the width(x) and the height(x)
|
||||
in characters of the terminal window."""
|
||||
def ioctl_GWINSZ(fd):
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
import struct
|
||||
cr = struct.unpack(
|
||||
'hh',
|
||||
fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')
|
||||
)
|
||||
except:
|
||||
return None
|
||||
if cr == (0, 0):
|
||||
return None
|
||||
return cr
|
||||
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
||||
if not cr:
|
||||
try:
|
||||
fd = os.open(os.ctermid(), os.O_RDONLY)
|
||||
cr = ioctl_GWINSZ(fd)
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
if not cr:
|
||||
cr = (os.environ.get('LINES', 25), os.environ.get('COLUMNS', 80))
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
|
||||
def current_umask():
|
||||
"""Get the current umask which involves having to set it temporarily."""
|
||||
mask = os.umask(0)
|
||||
@@ -606,7 +582,7 @@ def unpack_file(filename, location, content_type, link):
|
||||
elif (content_type and content_type.startswith('text/html') and
|
||||
is_svn_page(file_contents(filename))):
|
||||
# We don't really care about this
|
||||
from pip9.vcs.subversion import Subversion
|
||||
from notpip._internal.vcs.subversion import Subversion
|
||||
Subversion('svn+' + link.url).unpack(location)
|
||||
else:
|
||||
# FIXME: handle?
|
||||
@@ -624,7 +600,14 @@ def unpack_file(filename, location, content_type, link):
|
||||
def call_subprocess(cmd, show_stdout=True, cwd=None,
|
||||
on_returncode='raise',
|
||||
command_desc=None,
|
||||
extra_environ=None, spinner=None):
|
||||
extra_environ=None, unset_environ=None, spinner=None):
|
||||
"""
|
||||
Args:
|
||||
unset_environ: an iterable of environment variable names to unset
|
||||
prior to calling subprocess.Popen().
|
||||
"""
|
||||
if unset_environ is None:
|
||||
unset_environ = []
|
||||
# This function's handling of subprocess output is confusing and I
|
||||
# previously broke it terribly, so as penance I will write a long comment
|
||||
# explaining things.
|
||||
@@ -661,17 +644,21 @@ def call_subprocess(cmd, show_stdout=True, cwd=None,
|
||||
env = os.environ.copy()
|
||||
if extra_environ:
|
||||
env.update(extra_environ)
|
||||
for name in unset_environ:
|
||||
env.pop(name, None)
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
|
||||
cwd=cwd, env=env)
|
||||
cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
|
||||
stdout=stdout, cwd=cwd, env=env,
|
||||
)
|
||||
proc.stdin.close()
|
||||
except Exception as exc:
|
||||
logger.critical(
|
||||
"Error %s while executing command %s", exc, command_desc,
|
||||
)
|
||||
raise
|
||||
all_output = []
|
||||
if stdout is not None:
|
||||
all_output = []
|
||||
while True:
|
||||
line = console_to_str(proc.stdout.readline())
|
||||
if not line:
|
||||
@@ -685,7 +672,11 @@ def call_subprocess(cmd, show_stdout=True, cwd=None,
|
||||
# Update the spinner
|
||||
if spinner is not None:
|
||||
spinner.spin()
|
||||
proc.wait()
|
||||
try:
|
||||
proc.wait()
|
||||
finally:
|
||||
if proc.stdout:
|
||||
proc.stdout.close()
|
||||
if spinner is not None:
|
||||
if proc.returncode:
|
||||
spinner.finish("error")
|
||||
@@ -850,3 +841,11 @@ def get_installed_version(dist_name, lookup_dirs=None):
|
||||
def consume(iterator):
|
||||
"""Consume an iterable at C speed."""
|
||||
deque(iterator, maxlen=0)
|
||||
|
||||
|
||||
# Simulates an enum
|
||||
def enum(*sequential, **named):
|
||||
enums = dict(zip(sequential, range(len(sequential))), **named)
|
||||
reverse = {value: key for key, value in enums.items()}
|
||||
enums['reverse_mapping'] = reverse
|
||||
return type('Enum', (), enums)
|
||||
+26
-25
@@ -6,15 +6,14 @@ import logging
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from pip9._vendor import lockfile
|
||||
from pip9._vendor.packaging import version as packaging_version
|
||||
|
||||
from pip9.compat import total_seconds, WINDOWS
|
||||
from pip9.models import PyPI
|
||||
from pip9.locations import USER_CACHE_DIR, running_under_virtualenv
|
||||
from pip9.utils import ensure_dir, get_installed_version
|
||||
from pip9.utils.filesystem import check_path_owner
|
||||
from notpip._vendor import lockfile
|
||||
from notpip._vendor.packaging import version as packaging_version
|
||||
|
||||
from notpip._internal.compat import WINDOWS
|
||||
from notpip._internal.index import PackageFinder
|
||||
from notpip._internal.locations import USER_CACHE_DIR, running_under_virtualenv
|
||||
from notpip._internal.utils.filesystem import check_path_owner
|
||||
from notpip._internal.utils.misc import ensure_dir, get_installed_version
|
||||
|
||||
SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
@@ -92,15 +91,15 @@ def load_selfcheck_statefile():
|
||||
return GlobalSelfCheckState()
|
||||
|
||||
|
||||
def pip_version_check(session):
|
||||
"""Check for an update for pip9.
|
||||
def pip_version_check(session, options):
|
||||
"""Check for an update for pip.
|
||||
|
||||
Limit the frequency of checks to once per week. State is stored either in
|
||||
the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix
|
||||
of the pip script path.
|
||||
"""
|
||||
installed_version = get_installed_version("pip")
|
||||
if installed_version is None:
|
||||
if not installed_version:
|
||||
return
|
||||
|
||||
pip_version = packaging_version.parse(installed_version)
|
||||
@@ -116,23 +115,26 @@ def pip_version_check(session):
|
||||
state.state["last_check"],
|
||||
SELFCHECK_DATE_FMT
|
||||
)
|
||||
if total_seconds(current_time - last_check) < 7 * 24 * 60 * 60:
|
||||
if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60:
|
||||
pypi_version = state.state["pypi_version"]
|
||||
|
||||
# Refresh the version if we need to or just see if we need to warn
|
||||
if pypi_version is None:
|
||||
resp = session.get(
|
||||
PyPI.pip_json_url,
|
||||
headers={"Accept": "application/json"},
|
||||
# Lets use PackageFinder to see what the latest pip version is
|
||||
finder = PackageFinder(
|
||||
find_links=options.find_links,
|
||||
index_urls=[options.index_url] + options.extra_index_urls,
|
||||
allow_all_prereleases=False, # Explicitly set to False
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
process_dependency_links=options.process_dependency_links,
|
||||
session=session,
|
||||
)
|
||||
all_candidates = finder.find_all_candidates("pip")
|
||||
if not all_candidates:
|
||||
return
|
||||
pypi_version = str(
|
||||
max(all_candidates, key=lambda c: c.version).version
|
||||
)
|
||||
resp.raise_for_status()
|
||||
pypi_version = [
|
||||
v for v in sorted(
|
||||
list(resp.json()["releases"]),
|
||||
key=packaging_version.parse,
|
||||
)
|
||||
if not packaging_version.parse(v).is_prerelease
|
||||
][-1]
|
||||
|
||||
# save that we've performed a check
|
||||
state.save(pypi_version, current_time)
|
||||
@@ -143,7 +145,7 @@ def pip_version_check(session):
|
||||
if (pip_version < remote_version and
|
||||
pip_version.base_version != remote_version.base_version):
|
||||
# Advise "python -m pip" on Windows to avoid issues
|
||||
# with overwriting pip9.exe.
|
||||
# with overwriting pip.exe.
|
||||
if WINDOWS:
|
||||
pip_cmd = "python -m pip"
|
||||
else:
|
||||
@@ -154,7 +156,6 @@ def pip_version_check(session):
|
||||
"'%s install --upgrade pip' command.",
|
||||
pip_version, pypi_version, pip_cmd
|
||||
)
|
||||
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"There was an error checking the latest version of pip",
|
||||
+19
-12
@@ -1,19 +1,18 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from email.parser import FeedParser
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
import sys
|
||||
from email.parser import FeedParser # type: ignore
|
||||
|
||||
from pip9._vendor.packaging import specifiers
|
||||
from pip9._vendor.packaging import version
|
||||
from pip9._vendor import pkg_resources
|
||||
from notpip._vendor import pkg_resources
|
||||
from notpip._vendor.packaging import specifiers, version
|
||||
|
||||
from pip9 import exceptions
|
||||
from notpip._internal import exceptions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_requires_python(requires_python):
|
||||
"""
|
||||
Check if the python version in use match the `requires_python` specifier.
|
||||
@@ -28,6 +27,7 @@ def check_requires_python(requires_python):
|
||||
# The package provides no information
|
||||
return True
|
||||
requires_python_specifier = specifiers.SpecifierSet(requires_python)
|
||||
|
||||
# We only use major.minor.micro
|
||||
python_version = version.parse('{0}.{1}.{2}'.format(*sys.version_info[:3]))
|
||||
return python_version in requires_python_specifier
|
||||
@@ -49,19 +49,26 @@ def check_dist_requires_python(dist, absorb=True):
|
||||
requires_python = pkg_info_dict.get('Requires-Python')
|
||||
if not absorb:
|
||||
return requires_python
|
||||
|
||||
try:
|
||||
if not check_requires_python(requires_python):
|
||||
# raise exceptions.UnsupportedPythonVersion(
|
||||
# "%s requires Python '%s' but the running Python is %s" % (
|
||||
# dist.project_name,
|
||||
# requires_python,
|
||||
# '{0}.{1}.{2}'.format(*sys.version_info[:3])
|
||||
# )
|
||||
# '.'.join(map(str, sys.version_info[:3])),)
|
||||
# )
|
||||
return
|
||||
except specifiers.InvalidSpecifier as e:
|
||||
logger.warning(
|
||||
"Package %s has an invalid Requires-Python entry %s - %s" % (
|
||||
dist.project_name, requires_python, e))
|
||||
"Package %s has an invalid Requires-Python entry %s - %s",
|
||||
dist.project_name, requires_python, e,
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
def get_installer(dist):
|
||||
if dist.has_metadata('INSTALLER'):
|
||||
for line in dist.get_metadata_lines('INSTALLER'):
|
||||
if line.strip():
|
||||
return line.strip()
|
||||
return ''
|
||||
@@ -0,0 +1,82 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
from notpip._internal.utils.misc import rmtree
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TempDirectory(object):
|
||||
"""Helper class that owns and cleans up a temporary directory.
|
||||
|
||||
This class can be used as a context manager or as an OO representation of a
|
||||
temporary directory.
|
||||
|
||||
Attributes:
|
||||
path
|
||||
Location to the created temporary directory or None
|
||||
delete
|
||||
Whether the directory should be deleted when exiting
|
||||
(when used as a contextmanager)
|
||||
|
||||
Methods:
|
||||
create()
|
||||
Creates a temporary directory and stores its path in the path
|
||||
attribute.
|
||||
cleanup()
|
||||
Deletes the temporary directory and sets path attribute to None
|
||||
|
||||
When used as a context manager, a temporary directory is created on
|
||||
entering the context and, if the delete attribute is True, on exiting the
|
||||
context the created directory is deleted.
|
||||
"""
|
||||
|
||||
def __init__(self, path=None, delete=None, kind="temp"):
|
||||
super(TempDirectory, self).__init__()
|
||||
|
||||
if path is None and delete is None:
|
||||
# If we were not given an explicit directory, and we were not given
|
||||
# an explicit delete option, then we'll default to deleting.
|
||||
delete = True
|
||||
|
||||
self.path = path
|
||||
self.delete = delete
|
||||
self.kind = kind
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {!r}>".format(self.__class__.__name__, self.path)
|
||||
|
||||
def __enter__(self):
|
||||
self.create()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
if self.delete:
|
||||
self.cleanup()
|
||||
|
||||
def create(self):
|
||||
"""Create a temporary directory and store it's path in self.path
|
||||
"""
|
||||
if self.path is not None:
|
||||
logger.debug(
|
||||
"Skipped creation of temporary directory: {}".format(self.path)
|
||||
)
|
||||
return
|
||||
# We realpath here because some systems have their default tmpdir
|
||||
# symlinked to another directory. This tends to confuse build
|
||||
# scripts, so we canonicalize the path by traversing potential
|
||||
# symlinks here.
|
||||
self.path = os.path.realpath(
|
||||
tempfile.mkdtemp(prefix="pip-{}-".format(self.kind))
|
||||
)
|
||||
logger.debug("Created temporary directory: {}".format(self.path))
|
||||
|
||||
def cleanup(self):
|
||||
"""Remove the temporary directory created and reset state
|
||||
"""
|
||||
if self.path is not None and os.path.exists(self.path):
|
||||
rmtree(self.path)
|
||||
self.path = None
|
||||
@@ -0,0 +1,29 @@
|
||||
"""For neatly implementing static typing in pip.
|
||||
|
||||
`mypy` - the static type analysis tool we use - uses the `typing` module, which
|
||||
provides core functionality fundamental to mypy's functioning.
|
||||
|
||||
Generally, `typing` would be imported at runtime and used in that fashion -
|
||||
it acts as a no-op at runtime and does not have any run-time overhead by
|
||||
design.
|
||||
|
||||
As it turns out, `typing` is not vendorable - it uses separate sources for
|
||||
Python 2/Python 3. Thus, this codebase can not expect it to be present.
|
||||
To work around this, mypy allows the typing import to be behind a False-y
|
||||
optional to prevent it from running at runtime and type-comments can be used
|
||||
to remove the need for the types to be accessible directly during runtime.
|
||||
|
||||
This module provides the False-y guard in a nicely named fashion so that a
|
||||
curious maintainer can reach here to read this.
|
||||
|
||||
In pip, all static-typing related imports should be guarded as follows:
|
||||
|
||||
from notpip.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ...
|
||||
|
||||
Ref: https://github.com/python/mypy/issues/3216
|
||||
"""
|
||||
|
||||
MYPY_CHECK_RUNNING = False
|
||||
@@ -1,24 +1,30 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import itertools
|
||||
import sys
|
||||
from signal import signal, SIGINT, default_int_handler
|
||||
import time
|
||||
import contextlib
|
||||
import itertools
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from signal import SIGINT, default_int_handler, signal
|
||||
|
||||
from pip9.compat import WINDOWS
|
||||
from pip9.utils import format_size
|
||||
from pip9.utils.logging import get_indentation
|
||||
from pip9._vendor import six
|
||||
from pip9._vendor.progress.bar import Bar, IncrementalBar
|
||||
from pip9._vendor.progress.helpers import (WritelnMixin,
|
||||
HIDE_CURSOR, SHOW_CURSOR)
|
||||
from pip9._vendor.progress.spinner import Spinner
|
||||
from notpip._vendor import six
|
||||
from notpip._vendor.progress.bar import (
|
||||
Bar, ChargingBar, FillingCirclesBar, FillingSquaresBar, IncrementalBar,
|
||||
ShadyBar,
|
||||
)
|
||||
from notpip._vendor.progress.helpers import HIDE_CURSOR, SHOW_CURSOR, WritelnMixin
|
||||
from notpip._vendor.progress.spinner import Spinner
|
||||
|
||||
from notpip._internal.compat import WINDOWS
|
||||
from notpip._internal.utils.logging import get_indentation
|
||||
from notpip._internal.utils.misc import format_size
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from pip9._vendor import colorama
|
||||
from notpip._vendor import colorama
|
||||
# Lots of different errors can come from this, including SystemError and
|
||||
# ImportError.
|
||||
except Exception:
|
||||
@@ -54,7 +60,7 @@ def _select_progress_class(preferred, fallback):
|
||||
return preferred
|
||||
|
||||
|
||||
_BaseBar = _select_progress_class(IncrementalBar, Bar)
|
||||
_BaseBar = _select_progress_class(IncrementalBar, Bar) # type: Any
|
||||
|
||||
|
||||
class InterruptibleMixin(object):
|
||||
@@ -112,6 +118,20 @@ class InterruptibleMixin(object):
|
||||
self.original_handler(signum, frame)
|
||||
|
||||
|
||||
class SilentBar(Bar):
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
|
||||
class BlueEmojiBar(IncrementalBar):
|
||||
|
||||
suffix = "%(percent)d%%"
|
||||
bar_prefix = " "
|
||||
bar_suffix = " "
|
||||
phases = (u"\U0001F539", u"\U0001F537", u"\U0001F535") # type: Any
|
||||
|
||||
|
||||
class DownloadProgressMixin(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -171,13 +191,54 @@ class WindowsMixin(object):
|
||||
self.file.flush = lambda: self.file.wrapped.flush()
|
||||
|
||||
|
||||
class DownloadProgressBar(WindowsMixin, InterruptibleMixin,
|
||||
DownloadProgressMixin, _BaseBar):
|
||||
class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin,
|
||||
DownloadProgressMixin):
|
||||
|
||||
file = sys.stdout
|
||||
message = "%(percent)d%%"
|
||||
suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s"
|
||||
|
||||
# NOTE: The "type: ignore" comments on the following classes are there to
|
||||
# work around https://github.com/python/typing/issues/241
|
||||
|
||||
|
||||
class DefaultDownloadProgressBar(BaseDownloadProgressBar,
|
||||
_BaseBar): # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class DownloadIncrementalBar(BaseDownloadProgressBar, # type: ignore
|
||||
IncrementalBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadChargingBar(BaseDownloadProgressBar, # type: ignore
|
||||
ChargingBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadShadyBar(BaseDownloadProgressBar, ShadyBar): # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class DownloadFillingSquaresBar(BaseDownloadProgressBar, # type: ignore
|
||||
FillingSquaresBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadFillingCirclesBar(BaseDownloadProgressBar, # type: ignore
|
||||
FillingCirclesBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, # type: ignore
|
||||
BlueEmojiBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin,
|
||||
DownloadProgressMixin, WritelnMixin, Spinner):
|
||||
@@ -205,6 +266,22 @@ class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin,
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
BAR_TYPES = {
|
||||
"off": (DownloadSilentBar, DownloadSilentBar),
|
||||
"on": (DefaultDownloadProgressBar, DownloadProgressSpinner),
|
||||
"ascii": (DownloadIncrementalBar, DownloadProgressSpinner),
|
||||
"pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner),
|
||||
"emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner)
|
||||
}
|
||||
|
||||
|
||||
def DownloadProgressProvider(progress_bar, max=None):
|
||||
if max is None or max == 0:
|
||||
return BAR_TYPES[progress_bar][1]().iter
|
||||
else:
|
||||
return BAR_TYPES[progress_bar][0](max=max).iter
|
||||
|
||||
|
||||
################################################################
|
||||
# Generic "something is happening" spinners
|
||||
#
|
||||
+120
-15
@@ -1,18 +1,24 @@
|
||||
"""Handles all VCS (version control) support"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from pip9._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from pip9.exceptions import BadCommand
|
||||
from pip9.utils import (display_path, backup_dir, call_subprocess,
|
||||
rmtree, ask_path_exists)
|
||||
from notpip._internal.exceptions import BadCommand
|
||||
from notpip._internal.utils.misc import (
|
||||
display_path, backup_dir, call_subprocess, rmtree, ask_path_exists,
|
||||
)
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Dict, Optional, Tuple
|
||||
from notpip._internal.basecommand import Command
|
||||
|
||||
__all__ = ['vcs', 'get_src_requirement']
|
||||
|
||||
@@ -20,8 +26,69 @@ __all__ = ['vcs', 'get_src_requirement']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RevOptions(object):
|
||||
|
||||
"""
|
||||
Encapsulates a VCS-specific revision to install, along with any VCS
|
||||
install options.
|
||||
|
||||
Instances of this class should be treated as if immutable.
|
||||
"""
|
||||
|
||||
def __init__(self, vcs, rev=None, extra_args=None):
|
||||
"""
|
||||
Args:
|
||||
vcs: a VersionControl object.
|
||||
rev: the name of the revision to install.
|
||||
extra_args: a list of extra options.
|
||||
"""
|
||||
if extra_args is None:
|
||||
extra_args = []
|
||||
|
||||
self.extra_args = extra_args
|
||||
self.rev = rev
|
||||
self.vcs = vcs
|
||||
|
||||
def __repr__(self):
|
||||
return '<RevOptions {}: rev={!r}>'.format(self.vcs.name, self.rev)
|
||||
|
||||
@property
|
||||
def arg_rev(self):
|
||||
if self.rev is None:
|
||||
return self.vcs.default_arg_rev
|
||||
|
||||
return self.rev
|
||||
|
||||
def to_args(self):
|
||||
"""
|
||||
Return the VCS-specific command arguments.
|
||||
"""
|
||||
args = []
|
||||
rev = self.arg_rev
|
||||
if rev is not None:
|
||||
args += self.vcs.get_base_rev_args(rev)
|
||||
args += self.extra_args
|
||||
|
||||
return args
|
||||
|
||||
def to_display(self):
|
||||
if not self.rev:
|
||||
return ''
|
||||
|
||||
return ' (to revision {})'.format(self.rev)
|
||||
|
||||
def make_new(self, rev):
|
||||
"""
|
||||
Make a copy of the current instance, but with a new rev.
|
||||
|
||||
Args:
|
||||
rev: the name of the revision for the new object.
|
||||
"""
|
||||
return self.vcs.make_rev_options(rev, extra_args=self.extra_args)
|
||||
|
||||
|
||||
class VcsSupport(object):
|
||||
_registry = {}
|
||||
_registry = {} # type: Dict[str, Command]
|
||||
schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp', 'svn']
|
||||
|
||||
def __init__(self):
|
||||
@@ -98,12 +165,34 @@ class VersionControl(object):
|
||||
name = ''
|
||||
dirname = ''
|
||||
# List of supported schemes for this Version Control
|
||||
schemes = ()
|
||||
schemes = () # type: Tuple[str, ...]
|
||||
# Iterable of environment variable names to pass to call_subprocess().
|
||||
unset_environ = () # type: Tuple[str, ...]
|
||||
default_arg_rev = None # type: Optional[str]
|
||||
|
||||
def __init__(self, url=None, *args, **kwargs):
|
||||
self.url = url
|
||||
super(VersionControl, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_base_rev_args(self, rev):
|
||||
"""
|
||||
Return the base revision arguments for a vcs command.
|
||||
|
||||
Args:
|
||||
rev: the name of a revision to install. Cannot be None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def make_rev_options(self, rev=None, extra_args=None):
|
||||
"""
|
||||
Return a RevOptions object.
|
||||
|
||||
Args:
|
||||
rev: the name of a revision to install.
|
||||
extra_args: a list of extra options.
|
||||
"""
|
||||
return RevOptions(self, rev, extra_args=extra_args)
|
||||
|
||||
def _is_local_repository(self, repo):
|
||||
"""
|
||||
posix absolute paths start with os.path.sep,
|
||||
@@ -175,31 +264,44 @@ class VersionControl(object):
|
||||
def switch(self, dest, url, rev_options):
|
||||
"""
|
||||
Switch the repo at ``dest`` to point to ``URL``.
|
||||
|
||||
Args:
|
||||
rev_options: a RevOptions object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, dest, rev_options):
|
||||
"""
|
||||
Update an already-existing repo to the given ``rev_options``.
|
||||
|
||||
Args:
|
||||
rev_options: a RevOptions object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def check_version(self, dest, rev_options):
|
||||
def is_commit_id_equal(self, dest, name):
|
||||
"""
|
||||
Return True if the version is identical to what exists and
|
||||
doesn't need to be updated.
|
||||
Return whether the id of the current commit equals the given name.
|
||||
|
||||
Args:
|
||||
dest: the repository directory.
|
||||
name: a string name.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def check_destination(self, dest, url, rev_options, rev_display):
|
||||
def check_destination(self, dest, url, rev_options):
|
||||
"""
|
||||
Prepare a location to receive a checkout/clone.
|
||||
|
||||
Return True if the location is ready for (and requires) a
|
||||
checkout/clone, False otherwise.
|
||||
|
||||
Args:
|
||||
rev_options: a RevOptions object.
|
||||
"""
|
||||
checkout = True
|
||||
prompt = False
|
||||
rev_display = rev_options.to_display()
|
||||
if os.path.exists(dest):
|
||||
checkout = False
|
||||
if os.path.exists(os.path.join(dest, self.dirname)):
|
||||
@@ -211,7 +313,7 @@ class VersionControl(object):
|
||||
display_path(dest),
|
||||
url,
|
||||
)
|
||||
if not self.check_version(dest, rev_options):
|
||||
if not self.is_commit_id_equal(dest, rev_options.rev):
|
||||
logger.info(
|
||||
'Updating %s %s%s',
|
||||
display_path(dest),
|
||||
@@ -303,8 +405,7 @@ class VersionControl(object):
|
||||
|
||||
def get_revision(self, location):
|
||||
"""
|
||||
Return the current revision of the files at location
|
||||
Used in get_info
|
||||
Return the current commit id of the files at the given location.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -322,12 +423,16 @@ class VersionControl(object):
|
||||
return call_subprocess(cmd, show_stdout, cwd,
|
||||
on_returncode,
|
||||
command_desc, extra_environ,
|
||||
spinner)
|
||||
unset_environ=self.unset_environ,
|
||||
spinner=spinner)
|
||||
except OSError as e:
|
||||
# errno.ENOENT = no such file or directory
|
||||
# In other words, the VCS executable isn't available
|
||||
if e.errno == errno.ENOENT:
|
||||
raise BadCommand('Cannot find command %r' % self.name)
|
||||
raise BadCommand(
|
||||
'Cannot find command %r - do you have '
|
||||
'%r installed and in your '
|
||||
'PATH?' % (self.name, self.name))
|
||||
else:
|
||||
raise # re-raise exception if a different error occurred
|
||||
|
||||
+28
-31
@@ -2,18 +2,13 @@ from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
# TODO: Get this into six.moves.urllib.parse
|
||||
try:
|
||||
from urllib import parse as urllib_parse
|
||||
except ImportError:
|
||||
import urlparse as urllib_parse
|
||||
|
||||
from pip9.utils import rmtree, display_path
|
||||
from pip9.vcs import vcs, VersionControl
|
||||
from pip9.download import path_to_url
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from notpip._internal.download import path_to_url
|
||||
from notpip._internal.utils.misc import display_path, rmtree
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.vcs import VersionControl, vcs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -29,49 +24,50 @@ class Bazaar(VersionControl):
|
||||
|
||||
def __init__(self, url=None, *args, **kwargs):
|
||||
super(Bazaar, self).__init__(url, *args, **kwargs)
|
||||
# Python >= 2.7.4, 3.3 doesn't have uses_fragment or non_hierarchical
|
||||
# This is only needed for python <2.7.5
|
||||
# Register lp but do not expose as a scheme to support bzr+lp.
|
||||
if getattr(urllib_parse, 'uses_fragment', None):
|
||||
urllib_parse.uses_fragment.extend(['lp'])
|
||||
urllib_parse.non_hierarchical.extend(['lp'])
|
||||
|
||||
def get_base_rev_args(self, rev):
|
||||
return ['-r', rev]
|
||||
|
||||
def export(self, location):
|
||||
"""
|
||||
Export the Bazaar repository at the url to the destination location
|
||||
"""
|
||||
temp_dir = tempfile.mkdtemp('-export', 'pip-')
|
||||
self.unpack(temp_dir)
|
||||
# Remove the location to make sure Bazaar can export it correctly
|
||||
if os.path.exists(location):
|
||||
# Remove the location to make sure Bazaar can export it correctly
|
||||
rmtree(location)
|
||||
try:
|
||||
self.run_command(['export', location], cwd=temp_dir,
|
||||
show_stdout=False)
|
||||
finally:
|
||||
rmtree(temp_dir)
|
||||
|
||||
with TempDirectory(kind="export") as temp_dir:
|
||||
self.unpack(temp_dir.path)
|
||||
|
||||
self.run_command(
|
||||
['export', location],
|
||||
cwd=temp_dir.path, show_stdout=False,
|
||||
)
|
||||
|
||||
def switch(self, dest, url, rev_options):
|
||||
self.run_command(['switch', url], cwd=dest)
|
||||
|
||||
def update(self, dest, rev_options):
|
||||
self.run_command(['pull', '-q'] + rev_options, cwd=dest)
|
||||
cmd_args = ['pull', '-q'] + rev_options.to_args()
|
||||
self.run_command(cmd_args, cwd=dest)
|
||||
|
||||
def obtain(self, dest):
|
||||
url, rev = self.get_url_rev()
|
||||
if rev:
|
||||
rev_options = ['-r', rev]
|
||||
rev_display = ' (to revision %s)' % rev
|
||||
else:
|
||||
rev_options = []
|
||||
rev_display = ''
|
||||
if self.check_destination(dest, url, rev_options, rev_display):
|
||||
rev_options = self.make_rev_options(rev)
|
||||
if self.check_destination(dest, url, rev_options):
|
||||
rev_display = rev_options.to_display()
|
||||
logger.info(
|
||||
'Checking out %s%s to %s',
|
||||
url,
|
||||
rev_display,
|
||||
display_path(dest),
|
||||
)
|
||||
self.run_command(['branch', '-q'] + rev_options + [url, dest])
|
||||
cmd_args = ['branch', '-q'] + rev_options.to_args() + [url, dest]
|
||||
self.run_command(cmd_args)
|
||||
|
||||
def get_url_rev(self):
|
||||
# hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it
|
||||
@@ -95,7 +91,8 @@ class Bazaar(VersionControl):
|
||||
|
||||
def get_revision(self, location):
|
||||
revision = self.run_command(
|
||||
['revno'], show_stdout=False, cwd=location)
|
||||
['revno'], show_stdout=False, cwd=location,
|
||||
)
|
||||
return revision.splitlines()[-1]
|
||||
|
||||
def get_src_requirement(self, dist, location):
|
||||
@@ -108,7 +105,7 @@ class Bazaar(VersionControl):
|
||||
current_rev = self.get_revision(location)
|
||||
return '%s@%s#egg=%s' % (repo, current_rev, egg_project_name)
|
||||
|
||||
def check_version(self, dest, rev_options):
|
||||
def is_commit_id_equal(self, dest, name):
|
||||
"""Always assume the versions don't match"""
|
||||
return False
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import tempfile
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from pip9.compat import samefile
|
||||
from pip9.exceptions import BadCommand
|
||||
from pip9._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pip9._vendor.six.moves.urllib import request as urllib_request
|
||||
from pip9._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pip9.utils import display_path, rmtree
|
||||
from pip9.vcs import vcs, VersionControl
|
||||
from notpip._vendor.packaging.version import parse as parse_version
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from notpip._vendor.six.moves.urllib import request as urllib_request
|
||||
|
||||
from notpip._internal.compat import samefile
|
||||
from notpip._internal.exceptions import BadCommand
|
||||
from notpip._internal.utils.misc import display_path
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.vcs import VersionControl, vcs
|
||||
|
||||
urlsplit = urllib_parse.urlsplit
|
||||
urlunsplit = urllib_parse.urlunsplit
|
||||
@@ -21,6 +21,13 @@ urlunsplit = urllib_parse.urlunsplit
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
HASH_REGEX = re.compile('[a-fA-F0-9]{40}')
|
||||
|
||||
|
||||
def looks_like_hash(sha):
|
||||
return bool(HASH_REGEX.match(sha))
|
||||
|
||||
|
||||
class Git(VersionControl):
|
||||
name = 'git'
|
||||
dirname = '.git'
|
||||
@@ -28,6 +35,10 @@ class Git(VersionControl):
|
||||
schemes = (
|
||||
'git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file',
|
||||
)
|
||||
# Prevent the user's environment variables from interfering with pip:
|
||||
# https://github.com/pypa/pip/issues/1130
|
||||
unset_environ = ('GIT_DIR', 'GIT_WORK_TREE')
|
||||
default_arg_rev = 'HEAD'
|
||||
|
||||
def __init__(self, url=None, *args, **kwargs):
|
||||
|
||||
@@ -50,11 +61,14 @@ class Git(VersionControl):
|
||||
|
||||
super(Git, self).__init__(url, *args, **kwargs)
|
||||
|
||||
def get_base_rev_args(self, rev):
|
||||
return [rev]
|
||||
|
||||
def get_git_version(self):
|
||||
VERSION_PFX = 'git version '
|
||||
version = self.run_command(['version'], show_stdout=False)
|
||||
if version.startswith(VERSION_PFX):
|
||||
version = version[len(VERSION_PFX):]
|
||||
version = version[len(VERSION_PFX):].split()[0]
|
||||
else:
|
||||
version = ''
|
||||
# get first 3 positions of the git version becasue
|
||||
@@ -65,49 +79,86 @@ class Git(VersionControl):
|
||||
|
||||
def export(self, location):
|
||||
"""Export the Git repository at the url to the destination location"""
|
||||
temp_dir = tempfile.mkdtemp('-export', 'pip-')
|
||||
self.unpack(temp_dir)
|
||||
try:
|
||||
if not location.endswith('/'):
|
||||
location = location + '/'
|
||||
if not location.endswith('/'):
|
||||
location = location + '/'
|
||||
|
||||
with TempDirectory(kind="export") as temp_dir:
|
||||
self.unpack(temp_dir.path)
|
||||
self.run_command(
|
||||
['checkout-index', '-a', '-f', '--prefix', location],
|
||||
show_stdout=False, cwd=temp_dir)
|
||||
finally:
|
||||
rmtree(temp_dir)
|
||||
|
||||
def check_rev_options(self, rev, dest, rev_options):
|
||||
"""Check the revision options before checkout to compensate that tags
|
||||
and branches may need origin/ as a prefix.
|
||||
Returns the SHA1 of the branch or tag if found.
|
||||
"""
|
||||
revisions = self.get_short_refs(dest)
|
||||
|
||||
origin_rev = 'origin/%s' % rev
|
||||
if origin_rev in revisions:
|
||||
# remote branch
|
||||
return [revisions[origin_rev]]
|
||||
elif rev in revisions:
|
||||
# a local tag or branch name
|
||||
return [revisions[rev]]
|
||||
else:
|
||||
logger.warning(
|
||||
"Could not find a tag or branch '%s', assuming commit.", rev,
|
||||
show_stdout=False, cwd=temp_dir.path
|
||||
)
|
||||
return rev_options
|
||||
|
||||
def check_version(self, dest, rev_options):
|
||||
def get_revision_sha(self, dest, rev):
|
||||
"""
|
||||
Compare the current sha to the ref. ref may be a branch or tag name,
|
||||
but current rev will always point to a sha. This means that a branch
|
||||
or tag will never compare as True. So this ultimately only matches
|
||||
against exact shas.
|
||||
Return a commit hash for the given revision if it names a remote
|
||||
branch or tag. Otherwise, return None.
|
||||
|
||||
Args:
|
||||
dest: the repository directory.
|
||||
rev: the revision name.
|
||||
"""
|
||||
return self.get_revision(dest).startswith(rev_options[0])
|
||||
# Pass rev to pre-filter the list.
|
||||
output = self.run_command(['show-ref', rev], cwd=dest,
|
||||
show_stdout=False, on_returncode='ignore')
|
||||
refs = {}
|
||||
for line in output.strip().splitlines():
|
||||
try:
|
||||
sha, ref = line.split()
|
||||
except ValueError:
|
||||
# Include the offending line to simplify troubleshooting if
|
||||
# this error ever occurs.
|
||||
raise ValueError('unexpected show-ref line: {!r}'.format(line))
|
||||
|
||||
refs[ref] = sha
|
||||
|
||||
branch_ref = 'refs/remotes/origin/{}'.format(rev)
|
||||
tag_ref = 'refs/tags/{}'.format(rev)
|
||||
|
||||
return refs.get(branch_ref) or refs.get(tag_ref)
|
||||
|
||||
def check_rev_options(self, dest, rev_options):
|
||||
"""Check the revision options before checkout.
|
||||
|
||||
Returns a new RevOptions object for the SHA1 of the branch or tag
|
||||
if found.
|
||||
|
||||
Args:
|
||||
rev_options: a RevOptions object.
|
||||
"""
|
||||
rev = rev_options.arg_rev
|
||||
sha = self.get_revision_sha(dest, rev)
|
||||
|
||||
if sha is not None:
|
||||
return rev_options.make_new(sha)
|
||||
|
||||
# Do not show a warning for the common case of something that has
|
||||
# the form of a Git commit hash.
|
||||
if not looks_like_hash(rev):
|
||||
logger.warning(
|
||||
"Did not find branch or tag '%s', assuming revision or ref.",
|
||||
rev,
|
||||
)
|
||||
return rev_options
|
||||
|
||||
def is_commit_id_equal(self, dest, name):
|
||||
"""
|
||||
Return whether the current commit hash equals the given name.
|
||||
|
||||
Args:
|
||||
dest: the repository directory.
|
||||
name: a string name.
|
||||
"""
|
||||
if not name:
|
||||
# Then avoid an unnecessary subprocess call.
|
||||
return False
|
||||
|
||||
return self.get_revision(dest) == name
|
||||
|
||||
def switch(self, dest, url, rev_options):
|
||||
self.run_command(['config', 'remote.origin.url', url], cwd=dest)
|
||||
self.run_command(['checkout', '-q'] + rev_options, cwd=dest)
|
||||
cmd_args = ['checkout', '-q'] + rev_options.to_args()
|
||||
self.run_command(cmd_args, cwd=dest)
|
||||
|
||||
self.update_submodules(dest)
|
||||
|
||||
@@ -119,44 +170,47 @@ class Git(VersionControl):
|
||||
else:
|
||||
self.run_command(['fetch', '-q'], cwd=dest)
|
||||
# Then reset to wanted revision (maybe even origin/master)
|
||||
if rev_options:
|
||||
rev_options = self.check_rev_options(
|
||||
rev_options[0], dest, rev_options,
|
||||
)
|
||||
self.run_command(['reset', '--hard', '-q'] + rev_options, cwd=dest)
|
||||
rev_options = self.check_rev_options(dest, rev_options)
|
||||
cmd_args = ['reset', '--hard', '-q'] + rev_options.to_args()
|
||||
self.run_command(cmd_args, cwd=dest)
|
||||
#: update submodules
|
||||
self.update_submodules(dest)
|
||||
|
||||
def obtain(self, dest):
|
||||
url, rev = self.get_url_rev()
|
||||
if rev:
|
||||
rev_options = [rev]
|
||||
rev_display = ' (to %s)' % rev
|
||||
else:
|
||||
rev_options = ['origin/master']
|
||||
rev_display = ''
|
||||
if self.check_destination(dest, url, rev_options, rev_display):
|
||||
rev_options = self.make_rev_options(rev)
|
||||
if self.check_destination(dest, url, rev_options):
|
||||
rev_display = rev_options.to_display()
|
||||
logger.info(
|
||||
'Cloning %s%s to %s', url, rev_display, display_path(dest),
|
||||
)
|
||||
self.run_command(['clone', '-q', url, dest])
|
||||
|
||||
if rev:
|
||||
rev_options = self.check_rev_options(rev, dest, rev_options)
|
||||
# Only do a checkout if rev_options differs from HEAD
|
||||
if not self.check_version(dest, rev_options):
|
||||
self.run_command(
|
||||
['checkout', '-q'] + rev_options,
|
||||
cwd=dest,
|
||||
)
|
||||
rev_options = self.check_rev_options(dest, rev_options)
|
||||
# Only do a checkout if the current commit id doesn't match
|
||||
# the requested revision.
|
||||
if not self.is_commit_id_equal(dest, rev_options.rev):
|
||||
rev = rev_options.rev
|
||||
# Only fetch the revision if it's a ref
|
||||
if rev.startswith('refs/'):
|
||||
self.run_command(
|
||||
['fetch', '-q', url] + rev_options.to_args(),
|
||||
cwd=dest,
|
||||
)
|
||||
# Change the revision to the SHA of the ref we fetched
|
||||
rev = 'FETCH_HEAD'
|
||||
self.run_command(['checkout', '-q', rev], cwd=dest)
|
||||
|
||||
#: repo may contain submodules
|
||||
self.update_submodules(dest)
|
||||
|
||||
def get_url(self, location):
|
||||
"""Return URL of the first remote encountered."""
|
||||
remotes = self.run_command(
|
||||
['config', '--get-regexp', 'remote\..*\.url'],
|
||||
show_stdout=False, cwd=location)
|
||||
['config', '--get-regexp', r'remote\..*\.url'],
|
||||
show_stdout=False, cwd=location,
|
||||
)
|
||||
remotes = remotes.splitlines()
|
||||
found_remote = remotes[0]
|
||||
for remote in remotes:
|
||||
@@ -168,53 +222,10 @@ class Git(VersionControl):
|
||||
|
||||
def get_revision(self, location):
|
||||
current_rev = self.run_command(
|
||||
['rev-parse', 'HEAD'], show_stdout=False, cwd=location)
|
||||
['rev-parse', 'HEAD'], show_stdout=False, cwd=location,
|
||||
)
|
||||
return current_rev.strip()
|
||||
|
||||
def get_full_refs(self, location):
|
||||
"""Yields tuples of (commit, ref) for branches and tags"""
|
||||
output = self.run_command(['show-ref'],
|
||||
show_stdout=False, cwd=location)
|
||||
for line in output.strip().splitlines():
|
||||
commit, ref = line.split(' ', 1)
|
||||
yield commit.strip(), ref.strip()
|
||||
|
||||
def is_ref_remote(self, ref):
|
||||
return ref.startswith('refs/remotes/')
|
||||
|
||||
def is_ref_branch(self, ref):
|
||||
return ref.startswith('refs/heads/')
|
||||
|
||||
def is_ref_tag(self, ref):
|
||||
return ref.startswith('refs/tags/')
|
||||
|
||||
def is_ref_commit(self, ref):
|
||||
"""A ref is a commit sha if it is not anything else"""
|
||||
return not any((
|
||||
self.is_ref_remote(ref),
|
||||
self.is_ref_branch(ref),
|
||||
self.is_ref_tag(ref),
|
||||
))
|
||||
|
||||
# Should deprecate `get_refs` since it's ambiguous
|
||||
def get_refs(self, location):
|
||||
return self.get_short_refs(location)
|
||||
|
||||
def get_short_refs(self, location):
|
||||
"""Return map of named refs (branches or tags) to commit hashes."""
|
||||
rv = {}
|
||||
for commit, ref in self.get_full_refs(location):
|
||||
ref_name = None
|
||||
if self.is_ref_remote(ref):
|
||||
ref_name = ref[len('refs/remotes/'):]
|
||||
elif self.is_ref_branch(ref):
|
||||
ref_name = ref[len('refs/heads/'):]
|
||||
elif self.is_ref_tag(ref):
|
||||
ref_name = ref[len('refs/tags/'):]
|
||||
if ref_name is not None:
|
||||
rv[ref_name] = commit
|
||||
return rv
|
||||
|
||||
def _get_subdirectory(self, location):
|
||||
"""Return the relative path of setup.py to the git repo root."""
|
||||
# find the repo root
|
||||
+24
-22
@@ -2,13 +2,13 @@ from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from pip9.utils import display_path, rmtree
|
||||
from pip9.vcs import vcs, VersionControl
|
||||
from pip9.download import path_to_url
|
||||
from pip9._vendor.six.moves import configparser
|
||||
from notpip._vendor.six.moves import configparser
|
||||
|
||||
from notpip._internal.download import path_to_url
|
||||
from notpip._internal.utils.misc import display_path
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.vcs import VersionControl, vcs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,15 +19,17 @@ class Mercurial(VersionControl):
|
||||
repo_name = 'clone'
|
||||
schemes = ('hg', 'hg+http', 'hg+https', 'hg+ssh', 'hg+static-http')
|
||||
|
||||
def get_base_rev_args(self, rev):
|
||||
return [rev]
|
||||
|
||||
def export(self, location):
|
||||
"""Export the Hg repository at the url to the destination location"""
|
||||
temp_dir = tempfile.mkdtemp('-export', 'pip-')
|
||||
self.unpack(temp_dir)
|
||||
try:
|
||||
with TempDirectory(kind="export") as temp_dir:
|
||||
self.unpack(temp_dir.path)
|
||||
|
||||
self.run_command(
|
||||
['archive', location], show_stdout=False, cwd=temp_dir)
|
||||
finally:
|
||||
rmtree(temp_dir)
|
||||
['archive', location], show_stdout=False, cwd=temp_dir.path
|
||||
)
|
||||
|
||||
def switch(self, dest, url, rev_options):
|
||||
repo_config = os.path.join(dest, self.dirname, 'hgrc')
|
||||
@@ -42,21 +44,19 @@ class Mercurial(VersionControl):
|
||||
'Could not switch Mercurial repository to %s: %s', url, exc,
|
||||
)
|
||||
else:
|
||||
self.run_command(['update', '-q'] + rev_options, cwd=dest)
|
||||
cmd_args = ['update', '-q'] + rev_options.to_args()
|
||||
self.run_command(cmd_args, cwd=dest)
|
||||
|
||||
def update(self, dest, rev_options):
|
||||
self.run_command(['pull', '-q'], cwd=dest)
|
||||
self.run_command(['update', '-q'] + rev_options, cwd=dest)
|
||||
cmd_args = ['update', '-q'] + rev_options.to_args()
|
||||
self.run_command(cmd_args, cwd=dest)
|
||||
|
||||
def obtain(self, dest):
|
||||
url, rev = self.get_url_rev()
|
||||
if rev:
|
||||
rev_options = [rev]
|
||||
rev_display = ' (to revision %s)' % rev
|
||||
else:
|
||||
rev_options = []
|
||||
rev_display = ''
|
||||
if self.check_destination(dest, url, rev_options, rev_display):
|
||||
rev_options = self.make_rev_options(rev)
|
||||
if self.check_destination(dest, url, rev_options):
|
||||
rev_display = rev_options.to_display()
|
||||
logger.info(
|
||||
'Cloning hg %s%s to %s',
|
||||
url,
|
||||
@@ -64,7 +64,8 @@ class Mercurial(VersionControl):
|
||||
display_path(dest),
|
||||
)
|
||||
self.run_command(['clone', '--noupdate', '-q', url, dest])
|
||||
self.run_command(['update', '-q'] + rev_options, cwd=dest)
|
||||
cmd_args = ['update', '-q'] + rev_options.to_args()
|
||||
self.run_command(cmd_args, cwd=dest)
|
||||
|
||||
def get_url(self, location):
|
||||
url = self.run_command(
|
||||
@@ -96,8 +97,9 @@ class Mercurial(VersionControl):
|
||||
current_rev_hash = self.get_revision_hash(location)
|
||||
return '%s@%s#egg=%s' % (repo, current_rev_hash, egg_project_name)
|
||||
|
||||
def check_version(self, dest, rev_options):
|
||||
def is_commit_id_equal(self, dest, name):
|
||||
"""Always assume the versions don't match"""
|
||||
return False
|
||||
|
||||
|
||||
vcs.register(Mercurial)
|
||||
+34
-32
@@ -4,15 +4,15 @@ import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from pip9._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from pip9.index import Link
|
||||
from pip9.utils import rmtree, display_path
|
||||
from pip9.utils.logging import indent_log
|
||||
from pip9.vcs import vcs, VersionControl
|
||||
from notpip._internal.index import Link
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import display_path, rmtree
|
||||
from notpip._internal.vcs import VersionControl, vcs
|
||||
|
||||
_svn_xml_url_re = re.compile('url="([^"]+)"')
|
||||
_svn_rev_re = re.compile('committed-rev="(\d+)"')
|
||||
_svn_rev_re = re.compile(r'committed-rev="(\d+)"')
|
||||
_svn_url_re = re.compile(r'URL: (.+)')
|
||||
_svn_revision_re = re.compile(r'Revision: (.+)')
|
||||
_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')
|
||||
@@ -28,6 +28,9 @@ class Subversion(VersionControl):
|
||||
repo_name = 'checkout'
|
||||
schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn')
|
||||
|
||||
def get_base_rev_args(self, rev):
|
||||
return ['-r', rev]
|
||||
|
||||
def get_info(self, location):
|
||||
"""Returns (url, revision), where both are strings"""
|
||||
assert not location.rstrip('/').endswith(self.dirname), \
|
||||
@@ -59,7 +62,7 @@ class Subversion(VersionControl):
|
||||
def export(self, location):
|
||||
"""Export the svn repository at the url to the destination location"""
|
||||
url, rev = self.get_url_rev()
|
||||
rev_options = get_rev_options(url, rev)
|
||||
rev_options = get_rev_options(self, url, rev)
|
||||
url = self.remove_auth_from_url(url)
|
||||
logger.info('Exporting svn repository %s to %s', url, location)
|
||||
with indent_log():
|
||||
@@ -67,32 +70,31 @@ class Subversion(VersionControl):
|
||||
# Subversion doesn't like to check out over an existing
|
||||
# directory --force fixes this, but was only added in svn 1.5
|
||||
rmtree(location)
|
||||
self.run_command(
|
||||
['export'] + rev_options + [url, location],
|
||||
show_stdout=False)
|
||||
cmd_args = ['export'] + rev_options.to_args() + [url, location]
|
||||
self.run_command(cmd_args, show_stdout=False)
|
||||
|
||||
def switch(self, dest, url, rev_options):
|
||||
self.run_command(['switch'] + rev_options + [url, dest])
|
||||
cmd_args = ['switch'] + rev_options.to_args() + [url, dest]
|
||||
self.run_command(cmd_args)
|
||||
|
||||
def update(self, dest, rev_options):
|
||||
self.run_command(['update'] + rev_options + [dest])
|
||||
cmd_args = ['update'] + rev_options.to_args() + [dest]
|
||||
self.run_command(cmd_args)
|
||||
|
||||
def obtain(self, dest):
|
||||
url, rev = self.get_url_rev()
|
||||
rev_options = get_rev_options(url, rev)
|
||||
rev_options = get_rev_options(self, url, rev)
|
||||
url = self.remove_auth_from_url(url)
|
||||
if rev:
|
||||
rev_display = ' (to revision %s)' % rev
|
||||
else:
|
||||
rev_display = ''
|
||||
if self.check_destination(dest, url, rev_options, rev_display):
|
||||
if self.check_destination(dest, url, rev_options):
|
||||
rev_display = rev_options.to_display()
|
||||
logger.info(
|
||||
'Checking out %s%s to %s',
|
||||
url,
|
||||
rev_display,
|
||||
display_path(dest),
|
||||
)
|
||||
self.run_command(['checkout', '-q'] + rev_options + [url, dest])
|
||||
cmd_args = ['checkout', '-q'] + rev_options.to_args() + [url, dest]
|
||||
self.run_command(cmd_args)
|
||||
|
||||
def get_location(self, dist, dependency_links):
|
||||
for url in dependency_links:
|
||||
@@ -128,8 +130,8 @@ class Subversion(VersionControl):
|
||||
dirurl, localrev = self._get_svn_url_rev(base)
|
||||
|
||||
if base == location:
|
||||
base_url = dirurl + '/' # save the root url
|
||||
elif not dirurl or not dirurl.startswith(base_url):
|
||||
base = dirurl + '/' # save the root url
|
||||
elif not dirurl or not dirurl.startswith(base):
|
||||
dirs[:] = []
|
||||
continue # not part of the same svn tree, skip it
|
||||
revision = max(revision, localrev)
|
||||
@@ -163,7 +165,7 @@ class Subversion(VersionControl):
|
||||
return self._get_svn_url_rev(location)[0]
|
||||
|
||||
def _get_svn_url_rev(self, location):
|
||||
from pip9.exceptions import InstallationError
|
||||
from notpip._internal.exceptions import InstallationError
|
||||
|
||||
entries_path = os.path.join(location, self.dirname, 'entries')
|
||||
if os.path.exists(entries_path):
|
||||
@@ -215,7 +217,7 @@ class Subversion(VersionControl):
|
||||
rev = self.get_revision(location)
|
||||
return 'svn+%s@%s#egg=%s' % (repo, rev, egg_project_name)
|
||||
|
||||
def check_version(self, dest, rev_options):
|
||||
def is_commit_id_equal(self, dest, name):
|
||||
"""Always assume the versions don't match"""
|
||||
return False
|
||||
|
||||
@@ -238,12 +240,10 @@ class Subversion(VersionControl):
|
||||
return surl
|
||||
|
||||
|
||||
def get_rev_options(url, rev):
|
||||
if rev:
|
||||
rev_options = ['-r', rev]
|
||||
else:
|
||||
rev_options = []
|
||||
|
||||
def get_rev_options(vcs, url, rev):
|
||||
"""
|
||||
Return a RevOptions object.
|
||||
"""
|
||||
r = urllib_parse.urlsplit(url)
|
||||
if hasattr(r, 'username'):
|
||||
# >= Python-2.5
|
||||
@@ -259,11 +259,13 @@ def get_rev_options(url, rev):
|
||||
else:
|
||||
username, password = None, None
|
||||
|
||||
extra_args = []
|
||||
if username:
|
||||
rev_options += ['--username', username]
|
||||
extra_args += ['--username', username]
|
||||
if password:
|
||||
rev_options += ['--password', password]
|
||||
return rev_options
|
||||
extra_args += ['--password', password]
|
||||
|
||||
return vcs.make_rev_options(rev, extra_args=extra_args)
|
||||
|
||||
|
||||
vcs.register(Subversion)
|
||||
@@ -3,44 +3,46 @@ Support for installing and building the "wheel" binary package format.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
import compileall
|
||||
import copy
|
||||
import csv
|
||||
import errno
|
||||
import functools
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
from base64 import urlsafe_b64encode
|
||||
from email.parser import Parser
|
||||
|
||||
from pip9._vendor.six import StringIO
|
||||
from notpip._vendor import pkg_resources
|
||||
from notpip._vendor.distlib.scripts import ScriptMaker
|
||||
from notpip._vendor.packaging.utils import canonicalize_name
|
||||
from notpip._vendor.six import StringIO
|
||||
|
||||
import pip9
|
||||
from pip9.compat import expanduser
|
||||
from pip9.download import path_to_url, unpack_url
|
||||
from pip9.exceptions import (
|
||||
InstallationError, InvalidWheelFilename, UnsupportedWheel)
|
||||
from pip9.locations import distutils_scheme, PIP_DELETE_MARKER_FILENAME
|
||||
from notpip import pep425tags
|
||||
from pip9.utils import (
|
||||
call_subprocess, ensure_dir, captured_stdout, rmtree, read_chunks,
|
||||
from notpip._internal import pep425tags
|
||||
from notpip._internal.build_env import BuildEnvironment
|
||||
from notpip._internal.download import path_to_url, unpack_url
|
||||
from notpip._internal.exceptions import (
|
||||
InstallationError, InvalidWheelFilename, UnsupportedWheel,
|
||||
)
|
||||
from pip9.utils.ui import open_spinner
|
||||
from pip9.utils.logging import indent_log
|
||||
from pip9.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||
from pip9._vendor.distlib.scripts import ScriptMaker
|
||||
from pip9._vendor import pkg_resources
|
||||
from pip9._vendor.packaging.utils import canonicalize_name
|
||||
from pip9._vendor.six.moves import configparser
|
||||
from notpip._internal.locations import (
|
||||
PIP_DELETE_MARKER_FILENAME, distutils_scheme,
|
||||
)
|
||||
from notpip._internal.utils.logging import indent_log
|
||||
from notpip._internal.utils.misc import (
|
||||
call_subprocess, captured_stdout, ensure_dir, read_chunks,
|
||||
)
|
||||
from notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||
from notpip._internal.utils.temp_dir import TempDirectory
|
||||
from notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from notpip._internal.utils.ui import open_spinner
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
wheel_ext = '.whl'
|
||||
|
||||
@@ -50,104 +52,6 @@ VERSION_COMPATIBLE = (1, 0)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WheelCache(object):
|
||||
"""A cache of wheels for future installs."""
|
||||
|
||||
def __init__(self, cache_dir, format_control):
|
||||
"""Create a wheel cache.
|
||||
|
||||
:param cache_dir: The root of the cache.
|
||||
:param format_control: A pip9.index.FormatControl object to limit
|
||||
binaries being read from the cache.
|
||||
"""
|
||||
self._cache_dir = expanduser(cache_dir) if cache_dir else None
|
||||
self._format_control = format_control
|
||||
|
||||
def cached_wheel(self, link, package_name):
|
||||
return cached_wheel(
|
||||
self._cache_dir, link, self._format_control, package_name)
|
||||
|
||||
|
||||
def _cache_for_link(cache_dir, link):
|
||||
"""
|
||||
Return a directory to store cached wheels in for link.
|
||||
|
||||
Because there are M wheels for any one sdist, we provide a directory
|
||||
to cache them in, and then consult that directory when looking up
|
||||
cache hits.
|
||||
|
||||
We only insert things into the cache if they have plausible version
|
||||
numbers, so that we don't contaminate the cache with things that were not
|
||||
unique. E.g. ./package might have dozens of installs done for it and build
|
||||
a version of 0.0...and if we built and cached a wheel, we'd end up using
|
||||
the same wheel even if the source has been edited.
|
||||
|
||||
:param cache_dir: The cache_dir being used by pip9.
|
||||
:param link: The link of the sdist for which this will cache wheels.
|
||||
"""
|
||||
|
||||
# We want to generate an url to use as our cache key, we don't want to just
|
||||
# re-use the URL because it might have other items in the fragment and we
|
||||
# don't care about those.
|
||||
key_parts = [link.url_without_fragment]
|
||||
if link.hash_name is not None and link.hash is not None:
|
||||
key_parts.append("=".join([link.hash_name, link.hash]))
|
||||
key_url = "#".join(key_parts)
|
||||
|
||||
# Encode our key url with sha224, we'll use this because it has similar
|
||||
# security properties to sha256, but with a shorter total output (and thus
|
||||
# less secure). However the differences don't make a lot of difference for
|
||||
# our use case here.
|
||||
hashed = hashlib.sha224(key_url.encode()).hexdigest()
|
||||
|
||||
# We want to nest the directories some to prevent having a ton of top level
|
||||
# directories where we might run out of sub directories on some FS.
|
||||
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
|
||||
|
||||
# Inside of the base location for cached wheels, expand our parts and join
|
||||
# them all together.
|
||||
return os.path.join(cache_dir, "wheels", *parts)
|
||||
|
||||
|
||||
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 link.is_artifact:
|
||||
return link
|
||||
if not package_name:
|
||||
return link
|
||||
canonical_name = canonicalize_name(package_name)
|
||||
formats = pip9.index.fmt_ctl_formats(format_control, canonical_name)
|
||||
if "binary" not in formats:
|
||||
return link
|
||||
root = _cache_for_link(cache_dir, link)
|
||||
try:
|
||||
wheel_names = os.listdir(root)
|
||||
except OSError as e:
|
||||
if e.errno in (errno.ENOENT, errno.ENOTDIR):
|
||||
return link
|
||||
raise
|
||||
candidates = []
|
||||
for wheel_name in wheel_names:
|
||||
try:
|
||||
wheel = Wheel(wheel_name)
|
||||
except InvalidWheelFilename:
|
||||
continue
|
||||
if not wheel.supported():
|
||||
# Built for a different python/arch/etc
|
||||
continue
|
||||
candidates.append((wheel.support_index_min(), wheel_name))
|
||||
if not candidates:
|
||||
return link
|
||||
candidates.sort()
|
||||
path = os.path.join(root, candidates[0][1])
|
||||
return pip9.index.Link(path_to_url(path))
|
||||
|
||||
|
||||
def rehash(path, algo='sha256', blocksize=1 << 20):
|
||||
"""Return (hash, length) for path using hashlib.new(algo)"""
|
||||
h = hashlib.new(algo)
|
||||
@@ -181,7 +85,7 @@ def fix_script(path):
|
||||
firstline = script.readline()
|
||||
if not firstline.startswith(b'#!python'):
|
||||
return False
|
||||
exename = os.environ['PIP_PYTHON_PATH'].encode(sys.getfilesystemencoding())
|
||||
exename = os.environ.get('PIP_PYTHON_PATH', sys.executable).encode(sys.getfilesystemencoding())
|
||||
firstline = b'#!' + exename + os.linesep.encode("ascii")
|
||||
rest = script.read()
|
||||
with open(path, 'wb') as script:
|
||||
@@ -189,7 +93,8 @@ def fix_script(path):
|
||||
script.write(rest)
|
||||
return True
|
||||
|
||||
dist_info_re = re.compile(r"""^(?P<namever>(?P<name>.+?)(-(?P<ver>\d.+?))?)
|
||||
|
||||
dist_info_re = re.compile(r"""^(?P<namever>(?P<name>.+?)(-(?P<ver>.+?))?)
|
||||
\.dist-info$""", re.VERBOSE)
|
||||
|
||||
|
||||
@@ -224,21 +129,85 @@ def get_entrypoints(filename):
|
||||
data.write("\n")
|
||||
data.seek(0)
|
||||
|
||||
cp = configparser.RawConfigParser()
|
||||
cp.optionxform = lambda option: option
|
||||
cp.readfp(data)
|
||||
# get the entry points and then the script names
|
||||
entry_points = pkg_resources.EntryPoint.parse_map(data)
|
||||
console = entry_points.get('console_scripts', {})
|
||||
gui = entry_points.get('gui_scripts', {})
|
||||
|
||||
console = {}
|
||||
gui = {}
|
||||
if cp.has_section('console_scripts'):
|
||||
console = dict(cp.items('console_scripts'))
|
||||
if cp.has_section('gui_scripts'):
|
||||
gui = dict(cp.items('gui_scripts'))
|
||||
def _split_ep(s):
|
||||
"""get the string representation of EntryPoint, remove space and split
|
||||
on '='"""
|
||||
return str(s).replace(" ", "").split("=")
|
||||
|
||||
# convert the EntryPoint objects into strings with module:function
|
||||
console = dict(_split_ep(v) for v in console.values())
|
||||
gui = dict(_split_ep(v) for v in gui.values())
|
||||
return console, gui
|
||||
|
||||
|
||||
def message_about_scripts_not_on_PATH(scripts):
|
||||
# type: (List[str]) -> Optional[str]
|
||||
"""Determine if any scripts are not on PATH and format a warning.
|
||||
|
||||
Returns a warning message if one or more scripts are not on PATH,
|
||||
otherwise None.
|
||||
"""
|
||||
if not scripts:
|
||||
return None
|
||||
|
||||
# Group scripts by the path they were installed in
|
||||
grouped_by_dir = collections.defaultdict(set) # type: Dict[str, set]
|
||||
for destfile in scripts:
|
||||
parent_dir = os.path.dirname(destfile)
|
||||
script_name = os.path.basename(destfile)
|
||||
grouped_by_dir[parent_dir].add(script_name)
|
||||
|
||||
# We don't want to warn for directories that are on PATH.
|
||||
not_warn_dirs = [
|
||||
os.path.normcase(i) for i in os.environ["PATH"].split(os.pathsep)
|
||||
]
|
||||
# If an executable sits with sys.executable, we don't warn for it.
|
||||
# This covers the case of venv invocations without activating the venv.
|
||||
not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable)))
|
||||
warn_for = {
|
||||
parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items()
|
||||
if os.path.normcase(parent_dir) not in not_warn_dirs
|
||||
}
|
||||
if not warn_for:
|
||||
return None
|
||||
|
||||
# Format a message
|
||||
msg_lines = []
|
||||
for parent_dir, scripts in warn_for.items():
|
||||
scripts = sorted(scripts)
|
||||
if len(scripts) == 1:
|
||||
start_text = "script {} is".format(scripts[0])
|
||||
else:
|
||||
start_text = "scripts {} are".format(
|
||||
", ".join(scripts[:-1]) + " and " + scripts[-1]
|
||||
)
|
||||
|
||||
msg_lines.append(
|
||||
"The {} installed in '{}' which is not on PATH."
|
||||
.format(start_text, parent_dir)
|
||||
)
|
||||
|
||||
last_line_fmt = (
|
||||
"Consider adding {} to PATH or, if you prefer "
|
||||
"to suppress this warning, use --no-warn-script-location."
|
||||
)
|
||||
if len(msg_lines) == 1:
|
||||
msg_lines.append(last_line_fmt.format("this directory"))
|
||||
else:
|
||||
msg_lines.append(last_line_fmt.format("these directories"))
|
||||
|
||||
# Returns the formatted multiline message
|
||||
return "\n".join(msg_lines)
|
||||
|
||||
|
||||
def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None,
|
||||
pycompile=True, scheme=None, isolated=False, prefix=None):
|
||||
pycompile=True, scheme=None, isolated=False, prefix=None,
|
||||
warn_script_location=True):
|
||||
"""Install a wheel"""
|
||||
|
||||
if not scheme:
|
||||
@@ -385,7 +354,7 @@ def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None,
|
||||
# Ensure we don't generate any variants for scripts because this is almost
|
||||
# never what somebody wants.
|
||||
# See https://bitbucket.org/pypa/distlib/issue/35/
|
||||
maker.variants = set(('', ))
|
||||
maker.variants = {''}
|
||||
|
||||
# This is required because otherwise distlib creates scripts that are not
|
||||
# executable.
|
||||
@@ -411,7 +380,7 @@ def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None,
|
||||
}
|
||||
|
||||
maker._get_script_text = _get_script_text
|
||||
maker.script_template = """# -*- coding: utf-8 -*-
|
||||
maker.script_template = r"""# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
@@ -488,9 +457,16 @@ if __name__ == '__main__':
|
||||
|
||||
# Generate the console and GUI entry points specified in the wheel
|
||||
if len(console) > 0:
|
||||
generated.extend(
|
||||
maker.make_multiple(['%s = %s' % kv for kv in console.items()])
|
||||
generated_console_scripts = maker.make_multiple(
|
||||
['%s = %s' % kv for kv in console.items()]
|
||||
)
|
||||
generated.extend(generated_console_scripts)
|
||||
|
||||
if warn_script_location:
|
||||
msg = message_about_scripts_not_on_PATH(generated_console_scripts)
|
||||
if msg is not None:
|
||||
logger.warn(msg)
|
||||
|
||||
if len(gui) > 0:
|
||||
generated.extend(
|
||||
maker.make_multiple(
|
||||
@@ -527,40 +503,6 @@ if __name__ == '__main__':
|
||||
shutil.move(temp_record, record)
|
||||
|
||||
|
||||
def _unique(fn):
|
||||
@functools.wraps(fn)
|
||||
def unique(*args, **kw):
|
||||
seen = set()
|
||||
for item in fn(*args, **kw):
|
||||
if item not in seen:
|
||||
seen.add(item)
|
||||
yield item
|
||||
return unique
|
||||
|
||||
|
||||
# TODO: this goes somewhere besides the wheel module
|
||||
@_unique
|
||||
def uninstallation_paths(dist):
|
||||
"""
|
||||
Yield all the uninstallation paths for dist based on RECORD-without-.pyc
|
||||
|
||||
Yield paths to all the files in RECORD. For each .py file in RECORD, add
|
||||
the .pyc in the same directory.
|
||||
|
||||
UninstallPathSet.add() takes care of the __pycache__ .pyc.
|
||||
"""
|
||||
from pip9.utils import FakeFile # circular import
|
||||
r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD')))
|
||||
for row in r:
|
||||
path = os.path.join(dist.location, row[0])
|
||||
yield path
|
||||
if path.endswith('.py'):
|
||||
dn, fn = os.path.split(path)
|
||||
base = fn[:-3]
|
||||
path = os.path.join(dn, base + '.pyc')
|
||||
yield path
|
||||
|
||||
|
||||
def wheel_version(source_dir):
|
||||
"""
|
||||
Return the Wheel-Version of an extracted wheel, if possible.
|
||||
@@ -615,8 +557,8 @@ class Wheel(object):
|
||||
# TODO: maybe move the install code into this class
|
||||
|
||||
wheel_file_re = re.compile(
|
||||
r"""^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))
|
||||
((-(?P<build>\d.*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)
|
||||
r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.*?))
|
||||
((-(?P<build>\d[^-]*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)
|
||||
\.whl|\.dist-info)$""",
|
||||
re.VERBOSE
|
||||
)
|
||||
@@ -635,15 +577,16 @@ class Wheel(object):
|
||||
# we'll assume "_" means "-" due to wheel naming scheme
|
||||
# (https://github.com/pypa/pip/issues/1150)
|
||||
self.version = wheel_info.group('ver').replace('_', '-')
|
||||
self.build_tag = wheel_info.group('build')
|
||||
self.pyversions = wheel_info.group('pyver').split('.')
|
||||
self.abis = wheel_info.group('abi').split('.')
|
||||
self.plats = wheel_info.group('plat').split('.')
|
||||
|
||||
# All the tag combinations from this file
|
||||
self.file_tags = set(
|
||||
self.file_tags = {
|
||||
(x, y, z) for x in self.pyversions
|
||||
for y in self.abis for z in self.plats
|
||||
)
|
||||
}
|
||||
|
||||
def support_index_min(self, tags=None):
|
||||
"""
|
||||
@@ -653,41 +596,51 @@ class Wheel(object):
|
||||
None is the wheel is not supported.
|
||||
"""
|
||||
if tags is None: # for mock
|
||||
tags = pep425tags.supported_tags
|
||||
tags = pep425tags.get_supported()
|
||||
indexes = [tags.index(c) for c in self.file_tags if c in tags]
|
||||
return min(indexes) if indexes else None
|
||||
|
||||
def supported(self, tags=None):
|
||||
"""Is this wheel supported on this system?"""
|
||||
if tags is None: # for mock
|
||||
tags = pep425tags.supported_tags
|
||||
tags = pep425tags.get_supported()
|
||||
return bool(set(tags).intersection(self.file_tags))
|
||||
|
||||
|
||||
class WheelBuilder(object):
|
||||
"""Build wheels from a RequirementSet."""
|
||||
|
||||
def __init__(self, requirement_set, finder, build_options=None,
|
||||
global_options=None):
|
||||
self.requirement_set = requirement_set
|
||||
def __init__(self, finder, preparer, wheel_cache,
|
||||
build_options=None, global_options=None, no_clean=False):
|
||||
self.finder = finder
|
||||
self._cache_root = requirement_set._wheel_cache._cache_dir
|
||||
self._wheel_dir = requirement_set.wheel_download_dir
|
||||
self.preparer = preparer
|
||||
self.wheel_cache = wheel_cache
|
||||
|
||||
self._wheel_dir = preparer.wheel_download_dir
|
||||
|
||||
self.build_options = build_options or []
|
||||
self.global_options = global_options or []
|
||||
self.no_clean = no_clean
|
||||
|
||||
def _build_one(self, req, output_dir, python_tag=None):
|
||||
"""Build one wheel.
|
||||
|
||||
:return: The filename of the built wheel, or None if the build failed.
|
||||
"""
|
||||
tempd = tempfile.mkdtemp('pip-wheel-')
|
||||
try:
|
||||
if self.__build_one(req, tempd, python_tag=python_tag):
|
||||
# Install build deps into temporary directory (PEP 518)
|
||||
with req.build_env:
|
||||
return self._build_one_inside_env(req, output_dir,
|
||||
python_tag=python_tag)
|
||||
|
||||
def _build_one_inside_env(self, req, output_dir, python_tag=None):
|
||||
with TempDirectory(kind="wheel") as temp_dir:
|
||||
if self.__build_one(req, temp_dir.path, python_tag=python_tag):
|
||||
try:
|
||||
wheel_name = os.listdir(tempd)[0]
|
||||
wheel_name = os.listdir(temp_dir.path)[0]
|
||||
wheel_path = os.path.join(output_dir, wheel_name)
|
||||
shutil.move(os.path.join(tempd, wheel_name), wheel_path)
|
||||
shutil.move(
|
||||
os.path.join(temp_dir.path, wheel_name), wheel_path
|
||||
)
|
||||
logger.info('Stored in directory: %s', output_dir)
|
||||
return wheel_path
|
||||
except:
|
||||
@@ -695,12 +648,14 @@ class WheelBuilder(object):
|
||||
# Ignore return, we can't do anything else useful.
|
||||
self._clean_one(req)
|
||||
return None
|
||||
finally:
|
||||
rmtree(tempd)
|
||||
|
||||
def _base_setup_args(self, req):
|
||||
# NOTE: Eventually, we'd want to also -S to the flags here, when we're
|
||||
# isolating. Currently, it breaks Python in virtualenvs, because it
|
||||
# relies on site.py to find parts of the standard library outside the
|
||||
# virtualenv.
|
||||
return [
|
||||
(PIP_PYTHON_PATH or sys.executable), "-u", '-c',
|
||||
os.environ.get('PIP_PYTHON_PATH', sys.executable), '-u', '-c',
|
||||
SETUPTOOLS_SHIM % req.setup_py
|
||||
] + list(self.global_options)
|
||||
|
||||
@@ -737,49 +692,53 @@ class WheelBuilder(object):
|
||||
logger.error('Failed cleaning build dir for %s', req.name)
|
||||
return False
|
||||
|
||||
def build(self, autobuilding=False):
|
||||
def build(self, requirements, session, autobuilding=False):
|
||||
"""Build wheels.
|
||||
|
||||
:param unpack: If True, replace the sdist we built from with the
|
||||
newly built wheel, in preparation for installation.
|
||||
:return: True if all the wheels built correctly.
|
||||
"""
|
||||
assert self._wheel_dir or (autobuilding and self._cache_root)
|
||||
# unpack sdists and constructs req set
|
||||
self.requirement_set.prepare_files(self.finder)
|
||||
from notpip._internal import index
|
||||
|
||||
reqset = self.requirement_set.requirements.values()
|
||||
building_is_possible = self._wheel_dir or (
|
||||
autobuilding and self.wheel_cache.cache_dir
|
||||
)
|
||||
assert building_is_possible
|
||||
|
||||
buildset = []
|
||||
for req in reqset:
|
||||
for req in requirements:
|
||||
if req.constraint:
|
||||
continue
|
||||
if req.is_wheel:
|
||||
if not autobuilding:
|
||||
logger.info(
|
||||
'Skipping %s, due to already being wheel.', req.name)
|
||||
'Skipping %s, due to already being wheel.', req.name,
|
||||
)
|
||||
elif autobuilding and req.editable:
|
||||
pass
|
||||
elif autobuilding and req.link and not req.link.is_artifact:
|
||||
pass
|
||||
elif autobuilding and not req.source_dir:
|
||||
pass
|
||||
elif autobuilding and req.link and not req.link.is_artifact:
|
||||
# VCS checkout. Build wheel just for this run.
|
||||
buildset.append((req, True))
|
||||
else:
|
||||
ephem_cache = False
|
||||
if autobuilding:
|
||||
link = req.link
|
||||
base, ext = link.splitext()
|
||||
if pip9.index.egg_info_matches(base, None, link) is None:
|
||||
# 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 pip9.index.fmt_ctl_formats(
|
||||
if index.egg_info_matches(base, None, link) is None:
|
||||
# E.g. local directory. Build wheel just for this run.
|
||||
ephem_cache = True
|
||||
if "binary" not in index.fmt_ctl_formats(
|
||||
self.finder.format_control,
|
||||
canonicalize_name(req.name)):
|
||||
logger.info(
|
||||
"Skipping bdist_wheel for %s, due to binaries "
|
||||
"being disabled for it.", req.name)
|
||||
"being disabled for it.", req.name,
|
||||
)
|
||||
continue
|
||||
buildset.append(req)
|
||||
buildset.append((req, ephem_cache))
|
||||
|
||||
if not buildset:
|
||||
return True
|
||||
@@ -787,15 +746,19 @@ class WheelBuilder(object):
|
||||
# Build the wheels.
|
||||
logger.info(
|
||||
'Building wheels for collected packages: %s',
|
||||
', '.join([req.name for req in buildset]),
|
||||
', '.join([req.name for (req, _) in buildset]),
|
||||
)
|
||||
_cache = self.wheel_cache # shorter name
|
||||
with indent_log():
|
||||
build_success, build_failure = [], []
|
||||
for req in buildset:
|
||||
for req, ephem in buildset:
|
||||
python_tag = None
|
||||
if autobuilding:
|
||||
python_tag = pep425tags.implementation_tag
|
||||
output_dir = _cache_for_link(self._cache_root, req.link)
|
||||
if ephem:
|
||||
output_dir = _cache.get_ephem_path_for_link(req.link)
|
||||
else:
|
||||
output_dir = _cache.get_path_for_link(req.link)
|
||||
try:
|
||||
ensure_dir(output_dir)
|
||||
except OSError as e:
|
||||
@@ -826,15 +789,16 @@ class WheelBuilder(object):
|
||||
# set the build directory again - name is known from
|
||||
# the work prepare_files did.
|
||||
req.source_dir = req.build_location(
|
||||
self.requirement_set.build_dir)
|
||||
self.preparer.build_dir
|
||||
)
|
||||
# Update the link for this.
|
||||
req.link = pip9.index.Link(
|
||||
path_to_url(wheel_file))
|
||||
req.link = index.Link(path_to_url(wheel_file))
|
||||
assert req.link.is_wheel
|
||||
# extract the wheel into the dir
|
||||
unpack_url(
|
||||
req.link, req.source_dir, None, False,
|
||||
session=self.requirement_set.session)
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
build_failure.append(req)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
pip9._vendor is for vendoring dependencies of pip to prevent needing pip to
|
||||
pip._vendor is for vendoring dependencies of pip to prevent needing pip to
|
||||
depend on something external.
|
||||
|
||||
Files inside of pip9._vendor should be considered immutable and should only be
|
||||
Files inside of pip._vendor should be considered immutable and should only be
|
||||
updated to versions from upstream.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
@@ -13,7 +13,7 @@ import sys
|
||||
|
||||
# Downstream redistributors which have debundled our dependencies should also
|
||||
# patch this value to be true. This will trigger the additional patching
|
||||
# to cause things like "six" to be available as pip9.
|
||||
# to cause things like "six" to be available as pip.
|
||||
DEBUNDLED = False
|
||||
|
||||
# By default, look in this directory for a bunch of .whl files which we will
|
||||
@@ -36,7 +36,7 @@ def vendored(modulename):
|
||||
__import__(modulename, globals(), locals(), level=0)
|
||||
except ImportError:
|
||||
# We can just silently allow import failures to pass here. If we
|
||||
# got to this point it means that ``import pip9._vendor.whatever``
|
||||
# got to this point it means that ``import notpip._vendor.whatever``
|
||||
# failed and so did ``import whatever``. Since we're importing this
|
||||
# upfront in an attempt to alias imports, not erroring here will
|
||||
# just mean we get a regular import error whenever pip *actually*
|
||||
@@ -70,11 +70,13 @@ if DEBUNDLED:
|
||||
vendored("six")
|
||||
vendored("six.moves")
|
||||
vendored("six.moves.urllib")
|
||||
vendored("six.moves.urllib.parse")
|
||||
vendored("packaging")
|
||||
vendored("packaging.version")
|
||||
vendored("packaging.specifiers")
|
||||
vendored("pkg_resources")
|
||||
vendored("progress")
|
||||
vendored("pytoml")
|
||||
vendored("retrying")
|
||||
vendored("requests")
|
||||
vendored("requests.packages")
|
||||
|
||||
@@ -10,10 +10,10 @@ See <http://github.com/ActiveState/appdirs> for details and usage.
|
||||
# Dev Notes:
|
||||
# - MSDN on where to store app data files:
|
||||
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
|
||||
# - macOS: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
|
||||
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
|
||||
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
|
||||
__version_info__ = (1, 4, 0)
|
||||
__version_info__ = (1, 4, 3)
|
||||
__version__ = '.'.join(map(str, __version_info__))
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ if sys.platform.startswith('java'):
|
||||
os_name = platform.java_ver()[3][0]
|
||||
if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc.
|
||||
system = 'win32'
|
||||
elif os_name.startswith('Mac'): # "macOS", etc.
|
||||
elif os_name.startswith('Mac'): # "Mac OS X", etc.
|
||||
system = 'darwin'
|
||||
else: # "Linux", "SunOS", "FreeBSD", etc.
|
||||
# Setting this to "linux2" is not ideal, but only Windows or Mac
|
||||
@@ -64,7 +64,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
for a discussion of issues.
|
||||
|
||||
Typical user data directories are:
|
||||
macOS: ~/Library/Application Support/<AppName>
|
||||
Mac OS X: ~/Library/Application Support/<AppName>
|
||||
Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
|
||||
Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
|
||||
Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
|
||||
@@ -98,7 +98,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
|
||||
|
||||
def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
"""Return full path to the user-shared data dir for this application.
|
||||
r"""Return full path to the user-shared data dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
@@ -117,8 +117,8 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
returned, or '/usr/local/share/<AppName>',
|
||||
if XDG_DATA_DIRS is not set
|
||||
|
||||
Typical user data directories are:
|
||||
macOS: /Library/Application Support/<AppName>
|
||||
Typical site data directories are:
|
||||
Mac OS X: /Library/Application Support/<AppName>
|
||||
Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
|
||||
Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
|
||||
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
|
||||
@@ -184,13 +184,13 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
|
||||
for a discussion of issues.
|
||||
|
||||
Typical user data directories are:
|
||||
macOS: same as user_data_dir
|
||||
Typical user config directories are:
|
||||
Mac OS X: same as user_data_dir
|
||||
Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
|
||||
Win *: same as user_data_dir
|
||||
|
||||
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
|
||||
That means, by deafult "~/.config/<AppName>".
|
||||
That means, by default "~/.config/<AppName>".
|
||||
"""
|
||||
if system in ["win32", "darwin"]:
|
||||
path = user_data_dir(appname, appauthor, None, roaming)
|
||||
@@ -204,7 +204,7 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
|
||||
|
||||
def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
"""Return full path to the user-shared data dir for this application.
|
||||
r"""Return full path to the user-shared data dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
@@ -222,8 +222,8 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False)
|
||||
returned. By default, the first item from XDG_CONFIG_DIRS is
|
||||
returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
|
||||
|
||||
Typical user data directories are:
|
||||
macOS: same as site_data_dir
|
||||
Typical site config directories are:
|
||||
Mac OS X: same as site_data_dir
|
||||
Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
|
||||
$XDG_CONFIG_DIRS
|
||||
Win *: same as site_data_dir
|
||||
@@ -273,7 +273,7 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
discussion below.
|
||||
|
||||
Typical user cache directories are:
|
||||
macOS: ~/Library/Caches/<AppName>
|
||||
Mac OS X: ~/Library/Caches/<AppName>
|
||||
Unix: ~/.cache/<AppName> (XDG default)
|
||||
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
|
||||
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
|
||||
@@ -311,6 +311,48 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
return path
|
||||
|
||||
|
||||
def user_state_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
r"""Return full path to the user-specific state dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname. You may
|
||||
pass False to disable it.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"roaming" (boolean, default False) can be set True to use the Windows
|
||||
roaming appdata directory. That means that for users on a Windows
|
||||
network setup for roaming profiles, this user data will be
|
||||
sync'd on login. See
|
||||
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
|
||||
for a discussion of issues.
|
||||
|
||||
Typical user state directories are:
|
||||
Mac OS X: same as user_data_dir
|
||||
Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined
|
||||
Win *: same as user_data_dir
|
||||
|
||||
For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state>
|
||||
to extend the XDG spec and support $XDG_STATE_HOME.
|
||||
|
||||
That means, by default "~/.local/state/<AppName>".
|
||||
"""
|
||||
if system in ["win32", "darwin"]:
|
||||
path = user_data_dir(appname, appauthor, None, roaming)
|
||||
else:
|
||||
path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state"))
|
||||
if appname:
|
||||
path = os.path.join(path, appname)
|
||||
if appname and version:
|
||||
path = os.path.join(path, version)
|
||||
return path
|
||||
|
||||
|
||||
def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
r"""Return full path to the user-specific log dir for this application.
|
||||
|
||||
@@ -329,8 +371,8 @@ def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
"Logs" to the base app data dir for Windows, and "log" to the
|
||||
base cache dir for Unix. See discussion below.
|
||||
|
||||
Typical user cache directories are:
|
||||
macOS: ~/Library/Logs/<AppName>
|
||||
Typical user log directories are:
|
||||
Mac OS X: ~/Library/Logs/<AppName>
|
||||
Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
|
||||
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
|
||||
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
|
||||
@@ -364,8 +406,8 @@ def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
|
||||
class AppDirs(object):
|
||||
"""Convenience wrapper for getting application dirs."""
|
||||
def __init__(self, appname, appauthor=None, version=None, roaming=False,
|
||||
multipath=False):
|
||||
def __init__(self, appname=None, appauthor=None, version=None,
|
||||
roaming=False, multipath=False):
|
||||
self.appname = appname
|
||||
self.appauthor = appauthor
|
||||
self.version = version
|
||||
@@ -397,6 +439,11 @@ class AppDirs(object):
|
||||
return user_cache_dir(self.appname, self.appauthor,
|
||||
version=self.version)
|
||||
|
||||
@property
|
||||
def user_state_dir(self):
|
||||
return user_state_dir(self.appname, self.appauthor,
|
||||
version=self.version)
|
||||
|
||||
@property
|
||||
def user_log_dir(self):
|
||||
return user_log_dir(self.appname, self.appauthor,
|
||||
@@ -410,7 +457,10 @@ def _get_win_folder_from_registry(csidl_name):
|
||||
registry for this guarantees us the correct answer for all CSIDL_*
|
||||
names.
|
||||
"""
|
||||
import _winreg
|
||||
if PY3:
|
||||
import winreg as _winreg
|
||||
else:
|
||||
import _winreg
|
||||
|
||||
shell_folder_name = {
|
||||
"CSIDL_APPDATA": "AppData",
|
||||
@@ -500,25 +550,21 @@ def _get_win_folder_with_jna(csidl_name):
|
||||
if has_high_char:
|
||||
buf = array.zeros('c', buf_size)
|
||||
kernel = win32.Kernel32.INSTANCE
|
||||
if kernal.GetShortPathName(dir, buf, buf_size):
|
||||
if kernel.GetShortPathName(dir, buf, buf_size):
|
||||
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
|
||||
|
||||
return dir
|
||||
|
||||
if system == "win32":
|
||||
try:
|
||||
import win32com.shell
|
||||
_get_win_folder = _get_win_folder_with_pywin32
|
||||
from ctypes import windll
|
||||
_get_win_folder = _get_win_folder_with_ctypes
|
||||
except ImportError:
|
||||
try:
|
||||
from ctypes import windll
|
||||
_get_win_folder = _get_win_folder_with_ctypes
|
||||
import com.sun.jna
|
||||
_get_win_folder = _get_win_folder_with_jna
|
||||
except ImportError:
|
||||
try:
|
||||
import com.sun.jna
|
||||
_get_win_folder = _get_win_folder_with_jna
|
||||
except ImportError:
|
||||
_get_win_folder = _get_win_folder_from_registry
|
||||
_get_win_folder = _get_win_folder_from_registry
|
||||
|
||||
|
||||
#---- self test code
|
||||
@@ -527,9 +573,15 @@ if __name__ == "__main__":
|
||||
appname = "MyApp"
|
||||
appauthor = "MyCompany"
|
||||
|
||||
props = ("user_data_dir", "site_data_dir",
|
||||
"user_config_dir", "site_config_dir",
|
||||
"user_cache_dir", "user_log_dir")
|
||||
props = ("user_data_dir",
|
||||
"user_config_dir",
|
||||
"user_cache_dir",
|
||||
"user_state_dir",
|
||||
"user_log_dir",
|
||||
"site_data_dir",
|
||||
"site_config_dir")
|
||||
|
||||
print("-- app dirs %s --" % __version__)
|
||||
|
||||
print("-- app dirs (with optional 'version')")
|
||||
dirs = AppDirs(appname, appauthor, version="1.0")
|
||||
|
||||
@@ -4,7 +4,7 @@ Make it easy to import from cachecontrol without long namespaces.
|
||||
"""
|
||||
__author__ = 'Eric Larson'
|
||||
__email__ = 'eric@ionrock.org'
|
||||
__version__ = '0.11.7'
|
||||
__version__ = '0.12.4'
|
||||
|
||||
from .wrapper import CacheControl
|
||||
from .adapter import CacheControlAdapter
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import logging
|
||||
|
||||
from pip9._vendor import requests
|
||||
from notpip._vendor import requests
|
||||
|
||||
from pip9._vendor.cachecontrol.adapter import CacheControlAdapter
|
||||
from pip9._vendor.cachecontrol.cache import DictCache
|
||||
from pip9._vendor.cachecontrol.controller import logger
|
||||
from notpip._vendor.cachecontrol.adapter import CacheControlAdapter
|
||||
from notpip._vendor.cachecontrol.cache import DictCache
|
||||
from notpip._vendor.cachecontrol.controller import logger
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import types
|
||||
import functools
|
||||
import zlib
|
||||
|
||||
from pip9._vendor.requests.adapters import HTTPAdapter
|
||||
from notpip._vendor.requests.adapters import HTTPAdapter
|
||||
|
||||
from .controller import CacheController
|
||||
from .cache import DictCache
|
||||
@@ -16,10 +17,12 @@ class CacheControlAdapter(HTTPAdapter):
|
||||
controller_class=None,
|
||||
serializer=None,
|
||||
heuristic=None,
|
||||
cacheable_methods=None,
|
||||
*args, **kw):
|
||||
super(CacheControlAdapter, self).__init__(*args, **kw)
|
||||
self.cache = cache or DictCache()
|
||||
self.heuristic = heuristic
|
||||
self.cacheable_methods = cacheable_methods or ('GET',)
|
||||
|
||||
controller_factory = controller_class or CacheController
|
||||
self.controller = controller_factory(
|
||||
@@ -28,13 +31,17 @@ class CacheControlAdapter(HTTPAdapter):
|
||||
serializer=serializer,
|
||||
)
|
||||
|
||||
def send(self, request, **kw):
|
||||
def send(self, request, cacheable_methods=None, **kw):
|
||||
"""
|
||||
Send a request. Use the request information to see if it
|
||||
exists in the cache and cache the response if we need to and can.
|
||||
"""
|
||||
if request.method == 'GET':
|
||||
cached_response = self.controller.cached_request(request)
|
||||
cacheable = cacheable_methods or self.cacheable_methods
|
||||
if request.method in cacheable:
|
||||
try:
|
||||
cached_response = self.controller.cached_request(request)
|
||||
except zlib.error:
|
||||
cached_response = None
|
||||
if cached_response:
|
||||
return self.build_response(request, cached_response,
|
||||
from_cache=True)
|
||||
@@ -48,14 +55,16 @@ class CacheControlAdapter(HTTPAdapter):
|
||||
|
||||
return resp
|
||||
|
||||
def build_response(self, request, response, from_cache=False):
|
||||
def build_response(self, request, response, from_cache=False,
|
||||
cacheable_methods=None):
|
||||
"""
|
||||
Build a response by making a request or using the cache.
|
||||
|
||||
This will end up calling send and returning a potentially
|
||||
cached response
|
||||
"""
|
||||
if not from_cache and request.method == 'GET':
|
||||
cacheable = cacheable_methods or self.cacheable_methods
|
||||
if not from_cache and request.method in cacheable:
|
||||
# Check for any heuristics that might update headers
|
||||
# before trying to cache.
|
||||
if self.heuristic:
|
||||
|
||||
@@ -1,18 +1,2 @@
|
||||
from textwrap import dedent
|
||||
|
||||
try:
|
||||
from .file_cache import FileCache
|
||||
except ImportError:
|
||||
notice = dedent('''
|
||||
NOTE: In order to use the FileCache you must have
|
||||
lockfile installed. You can install it via pip:
|
||||
pip install lockfile
|
||||
''')
|
||||
print(notice)
|
||||
|
||||
|
||||
try:
|
||||
import redis
|
||||
from .redis_cache import RedisCache
|
||||
except ImportError:
|
||||
pass
|
||||
from .file_cache import FileCache # noqa
|
||||
from .redis_cache import RedisCache # noqa
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from pip9._vendor.lockfile import LockFile
|
||||
from pip9._vendor.lockfile.mkdirlockfile import MkdirLockFile
|
||||
from textwrap import dedent
|
||||
|
||||
from ..cache import BaseCache
|
||||
from ..controller import CacheController
|
||||
|
||||
try:
|
||||
FileNotFoundError
|
||||
except NameError:
|
||||
# py2.X
|
||||
FileNotFoundError = OSError
|
||||
|
||||
|
||||
def _secure_open_write(filename, fmode):
|
||||
# We only want to write to this file, so open it in write only mode
|
||||
@@ -55,11 +59,22 @@ class FileCache(BaseCache):
|
||||
if use_dir_lock is not None and lock_class is not None:
|
||||
raise ValueError("Cannot use use_dir_lock and lock_class together")
|
||||
|
||||
if use_dir_lock:
|
||||
lock_class = MkdirLockFile
|
||||
try:
|
||||
from notpip._vendor.lockfile import LockFile
|
||||
from notpip._vendor.lockfile.mkdirlockfile import MkdirLockFile
|
||||
except ImportError:
|
||||
notice = dedent("""
|
||||
NOTE: In order to use the FileCache you must have
|
||||
lockfile installed. You can install it via pip:
|
||||
pip install lockfile
|
||||
""")
|
||||
raise ImportError(notice)
|
||||
else:
|
||||
if use_dir_lock:
|
||||
lock_class = MkdirLockFile
|
||||
|
||||
if lock_class is None:
|
||||
lock_class = LockFile
|
||||
elif lock_class is None:
|
||||
lock_class = LockFile
|
||||
|
||||
self.directory = directory
|
||||
self.forever = forever
|
||||
@@ -67,7 +82,6 @@ class FileCache(BaseCache):
|
||||
self.dirmode = dirmode
|
||||
self.lock_class = lock_class
|
||||
|
||||
|
||||
@staticmethod
|
||||
def encode(x):
|
||||
return hashlib.sha224(x.encode()).hexdigest()
|
||||
@@ -104,7 +118,10 @@ class FileCache(BaseCache):
|
||||
def delete(self, key):
|
||||
name = self._fn(key)
|
||||
if not self.forever:
|
||||
os.remove(name)
|
||||
try:
|
||||
os.remove(name)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def url_to_file_path(url, filecache):
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
from __future__ import division
|
||||
|
||||
from datetime import datetime
|
||||
from notpip._vendor.cachecontrol.cache import BaseCache
|
||||
|
||||
|
||||
def total_seconds(td):
|
||||
"""Python 2.6 compatability"""
|
||||
if hasattr(td, 'total_seconds'):
|
||||
return td.total_seconds()
|
||||
return int(td.total_seconds())
|
||||
|
||||
ms = td.microseconds
|
||||
secs = (td.seconds + td.days * 24 * 3600)
|
||||
return (ms + secs * 10**6) / 10**6
|
||||
return int((ms + secs * 10**6) / 10**6)
|
||||
|
||||
|
||||
class RedisCache(object):
|
||||
class RedisCache(BaseCache):
|
||||
|
||||
def __init__(self, conn):
|
||||
self.conn = conn
|
||||
@@ -25,7 +26,7 @@ class RedisCache(object):
|
||||
if not expires:
|
||||
self.conn.set(key, value)
|
||||
else:
|
||||
expires = expires - datetime.now()
|
||||
expires = expires - datetime.utcnow()
|
||||
self.conn.setex(key, total_seconds(expires), value)
|
||||
|
||||
def delete(self, key):
|
||||
@@ -38,4 +39,5 @@ class RedisCache(object):
|
||||
self.conn.delete(key)
|
||||
|
||||
def close(self):
|
||||
self.conn.disconnect()
|
||||
"""Redis uses connection pooling, no need to close the connection."""
|
||||
pass
|
||||
|
||||
@@ -10,11 +10,20 @@ except ImportError:
|
||||
import pickle
|
||||
|
||||
|
||||
from pip9._vendor.urllib3.response import HTTPResponse
|
||||
from pip9._vendor.urllib3.util import is_fp_closed
|
||||
# Handle the case where the requests module has been patched to not have
|
||||
# urllib3 bundled as part of its source.
|
||||
try:
|
||||
from notpip._vendor.requests.packages.urllib3.response import HTTPResponse
|
||||
except ImportError:
|
||||
from notpip._vendor.urllib3.response import HTTPResponse
|
||||
|
||||
try:
|
||||
from notpip._vendor.requests.packages.urllib3.util import is_fp_closed
|
||||
except ImportError:
|
||||
from notpip._vendor.urllib3.util import is_fp_closed
|
||||
|
||||
# Replicate some six behaviour
|
||||
try:
|
||||
text_type = (unicode,)
|
||||
text_type = unicode
|
||||
except NameError:
|
||||
text_type = (str,)
|
||||
text_type = str
|
||||
|
||||
@@ -7,7 +7,7 @@ import calendar
|
||||
import time
|
||||
from email.utils import parsedate_tz
|
||||
|
||||
from pip9._vendor.requests.structures import CaseInsensitiveDict
|
||||
from notpip._vendor.requests.structures import CaseInsensitiveDict
|
||||
|
||||
from .cache import DictCache
|
||||
from .serialize import Serializer
|
||||
@@ -30,10 +30,12 @@ def parse_uri(uri):
|
||||
class CacheController(object):
|
||||
"""An interface to see if request should cached or not.
|
||||
"""
|
||||
def __init__(self, cache=None, cache_etags=True, serializer=None):
|
||||
def __init__(self, cache=None, cache_etags=True, serializer=None,
|
||||
status_codes=None):
|
||||
self.cache = cache or DictCache()
|
||||
self.cache_etags = cache_etags
|
||||
self.serializer = serializer or Serializer()
|
||||
self.cacheable_status_codes = status_codes or (200, 203, 300, 301)
|
||||
|
||||
@classmethod
|
||||
def _urlnorm(cls, uri):
|
||||
@@ -60,27 +62,51 @@ class CacheController(object):
|
||||
return cls._urlnorm(uri)
|
||||
|
||||
def parse_cache_control(self, headers):
|
||||
"""
|
||||
Parse the cache control headers returning a dictionary with values
|
||||
for the different directives.
|
||||
"""
|
||||
known_directives = {
|
||||
# https://tools.ietf.org/html/rfc7234#section-5.2
|
||||
'max-age': (int, True,),
|
||||
'max-stale': (int, False,),
|
||||
'min-fresh': (int, True,),
|
||||
'no-cache': (None, False,),
|
||||
'no-store': (None, False,),
|
||||
'no-transform': (None, False,),
|
||||
'only-if-cached' : (None, False,),
|
||||
'must-revalidate': (None, False,),
|
||||
'public': (None, False,),
|
||||
'private': (None, False,),
|
||||
'proxy-revalidate': (None, False,),
|
||||
's-maxage': (int, True,)
|
||||
}
|
||||
|
||||
cc_headers = headers.get('cache-control',
|
||||
headers.get('Cache-Control', ''))
|
||||
|
||||
retval = {}
|
||||
|
||||
cc_header = 'cache-control'
|
||||
if 'Cache-Control' in headers:
|
||||
cc_header = 'Cache-Control'
|
||||
for cc_directive in cc_headers.split(','):
|
||||
parts = cc_directive.split('=', 1)
|
||||
directive = parts[0].strip()
|
||||
|
||||
try:
|
||||
typ, required = known_directives[directive]
|
||||
except KeyError:
|
||||
logger.debug('Ignoring unknown cache-control directive: %s',
|
||||
directive)
|
||||
continue
|
||||
|
||||
if not typ or not required:
|
||||
retval[directive] = None
|
||||
if typ:
|
||||
try:
|
||||
retval[directive] = typ(parts[1].strip())
|
||||
except IndexError:
|
||||
if required:
|
||||
logger.debug('Missing value for cache-control '
|
||||
'directive: %s', directive)
|
||||
except ValueError:
|
||||
logger.debug('Invalid value for cache-control directive '
|
||||
'%s, must be %s', directive, typ.__name__)
|
||||
|
||||
if cc_header in headers:
|
||||
parts = headers[cc_header].split(',')
|
||||
parts_with_args = [
|
||||
tuple([x.strip().lower() for x in part.split("=", 1)])
|
||||
for part in parts if -1 != part.find("=")
|
||||
]
|
||||
parts_wo_args = [
|
||||
(name.strip().lower(), 1)
|
||||
for name in parts if -1 == name.find("=")
|
||||
]
|
||||
retval = dict(parts_with_args + parts_wo_args)
|
||||
return retval
|
||||
|
||||
def cached_request(self, request):
|
||||
@@ -154,8 +180,8 @@ class CacheController(object):
|
||||
freshness_lifetime = 0
|
||||
|
||||
# Check the max-age pragma in the cache control header
|
||||
if 'max-age' in resp_cc and resp_cc['max-age'].isdigit():
|
||||
freshness_lifetime = int(resp_cc['max-age'])
|
||||
if 'max-age' in resp_cc:
|
||||
freshness_lifetime = resp_cc['max-age']
|
||||
logger.debug('Freshness lifetime from max-age: %i',
|
||||
freshness_lifetime)
|
||||
|
||||
@@ -171,18 +197,12 @@ class CacheController(object):
|
||||
# Determine if we are setting freshness limit in the
|
||||
# request. Note, this overrides what was in the response.
|
||||
if 'max-age' in cc:
|
||||
try:
|
||||
freshness_lifetime = int(cc['max-age'])
|
||||
logger.debug('Freshness lifetime from request max-age: %i',
|
||||
freshness_lifetime)
|
||||
except ValueError:
|
||||
freshness_lifetime = 0
|
||||
freshness_lifetime = cc['max-age']
|
||||
logger.debug('Freshness lifetime from request max-age: %i',
|
||||
freshness_lifetime)
|
||||
|
||||
if 'min-fresh' in cc:
|
||||
try:
|
||||
min_fresh = int(cc['min-fresh'])
|
||||
except ValueError:
|
||||
min_fresh = 0
|
||||
min_fresh = cc['min-fresh']
|
||||
# adjust our current age by our min fresh
|
||||
current_age += min_fresh
|
||||
logger.debug('Adjusted current age from min-fresh: %i',
|
||||
@@ -220,7 +240,8 @@ class CacheController(object):
|
||||
|
||||
return new_headers
|
||||
|
||||
def cache_response(self, request, response, body=None):
|
||||
def cache_response(self, request, response, body=None,
|
||||
status_codes=None):
|
||||
"""
|
||||
Algorithm for caching requests.
|
||||
|
||||
@@ -228,7 +249,7 @@ class CacheController(object):
|
||||
"""
|
||||
# From httplib2: Don't cache 206's since we aren't going to
|
||||
# handle byte range requests
|
||||
cacheable_status_codes = [200, 203, 300, 301]
|
||||
cacheable_status_codes = status_codes or self.cacheable_status_codes
|
||||
if response.status not in cacheable_status_codes:
|
||||
logger.debug(
|
||||
'Status code %s not in %s',
|
||||
@@ -257,10 +278,10 @@ class CacheController(object):
|
||||
|
||||
# Delete it from the cache if we happen to have it stored there
|
||||
no_store = False
|
||||
if cc.get('no-store'):
|
||||
if 'no-store' in cc:
|
||||
no_store = True
|
||||
logger.debug('Response header has "no-store"')
|
||||
if cc_req.get('no-store'):
|
||||
if 'no-store' in cc_req:
|
||||
no_store = True
|
||||
logger.debug('Request header has "no-store"')
|
||||
if no_store and self.cache.get(cache_url):
|
||||
@@ -289,13 +310,12 @@ class CacheController(object):
|
||||
# the cache.
|
||||
elif 'date' in response_headers:
|
||||
# cache when there is a max-age > 0
|
||||
if cc and cc.get('max-age'):
|
||||
if cc['max-age'].isdigit() and int(cc['max-age']) > 0:
|
||||
logger.debug('Caching b/c date exists and max-age > 0')
|
||||
self.cache.set(
|
||||
cache_url,
|
||||
self.serializer.dumps(request, response, body=body),
|
||||
)
|
||||
if 'max-age' in cc and cc['max-age'] > 0:
|
||||
logger.debug('Caching b/c date exists and max-age > 0')
|
||||
self.cache.set(
|
||||
cache_url,
|
||||
self.serializer.dumps(request, response, body=body),
|
||||
)
|
||||
|
||||
# If the request can expire, it means we should cache it
|
||||
# in the meantime.
|
||||
|
||||
@@ -9,7 +9,7 @@ TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||
|
||||
|
||||
def expire_after(delta, date=None):
|
||||
date = date or datetime.now()
|
||||
date = date or datetime.utcnow()
|
||||
return date + delta
|
||||
|
||||
|
||||
|
||||
@@ -3,25 +3,12 @@ import io
|
||||
import json
|
||||
import zlib
|
||||
|
||||
from pip9._vendor.requests.structures import CaseInsensitiveDict
|
||||
from notpip._vendor import msgpack
|
||||
from notpip._vendor.requests.structures import CaseInsensitiveDict
|
||||
|
||||
from .compat import HTTPResponse, pickle, text_type
|
||||
|
||||
|
||||
def _b64_encode_bytes(b):
|
||||
return base64.b64encode(b).decode("ascii")
|
||||
|
||||
|
||||
def _b64_encode_str(s):
|
||||
return _b64_encode_bytes(s.encode("utf8"))
|
||||
|
||||
|
||||
def _b64_encode(s):
|
||||
if isinstance(s, text_type):
|
||||
return _b64_encode_str(s)
|
||||
return _b64_encode_bytes(s)
|
||||
|
||||
|
||||
def _b64_decode_bytes(b):
|
||||
return base64.b64decode(b.encode("ascii"))
|
||||
|
||||
@@ -50,43 +37,40 @@ class Serializer(object):
|
||||
# `Serializer.dump`.
|
||||
response._fp = io.BytesIO(body)
|
||||
|
||||
# NOTE: This is all a bit weird, but it's really important that on
|
||||
# Python 2.x these objects are unicode and not str, even when
|
||||
# they contain only ascii. The problem here is that msgpack
|
||||
# understands the difference between unicode and bytes and we
|
||||
# have it set to differentiate between them, however Python 2
|
||||
# doesn't know the difference. Forcing these to unicode will be
|
||||
# enough to have msgpack know the difference.
|
||||
data = {
|
||||
"response": {
|
||||
"body": _b64_encode_bytes(body),
|
||||
"headers": dict(
|
||||
(_b64_encode(k), _b64_encode(v))
|
||||
u"response": {
|
||||
u"body": body,
|
||||
u"headers": dict(
|
||||
(text_type(k), text_type(v))
|
||||
for k, v in response.headers.items()
|
||||
),
|
||||
"status": response.status,
|
||||
"version": response.version,
|
||||
"reason": _b64_encode_str(response.reason),
|
||||
"strict": response.strict,
|
||||
"decode_content": response.decode_content,
|
||||
u"status": response.status,
|
||||
u"version": response.version,
|
||||
u"reason": text_type(response.reason),
|
||||
u"strict": response.strict,
|
||||
u"decode_content": response.decode_content,
|
||||
},
|
||||
}
|
||||
|
||||
# Construct our vary headers
|
||||
data["vary"] = {}
|
||||
if "vary" in response_headers:
|
||||
varied_headers = response_headers['vary'].split(',')
|
||||
data[u"vary"] = {}
|
||||
if u"vary" in response_headers:
|
||||
varied_headers = response_headers[u'vary'].split(',')
|
||||
for header in varied_headers:
|
||||
header = header.strip()
|
||||
data["vary"][header] = request.headers.get(header, None)
|
||||
header_value = request.headers.get(header, None)
|
||||
if header_value is not None:
|
||||
header_value = text_type(header_value)
|
||||
data[u"vary"][header] = header_value
|
||||
|
||||
# Encode our Vary headers to ensure they can be serialized as JSON
|
||||
data["vary"] = dict(
|
||||
(_b64_encode(k), _b64_encode(v) if v is not None else v)
|
||||
for k, v in data["vary"].items()
|
||||
)
|
||||
|
||||
return b",".join([
|
||||
b"cc=2",
|
||||
zlib.compress(
|
||||
json.dumps(
|
||||
data, separators=(",", ":"), sort_keys=True,
|
||||
).encode("utf8"),
|
||||
),
|
||||
])
|
||||
return b",".join([b"cc=4", msgpack.dumps(data, use_bin_type=True)])
|
||||
|
||||
def loads(self, request, data):
|
||||
# Short circuit if we've been given an empty set of data
|
||||
@@ -174,7 +158,7 @@ class Serializer(object):
|
||||
def _loads_v2(self, request, data):
|
||||
try:
|
||||
cached = json.loads(zlib.decompress(data).decode("utf8"))
|
||||
except ValueError:
|
||||
except (ValueError, zlib.error):
|
||||
return
|
||||
|
||||
# We need to decode the items that we've base64 encoded
|
||||
@@ -194,3 +178,17 @@ class Serializer(object):
|
||||
)
|
||||
|
||||
return self.prepare_response(request, cached)
|
||||
|
||||
def _loads_v3(self, request, data):
|
||||
# Due to Python 2 encoding issues, it's impossible to know for sure
|
||||
# exactly how to load v3 entries, thus we'll treat these as a miss so
|
||||
# that they get rewritten out as v4 entries.
|
||||
return
|
||||
|
||||
def _loads_v4(self, request, data):
|
||||
try:
|
||||
cached = msgpack.loads(data, encoding='utf-8')
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
return self.prepare_response(request, cached)
|
||||
|
||||
@@ -6,14 +6,20 @@ def CacheControl(sess,
|
||||
cache=None,
|
||||
cache_etags=True,
|
||||
serializer=None,
|
||||
heuristic=None):
|
||||
heuristic=None,
|
||||
controller_class=None,
|
||||
adapter_class=None,
|
||||
cacheable_methods=None):
|
||||
|
||||
cache = cache or DictCache()
|
||||
adapter = CacheControlAdapter(
|
||||
adapter_class = adapter_class or CacheControlAdapter
|
||||
adapter = adapter_class(
|
||||
cache,
|
||||
cache_etags=cache_etags,
|
||||
serializer=serializer,
|
||||
heuristic=heuristic,
|
||||
controller_class=controller_class,
|
||||
cacheable_methods=cacheable_methods
|
||||
)
|
||||
sess.mount('http://', adapter)
|
||||
sess.mount('https://', adapter)
|
||||
|
||||
@@ -18,9 +18,9 @@ from __future__ import absolute_import, print_function, unicode_literals
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from chardet import __version__
|
||||
from chardet.compat import PY2
|
||||
from chardet.universaldetector import UniversalDetector
|
||||
from notpip._vendor.chardet import __version__
|
||||
from notpip._vendor.chardet.compat import PY2
|
||||
from notpip._vendor.chardet.universaldetector import UniversalDetector
|
||||
|
||||
|
||||
def description_of(lines, name='stdin'):
|
||||
|
||||
@@ -3,5 +3,5 @@ from .initialise import init, deinit, reinit, colorama_text
|
||||
from .ansi import Fore, Back, Style, Cursor
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
__version__ = '0.3.7'
|
||||
__version__ = '0.3.9'
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ class AnsiToWin32(object):
|
||||
sequences from the text, and if outputting to a tty, will convert them into
|
||||
win32 function calls.
|
||||
'''
|
||||
ANSI_CSI_RE = re.compile('\001?\033\[((?:\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
|
||||
ANSI_OSC_RE = re.compile('\001?\033\]((?:.|;)*?)(\x07)\002?') # Operating System Command
|
||||
ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
|
||||
ANSI_OSC_RE = re.compile('\001?\033\\]((?:.|;)*?)(\x07)\002?') # Operating System Command
|
||||
|
||||
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
|
||||
# The wrapped stream (normally sys.stdout or sys.stderr)
|
||||
|
||||
@@ -83,9 +83,9 @@ else:
|
||||
]
|
||||
_FillConsoleOutputAttribute.restype = wintypes.BOOL
|
||||
|
||||
_SetConsoleTitleW = windll.kernel32.SetConsoleTitleA
|
||||
_SetConsoleTitleW = windll.kernel32.SetConsoleTitleW
|
||||
_SetConsoleTitleW.argtypes = [
|
||||
wintypes.LPCSTR
|
||||
wintypes.LPCWSTR
|
||||
]
|
||||
_SetConsoleTitleW.restype = wintypes.BOOL
|
||||
|
||||
@@ -94,13 +94,15 @@ else:
|
||||
STDERR: _GetStdHandle(STDERR),
|
||||
}
|
||||
|
||||
def winapi_test():
|
||||
handle = handles[STDOUT]
|
||||
def _winapi_test(handle):
|
||||
csbi = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
success = _GetConsoleScreenBufferInfo(
|
||||
handle, byref(csbi))
|
||||
return bool(success)
|
||||
|
||||
def winapi_test():
|
||||
return any(_winapi_test(h) for h in handles.values())
|
||||
|
||||
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
|
||||
handle = handles[stream_id]
|
||||
csbi = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2016 Vinay Sajip.
|
||||
# Copyright (C) 2012-2017 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
import logging
|
||||
|
||||
__version__ = '0.2.4'
|
||||
__version__ = '0.2.7'
|
||||
|
||||
class DistlibException(Exception):
|
||||
pass
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
[posix_prefix]
|
||||
# Configuration directories. Some of these come straight out of the
|
||||
# configure script. They are for implementing the other variables, not to
|
||||
# be used directly in [resource_locations].
|
||||
confdir = /etc
|
||||
datadir = /usr/share
|
||||
libdir = /usr/lib
|
||||
statedir = /var
|
||||
# User resource directory
|
||||
local = ~/.local/{distribution.name}
|
||||
|
||||
stdlib = {base}/lib/python{py_version_short}
|
||||
platstdlib = {platbase}/lib/python{py_version_short}
|
||||
purelib = {base}/lib/python{py_version_short}/site-packages
|
||||
platlib = {platbase}/lib/python{py_version_short}/site-packages
|
||||
include = {base}/include/python{py_version_short}{abiflags}
|
||||
platinclude = {platbase}/include/python{py_version_short}{abiflags}
|
||||
data = {base}
|
||||
|
||||
[posix_home]
|
||||
stdlib = {base}/lib/python
|
||||
platstdlib = {base}/lib/python
|
||||
purelib = {base}/lib/python
|
||||
platlib = {base}/lib/python
|
||||
include = {base}/include/python
|
||||
platinclude = {base}/include/python
|
||||
scripts = {base}/bin
|
||||
data = {base}
|
||||
|
||||
[nt]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2_home]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[nt_user]
|
||||
stdlib = {userbase}/Python{py_version_nodot}
|
||||
platstdlib = {userbase}/Python{py_version_nodot}
|
||||
purelib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
platlib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
include = {userbase}/Python{py_version_nodot}/Include
|
||||
scripts = {userbase}/Scripts
|
||||
data = {userbase}
|
||||
|
||||
[posix_user]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[osx_framework_user]
|
||||
stdlib = {userbase}/lib/python
|
||||
platstdlib = {userbase}/lib/python
|
||||
purelib = {userbase}/lib/python/site-packages
|
||||
platlib = {userbase}/lib/python/site-packages
|
||||
include = {userbase}/include
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
[posix_prefix]
|
||||
# Configuration directories. Some of these come straight out of the
|
||||
# configure script. They are for implementing the other variables, not to
|
||||
# be used directly in [resource_locations].
|
||||
confdir = /etc
|
||||
datadir = /usr/share
|
||||
libdir = /usr/lib
|
||||
statedir = /var
|
||||
# User resource directory
|
||||
local = ~/.local/{distribution.name}
|
||||
|
||||
stdlib = {base}/lib/python{py_version_short}
|
||||
platstdlib = {platbase}/lib/python{py_version_short}
|
||||
purelib = {base}/lib/python{py_version_short}/site-packages
|
||||
platlib = {platbase}/lib/python{py_version_short}/site-packages
|
||||
include = {base}/include/python{py_version_short}{abiflags}
|
||||
platinclude = {platbase}/include/python{py_version_short}{abiflags}
|
||||
data = {base}
|
||||
|
||||
[posix_home]
|
||||
stdlib = {base}/lib/python
|
||||
platstdlib = {base}/lib/python
|
||||
purelib = {base}/lib/python
|
||||
platlib = {base}/lib/python
|
||||
include = {base}/include/python
|
||||
platinclude = {base}/include/python
|
||||
scripts = {base}/bin
|
||||
data = {base}
|
||||
|
||||
[nt]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2_home]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[nt_user]
|
||||
stdlib = {userbase}/Python{py_version_nodot}
|
||||
platstdlib = {userbase}/Python{py_version_nodot}
|
||||
purelib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
platlib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
include = {userbase}/Python{py_version_nodot}/Include
|
||||
scripts = {userbase}/Scripts
|
||||
data = {userbase}
|
||||
|
||||
[posix_user]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[osx_framework_user]
|
||||
stdlib = {userbase}/lib/python
|
||||
platstdlib = {userbase}/lib/python
|
||||
purelib = {userbase}/lib/python/site-packages
|
||||
platlib = {userbase}/lib/python/site-packages
|
||||
include = {userbase}/include
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
@@ -221,7 +221,7 @@ def _parse_makefile(filename, vars=None):
|
||||
"""
|
||||
# Regexes needed for parsing Makefile (and similar syntaxes,
|
||||
# like old-style Setup files).
|
||||
_variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
|
||||
_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
|
||||
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
|
||||
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
|
||||
|
||||
@@ -528,7 +528,7 @@ def get_config_vars(*args):
|
||||
major_version = int(kernel_version.split('.')[0])
|
||||
|
||||
if major_version < 8:
|
||||
# On macOS before 10.4, check if -arch and -isysroot
|
||||
# On Mac OS X before 10.4, check if -arch and -isysroot
|
||||
# are in CFLAGS or LDFLAGS and remove them if they are.
|
||||
# This is needed when building extensions on a 10.3 system
|
||||
# using a universal build of python.
|
||||
@@ -537,7 +537,7 @@ def get_config_vars(*args):
|
||||
# patched up as well.
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub('-arch\s+\w+\s', ' ', flags)
|
||||
flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
|
||||
flags = re.sub('-isysroot [^ \t]*', ' ', flags)
|
||||
_CONFIG_VARS[key] = flags
|
||||
else:
|
||||
@@ -554,7 +554,7 @@ def get_config_vars(*args):
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub('-arch\s+\w+\s', ' ', flags)
|
||||
flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
|
||||
flags = flags + ' ' + arch
|
||||
_CONFIG_VARS[key] = flags
|
||||
|
||||
@@ -569,7 +569,7 @@ def get_config_vars(*args):
|
||||
# when you install Xcode.
|
||||
#
|
||||
CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
|
||||
m = re.search('-isysroot\s+(\S+)', CFLAGS)
|
||||
m = re.search(r'-isysroot\s+(\S+)', CFLAGS)
|
||||
if m is not None:
|
||||
sdk = m.group(1)
|
||||
if not os.path.exists(sdk):
|
||||
@@ -579,7 +579,7 @@ def get_config_vars(*args):
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
|
||||
flags = re.sub(r'-isysroot\s+\S+(\s|$)', ' ', flags)
|
||||
_CONFIG_VARS[key] = flags
|
||||
|
||||
if args:
|
||||
@@ -725,7 +725,7 @@ def get_platform():
|
||||
machine = 'fat'
|
||||
cflags = get_config_vars().get('CFLAGS')
|
||||
|
||||
archs = re.findall('-arch\s+(\S+)', cflags)
|
||||
archs = re.findall(r'-arch\s+(\S+)', cflags)
|
||||
archs = tuple(sorted(set(archs)))
|
||||
|
||||
if len(archs) == 1:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013-2016 Vinay Sajip.
|
||||
# Copyright (C) 2013-2017 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
@@ -12,7 +12,7 @@ import sys
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
ssl = None
|
||||
|
||||
if sys.version_info[0] < 3: # pragma: no cover
|
||||
@@ -272,7 +272,7 @@ from zipfile import ZipFile as BaseZipFile
|
||||
|
||||
if hasattr(BaseZipFile, '__enter__'): # pragma: no cover
|
||||
ZipFile = BaseZipFile
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
from zipfile import ZipExtFile as BaseZipExtFile
|
||||
|
||||
class ZipExtFile(BaseZipExtFile):
|
||||
@@ -329,7 +329,13 @@ try:
|
||||
fsencode = os.fsencode
|
||||
fsdecode = os.fsdecode
|
||||
except AttributeError: # pragma: no cover
|
||||
_fsencoding = sys.getfilesystemencoding()
|
||||
# Issue #99: on some systems (e.g. containerised),
|
||||
# sys.getfilesystemencoding() returns None, and we need a real value,
|
||||
# so fall back to utf-8. From the CPython 2.7 docs relating to Unix and
|
||||
# sys.getfilesystemencoding(): the return value is "the user’s preference
|
||||
# according to the result of nl_langinfo(CODESET), or None if the
|
||||
# nl_langinfo(CODESET) failed."
|
||||
_fsencoding = sys.getfilesystemencoding() or 'utf-8'
|
||||
if _fsencoding == 'mbcs':
|
||||
_fserrors = 'strict'
|
||||
else:
|
||||
@@ -359,7 +365,7 @@ except ImportError: # pragma: no cover
|
||||
from codecs import BOM_UTF8, lookup
|
||||
import re
|
||||
|
||||
cookie_re = re.compile("coding[:=]\s*([-\w.]+)")
|
||||
cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)")
|
||||
|
||||
def _get_normal_name(orig_enc):
|
||||
"""Imitates get_normal_name in tokenizer.c."""
|
||||
@@ -608,17 +614,20 @@ except ImportError: # pragma: no cover
|
||||
self.maps[0].clear()
|
||||
|
||||
try:
|
||||
from imp import cache_from_source
|
||||
except ImportError: # pragma: no cover
|
||||
def cache_from_source(path, debug_override=None):
|
||||
assert path.endswith('.py')
|
||||
if debug_override is None:
|
||||
debug_override = __debug__
|
||||
if debug_override:
|
||||
suffix = 'c'
|
||||
else:
|
||||
suffix = 'o'
|
||||
return path + suffix
|
||||
from importlib.util import cache_from_source # Python >= 3.4
|
||||
except ImportError: # pragma: no cover
|
||||
try:
|
||||
from imp import cache_from_source
|
||||
except ImportError: # pragma: no cover
|
||||
def cache_from_source(path, debug_override=None):
|
||||
assert path.endswith('.py')
|
||||
if debug_override is None:
|
||||
debug_override = __debug__
|
||||
if debug_override:
|
||||
suffix = 'c'
|
||||
else:
|
||||
suffix = 'o'
|
||||
return path + suffix
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2016 The Python Software Foundation.
|
||||
# Copyright (C) 2012-2017 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""PEP 376 implementation."""
|
||||
@@ -257,7 +257,7 @@ class DistributionPath(object):
|
||||
:type version: string
|
||||
"""
|
||||
matcher = None
|
||||
if not version is None:
|
||||
if version is not None:
|
||||
try:
|
||||
matcher = self._scheme.matcher('%s (%s)' % (name, version))
|
||||
except ValueError:
|
||||
@@ -265,18 +265,23 @@ class DistributionPath(object):
|
||||
(name, version))
|
||||
|
||||
for dist in self.get_distributions():
|
||||
provided = dist.provides
|
||||
# We hit a problem on Travis where enum34 was installed and doesn't
|
||||
# have a provides attribute ...
|
||||
if not hasattr(dist, 'provides'):
|
||||
logger.debug('No "provides": %s', dist)
|
||||
else:
|
||||
provided = dist.provides
|
||||
|
||||
for p in provided:
|
||||
p_name, p_ver = parse_name_and_version(p)
|
||||
if matcher is None:
|
||||
if p_name == name:
|
||||
yield dist
|
||||
break
|
||||
else:
|
||||
if p_name == name and matcher.match(p_ver):
|
||||
yield dist
|
||||
break
|
||||
for p in provided:
|
||||
p_name, p_ver = parse_name_and_version(p)
|
||||
if matcher is None:
|
||||
if p_name == name:
|
||||
yield dist
|
||||
break
|
||||
else:
|
||||
if p_name == name and matcher.match(p_ver):
|
||||
yield dist
|
||||
break
|
||||
|
||||
def get_file_path(self, name, relative_path):
|
||||
"""
|
||||
@@ -529,9 +534,10 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
hasher = 'sha256'
|
||||
|
||||
def __init__(self, path, metadata=None, env=None):
|
||||
self.modules = []
|
||||
self.finder = finder = resources.finder_for_path(path)
|
||||
if finder is None:
|
||||
import pdb; pdb.set_trace ()
|
||||
raise ValueError('finder unavailable for %s' % path)
|
||||
if env and env._cache_enabled and path in env._cache.path:
|
||||
metadata = env._cache.path[path].metadata
|
||||
elif metadata is None:
|
||||
@@ -553,11 +559,13 @@ class InstalledDistribution(BaseInstalledDistribution):
|
||||
if env and env._cache_enabled:
|
||||
env._cache.add(self)
|
||||
|
||||
try:
|
||||
r = finder.find('REQUESTED')
|
||||
except AttributeError:
|
||||
import pdb; pdb.set_trace ()
|
||||
r = finder.find('REQUESTED')
|
||||
self.requested = r is not None
|
||||
p = os.path.join(path, 'top_level.txt')
|
||||
if os.path.exists(p):
|
||||
with open(p, 'rb') as f:
|
||||
data = f.read()
|
||||
self.modules = data.splitlines()
|
||||
|
||||
def __repr__(self):
|
||||
return '<InstalledDistribution %r %s at %r>' % (
|
||||
@@ -917,11 +925,14 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
pass
|
||||
return reqs
|
||||
|
||||
tl_path = tl_data = None
|
||||
if path.endswith('.egg'):
|
||||
if os.path.isdir(path):
|
||||
meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
|
||||
p = os.path.join(path, 'EGG-INFO')
|
||||
meta_path = os.path.join(p, 'PKG-INFO')
|
||||
metadata = Metadata(path=meta_path, scheme='legacy')
|
||||
req_path = os.path.join(path, 'EGG-INFO', 'requires.txt')
|
||||
req_path = os.path.join(p, 'requires.txt')
|
||||
tl_path = os.path.join(p, 'top_level.txt')
|
||||
requires = parse_requires_path(req_path)
|
||||
else:
|
||||
# FIXME handle the case where zipfile is not available
|
||||
@@ -931,6 +942,7 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
metadata = Metadata(fileobj=fileobj, scheme='legacy')
|
||||
try:
|
||||
data = zipf.get_data('EGG-INFO/requires.txt')
|
||||
tl_data = zipf.get_data('EGG-INFO/top_level.txt').decode('utf-8')
|
||||
requires = parse_requires_data(data.decode('utf-8'))
|
||||
except IOError:
|
||||
requires = None
|
||||
@@ -939,6 +951,7 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
req_path = os.path.join(path, 'requires.txt')
|
||||
requires = parse_requires_path(req_path)
|
||||
path = os.path.join(path, 'PKG-INFO')
|
||||
tl_path = os.path.join(path, 'top_level.txt')
|
||||
metadata = Metadata(path=path, scheme='legacy')
|
||||
else:
|
||||
raise DistlibException('path must end with .egg-info or .egg, '
|
||||
@@ -946,6 +959,16 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
|
||||
if requires:
|
||||
metadata.add_requirements(requires)
|
||||
# look for top-level modules in top_level.txt, if present
|
||||
if tl_data is None:
|
||||
if tl_path is not None and os.path.exists(tl_path):
|
||||
with open(tl_path, 'rb') as f:
|
||||
tl_data = f.read().decode('utf-8')
|
||||
if not tl_data:
|
||||
tl_data = []
|
||||
else:
|
||||
tl_data = tl_data.splitlines()
|
||||
self.modules = tl_data
|
||||
return metadata
|
||||
|
||||
def __repr__(self):
|
||||
@@ -1025,20 +1048,21 @@ class EggInfoDistribution(BaseInstalledDistribution):
|
||||
:returns: iterator of paths
|
||||
"""
|
||||
record_path = os.path.join(self.path, 'installed-files.txt')
|
||||
skip = True
|
||||
with codecs.open(record_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line == './':
|
||||
skip = False
|
||||
continue
|
||||
if not skip:
|
||||
p = os.path.normpath(os.path.join(self.path, line))
|
||||
if p.startswith(self.path):
|
||||
if absolute:
|
||||
yield p
|
||||
else:
|
||||
yield line
|
||||
if os.path.exists(record_path):
|
||||
skip = True
|
||||
with codecs.open(record_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line == './':
|
||||
skip = False
|
||||
continue
|
||||
if not skip:
|
||||
p = os.path.normpath(os.path.join(self.path, line))
|
||||
if p.startswith(self.path):
|
||||
if absolute:
|
||||
yield p
|
||||
else:
|
||||
yield line
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, EggInfoDistribution) and
|
||||
|
||||
@@ -49,7 +49,6 @@ class PackageIndex(object):
|
||||
self.ssl_verifier = None
|
||||
self.gpg = None
|
||||
self.gpg_home = None
|
||||
self.rpc_proxy = None
|
||||
with open(os.devnull, 'w') as sink:
|
||||
# Use gpg by default rather than gpg2, as gpg2 insists on
|
||||
# prompting for passwords
|
||||
@@ -510,6 +509,8 @@ class PackageIndex(object):
|
||||
def search(self, terms, operator=None):
|
||||
if isinstance(terms, string_types):
|
||||
terms = {'name': terms}
|
||||
if self.rpc_proxy is None:
|
||||
self.rpc_proxy = ServerProxy(self.url, timeout=3.0)
|
||||
return self.rpc_proxy.search(terms, operator or 'and')
|
||||
rpc_proxy = ServerProxy(self.url, timeout=3.0)
|
||||
try:
|
||||
return rpc_proxy.search(terms, operator or 'and')
|
||||
finally:
|
||||
rpc_proxy('close')()
|
||||
|
||||
@@ -24,7 +24,7 @@ from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url,
|
||||
HTTPRedirectHandler as BaseRedirectHandler, text_type,
|
||||
Request, HTTPError, URLError)
|
||||
from .database import Distribution, DistributionPath, make_dist
|
||||
from .metadata import Metadata
|
||||
from .metadata import Metadata, MetadataInvalidError
|
||||
from .util import (cached_property, parse_credentials, ensure_slash,
|
||||
split_filename, get_project_data, parse_requirement,
|
||||
parse_name_and_version, ServerProxy, normalize_name)
|
||||
@@ -33,7 +33,7 @@ from .wheel import Wheel, is_compatible
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HASHER_HASH = re.compile('^(\w+)=([a-f0-9]+)')
|
||||
HASHER_HASH = re.compile(r'^(\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 = 'https://pypi.org/pypi'
|
||||
@@ -47,7 +47,10 @@ def get_all_distribution_names(url=None):
|
||||
if url is None:
|
||||
url = DEFAULT_INDEX
|
||||
client = ServerProxy(url, timeout=3.0)
|
||||
return client.list_packages()
|
||||
try:
|
||||
return client.list_packages()
|
||||
finally:
|
||||
client('close')()
|
||||
|
||||
class RedirectHandler(BaseRedirectHandler):
|
||||
"""
|
||||
@@ -66,7 +69,7 @@ class RedirectHandler(BaseRedirectHandler):
|
||||
if key in headers:
|
||||
newurl = headers[key]
|
||||
break
|
||||
if newurl is None:
|
||||
if newurl is None: # pragma: no cover
|
||||
return
|
||||
urlparts = urlparse(newurl)
|
||||
if urlparts.scheme == '':
|
||||
@@ -172,7 +175,7 @@ class Locator(object):
|
||||
|
||||
This calls _get_project to do all the work, and just implements a caching layer on top.
|
||||
"""
|
||||
if self._cache is None:
|
||||
if self._cache is None: # pragma: no cover
|
||||
result = self._get_project(name)
|
||||
elif name in self._cache:
|
||||
result = self._cache[name]
|
||||
@@ -191,10 +194,11 @@ class Locator(object):
|
||||
basename = posixpath.basename(t.path)
|
||||
compatible = True
|
||||
is_wheel = basename.endswith('.whl')
|
||||
is_downloadable = basename.endswith(self.downloadable_extensions)
|
||||
if is_wheel:
|
||||
compatible = is_compatible(Wheel(basename), self.wheel_tags)
|
||||
return (t.scheme != 'https', 'pypi.org' in t.netloc,
|
||||
is_wheel, compatible, basename)
|
||||
return (t.scheme == 'https', 'pypi.org' in t.netloc,
|
||||
is_downloadable, is_wheel, compatible, basename)
|
||||
|
||||
def prefer_url(self, url1, url2):
|
||||
"""
|
||||
@@ -237,7 +241,7 @@ class Locator(object):
|
||||
|
||||
result = None
|
||||
scheme, netloc, path, params, query, frag = urlparse(url)
|
||||
if frag.lower().startswith('egg='):
|
||||
if frag.lower().startswith('egg='): # pragma: no cover
|
||||
logger.debug('%s: version hint in fragment: %r',
|
||||
project_name, frag)
|
||||
m = HASHER_HASH.match(frag)
|
||||
@@ -246,7 +250,7 @@ class Locator(object):
|
||||
else:
|
||||
algo, digest = None, None
|
||||
origpath = path
|
||||
if path and path[-1] == '/':
|
||||
if path and path[-1] == '/': # pragma: no cover
|
||||
path = path[:-1]
|
||||
if path.endswith('.whl'):
|
||||
try:
|
||||
@@ -268,13 +272,15 @@ class Locator(object):
|
||||
}
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.warning('invalid path for wheel: %s', path)
|
||||
elif path.endswith(self.downloadable_extensions):
|
||||
elif not path.endswith(self.downloadable_extensions): # pragma: no cover
|
||||
logger.debug('Not downloadable: %s', path)
|
||||
else: # downloadable extension
|
||||
path = filename = posixpath.basename(path)
|
||||
for ext in self.downloadable_extensions:
|
||||
if path.endswith(ext):
|
||||
path = path[:-len(ext)]
|
||||
t = self.split_filename(path, project_name)
|
||||
if not t:
|
||||
if not t: # pragma: no cover
|
||||
logger.debug('No match for project/version: %s', path)
|
||||
else:
|
||||
name, version, pyver = t
|
||||
@@ -287,7 +293,7 @@ class Locator(object):
|
||||
params, query, '')),
|
||||
#'packagetype': 'sdist',
|
||||
}
|
||||
if pyver:
|
||||
if pyver: # pragma: no cover
|
||||
result['python-version'] = pyver
|
||||
break
|
||||
if result and algo:
|
||||
@@ -348,7 +354,7 @@ class Locator(object):
|
||||
"""
|
||||
result = None
|
||||
r = parse_requirement(requirement)
|
||||
if r is None:
|
||||
if r is None: # pragma: no cover
|
||||
raise DistlibException('Not a valid requirement: %r' % requirement)
|
||||
scheme = get_scheme(self.scheme)
|
||||
self.matcher = matcher = scheme.matcher(r.requirement)
|
||||
@@ -386,7 +392,7 @@ class Locator(object):
|
||||
d = {}
|
||||
sd = versions.get('digests', {})
|
||||
for url in result.download_urls:
|
||||
if url in sd:
|
||||
if url in sd: # pragma: no cover
|
||||
d[url] = sd[url]
|
||||
result.digests = d
|
||||
self.matcher = None
|
||||
@@ -520,9 +526,9 @@ class Page(object):
|
||||
# declared with double quotes, single quotes or no quotes - which leads to
|
||||
# the length of the expression.
|
||||
_href = re.compile("""
|
||||
(rel\s*=\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\s\n]*))\s+)?
|
||||
href\s*=\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\s\n]*))
|
||||
(\s+rel\s*=\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\s\n]*)))?
|
||||
(rel\\s*=\\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)?
|
||||
href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
|
||||
(\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))?
|
||||
""", re.I | re.S | re.X)
|
||||
_base = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.I | re.S)
|
||||
|
||||
@@ -726,11 +732,14 @@ class SimpleScrapingLocator(Locator):
|
||||
continue
|
||||
for link, rel in page.links:
|
||||
if link not in self._seen:
|
||||
self._seen.add(link)
|
||||
if (not self._process_download(link) and
|
||||
self._should_queue(link, url, rel)):
|
||||
logger.debug('Queueing %s from %s', link, url)
|
||||
self._to_fetch.put(link)
|
||||
try:
|
||||
self._seen.add(link)
|
||||
if (not self._process_download(link) and
|
||||
self._should_queue(link, url, rel)):
|
||||
logger.debug('Queueing %s from %s', link, url)
|
||||
self._to_fetch.put(link)
|
||||
except MetadataInvalidError: # e.g. invalid versions
|
||||
pass
|
||||
except Exception as e: # pragma: no cover
|
||||
self.errors.put(text_type(e))
|
||||
finally:
|
||||
@@ -1236,7 +1245,7 @@ class DependencyFinder(object):
|
||||
ireqts = dist.run_requires | dist.meta_requires
|
||||
sreqts = dist.build_requires
|
||||
ereqts = set()
|
||||
if dist in install_dists:
|
||||
if meta_extras and dist in install_dists:
|
||||
for key in ('test', 'build', 'dev'):
|
||||
e = ':%s:' % key
|
||||
if e in meta_extras:
|
||||
|
||||
@@ -24,7 +24,7 @@ __all__ = ['Manifest']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# a \ followed by some spaces + EOL
|
||||
_COLLAPSE_PATTERN = re.compile('\\\w*\n', re.M)
|
||||
_COLLAPSE_PATTERN = re.compile('\\\\w*\n', re.M)
|
||||
_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
|
||||
|
||||
#
|
||||
|
||||
@@ -1,182 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2013 Vinay Sajip.
|
||||
# Copyright (C) 2012-2017 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Parser for the environment markers micro-language defined in PEP 345."""
|
||||
"""
|
||||
Parser for the environment markers micro-language defined in PEP 508.
|
||||
"""
|
||||
|
||||
# Note: In PEP 345, the micro-language was Python compatible, so the ast
|
||||
# module could be used to parse it. However, PEP 508 introduced operators such
|
||||
# as ~= and === which aren't in Python, necessitating a different approach.
|
||||
|
||||
import ast
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import re
|
||||
|
||||
from .compat import python_implementation, string_types
|
||||
from .util import in_venv
|
||||
from .compat import python_implementation, urlparse, string_types
|
||||
from .util import in_venv, parse_marker
|
||||
|
||||
__all__ = ['interpret']
|
||||
|
||||
def _is_literal(o):
|
||||
if not isinstance(o, string_types) or not o:
|
||||
return False
|
||||
return o[0] in '\'"'
|
||||
|
||||
class Evaluator(object):
|
||||
"""
|
||||
A limited evaluator for Python expressions.
|
||||
This class is used to evaluate marker expessions.
|
||||
"""
|
||||
|
||||
operators = {
|
||||
'eq': lambda x, y: x == y,
|
||||
'gt': lambda x, y: x > y,
|
||||
'gte': lambda x, y: x >= y,
|
||||
operations = {
|
||||
'==': lambda x, y: x == y,
|
||||
'===': lambda x, y: x == y,
|
||||
'~=': lambda x, y: x == y or x > y,
|
||||
'!=': lambda x, y: x != y,
|
||||
'<': lambda x, y: x < y,
|
||||
'<=': lambda x, y: x == y or x < y,
|
||||
'>': lambda x, y: x > y,
|
||||
'>=': lambda x, y: x == y or x > y,
|
||||
'and': lambda x, y: x and y,
|
||||
'or': lambda x, y: x or y,
|
||||
'in': lambda x, y: x in y,
|
||||
'lt': lambda x, y: x < y,
|
||||
'lte': lambda x, y: x <= y,
|
||||
'not': lambda x: not x,
|
||||
'noteq': lambda x, y: x != y,
|
||||
'notin': lambda x, y: x not in y,
|
||||
'not in': lambda x, y: x not in y,
|
||||
}
|
||||
|
||||
allowed_values = {
|
||||
'sys_platform': sys.platform,
|
||||
'python_version': '%s.%s' % sys.version_info[:2],
|
||||
# parsing sys.platform is not reliable, but there is no other
|
||||
# way to get e.g. 2.7.2+, and the PEP is defined with sys.version
|
||||
'python_full_version': sys.version.split(' ', 1)[0],
|
||||
'os_name': os.name,
|
||||
'platform_in_venv': str(in_venv()),
|
||||
'platform_release': platform.release(),
|
||||
'platform_version': platform.version(),
|
||||
'platform_machine': platform.machine(),
|
||||
'platform_python_implementation': python_implementation(),
|
||||
}
|
||||
|
||||
def __init__(self, context=None):
|
||||
def evaluate(self, expr, context):
|
||||
"""
|
||||
Initialise an instance.
|
||||
|
||||
:param context: If specified, names are looked up in this mapping.
|
||||
Evaluate a marker expression returned by the :func:`parse_requirement`
|
||||
function in the specified context.
|
||||
"""
|
||||
self.context = context or {}
|
||||
self.source = None
|
||||
|
||||
def get_fragment(self, offset):
|
||||
"""
|
||||
Get the part of the source which is causing a problem.
|
||||
"""
|
||||
fragment_len = 10
|
||||
s = '%r' % (self.source[offset:offset + fragment_len])
|
||||
if offset + fragment_len < len(self.source):
|
||||
s += '...'
|
||||
return s
|
||||
|
||||
def get_handler(self, node_type):
|
||||
"""
|
||||
Get a handler for the specified AST node type.
|
||||
"""
|
||||
return getattr(self, 'do_%s' % node_type, None)
|
||||
|
||||
def evaluate(self, node, filename=None):
|
||||
"""
|
||||
Evaluate a source string or node, using ``filename`` when
|
||||
displaying errors.
|
||||
"""
|
||||
if isinstance(node, string_types):
|
||||
self.source = node
|
||||
kwargs = {'mode': 'eval'}
|
||||
if filename:
|
||||
kwargs['filename'] = filename
|
||||
try:
|
||||
node = ast.parse(node, **kwargs)
|
||||
except SyntaxError as e:
|
||||
s = self.get_fragment(e.offset)
|
||||
raise SyntaxError('syntax error %s' % s)
|
||||
node_type = node.__class__.__name__.lower()
|
||||
handler = self.get_handler(node_type)
|
||||
if handler is None:
|
||||
if self.source is None:
|
||||
s = '(source not available)'
|
||||
if isinstance(expr, string_types):
|
||||
if expr[0] in '\'"':
|
||||
result = expr[1:-1]
|
||||
else:
|
||||
s = self.get_fragment(node.col_offset)
|
||||
raise SyntaxError("don't know how to evaluate %r %s" % (
|
||||
node_type, s))
|
||||
return handler(node)
|
||||
|
||||
def get_attr_key(self, node):
|
||||
assert isinstance(node, ast.Attribute), 'attribute node expected'
|
||||
return '%s.%s' % (node.value.id, node.attr)
|
||||
|
||||
def do_attribute(self, node):
|
||||
if not isinstance(node.value, ast.Name):
|
||||
valid = False
|
||||
if expr not in context:
|
||||
raise SyntaxError('unknown variable: %s' % expr)
|
||||
result = context[expr]
|
||||
else:
|
||||
key = self.get_attr_key(node)
|
||||
valid = key in self.context or key in self.allowed_values
|
||||
if not valid:
|
||||
raise SyntaxError('invalid expression: %s' % key)
|
||||
if key in self.context:
|
||||
result = self.context[key]
|
||||
else:
|
||||
result = self.allowed_values[key]
|
||||
assert isinstance(expr, dict)
|
||||
op = expr['op']
|
||||
if op not in self.operations:
|
||||
raise NotImplementedError('op not implemented: %s' % op)
|
||||
elhs = expr['lhs']
|
||||
erhs = expr['rhs']
|
||||
if _is_literal(expr['lhs']) and _is_literal(expr['rhs']):
|
||||
raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs))
|
||||
|
||||
lhs = self.evaluate(elhs, context)
|
||||
rhs = self.evaluate(erhs, context)
|
||||
result = self.operations[op](lhs, rhs)
|
||||
return result
|
||||
|
||||
def do_boolop(self, node):
|
||||
result = self.evaluate(node.values[0])
|
||||
is_or = node.op.__class__ is ast.Or
|
||||
is_and = node.op.__class__ is ast.And
|
||||
assert is_or or is_and
|
||||
if (is_and and result) or (is_or and not result):
|
||||
for n in node.values[1:]:
|
||||
result = self.evaluate(n)
|
||||
if (is_or and result) or (is_and and not result):
|
||||
break
|
||||
return result
|
||||
def default_context():
|
||||
def format_full_version(info):
|
||||
version = '%s.%s.%s' % (info.major, info.minor, info.micro)
|
||||
kind = info.releaselevel
|
||||
if kind != 'final':
|
||||
version += kind[0] + str(info.serial)
|
||||
return version
|
||||
|
||||
def do_compare(self, node):
|
||||
def sanity_check(lhsnode, rhsnode):
|
||||
valid = True
|
||||
if isinstance(lhsnode, ast.Str) and isinstance(rhsnode, ast.Str):
|
||||
valid = False
|
||||
#elif (isinstance(lhsnode, ast.Attribute)
|
||||
# and isinstance(rhsnode, ast.Attribute)):
|
||||
# klhs = self.get_attr_key(lhsnode)
|
||||
# krhs = self.get_attr_key(rhsnode)
|
||||
# valid = klhs != krhs
|
||||
if not valid:
|
||||
s = self.get_fragment(node.col_offset)
|
||||
raise SyntaxError('Invalid comparison: %s' % s)
|
||||
if hasattr(sys, 'implementation'):
|
||||
implementation_version = format_full_version(sys.implementation.version)
|
||||
implementation_name = sys.implementation.name
|
||||
else:
|
||||
implementation_version = '0'
|
||||
implementation_name = ''
|
||||
|
||||
lhsnode = node.left
|
||||
lhs = self.evaluate(lhsnode)
|
||||
result = True
|
||||
for op, rhsnode in zip(node.ops, node.comparators):
|
||||
sanity_check(lhsnode, rhsnode)
|
||||
op = op.__class__.__name__.lower()
|
||||
if op not in self.operators:
|
||||
raise SyntaxError('unsupported operation: %r' % op)
|
||||
rhs = self.evaluate(rhsnode)
|
||||
result = self.operators[op](lhs, rhs)
|
||||
if not result:
|
||||
break
|
||||
lhs = rhs
|
||||
lhsnode = rhsnode
|
||||
return result
|
||||
result = {
|
||||
'implementation_name': implementation_name,
|
||||
'implementation_version': implementation_version,
|
||||
'os_name': os.name,
|
||||
'platform_machine': platform.machine(),
|
||||
'platform_python_implementation': platform.python_implementation(),
|
||||
'platform_release': platform.release(),
|
||||
'platform_system': platform.system(),
|
||||
'platform_version': platform.version(),
|
||||
'platform_in_venv': str(in_venv()),
|
||||
'python_full_version': platform.python_version(),
|
||||
'python_version': platform.python_version()[:3],
|
||||
'sys_platform': sys.platform,
|
||||
}
|
||||
return result
|
||||
|
||||
def do_expression(self, node):
|
||||
return self.evaluate(node.body)
|
||||
|
||||
def do_name(self, node):
|
||||
valid = False
|
||||
if node.id in self.context:
|
||||
valid = True
|
||||
result = self.context[node.id]
|
||||
elif node.id in self.allowed_values:
|
||||
valid = True
|
||||
result = self.allowed_values[node.id]
|
||||
if not valid:
|
||||
raise SyntaxError('invalid expression: %s' % node.id)
|
||||
return result
|
||||
|
||||
def do_str(self, node):
|
||||
return node.s
|
||||
DEFAULT_CONTEXT = default_context()
|
||||
del default_context
|
||||
|
||||
evaluator = Evaluator()
|
||||
|
||||
def interpret(marker, execution_context=None):
|
||||
"""
|
||||
@@ -187,4 +119,13 @@ def interpret(marker, execution_context=None):
|
||||
:param execution_context: The context used for name lookup.
|
||||
:type execution_context: mapping
|
||||
"""
|
||||
return Evaluator(execution_context).evaluate(marker.strip())
|
||||
try:
|
||||
expr, rest = parse_marker(marker)
|
||||
except Exception as e:
|
||||
raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e))
|
||||
if rest and rest[0] != '#':
|
||||
raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest))
|
||||
context = dict(DEFAULT_CONTEXT)
|
||||
if execution_context:
|
||||
context.update(execution_context)
|
||||
return evaluator.evaluate(expr, context)
|
||||
|
||||
@@ -50,7 +50,7 @@ PKG_INFO_ENCODING = 'utf-8'
|
||||
# to 1.2 once PEP 345 is supported everywhere
|
||||
PKG_INFO_PREFERRED_VERSION = '1.1'
|
||||
|
||||
_LINE_PREFIX_1_2 = 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',
|
||||
@@ -91,11 +91,16 @@ _426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
|
||||
_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By',
|
||||
'Setup-Requires-Dist', 'Extension')
|
||||
|
||||
_566_FIELDS = _426_FIELDS + ('Description-Content-Type',)
|
||||
|
||||
_566_MARKERS = ('Description-Content-Type',)
|
||||
|
||||
_ALL_FIELDS = set()
|
||||
_ALL_FIELDS.update(_241_FIELDS)
|
||||
_ALL_FIELDS.update(_314_FIELDS)
|
||||
_ALL_FIELDS.update(_345_FIELDS)
|
||||
_ALL_FIELDS.update(_426_FIELDS)
|
||||
_ALL_FIELDS.update(_566_FIELDS)
|
||||
|
||||
EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''')
|
||||
|
||||
@@ -107,6 +112,8 @@ def _version2fieldlist(version):
|
||||
return _314_FIELDS
|
||||
elif version == '1.2':
|
||||
return _345_FIELDS
|
||||
elif version in ('1.3', '2.1'):
|
||||
return _345_FIELDS + _566_FIELDS
|
||||
elif version == '2.0':
|
||||
return _426_FIELDS
|
||||
raise MetadataUnrecognizedVersionError(version)
|
||||
@@ -126,38 +133,51 @@ def _best_version(fields):
|
||||
continue
|
||||
keys.append(key)
|
||||
|
||||
possible_versions = ['1.0', '1.1', '1.2', '2.0']
|
||||
possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.0', '2.1']
|
||||
|
||||
# first let's try to see if a field is not part of one of the version
|
||||
for key in keys:
|
||||
if key not in _241_FIELDS and '1.0' in possible_versions:
|
||||
possible_versions.remove('1.0')
|
||||
logger.debug('Removed 1.0 due to %s', key)
|
||||
if key not in _314_FIELDS and '1.1' in possible_versions:
|
||||
possible_versions.remove('1.1')
|
||||
logger.debug('Removed 1.1 due to %s', key)
|
||||
if key not in _345_FIELDS and '1.2' in possible_versions:
|
||||
possible_versions.remove('1.2')
|
||||
logger.debug('Removed 1.2 due to %s', key)
|
||||
if key not in _566_FIELDS and '1.3' in possible_versions:
|
||||
possible_versions.remove('1.3')
|
||||
logger.debug('Removed 1.3 due to %s', key)
|
||||
if key not in _566_FIELDS and '2.1' in possible_versions:
|
||||
if key != 'Description': # In 2.1, description allowed after headers
|
||||
possible_versions.remove('2.1')
|
||||
logger.debug('Removed 2.1 due to %s', key)
|
||||
if key not in _426_FIELDS and '2.0' in possible_versions:
|
||||
possible_versions.remove('2.0')
|
||||
logger.debug('Removed 2.0 due to %s', key)
|
||||
|
||||
# possible_version contains qualified versions
|
||||
if len(possible_versions) == 1:
|
||||
return possible_versions[0] # found !
|
||||
elif len(possible_versions) == 0:
|
||||
logger.debug('Out of options - unknown metadata set: %s', fields)
|
||||
raise MetadataConflictError('Unknown metadata set')
|
||||
|
||||
# let's see if one unique marker is found
|
||||
is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS)
|
||||
is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS)
|
||||
is_2_1 = '2.1' in possible_versions and _has_marker(keys, _566_MARKERS)
|
||||
is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS)
|
||||
if int(is_1_1) + int(is_1_2) + int(is_2_0) > 1:
|
||||
raise MetadataConflictError('You used incompatible 1.1/1.2/2.0 fields')
|
||||
if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_0) > 1:
|
||||
raise MetadataConflictError('You used incompatible 1.1/1.2/2.0/2.1 fields')
|
||||
|
||||
# we have the choice, 1.0, or 1.2, or 2.0
|
||||
# - 1.0 has a broken Summary field but works with all tools
|
||||
# - 1.1 is to avoid
|
||||
# - 1.2 fixes Summary but has little adoption
|
||||
# - 2.0 adds more features and is very new
|
||||
if not is_1_1 and not is_1_2 and not is_2_0:
|
||||
if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_0:
|
||||
# we couldn't find any specific marker
|
||||
if PKG_INFO_PREFERRED_VERSION in possible_versions:
|
||||
return PKG_INFO_PREFERRED_VERSION
|
||||
@@ -165,6 +185,8 @@ def _best_version(fields):
|
||||
return '1.1'
|
||||
if is_1_2:
|
||||
return '1.2'
|
||||
if is_2_1:
|
||||
return '2.1'
|
||||
|
||||
return '2.0'
|
||||
|
||||
@@ -355,6 +377,7 @@ class LegacyMetadata(object):
|
||||
value = msg[field]
|
||||
if value is not None and value != 'UNKNOWN':
|
||||
self.set(field, value)
|
||||
logger.debug('Attempting to set metadata for %s', self)
|
||||
self.set_metadata_version()
|
||||
|
||||
def write(self, filepath, skip_unknown=False):
|
||||
@@ -634,7 +657,7 @@ class Metadata(object):
|
||||
instance which handles the key-value metadata format.
|
||||
"""
|
||||
|
||||
METADATA_VERSION_MATCHER = re.compile('^\d+(\.\d+)*$')
|
||||
METADATA_VERSION_MATCHER = re.compile(r'^\d+(\.\d+)*$')
|
||||
|
||||
NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013-2016 Vinay Sajip.
|
||||
# Copyright (C) 2013-2017 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
|
||||
@@ -38,7 +38,7 @@ _DEFAULT_MANIFEST = '''
|
||||
|
||||
# check if Python is called on the first line with this expression
|
||||
FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$')
|
||||
SCRIPT_TEMPLATE = '''# -*- coding: utf-8 -*-
|
||||
SCRIPT_TEMPLATE = r'''# -*- coding: utf-8 -*-
|
||||
if __name__ == '__main__':
|
||||
import sys, re
|
||||
|
||||
@@ -57,7 +57,7 @@ if __name__ == '__main__':
|
||||
func = _resolve('%(module)s', '%(func)s')
|
||||
rc = func() # None interpreted as 0
|
||||
except Exception as e: # only supporting Python >= 2.6
|
||||
sys.stderr.write('%%s\\n' %% e)
|
||||
sys.stderr.write('%%s\n' %% e)
|
||||
rc = 1
|
||||
sys.exit(rc)
|
||||
'''
|
||||
@@ -136,6 +136,37 @@ class ScriptMaker(object):
|
||||
return executable
|
||||
return '/usr/bin/env %s' % executable
|
||||
|
||||
def _build_shebang(self, executable, post_interp):
|
||||
"""
|
||||
Build a shebang line. In the simple case (on Windows, or a shebang line
|
||||
which is not too long or contains spaces) use a simple formulation for
|
||||
the shebang. Otherwise, use /bin/sh as the executable, with a contrived
|
||||
shebang which allows the script to run either under Python or sh, using
|
||||
suitable quoting. Thanks to Harald Nordgren for his input.
|
||||
|
||||
See also: http://www.in-ulm.de/~mascheck/various/shebang/#length
|
||||
https://hg.mozilla.org/mozilla-central/file/tip/mach
|
||||
"""
|
||||
if os.name != 'posix':
|
||||
simple_shebang = True
|
||||
else:
|
||||
# Add 3 for '#!' prefix and newline suffix.
|
||||
shebang_length = len(executable) + len(post_interp) + 3
|
||||
if sys.platform == 'darwin':
|
||||
max_shebang_length = 512
|
||||
else:
|
||||
max_shebang_length = 127
|
||||
simple_shebang = ((b' ' not in executable) and
|
||||
(shebang_length <= max_shebang_length))
|
||||
|
||||
if simple_shebang:
|
||||
result = b'#!' + executable + post_interp + b'\n'
|
||||
else:
|
||||
result = b'#!/bin/sh\n'
|
||||
result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n'
|
||||
result += b"' '''"
|
||||
return result
|
||||
|
||||
def _get_shebang(self, encoding, post_interp=b'', options=None):
|
||||
enquote = True
|
||||
if self.executable:
|
||||
@@ -169,7 +200,7 @@ class ScriptMaker(object):
|
||||
if (sys.platform == 'cli' and '-X:Frames' 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'
|
||||
shebang = self._build_shebang(executable, post_interp)
|
||||
# Python parser starts to read a script using UTF-8 until
|
||||
# it gets a #coding:xxx cookie. The shebang has to be the
|
||||
# first line of a file, the #coding:xxx cookie cannot be
|
||||
|
||||
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (C) 2012-2016 The Python Software Foundation.
|
||||
# Copyright (C) 2012-2017 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
import codecs
|
||||
@@ -13,7 +13,6 @@ import logging
|
||||
import os
|
||||
import py_compile
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
try:
|
||||
import ssl
|
||||
@@ -36,101 +35,243 @@ from .compat import (string_types, text_type, shutil, raw_input, StringIO,
|
||||
cache_from_source, urlopen, urljoin, httplib, xmlrpclib,
|
||||
splittype, HTTPHandler, BaseConfigurator, valid_ident,
|
||||
Container, configparser, URLError, ZipFile, fsdecode,
|
||||
unquote)
|
||||
unquote, urlparse)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#
|
||||
# Requirement parsing code for name + optional constraints + optional extras
|
||||
#
|
||||
# e.g. 'foo >= 1.2, < 2.0 [bar, baz]'
|
||||
#
|
||||
# The regex can seem a bit hairy, so we build it up out of smaller pieces
|
||||
# which are manageable.
|
||||
# Requirement parsing code as per PEP 508
|
||||
#
|
||||
|
||||
COMMA = r'\s*,\s*'
|
||||
COMMA_RE = re.compile(COMMA)
|
||||
IDENTIFIER = re.compile(r'^([\w\.-]+)\s*')
|
||||
VERSION_IDENTIFIER = re.compile(r'^([\w\.*+-]+)\s*')
|
||||
COMPARE_OP = re.compile(r'^(<=?|>=?|={2,3}|[~!]=)\s*')
|
||||
MARKER_OP = re.compile(r'^((<=?)|(>=?)|={2,3}|[~!]=|in|not\s+in)\s*')
|
||||
OR = re.compile(r'^or\b\s*')
|
||||
AND = re.compile(r'^and\b\s*')
|
||||
NON_SPACE = re.compile(r'(\S+)\s*')
|
||||
STRING_CHUNK = re.compile(r'([\s\w\.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]+)')
|
||||
|
||||
IDENT = r'(\w|[.-])+'
|
||||
EXTRA_IDENT = r'(\*|:(\*|\w+):|' + IDENT + ')'
|
||||
VERSPEC = IDENT + r'\*?'
|
||||
|
||||
RELOP = '([<>=!~]=)|[<>]'
|
||||
def parse_marker(marker_string):
|
||||
"""
|
||||
Parse a marker string and return a dictionary containing a marker expression.
|
||||
|
||||
#
|
||||
# The first relop is optional - if absent, will be taken as '~='
|
||||
#
|
||||
BARE_CONSTRAINTS = ('(' + RELOP + r')?\s*(' + VERSPEC + ')(' + COMMA + '(' +
|
||||
RELOP + r')\s*(' + VERSPEC + '))*')
|
||||
|
||||
DIRECT_REF = '(from\s+(?P<diref>.*))'
|
||||
|
||||
#
|
||||
# Either the bare constraints or the bare constraints in parentheses
|
||||
#
|
||||
CONSTRAINTS = (r'\(\s*(?P<c1>' + BARE_CONSTRAINTS + '|' + DIRECT_REF +
|
||||
r')\s*\)|(?P<c2>' + BARE_CONSTRAINTS + '\s*)')
|
||||
|
||||
EXTRA_LIST = EXTRA_IDENT + '(' + COMMA + EXTRA_IDENT + ')*'
|
||||
EXTRAS = r'\[\s*(?P<ex>' + EXTRA_LIST + r')?\s*\]'
|
||||
REQUIREMENT = ('(?P<dn>' + IDENT + r')\s*(' + EXTRAS + r'\s*)?(\s*' +
|
||||
CONSTRAINTS + ')?$')
|
||||
REQUIREMENT_RE = re.compile(REQUIREMENT)
|
||||
|
||||
#
|
||||
# Used to scan through the constraints
|
||||
#
|
||||
RELOP_IDENT = '(?P<op>' + RELOP + r')\s*(?P<vn>' + VERSPEC + ')'
|
||||
RELOP_IDENT_RE = re.compile(RELOP_IDENT)
|
||||
|
||||
def parse_requirement(s):
|
||||
|
||||
def get_constraint(m):
|
||||
d = m.groupdict()
|
||||
return d['op'], d['vn']
|
||||
|
||||
result = None
|
||||
m = REQUIREMENT_RE.match(s)
|
||||
if m:
|
||||
d = m.groupdict()
|
||||
name = d['dn']
|
||||
cons = d['c1'] or d['c2']
|
||||
if not d['diref']:
|
||||
url = None
|
||||
The dictionary will contain keys "op", "lhs" and "rhs" for non-terminals in
|
||||
the expression grammar, or strings. A string contained in quotes is to be
|
||||
interpreted as a literal string, and a string not contained in quotes is a
|
||||
variable (such as os_name).
|
||||
"""
|
||||
def marker_var(remaining):
|
||||
# either identifier, or literal string
|
||||
m = IDENTIFIER.match(remaining)
|
||||
if m:
|
||||
result = m.groups()[0]
|
||||
remaining = remaining[m.end():]
|
||||
elif not remaining:
|
||||
raise SyntaxError('unexpected end of input')
|
||||
else:
|
||||
# direct reference
|
||||
cons = None
|
||||
url = d['diref'].strip()
|
||||
if not cons:
|
||||
cons = None
|
||||
constr = ''
|
||||
rs = d['dn']
|
||||
q = remaining[0]
|
||||
if q not in '\'"':
|
||||
raise SyntaxError('invalid expression: %s' % remaining)
|
||||
oq = '\'"'.replace(q, '')
|
||||
remaining = remaining[1:]
|
||||
parts = [q]
|
||||
while remaining:
|
||||
# either a string chunk, or oq, or q to terminate
|
||||
if remaining[0] == q:
|
||||
break
|
||||
elif remaining[0] == oq:
|
||||
parts.append(oq)
|
||||
remaining = remaining[1:]
|
||||
else:
|
||||
m = STRING_CHUNK.match(remaining)
|
||||
if not m:
|
||||
raise SyntaxError('error in string literal: %s' % remaining)
|
||||
parts.append(m.groups()[0])
|
||||
remaining = remaining[m.end():]
|
||||
else:
|
||||
s = ''.join(parts)
|
||||
raise SyntaxError('unterminated string: %s' % s)
|
||||
parts.append(q)
|
||||
result = ''.join(parts)
|
||||
remaining = remaining[1:].lstrip() # skip past closing quote
|
||||
return result, remaining
|
||||
|
||||
def marker_expr(remaining):
|
||||
if remaining and remaining[0] == '(':
|
||||
result, remaining = marker(remaining[1:].lstrip())
|
||||
if remaining[0] != ')':
|
||||
raise SyntaxError('unterminated parenthesis: %s' % remaining)
|
||||
remaining = remaining[1:].lstrip()
|
||||
else:
|
||||
if cons[0] not in '<>!=':
|
||||
cons = '~=' + cons
|
||||
iterator = RELOP_IDENT_RE.finditer(cons)
|
||||
cons = [get_constraint(m) for m in iterator]
|
||||
rs = '%s (%s)' % (name, ', '.join(['%s %s' % con for con in cons]))
|
||||
if not d['ex']:
|
||||
lhs, remaining = marker_var(remaining)
|
||||
while remaining:
|
||||
m = MARKER_OP.match(remaining)
|
||||
if not m:
|
||||
break
|
||||
op = m.groups()[0]
|
||||
remaining = remaining[m.end():]
|
||||
rhs, remaining = marker_var(remaining)
|
||||
lhs = {'op': op, 'lhs': lhs, 'rhs': rhs}
|
||||
result = lhs
|
||||
return result, remaining
|
||||
|
||||
def marker_and(remaining):
|
||||
lhs, remaining = marker_expr(remaining)
|
||||
while remaining:
|
||||
m = AND.match(remaining)
|
||||
if not m:
|
||||
break
|
||||
remaining = remaining[m.end():]
|
||||
rhs, remaining = marker_expr(remaining)
|
||||
lhs = {'op': 'and', 'lhs': lhs, 'rhs': rhs}
|
||||
return lhs, remaining
|
||||
|
||||
def marker(remaining):
|
||||
lhs, remaining = marker_and(remaining)
|
||||
while remaining:
|
||||
m = OR.match(remaining)
|
||||
if not m:
|
||||
break
|
||||
remaining = remaining[m.end():]
|
||||
rhs, remaining = marker_and(remaining)
|
||||
lhs = {'op': 'or', 'lhs': lhs, 'rhs': rhs}
|
||||
return lhs, remaining
|
||||
|
||||
return marker(marker_string)
|
||||
|
||||
|
||||
def parse_requirement(req):
|
||||
"""
|
||||
Parse a requirement passed in as a string. Return a Container
|
||||
whose attributes contain the various parts of the requirement.
|
||||
"""
|
||||
remaining = req.strip()
|
||||
if not remaining or remaining.startswith('#'):
|
||||
return None
|
||||
m = IDENTIFIER.match(remaining)
|
||||
if not m:
|
||||
raise SyntaxError('name expected: %s' % remaining)
|
||||
distname = m.groups()[0]
|
||||
remaining = remaining[m.end():]
|
||||
extras = mark_expr = versions = uri = None
|
||||
if remaining and remaining[0] == '[':
|
||||
i = remaining.find(']', 1)
|
||||
if i < 0:
|
||||
raise SyntaxError('unterminated extra: %s' % remaining)
|
||||
s = remaining[1:i]
|
||||
remaining = remaining[i + 1:].lstrip()
|
||||
extras = []
|
||||
while s:
|
||||
m = IDENTIFIER.match(s)
|
||||
if not m:
|
||||
raise SyntaxError('malformed extra: %s' % s)
|
||||
extras.append(m.groups()[0])
|
||||
s = s[m.end():]
|
||||
if not s:
|
||||
break
|
||||
if s[0] != ',':
|
||||
raise SyntaxError('comma expected in extras: %s' % s)
|
||||
s = s[1:].lstrip()
|
||||
if not extras:
|
||||
extras = None
|
||||
if remaining:
|
||||
if remaining[0] == '@':
|
||||
# it's a URI
|
||||
remaining = remaining[1:].lstrip()
|
||||
m = NON_SPACE.match(remaining)
|
||||
if not m:
|
||||
raise SyntaxError('invalid URI: %s' % remaining)
|
||||
uri = m.groups()[0]
|
||||
t = urlparse(uri)
|
||||
# there are issues with Python and URL parsing, so this test
|
||||
# is a bit crude. See bpo-20271, bpo-23505. Python doesn't
|
||||
# always parse invalid URLs correctly - it should raise
|
||||
# exceptions for malformed URLs
|
||||
if not (t.scheme and t.netloc):
|
||||
raise SyntaxError('Invalid URL: %s' % uri)
|
||||
remaining = remaining[m.end():].lstrip()
|
||||
else:
|
||||
extras = COMMA_RE.split(d['ex'])
|
||||
result = Container(name=name, constraints=cons, extras=extras,
|
||||
requirement=rs, source=s, url=url)
|
||||
return result
|
||||
|
||||
def get_versions(ver_remaining):
|
||||
"""
|
||||
Return a list of operator, version tuples if any are
|
||||
specified, else None.
|
||||
"""
|
||||
m = COMPARE_OP.match(ver_remaining)
|
||||
versions = None
|
||||
if m:
|
||||
versions = []
|
||||
while True:
|
||||
op = m.groups()[0]
|
||||
ver_remaining = ver_remaining[m.end():]
|
||||
m = VERSION_IDENTIFIER.match(ver_remaining)
|
||||
if not m:
|
||||
raise SyntaxError('invalid version: %s' % ver_remaining)
|
||||
v = m.groups()[0]
|
||||
versions.append((op, v))
|
||||
ver_remaining = ver_remaining[m.end():]
|
||||
if not ver_remaining or ver_remaining[0] != ',':
|
||||
break
|
||||
ver_remaining = ver_remaining[1:].lstrip()
|
||||
m = COMPARE_OP.match(ver_remaining)
|
||||
if not m:
|
||||
raise SyntaxError('invalid constraint: %s' % ver_remaining)
|
||||
if not versions:
|
||||
versions = None
|
||||
return versions, ver_remaining
|
||||
|
||||
if remaining[0] != '(':
|
||||
versions, remaining = get_versions(remaining)
|
||||
else:
|
||||
i = remaining.find(')', 1)
|
||||
if i < 0:
|
||||
raise SyntaxError('unterminated parenthesis: %s' % remaining)
|
||||
s = remaining[1:i]
|
||||
remaining = remaining[i + 1:].lstrip()
|
||||
# As a special diversion from PEP 508, allow a version number
|
||||
# a.b.c in parentheses as a synonym for ~= a.b.c (because this
|
||||
# is allowed in earlier PEPs)
|
||||
if COMPARE_OP.match(s):
|
||||
versions, _ = get_versions(s)
|
||||
else:
|
||||
m = VERSION_IDENTIFIER.match(s)
|
||||
if not m:
|
||||
raise SyntaxError('invalid constraint: %s' % s)
|
||||
v = m.groups()[0]
|
||||
s = s[m.end():].lstrip()
|
||||
if s:
|
||||
raise SyntaxError('invalid constraint: %s' % s)
|
||||
versions = [('~=', v)]
|
||||
|
||||
if remaining:
|
||||
if remaining[0] != ';':
|
||||
raise SyntaxError('invalid requirement: %s' % remaining)
|
||||
remaining = remaining[1:].lstrip()
|
||||
|
||||
mark_expr, remaining = parse_marker(remaining)
|
||||
|
||||
if remaining and remaining[0] != '#':
|
||||
raise SyntaxError('unexpected trailing data: %s' % remaining)
|
||||
|
||||
if not versions:
|
||||
rs = distname
|
||||
else:
|
||||
rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions]))
|
||||
return Container(name=distname, extras=extras, constraints=versions,
|
||||
marker=mark_expr, url=uri, requirement=rs)
|
||||
|
||||
|
||||
def get_resources_dests(resources_root, rules):
|
||||
"""Find destinations for resources files"""
|
||||
|
||||
def get_rel_path(base, path):
|
||||
def get_rel_path(root, path):
|
||||
# normalizes and returns a lstripped-/-separated path
|
||||
base = base.replace(os.path.sep, '/')
|
||||
root = root.replace(os.path.sep, '/')
|
||||
path = path.replace(os.path.sep, '/')
|
||||
assert path.startswith(base)
|
||||
return path[len(base):].lstrip('/')
|
||||
|
||||
assert path.startswith(root)
|
||||
return path[len(root):].lstrip('/')
|
||||
|
||||
destinations = {}
|
||||
for base, suffix, dest in rules:
|
||||
@@ -161,7 +302,7 @@ def in_venv():
|
||||
def get_executable():
|
||||
# The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as
|
||||
# changes to the stub launcher mean that sys.executable always points
|
||||
# to the stub on macOS
|
||||
# to the stub on OS X
|
||||
# if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
|
||||
# in os.environ):
|
||||
# result = os.environ['__PYVENV_LAUNCHER__']
|
||||
@@ -1443,6 +1584,9 @@ def _csv_open(fn, mode, **kwargs):
|
||||
mode += 'b'
|
||||
else:
|
||||
kwargs['newline'] = ''
|
||||
# Python 3 determines encoding from locale. Force 'utf-8'
|
||||
# file encoding to match other forced utf-8 encoding
|
||||
kwargs['encoding'] = 'utf-8'
|
||||
return open(fn, mode, **kwargs)
|
||||
|
||||
|
||||
@@ -1558,11 +1702,11 @@ class Configurator(BaseConfigurator):
|
||||
result = json.load(f)
|
||||
return result
|
||||
|
||||
#
|
||||
# Mixin for running subprocesses and capturing their output
|
||||
#
|
||||
|
||||
class SubprocessMixin(object):
|
||||
"""
|
||||
Mixin for running subprocesses and capturing their output
|
||||
"""
|
||||
def __init__(self, verbose=False, progress=None):
|
||||
self.verbose = verbose
|
||||
self.progress = progress
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2016 The Python Software Foundation.
|
||||
# Copyright (C) 2012-2017 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""
|
||||
@@ -12,6 +12,7 @@ import logging
|
||||
import re
|
||||
|
||||
from .compat import string_types
|
||||
from .util import parse_requirement
|
||||
|
||||
__all__ = ['NormalizedVersion', 'NormalizedMatcher',
|
||||
'LegacyVersion', 'LegacyMatcher',
|
||||
@@ -78,10 +79,6 @@ class Version(object):
|
||||
class Matcher(object):
|
||||
version_class = None
|
||||
|
||||
dist_re = re.compile(r"^(\w[\s\w'.-]*)(\((.*)\))?")
|
||||
comp_re = re.compile(r'^(<=|>=|<|>|!=|={2,3}|~=)?\s*([^\s,]+)$')
|
||||
num_re = re.compile(r'^\d+(\.\d+)*$')
|
||||
|
||||
# value is either a callable or the name of a method
|
||||
_operators = {
|
||||
'<': lambda v, c, p: v < c,
|
||||
@@ -95,26 +92,24 @@ class Matcher(object):
|
||||
'!=': lambda v, c, p: v != c,
|
||||
}
|
||||
|
||||
# this is a method only to support alternative implementations
|
||||
# via overriding
|
||||
def parse_requirement(self, s):
|
||||
return parse_requirement(s)
|
||||
|
||||
def __init__(self, s):
|
||||
if self.version_class is None:
|
||||
raise ValueError('Please specify a version class')
|
||||
self._string = s = s.strip()
|
||||
m = self.dist_re.match(s)
|
||||
if not m:
|
||||
r = self.parse_requirement(s)
|
||||
if not r:
|
||||
raise ValueError('Not valid: %r' % s)
|
||||
groups = m.groups('')
|
||||
self.name = groups[0].strip()
|
||||
self.name = r.name
|
||||
self.key = self.name.lower() # for case-insensitive comparisons
|
||||
clist = []
|
||||
if groups[2]:
|
||||
constraints = [c.strip() for c in groups[2].split(',')]
|
||||
for c in constraints:
|
||||
m = self.comp_re.match(c)
|
||||
if not m:
|
||||
raise ValueError('Invalid %r in %r' % (c, s))
|
||||
groups = m.groups()
|
||||
op = groups[0] or '~='
|
||||
s = groups[1]
|
||||
if r.constraints:
|
||||
# import pdb; pdb.set_trace()
|
||||
for op, s in r.constraints:
|
||||
if s.endswith('.*'):
|
||||
if op not in ('==', '!='):
|
||||
raise ValueError('\'.*\' not allowed for '
|
||||
@@ -122,9 +117,8 @@ class Matcher(object):
|
||||
# Could be a partial version (e.g. for '2.*') which
|
||||
# won't parse as a version, so keep it as a string
|
||||
vn, prefix = s[:-2], True
|
||||
if not self.num_re.match(vn):
|
||||
# Just to check that vn is a valid version
|
||||
self.version_class(vn)
|
||||
# Just to check that vn is a valid version
|
||||
self.version_class(vn)
|
||||
else:
|
||||
# Should parse as a version, so we can create an
|
||||
# instance for the comparison
|
||||
@@ -400,7 +394,7 @@ _REPLACEMENTS = (
|
||||
|
||||
_SUFFIX_REPLACEMENTS = (
|
||||
(re.compile('^[:~._+-]+'), ''), # remove leading puncts
|
||||
(re.compile('[,*")([\]]'), ''), # remove unwanted chars
|
||||
(re.compile('[,*")([\\]]'), ''), # remove unwanted chars
|
||||
(re.compile('[~:+_ -]'), '.'), # replace illegal chars
|
||||
(re.compile('[.]{2,}'), '.'), # multiple runs of '.'
|
||||
(re.compile(r'\.$'), ''), # trailing '.'
|
||||
@@ -628,7 +622,7 @@ class LegacyMatcher(Matcher):
|
||||
_operators = dict(Matcher._operators)
|
||||
_operators['~='] = '_match_compatible'
|
||||
|
||||
numeric_re = re.compile('^(\d+(\.\d+)*)')
|
||||
numeric_re = re.compile(r'^(\d+(\.\d+)*)')
|
||||
|
||||
def _match_compatible(self, version, constraint, prefix):
|
||||
if version < constraint:
|
||||
|
||||
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013-2016 Vinay Sajip.
|
||||
# Copyright (C) 2013-2017 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
@@ -26,7 +26,7 @@ import zipfile
|
||||
from . import __version__, DistlibException
|
||||
from .compat import sysconfig, ZipFile, fsdecode, text_type, filter
|
||||
from .database import InstalledDistribution
|
||||
from .metadata import Metadata, METADATA_FILENAME
|
||||
from .metadata import Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME
|
||||
from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache,
|
||||
cached_property, get_cache_base, read_exports, tempdir)
|
||||
from .version import NormalizedVersion, UnsupportedVersionError
|
||||
@@ -35,11 +35,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
cache = None # created when needed
|
||||
|
||||
if hasattr(sys, 'pypy_version_info'):
|
||||
if hasattr(sys, 'pypy_version_info'): # pragma: no cover
|
||||
IMP_PREFIX = 'pp'
|
||||
elif sys.platform.startswith('java'):
|
||||
elif sys.platform.startswith('java'): # pragma: no cover
|
||||
IMP_PREFIX = 'jy'
|
||||
elif sys.platform == 'cli':
|
||||
elif sys.platform == 'cli': # pragma: no cover
|
||||
IMP_PREFIX = 'ip'
|
||||
else:
|
||||
IMP_PREFIX = 'cp'
|
||||
@@ -222,17 +222,23 @@ class Wheel(object):
|
||||
wv = wheel_metadata['Wheel-Version'].split('.', 1)
|
||||
file_version = tuple([int(i) for i in wv])
|
||||
if file_version < (1, 1):
|
||||
fn = 'METADATA'
|
||||
fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, 'METADATA']
|
||||
else:
|
||||
fn = METADATA_FILENAME
|
||||
try:
|
||||
metadata_filename = posixpath.join(info_dir, fn)
|
||||
with zf.open(metadata_filename) as bf:
|
||||
wf = wrapper(bf)
|
||||
result = Metadata(fileobj=wf)
|
||||
except KeyError:
|
||||
raise ValueError('Invalid wheel, because %s is '
|
||||
'missing' % fn)
|
||||
fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME]
|
||||
result = None
|
||||
for fn in fns:
|
||||
try:
|
||||
metadata_filename = posixpath.join(info_dir, fn)
|
||||
with zf.open(metadata_filename) as bf:
|
||||
wf = wrapper(bf)
|
||||
result = Metadata(fileobj=wf)
|
||||
if result:
|
||||
break
|
||||
except KeyError:
|
||||
pass
|
||||
if not result:
|
||||
raise ValueError('Invalid wheel, because metadata is '
|
||||
'missing: looked in %s' % ', '.join(fns))
|
||||
return result
|
||||
|
||||
def get_wheel_metadata(self, zf):
|
||||
@@ -919,7 +925,7 @@ def compatible_tags():
|
||||
|
||||
arches = [ARCH]
|
||||
if sys.platform == 'darwin':
|
||||
m = re.match('(\w+)_(\d+)_(\d+)_(\w+)$', ARCH)
|
||||
m = re.match(r'(\w+)_(\d+)_(\d+)_(\w+)$', ARCH)
|
||||
if m:
|
||||
name, major, minor, arch = m.groups()
|
||||
minor = int(minor)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user