mirror of https://github.com/pypa/pip
Merge branch 'master' into cache/ephem-wheel-cache
This commit is contained in:
commit
01d97e71f0
|
@ -1,4 +1,4 @@
|
|||
[run]
|
||||
branch = True
|
||||
omit =
|
||||
pip/_vendor/*
|
||||
src/pip/_vendor/*
|
||||
|
|
|
@ -19,6 +19,7 @@ docs/build/
|
|||
|
||||
# Unit test / coverage reports
|
||||
.tox/
|
||||
htmlcov/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
|
|
|
@ -3,16 +3,19 @@ include LICENSE.txt
|
|||
include NEWS.rst
|
||||
include README.rst
|
||||
include pyproject.toml
|
||||
|
||||
include src/pip/_vendor/README.rst
|
||||
include src/pip/_vendor/vendor.txt
|
||||
|
||||
include docs/docutils.conf
|
||||
|
||||
exclude .coveragerc
|
||||
exclude .mailmap
|
||||
exclude .travis.yml
|
||||
exclude .landscape.yml
|
||||
exclude src/pip/_vendor/Makefile
|
||||
exclude tox.ini
|
||||
exclude dev-requirements.txt
|
||||
exclude *-requirements.txt
|
||||
exclude appveyor.yml
|
||||
|
||||
recursive-include src/pip/_vendor *.pem
|
||||
|
|
2
NEWS.rst
2
NEWS.rst
|
@ -769,7 +769,7 @@
|
|||
than erroring out. (#963)
|
||||
- ``pip bundle`` and support for installing from pybundle files is now
|
||||
considered deprecated and will be removed in pip v1.5.
|
||||
- Fix a number of isses related to cleaning up and not reusing build
|
||||
- Fix a number of issues related to cleaning up and not reusing build
|
||||
directories. (#413, #709, #634, #602, #939, #865, #948)
|
||||
- Added a User Agent so that pip is identifiable in logs. (#901)
|
||||
- Added ssl and --user support to get-pip.py. Thanks Gabriel de Perthuis.
|
||||
|
|
33
README.rst
33
README.rst
|
@ -1,9 +1,7 @@
|
|||
pip
|
||||
===
|
||||
|
||||
The `PyPA recommended
|
||||
<https://packaging.python.org/en/latest/current/>`_
|
||||
tool for installing Python packages.
|
||||
The `PyPA recommended`_ tool for installing Python packages.
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/pip.svg
|
||||
:target: https://pypi.python.org/pypi/pip
|
||||
|
@ -14,24 +12,31 @@ tool for installing Python packages.
|
|||
.. image:: https://img.shields.io/appveyor/ci/pypa/pip.svg
|
||||
:target: https://ci.appveyor.com/project/pypa/pip/history
|
||||
|
||||
.. image:: https://readthedocs.org/projects/pip/badge/?version=stable
|
||||
:target: https://pip.pypa.io/en/stable
|
||||
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
|
||||
:target: https://pip.pypa.io/en/latest
|
||||
|
||||
* `Installation <https://pip.pypa.io/en/stable/installing.html>`_
|
||||
* `Documentation <https://pip.pypa.io/>`_
|
||||
* `Changelog <https://pip.pypa.io/en/stable/news.html>`_
|
||||
* `GitHub Page <https://github.com/pypa/pip>`_
|
||||
* `Issue Tracking <https://github.com/pypa/pip/issues>`_
|
||||
* `User mailing list <http://groups.google.com/group/python-virtualenv>`_
|
||||
* `Dev mailing list <http://groups.google.com/group/pypa-dev>`_
|
||||
* `Installation`_
|
||||
* `Documentation`_
|
||||
* `Changelog`_
|
||||
* `GitHub Page`_
|
||||
* `Issue Tracking`_
|
||||
* `User mailing list`_
|
||||
* `Dev mailing list`_
|
||||
* User IRC: #pypa on Freenode.
|
||||
* Dev IRC: #pypa-dev on Freenode.
|
||||
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Everyone interacting in the pip project's codebases, issue trackers, chat
|
||||
rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_.
|
||||
rooms and mailing lists is expected to follow the `PyPA Code of Conduct`_.
|
||||
|
||||
.. _PyPA recommended: https://packaging.python.org/en/latest/current/
|
||||
.. _Installation: https://pip.pypa.io/en/stable/installing.html
|
||||
.. _Documentation: https://pip.pypa.io/en/stable/
|
||||
.. _Changelog: https://pip.pypa.io/en/stable/news.html
|
||||
.. _GitHub Page: https://github.com/pypa/pip
|
||||
.. _Issue Tracking: https://github.com/pypa/pip/issues
|
||||
.. _User mailing list: http://groups.google.com/group/python-virtualenv
|
||||
.. _Dev mailing list: http://groups.google.com/group/pypa-dev
|
||||
.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
|
||||
|
|
|
@ -2,6 +2,7 @@ freezegun
|
|||
pretend
|
||||
pytest
|
||||
pytest-catchlog
|
||||
pytest-cov
|
||||
pytest-rerunfailures
|
||||
pytest-timeout
|
||||
pytest-xdist
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
sphinx == 1.6.*
|
||||
git+https://github.com/python/python-docs-theme.git#egg=python-docs-theme
|
||||
git+https://github.com/pypa/pypa-docs-theme.git#egg=pypa-docs-theme
|
||||
|
||||
# XXX: This is a workaround for conf.py not seeing the development pip version
|
||||
# when the documentation is built on ReadTheDocs.
|
||||
.
|
|
@ -68,6 +68,11 @@ try:
|
|||
except ImportError:
|
||||
version = release = 'dev'
|
||||
|
||||
# We have this here because readthedocs plays tricks sometimes and there seems
|
||||
# to be a hiesenbug, related to the version of pip discovered. This is here to
|
||||
# help debug that if someone decides to do that in the future.
|
||||
print(version)
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
@ -122,7 +127,7 @@ html_theme = "pypa_theme"
|
|||
html_theme_options = {
|
||||
'collapsiblesidebar': True,
|
||||
'externalrefs': True,
|
||||
'navigation_depth': 2,
|
||||
'navigation_depth': 3,
|
||||
'issues_url': 'https://github.com/pypa/pip/issues'
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[restructuredtext parser]
|
||||
smart_quotes = no
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
import optparse
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers import rst
|
||||
from docutils.statemachine import ViewList
|
||||
from textwrap import dedent
|
||||
|
||||
from pip._internal import cmdoptions
|
||||
from pip._internal.commands import commands_dict as commands
|
||||
from pip._internal.utils.misc import get_prog
|
||||
|
||||
|
||||
class PipCommandUsage(rst.Directive):
|
||||
|
@ -16,8 +17,7 @@ class PipCommandUsage(rst.Directive):
|
|||
|
||||
def run(self):
|
||||
cmd = commands[self.arguments[0]]
|
||||
prog = '%s %s' % (get_prog(), cmd.name)
|
||||
usage = dedent(cmd.usage.replace('%prog', prog)).strip()
|
||||
usage = dedent(cmd.usage.replace('%prog', 'pip')).strip()
|
||||
node = nodes.literal_block(usage, usage)
|
||||
return [node]
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ Install a package from `PyPI`_:
|
|||
::
|
||||
|
||||
$ pip install SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
Install a package already downloaded from `PyPI`_ or got elsewhere.
|
||||
This is useful if the target machine does not have a network connection:
|
||||
|
@ -17,49 +17,49 @@ This is useful if the target machine does not have a network connection:
|
|||
::
|
||||
|
||||
$ pip install SomePackage-1.0-py2.py3-none-any.whl
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
[...]
|
||||
Successfully installed SomePackage
|
||||
|
||||
Show what files were installed:
|
||||
|
||||
::
|
||||
|
||||
$ pip show --files SomePackage
|
||||
Name: SomePackage
|
||||
Version: 1.0
|
||||
Location: /my/env/lib/pythonx.x/site-packages
|
||||
Files:
|
||||
../somepackage/__init__.py
|
||||
[...]
|
||||
Name: SomePackage
|
||||
Version: 1.0
|
||||
Location: /my/env/lib/pythonx.x/site-packages
|
||||
Files:
|
||||
../somepackage/__init__.py
|
||||
[...]
|
||||
|
||||
List what packages are outdated:
|
||||
|
||||
::
|
||||
|
||||
$ pip list --outdated
|
||||
SomePackage (Current: 1.0 Latest: 2.0)
|
||||
SomePackage (Current: 1.0 Latest: 2.0)
|
||||
|
||||
Upgrade a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip install --upgrade SomePackage
|
||||
[...]
|
||||
Found existing installation: SomePackage 1.0
|
||||
Uninstalling SomePackage:
|
||||
Successfully uninstalled SomePackage
|
||||
Running setup.py install for SomePackage
|
||||
Successfully installed SomePackage
|
||||
[...]
|
||||
Found existing installation: SomePackage 1.0
|
||||
Uninstalling SomePackage:
|
||||
Successfully uninstalled SomePackage
|
||||
Running setup.py install for SomePackage
|
||||
Successfully installed SomePackage
|
||||
|
||||
Uninstall a package:
|
||||
|
||||
::
|
||||
|
||||
$ pip uninstall SomePackage
|
||||
Uninstalling SomePackage:
|
||||
/my/env/lib/pythonx.x/site-packages/somepackage
|
||||
Proceed (y/n)? y
|
||||
Successfully uninstalled SomePackage
|
||||
Uninstalling SomePackage:
|
||||
/my/env/lib/pythonx.x/site-packages/somepackage
|
||||
Proceed (y/n)? y
|
||||
Successfully uninstalled SomePackage
|
||||
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/
|
||||
|
|
|
@ -3,7 +3,7 @@ Reference Guide
|
|||
===============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:maxdepth: 2
|
||||
|
||||
pip
|
||||
pip_install
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
pip
|
||||
---
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
@ -22,7 +24,9 @@ Console logging
|
|||
~~~~~~~~~~~~~~~
|
||||
|
||||
pip offers :ref:`-v, --verbose <--verbose>` and :ref:`-q, --quiet <--quiet>`
|
||||
to control the console log level.
|
||||
to control the console log level. By default, some messages (error and warnings)
|
||||
are colored in the terminal. If you want to suppress the colored output use
|
||||
:ref:`--no-color <--no-color>`.
|
||||
|
||||
|
||||
.. _`FileLogging`:
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
pip check
|
||||
---------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
pip config
|
||||
------------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
pip download
|
||||
------------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
pip freeze
|
||||
-----------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
pip hash
|
||||
------------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
pip install
|
||||
-----------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
pip list
|
||||
---------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
pip search
|
||||
----------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
pip show
|
||||
--------
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
pip uninstall
|
||||
-------------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
pip wheel
|
||||
---------
|
||||
|
||||
.. contents::
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
User Guide
|
||||
==========
|
||||
|
||||
.. contents::
|
||||
|
||||
Running pip
|
||||
***********
|
||||
|
||||
|
@ -728,7 +730,7 @@ change without notice. While we do try not to break things as much as possible,
|
|||
the internal APIs can change at any time, for any reason. It also means that we
|
||||
generally *won't* fix issues that are a result of using pip in an unsupported way.
|
||||
|
||||
It should also be noted that modifying the contents of ``sys.path`` in a running Python
|
||||
It should also be noted that installing packages into ``sys.path`` in a running Python
|
||||
process is something that should only be done with care. The import system caches
|
||||
certain data, and installing new packages while a program is running may not always
|
||||
behave as expected. In practice, there is rarely an issue, but it is something to be
|
||||
|
@ -756,7 +758,5 @@ of ability. Some examples that you could consider include:
|
|||
* ``setuptools`` (specifically ``pkg_resources``) - Functions for querying what
|
||||
packages the user has installed on their system.
|
||||
|
||||
* ``wheel`` - Code for manipulating (creating, querying and installing) wheels.
|
||||
|
||||
* ``distlib`` - Packaging and distribution utilities (including functions for
|
||||
interacting with PyPI).
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add `--no-color` to `pip`. All colored output is disabled
|
||||
if this flag is detected.
|
|
@ -0,0 +1 @@
|
|||
pip uninstall now ignores the absence of a requirement and prints a warning.
|
|
@ -0,0 +1 @@
|
|||
Fix ``pip uninstall`` when ``easy-install.pth`` lacks a trailing newline.
|
|
@ -0,0 +1,5 @@
|
|||
Fix for an incorrect ``freeze`` warning message due to a package being
|
||||
included in multiple requirements files that were passed to ``freeze``.
|
||||
Instead of warning incorrectly that the package is not installed, pip
|
||||
now warns that the package was declared multiple times and lists the
|
||||
name of each requirements file that contains the package in question.
|
|
@ -0,0 +1 @@
|
|||
pip uninstall now ignores the absence of a requirement and prints a warning.
|
|
@ -0,0 +1,2 @@
|
|||
pip now records installed files in a deterministic manner improving
|
||||
reproducibility.
|
|
@ -1 +1 @@
|
|||
Upgraded distlib to 0.2.5.
|
||||
Upgraded distlib to 0.2.6.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Upgraded pkg_resources (via setuptools) to 36.4.0.
|
||||
Upgraded pkg_resources (via setuptools) to 36.6.0.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Upgraded six to 1.11.0.
|
|
@ -132,6 +132,11 @@ class Command(object):
|
|||
if options.log:
|
||||
root_level = "DEBUG"
|
||||
|
||||
if options.no_color:
|
||||
logger_class = "logging.StreamHandler"
|
||||
else:
|
||||
logger_class = "pip._internal.utils.logging.ColorizedStreamHandler"
|
||||
|
||||
logging.config.dictConfig({
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
|
@ -150,16 +155,14 @@ class Command(object):
|
|||
"handlers": {
|
||||
"console": {
|
||||
"level": level,
|
||||
"class":
|
||||
"pip._internal.utils.logging.ColorizedStreamHandler",
|
||||
"class": logger_class,
|
||||
"stream": self.log_streams[0],
|
||||
"filters": ["exclude_warnings"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
"console_errors": {
|
||||
"level": "WARNING",
|
||||
"class":
|
||||
"pip._internal.utils.logging.ColorizedStreamHandler",
|
||||
"class": logger_class,
|
||||
"stream": self.log_streams[1],
|
||||
"formatter": "indent",
|
||||
},
|
||||
|
|
|
@ -102,6 +102,15 @@ 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',
|
||||
|
@ -566,6 +575,7 @@ general_group = {
|
|||
cache_dir,
|
||||
no_cache,
|
||||
disable_pip_version_check,
|
||||
no_color,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -277,22 +277,20 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
# on -d don't do complex things like building
|
||||
# wheels, and don't try to build wheels when wheel is
|
||||
# not installed.
|
||||
# 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(
|
||||
requirement_set,
|
||||
finder,
|
||||
preparer,
|
||||
wheel_cache,
|
||||
build_options=[],
|
||||
global_options=[],
|
||||
finder, preparer, wheel_cache,
|
||||
build_options=[], global_options=[],
|
||||
)
|
||||
# Ignore the result: a failed wheel will be
|
||||
# installed from the sdist/vcs whatever.
|
||||
wb.build(session=session, autobuilding=True)
|
||||
wb.build(
|
||||
requirement_set.requirements.values(),
|
||||
session=session, autobuilding=True
|
||||
)
|
||||
|
||||
installed = requirement_set.install(
|
||||
install_options,
|
||||
|
|
|
@ -64,7 +64,8 @@ class UninstallCommand(Command):
|
|||
'"pip help %(name)s")' % dict(name=self.name)
|
||||
)
|
||||
for req in reqs_to_uninstall.values():
|
||||
req.uninstall(
|
||||
uninstall_pathset = req.uninstall(
|
||||
auto_confirm=options.yes, verbose=options.verbose != 0
|
||||
)
|
||||
req.uninstalled_pathset.commit()
|
||||
if uninstall_pathset:
|
||||
uninstall_pathset.commit()
|
||||
|
|
|
@ -177,15 +177,14 @@ class WheelCommand(RequirementCommand):
|
|||
|
||||
# build wheels
|
||||
wb = WheelBuilder(
|
||||
requirement_set,
|
||||
finder,
|
||||
preparer,
|
||||
wheel_cache,
|
||||
finder, preparer, wheel_cache,
|
||||
build_options=options.build_options or [],
|
||||
global_options=options.global_options or [],
|
||||
no_clean=options.no_clean,
|
||||
)
|
||||
wheels_built_successfully = wb.build(session=session)
|
||||
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"
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from pip._vendor import pkg_resources
|
||||
from pip._vendor import pkg_resources, six
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.pkg_resources import RequirementParseError
|
||||
|
||||
|
@ -70,6 +71,9 @@ def freeze(
|
|||
# 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:
|
||||
|
@ -119,14 +123,28 @@ def freeze(
|
|||
" this warning)"
|
||||
)
|
||||
elif line_req.name not in installations:
|
||||
logger.warning(
|
||||
"Requirement file [%s] contains %s, but that "
|
||||
"package is not installed",
|
||||
req_file_path, COMMENT_RE.sub('', line).strip(),
|
||||
)
|
||||
# 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 '
|
||||
|
|
|
@ -648,13 +648,13 @@ class InstallRequirement(object):
|
|||
|
||||
"""
|
||||
if not self.check_if_exists():
|
||||
raise UninstallationError(
|
||||
"Cannot uninstall requirement %s, not installed" % (self.name,)
|
||||
)
|
||||
logger.warning("Skipping %s as it is not installed.", self.name)
|
||||
return
|
||||
dist = self.satisfied_by or self.conflicts_with
|
||||
|
||||
self.uninstalled_pathset = UninstallPathSet.from_dist(dist)
|
||||
self.uninstalled_pathset.remove(auto_confirm, verbose)
|
||||
uninstalled_pathset = UninstallPathSet.from_dist(dist)
|
||||
uninstalled_pathset.remove(auto_confirm, verbose)
|
||||
return uninstalled_pathset
|
||||
|
||||
def archive(self, build_dir):
|
||||
assert self.source_dir
|
||||
|
@ -802,6 +802,7 @@ class InstallRequirement(object):
|
|||
new_lines.append(
|
||||
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:
|
||||
|
|
|
@ -216,7 +216,9 @@ class RequirementSet(object):
|
|||
requirement.conflicts_with,
|
||||
)
|
||||
with indent_log():
|
||||
requirement.uninstall(auto_confirm=True)
|
||||
uninstalled_pathset = requirement.uninstall(
|
||||
auto_confirm=True
|
||||
)
|
||||
try:
|
||||
requirement.install(
|
||||
install_options,
|
||||
|
@ -231,7 +233,7 @@ class RequirementSet(object):
|
|||
)
|
||||
# if install did not succeed, rollback previous uninstall
|
||||
if should_rollback:
|
||||
requirement.uninstalled_pathset.rollback()
|
||||
uninstalled_pathset.rollback()
|
||||
raise
|
||||
else:
|
||||
should_commit = (
|
||||
|
@ -239,7 +241,7 @@ class RequirementSet(object):
|
|||
requirement.install_succeeded
|
||||
)
|
||||
if should_commit:
|
||||
requirement.uninstalled_pathset.commit()
|
||||
uninstalled_pathset.commit()
|
||||
requirement.remove_temporary_source()
|
||||
|
||||
return to_install
|
||||
|
|
|
@ -430,6 +430,9 @@ class UninstallPthEntries(object):
|
|||
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)
|
||||
|
|
|
@ -657,9 +657,8 @@ class BuildEnvironment(object):
|
|||
class WheelBuilder(object):
|
||||
"""Build wheels from a RequirementSet."""
|
||||
|
||||
def __init__(self, requirement_set, finder, preparer, wheel_cache,
|
||||
def __init__(self, finder, preparer, wheel_cache,
|
||||
build_options=None, global_options=None, no_clean=False):
|
||||
self.requirement_set = requirement_set
|
||||
self.finder = finder
|
||||
self.preparer = preparer
|
||||
self.wheel_cache = wheel_cache
|
||||
|
@ -791,7 +790,7 @@ class WheelBuilder(object):
|
|||
logger.error('Failed cleaning build dir for %s', req.name)
|
||||
return False
|
||||
|
||||
def build(self, session, autobuilding=False):
|
||||
def build(self, requirements, session, autobuilding=False):
|
||||
"""Build wheels.
|
||||
|
||||
:param unpack: If True, replace the sdist we built from with the
|
||||
|
@ -805,10 +804,8 @@ class WheelBuilder(object):
|
|||
)
|
||||
assert building_is_possible
|
||||
|
||||
reqset = self.requirement_set.requirements.values()
|
||||
|
||||
buildset = []
|
||||
for req in reqset:
|
||||
for req in requirements:
|
||||
ephem_cache = False
|
||||
if req.constraint:
|
||||
continue
|
||||
|
|
|
@ -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.5'
|
||||
__version__ = '0.2.6'
|
||||
|
||||
class DistlibException(Exception):
|
||||
pass
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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."""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -1025,20 +1030,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
|
||||
|
|
|
@ -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)
|
||||
|
@ -69,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 == '':
|
||||
|
@ -175,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]
|
||||
|
@ -241,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)
|
||||
|
@ -250,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:
|
||||
|
@ -272,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
|
||||
|
@ -291,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:
|
||||
|
@ -352,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)
|
||||
|
@ -390,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
|
||||
|
@ -730,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:
|
||||
|
|
|
@ -119,9 +119,12 @@ def interpret(marker, execution_context=None):
|
|||
:param execution_context: The context used for name lookup.
|
||||
:type execution_context: mapping
|
||||
"""
|
||||
expr, rest = parse_marker(marker)
|
||||
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: %s' % rest)
|
||||
raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest))
|
||||
context = dict(DEFAULT_CONTEXT)
|
||||
if execution_context:
|
||||
context.update(execution_context)
|
||||
|
|
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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.
|
||||
#
|
||||
"""
|
||||
|
|
Binary file not shown.
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.
|
||||
#
|
||||
|
@ -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'
|
||||
|
|
|
@ -2028,51 +2028,121 @@ def find_on_path(importer, path_item, only=False):
|
|||
path_item, os.path.join(path_item, 'EGG-INFO')
|
||||
)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
entries = os.listdir(path_item)
|
||||
except (PermissionError, NotADirectoryError):
|
||||
return
|
||||
except OSError as e:
|
||||
# Ignore the directory if does not exist, not a directory or we
|
||||
# don't have permissions
|
||||
if (e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
|
||||
# Python 2 on Windows needs to be handled this way :(
|
||||
or hasattr(e, "winerror") and e.winerror == 267):
|
||||
return
|
||||
return
|
||||
|
||||
entries = safe_listdir(path_item)
|
||||
|
||||
# for performance, before sorting by version,
|
||||
# screen entries for only those that will yield
|
||||
# distributions
|
||||
filtered = (
|
||||
entry
|
||||
for entry in entries
|
||||
if dist_factory(path_item, entry, only)
|
||||
)
|
||||
|
||||
# scan for .egg and .egg-info in directory
|
||||
path_item_entries = _by_version_descending(filtered)
|
||||
for entry in path_item_entries:
|
||||
fullpath = os.path.join(path_item, entry)
|
||||
factory = dist_factory(path_item, entry, only)
|
||||
for dist in factory(fullpath):
|
||||
yield dist
|
||||
|
||||
|
||||
def dist_factory(path_item, entry, only):
|
||||
"""
|
||||
Return a dist_factory for a path_item and entry
|
||||
"""
|
||||
lower = entry.lower()
|
||||
is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info')))
|
||||
return (
|
||||
distributions_from_metadata
|
||||
if is_meta else
|
||||
find_distributions
|
||||
if not only and _is_egg_path(entry) else
|
||||
resolve_egg_link
|
||||
if not only and lower.endswith('.egg-link') else
|
||||
NoDists()
|
||||
)
|
||||
|
||||
|
||||
class NoDists:
|
||||
"""
|
||||
>>> bool(NoDists())
|
||||
False
|
||||
|
||||
>>> list(NoDists()('anything'))
|
||||
[]
|
||||
"""
|
||||
def __bool__(self):
|
||||
return False
|
||||
if six.PY2:
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __call__(self, fullpath):
|
||||
return iter(())
|
||||
|
||||
|
||||
def safe_listdir(path):
|
||||
"""
|
||||
Attempt to list contents of path, but suppress some exceptions.
|
||||
"""
|
||||
try:
|
||||
return os.listdir(path)
|
||||
except (PermissionError, NotADirectoryError):
|
||||
pass
|
||||
except OSError as e:
|
||||
# Ignore the directory if does not exist, not a directory or
|
||||
# permission denied
|
||||
ignorable = (
|
||||
e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
|
||||
# Python 2 on Windows needs to be handled this way :(
|
||||
or getattr(e, "winerror", None) == 267
|
||||
)
|
||||
if not ignorable:
|
||||
raise
|
||||
# scan for .egg and .egg-info in directory
|
||||
path_item_entries = _by_version_descending(entries)
|
||||
for entry in path_item_entries:
|
||||
lower = entry.lower()
|
||||
if lower.endswith('.egg-info') or lower.endswith('.dist-info'):
|
||||
fullpath = os.path.join(path_item, entry)
|
||||
if os.path.isdir(fullpath):
|
||||
# egg-info directory, allow getting metadata
|
||||
if len(os.listdir(fullpath)) == 0:
|
||||
# Empty egg directory, skip.
|
||||
continue
|
||||
metadata = PathMetadata(path_item, fullpath)
|
||||
else:
|
||||
metadata = FileMetadata(fullpath)
|
||||
yield Distribution.from_location(
|
||||
path_item, entry, metadata, precedence=DEVELOP_DIST
|
||||
)
|
||||
elif not only and _is_egg_path(entry):
|
||||
dists = find_distributions(os.path.join(path_item, entry))
|
||||
for dist in dists:
|
||||
yield dist
|
||||
elif not only and lower.endswith('.egg-link'):
|
||||
with open(os.path.join(path_item, entry)) as entry_file:
|
||||
entry_lines = entry_file.readlines()
|
||||
for line in entry_lines:
|
||||
if not line.strip():
|
||||
continue
|
||||
path = os.path.join(path_item, line.rstrip())
|
||||
dists = find_distributions(path)
|
||||
for item in dists:
|
||||
yield item
|
||||
break
|
||||
return ()
|
||||
|
||||
|
||||
def distributions_from_metadata(path):
|
||||
root = os.path.dirname(path)
|
||||
if os.path.isdir(path):
|
||||
if len(os.listdir(path)) == 0:
|
||||
# empty metadata dir; skip
|
||||
return
|
||||
metadata = PathMetadata(root, path)
|
||||
else:
|
||||
metadata = FileMetadata(path)
|
||||
entry = os.path.basename(path)
|
||||
yield Distribution.from_location(
|
||||
root, entry, metadata, precedence=DEVELOP_DIST,
|
||||
)
|
||||
|
||||
|
||||
def non_empty_lines(path):
|
||||
"""
|
||||
Yield non-empty lines from file at path
|
||||
"""
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
yield line
|
||||
|
||||
|
||||
def resolve_egg_link(path):
|
||||
"""
|
||||
Given a path to an .egg-link, resolve distributions
|
||||
present in the referenced path.
|
||||
"""
|
||||
referenced_paths = non_empty_lines(path)
|
||||
resolved_paths = (
|
||||
os.path.join(os.path.dirname(path), ref)
|
||||
for ref in referenced_paths
|
||||
)
|
||||
dist_groups = map(find_distributions, resolved_paths)
|
||||
return next(dist_groups, ())
|
||||
|
||||
|
||||
register_finder(pkgutil.ImpImporter, find_on_path)
|
||||
|
@ -2250,9 +2320,7 @@ def _is_egg_path(path):
|
|||
"""
|
||||
Determine if given path appears to be an egg.
|
||||
"""
|
||||
return (
|
||||
path.lower().endswith('.egg')
|
||||
)
|
||||
return path.lower().endswith('.egg')
|
||||
|
||||
|
||||
def _is_unpacked_egg(path):
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||
# Copyright (c) 2010-2017 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -20,6 +18,8 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
|
@ -29,7 +29,7 @@ import sys
|
|||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.10.0"
|
||||
__version__ = "1.11.0"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
|
@ -241,6 +241,7 @@ _moved_attributes = [
|
|||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
|
||||
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
|
||||
MovedAttribute("getoutput", "commands", "subprocess"),
|
||||
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
|
@ -262,10 +263,11 @@ _moved_attributes = [
|
|||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
|
||||
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
||||
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
|
||||
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
|
@ -337,10 +339,12 @@ _urllib_parse_moved_attributes = [
|
|||
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splittag", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splituser", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
|
||||
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
|
||||
|
@ -416,6 +420,8 @@ _urllib_request_moved_attributes = [
|
|||
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
||||
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
|
||||
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
|
||||
]
|
||||
for attr in _urllib_request_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
||||
|
@ -679,11 +685,15 @@ if PY3:
|
|||
exec_ = getattr(moves.builtins, "exec")
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
try:
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
finally:
|
||||
value = None
|
||||
tb = None
|
||||
|
||||
else:
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
|
@ -699,19 +709,28 @@ else:
|
|||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
try:
|
||||
raise tp, value, tb
|
||||
finally:
|
||||
tb = None
|
||||
""")
|
||||
|
||||
|
||||
if sys.version_info[:2] == (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
if from_value is None:
|
||||
raise value
|
||||
raise value from from_value
|
||||
try:
|
||||
if from_value is None:
|
||||
raise value
|
||||
raise value from from_value
|
||||
finally:
|
||||
value = None
|
||||
""")
|
||||
elif sys.version_info[:2] > (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
raise value from from_value
|
||||
try:
|
||||
raise value from from_value
|
||||
finally:
|
||||
value = None
|
||||
""")
|
||||
else:
|
||||
def raise_from(value, from_value):
|
||||
|
@ -802,10 +821,14 @@ def with_metaclass(meta, *bases):
|
|||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta):
|
||||
class metaclass(type):
|
||||
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
|
||||
@classmethod
|
||||
def __prepare__(cls, name, this_bases):
|
||||
return meta.__prepare__(name, bases)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
appdirs==1.4.3
|
||||
distlib==0.2.5
|
||||
distlib==0.2.6
|
||||
distro==1.0.4
|
||||
html5lib==1.0b10
|
||||
six==1.10.0
|
||||
six==1.11.0
|
||||
colorama==0.3.9
|
||||
CacheControl==0.12.3
|
||||
msgpack-python==0.4.8
|
||||
|
@ -18,5 +18,5 @@ requests==2.18.4
|
|||
idna==2.6
|
||||
urllib3==1.22
|
||||
certifi==2017.7.27.1
|
||||
setuptools==36.4.0
|
||||
setuptools==36.6.0
|
||||
webencodings==0.5.1
|
||||
|
|
|
@ -498,6 +498,78 @@ def test_freeze_with_requirement_option_multiple(script):
|
|||
assert result.stdout.count("--index-url http://ignore") == 1
|
||||
|
||||
|
||||
def test_freeze_with_requirement_option_package_repeated_one_file(script):
|
||||
"""
|
||||
Test freezing with single requirements file that contains a package
|
||||
multiple times
|
||||
"""
|
||||
script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
|
||||
simple2
|
||||
simple2
|
||||
NoExist
|
||||
""") + _freeze_req_opts)
|
||||
result = script.pip_install_local('simple2==1.0')
|
||||
result = script.pip_install_local('meta')
|
||||
result = script.pip(
|
||||
'freeze', '--requirement', 'hint1.txt',
|
||||
expect_stderr=True,
|
||||
)
|
||||
expected_out = textwrap.dedent("""\
|
||||
simple2==1.0
|
||||
""")
|
||||
expected_out += _freeze_req_opts
|
||||
expected_out += "## The following requirements were added by pip freeze:"
|
||||
expected_out += '\n' + textwrap.dedent("""\
|
||||
...meta==1.0...
|
||||
""")
|
||||
_check_output(result.stdout, expected_out)
|
||||
err1 = ("Requirement file [hint1.txt] contains NoExist, "
|
||||
"but that package is not installed\n")
|
||||
err2 = "Requirement simple2 included multiple times [hint1.txt]\n"
|
||||
assert err1 in result.stderr
|
||||
assert err2 in result.stderr
|
||||
# there shouldn't be any other 'is not installed' warnings
|
||||
assert result.stderr.count('is not installed') == 1
|
||||
|
||||
|
||||
def test_freeze_with_requirement_option_package_repeated_multi_file(script):
|
||||
"""
|
||||
Test freezing with multiple requirements file that contain a package
|
||||
"""
|
||||
script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
|
||||
simple
|
||||
""") + _freeze_req_opts)
|
||||
script.scratch_path.join('hint2.txt').write(textwrap.dedent("""\
|
||||
simple
|
||||
NoExist
|
||||
""") + _freeze_req_opts)
|
||||
result = script.pip_install_local('simple==1.0')
|
||||
result = script.pip_install_local('meta')
|
||||
result = script.pip(
|
||||
'freeze', '--requirement', 'hint1.txt',
|
||||
'--requirement', 'hint2.txt',
|
||||
expect_stderr=True,
|
||||
)
|
||||
expected_out = textwrap.dedent("""\
|
||||
simple==1.0
|
||||
""")
|
||||
expected_out += _freeze_req_opts
|
||||
expected_out += "## The following requirements were added by pip freeze:"
|
||||
expected_out += '\n' + textwrap.dedent("""\
|
||||
...meta==1.0...
|
||||
""")
|
||||
_check_output(result.stdout, expected_out)
|
||||
|
||||
err1 = ("Requirement file [hint2.txt] contains NoExist, but that "
|
||||
"package is not installed\n")
|
||||
err2 = ("Requirement simple included multiple times "
|
||||
"[hint1.txt, hint2.txt]\n")
|
||||
assert err1 in result.stderr
|
||||
assert err2 in result.stderr
|
||||
# there shouldn't be any other 'is not installed' warnings
|
||||
assert result.stderr.count('is not installed') == 1
|
||||
|
||||
|
||||
@pytest.mark.network
|
||||
def test_freeze_user(script, virtualenv, data):
|
||||
"""
|
||||
|
|
|
@ -1258,3 +1258,25 @@ def test_installing_scripts_on_path_does_not_print_warning(script):
|
|||
result = script.pip_install_local("script_wheel1")
|
||||
assert "Successfully installed script-wheel1" in result.stdout, str(result)
|
||||
assert "--no-warn-script-location" not in result.stderr
|
||||
|
||||
|
||||
def test_installed_files_recorded_in_deterministic_order(script, data):
|
||||
"""
|
||||
Ensure that we record the files installed by a package in a deterministic
|
||||
order, to make installs reproducible.
|
||||
"""
|
||||
to_install = data.packages.join("FSPkg")
|
||||
result = script.pip('install', to_install, expect_error=False)
|
||||
fspkg_folder = script.site_packages / 'fspkg'
|
||||
egg_info = 'FSPkg-0.1.dev0-py%s.egg-info' % pyversion
|
||||
installed_files_path = (
|
||||
script.site_packages / egg_info / 'installed-files.txt'
|
||||
)
|
||||
assert fspkg_folder in result.files_created, str(result.stdout)
|
||||
assert installed_files_path in result.files_created, str(result)
|
||||
|
||||
installed_files_path = result.files_created[installed_files_path].full
|
||||
installed_files_lines = [
|
||||
p for p in Path(installed_files_path).read_text().split('\n') if p
|
||||
]
|
||||
assert installed_files_lines == sorted(installed_files_lines)
|
||||
|
|
|
@ -184,7 +184,7 @@ def test_git_with_tag_name_and_update(script, tmpdir):
|
|||
result = script.pip(
|
||||
'install', '-e', '%s#egg=pip-test-package' %
|
||||
local_checkout(
|
||||
'git+http://github.com/pypa/pip-test-package.git',
|
||||
'git+https://github.com/pypa/pip-test-package.git',
|
||||
tmpdir.join("cache"),
|
||||
),
|
||||
expect_error=True,
|
||||
|
@ -194,7 +194,7 @@ def test_git_with_tag_name_and_update(script, tmpdir):
|
|||
'install', '--global-option=--version', '-e',
|
||||
'%s@0.1.2#egg=pip-test-package' %
|
||||
local_checkout(
|
||||
'git+http://github.com/pypa/pip-test-package.git',
|
||||
'git+https://github.com/pypa/pip-test-package.git',
|
||||
tmpdir.join("cache"),
|
||||
),
|
||||
expect_error=True,
|
||||
|
@ -211,7 +211,7 @@ def test_git_branch_should_not_be_changed(script, tmpdir):
|
|||
script.pip(
|
||||
'install', '-e', '%s#egg=pip-test-package' %
|
||||
local_checkout(
|
||||
'git+http://github.com/pypa/pip-test-package.git',
|
||||
'git+https://github.com/pypa/pip-test-package.git',
|
||||
tmpdir.join("cache"),
|
||||
),
|
||||
expect_error=True,
|
||||
|
@ -229,7 +229,7 @@ def test_git_with_non_editable_unpacking(script, tmpdir):
|
|||
result = script.pip(
|
||||
'install', '--global-option=--version',
|
||||
local_checkout(
|
||||
'git+http://github.com/pypa/pip-test-package.git@0.1.2'
|
||||
'git+https://github.com/pypa/pip-test-package.git@0.1.2'
|
||||
'#egg=pip-test-package',
|
||||
tmpdir.join("cache")
|
||||
),
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
Test specific for the --no-color option
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
import subprocess as sp
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
reason="does not run on windows")
|
||||
def test_no_color(script):
|
||||
|
||||
"""
|
||||
Test uninstalling an existing package - should out put red error
|
||||
|
||||
We must use subprocess with the script command, since redirection
|
||||
in unix platform causes text coloring to disapper. Thus, we can't
|
||||
use the testing infrastructure that other options has.
|
||||
"""
|
||||
|
||||
sp.Popen("script --flush --quiet --return /tmp/colored-output.txt"
|
||||
" --command \"pip uninstall noSuchPackage\"", shell=True,
|
||||
stdout=sp.PIPE, stderr=sp.PIPE).communicate()
|
||||
|
||||
with open("/tmp/colored-output.txt", "r") as result:
|
||||
assert "\x1b" in result.read()
|
||||
|
||||
os.unlink("/tmp/colored-output.txt")
|
||||
|
||||
sp.Popen("script --flush --quiet --return /tmp/no-color-output.txt"
|
||||
" --command \"pip --no-color uninstall noSuchPackage\"",
|
||||
shell=True,
|
||||
stdout=sp.PIPE, stderr=sp.PIPE).communicate()
|
||||
|
||||
with open("/tmp/no-color-output.txt", "r") as result:
|
||||
assert "\x1b" not in result.read()
|
||||
|
||||
os.unlink("/tmp/no-color-output.txt")
|
|
@ -100,6 +100,39 @@ def test_uninstall_easy_install_after_import(script):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.network
|
||||
def test_uninstall_trailing_newline(script):
|
||||
"""
|
||||
Uninstall behaves appropriately if easy-install.pth
|
||||
lacks a trailing newline
|
||||
|
||||
"""
|
||||
script.run('easy_install', 'INITools==0.2', expect_stderr=True)
|
||||
script.run('easy_install', 'PyLogo', expect_stderr=True)
|
||||
easy_install_pth = script.site_packages_path / 'easy-install.pth'
|
||||
|
||||
# trim trailing newline from easy-install.pth
|
||||
with open(easy_install_pth) as f:
|
||||
pth_before = f.read()
|
||||
|
||||
with open(easy_install_pth, 'w') as f:
|
||||
f.write(pth_before.rstrip())
|
||||
|
||||
# uninstall initools
|
||||
script.pip('uninstall', 'INITools', '-y')
|
||||
with open(easy_install_pth) as f:
|
||||
pth_after = f.read()
|
||||
|
||||
# verify that only initools is removed
|
||||
before_without_initools = [
|
||||
line for line in pth_before.splitlines()
|
||||
if 'initools' not in line.lower()
|
||||
]
|
||||
lines_after = pth_after.splitlines()
|
||||
|
||||
assert lines_after == before_without_initools
|
||||
|
||||
|
||||
@pytest.mark.network
|
||||
def test_uninstall_namespace_package(script):
|
||||
"""
|
||||
|
@ -468,3 +501,25 @@ def test_uninstall_editable_and_pip_install(script, data):
|
|||
) in uninstall2.files_deleted, list(uninstall2.files_deleted.keys())
|
||||
list_result2 = script.pip('list', '--format=json')
|
||||
assert "FSPkg" not in {p["name"] for p in json.loads(list_result2.stdout)}
|
||||
|
||||
|
||||
def test_uninstall_ignores_missing_packages(script, data):
|
||||
"""Uninstall of a non existent package prints a warning and exits cleanly
|
||||
"""
|
||||
result = script.pip(
|
||||
'uninstall', '-y', 'non-existent-pkg', expect_stderr=True,
|
||||
)
|
||||
|
||||
assert "Skipping non-existent-pkg as it is not installed." in result.stderr
|
||||
assert result.returncode == 0, "Expected clean exit"
|
||||
|
||||
|
||||
def test_uninstall_ignores_missing_packages_and_uninstalls_rest(script, data):
|
||||
script.pip_install_local('simple')
|
||||
result = script.pip(
|
||||
'uninstall', '-y', 'non-existent-pkg', 'simple', expect_stderr=True,
|
||||
)
|
||||
|
||||
assert "Skipping non-existent-pkg as it is not installed." in result.stderr
|
||||
assert "Successfully uninstalled simple" in result.stdout
|
||||
assert result.returncode == 0, "Expected clean exit"
|
||||
|
|
|
@ -364,10 +364,10 @@ class TestWheelBuilder(object):
|
|||
with patch('pip._internal.wheel.WheelBuilder._build_one') \
|
||||
as mock_build_one:
|
||||
wheel_req = Mock(is_wheel=True, editable=False, constraint=False)
|
||||
reqset = Mock(requirements=Mock(values=lambda: [wheel_req]),
|
||||
wheel_download_dir='/wheel/dir')
|
||||
wb = wheel.WheelBuilder(reqset, Mock(), Mock(), wheel_cache=None)
|
||||
wb.build(Mock())
|
||||
wb = wheel.WheelBuilder(
|
||||
finder=Mock(), preparer=Mock(), wheel_cache=None,
|
||||
)
|
||||
wb.build([wheel_req], session=Mock())
|
||||
assert "due to already being wheel" in caplog.text
|
||||
assert mock_build_one.mock_calls == []
|
||||
|
||||
|
|
12
tox.ini
12
tox.ini
|
@ -4,7 +4,7 @@ envlist =
|
|||
py27, py33, py34, py35, py36, py37, pypy
|
||||
|
||||
[testenv]
|
||||
passenv = GIT_SSL_CAINFO
|
||||
passenv = CI GIT_SSL_CAINFO
|
||||
setenv =
|
||||
# This is required in order to get UTF-8 output inside of the subprocesses
|
||||
# that our tests use.
|
||||
|
@ -14,12 +14,12 @@ commands = py.test --timeout 300 []
|
|||
install_command = python -m pip install {opts} {packages}
|
||||
usedevelop = True
|
||||
|
||||
[testenv:docs]
|
||||
deps =
|
||||
sphinx == 1.6.1
|
||||
git+https://github.com/python/python-docs-theme.git#egg=python-docs-theme
|
||||
git+https://github.com/pypa/pypa-docs-theme.git#egg=pypa-docs-theme
|
||||
[testenv:coverage-py3]
|
||||
basepython = python3
|
||||
commands = py.test --timeout 300 --cov=pip --cov-report=term-missing --cov-report=xml --cov-report=html tests/unit {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
deps = -r{toxinidir}/docs-requirements.txt
|
||||
basepython = python2.7
|
||||
commands =
|
||||
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/build/html
|
||||
|
|
Loading…
Reference in New Issue