mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
merge latest master
This commit is contained in:
commit
2d3cd4de86
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -27,13 +27,18 @@ nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
tests/data/common_wheels/
|
tests/data/common_wheels/
|
||||||
|
pip-wheel-metadata
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
*~
|
*~
|
||||||
.*.sw?
|
.*.sw?
|
||||||
|
.env/
|
||||||
|
|
||||||
# For IntelliJ IDEs (basically PyCharm)
|
# For IntelliJ IDEs (basically PyCharm)
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# For Visual Studio Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# Scratch Pad for experiments
|
# Scratch Pad for experiments
|
||||||
.scratch/
|
.scratch/
|
||||||
|
|
|
@ -226,6 +226,7 @@ Joseph Long <jdl@fastmail.fm>
|
||||||
Josh Bronson <jabronson@gmail.com>
|
Josh Bronson <jabronson@gmail.com>
|
||||||
Josh Hansen <josh@skwash.net>
|
Josh Hansen <josh@skwash.net>
|
||||||
Josh Schneier <josh.schneier@gmail.com>
|
Josh Schneier <josh.schneier@gmail.com>
|
||||||
|
Julian Berman <Julian@GrayVines.com>
|
||||||
Julien Demoor <julien@jdemoor.com>
|
Julien Demoor <julien@jdemoor.com>
|
||||||
jwg4 <jack.grahl@yahoo.co.uk>
|
jwg4 <jack.grahl@yahoo.co.uk>
|
||||||
Jyrki Pulliainen <jyrki@spotify.com>
|
Jyrki Pulliainen <jyrki@spotify.com>
|
||||||
|
@ -306,6 +307,7 @@ Nathaniel J. Smith <njs@pobox.com>
|
||||||
Nehal J Wani <nehaljw.kkd1@gmail.com>
|
Nehal J Wani <nehaljw.kkd1@gmail.com>
|
||||||
Nick Coghlan <ncoghlan@gmail.com>
|
Nick Coghlan <ncoghlan@gmail.com>
|
||||||
Nick Stenning <nick@whiteink.com>
|
Nick Stenning <nick@whiteink.com>
|
||||||
|
Nick Timkovich <prometheus235@gmail.com>
|
||||||
Nikhil Benesch <nikhil.benesch@gmail.com>
|
Nikhil Benesch <nikhil.benesch@gmail.com>
|
||||||
Nitesh Sharma <nbsharma@outlook.com>
|
Nitesh Sharma <nbsharma@outlook.com>
|
||||||
Nowell Strite <nowell@strite.org>
|
Nowell Strite <nowell@strite.org>
|
||||||
|
@ -340,6 +342,7 @@ Phaneendra Chiruvella <hi@pcx.io>
|
||||||
Phil Freo <phil@philfreo.com>
|
Phil Freo <phil@philfreo.com>
|
||||||
Phil Pennock <phil@pennock-tech.com>
|
Phil Pennock <phil@pennock-tech.com>
|
||||||
Phil Whelan <phil123@gmail.com>
|
Phil Whelan <phil123@gmail.com>
|
||||||
|
Philip Jägenstedt <philip@foolip.org>
|
||||||
Philip Molloy <pamolloy@users.noreply.github.com>
|
Philip Molloy <pamolloy@users.noreply.github.com>
|
||||||
Philippe Ombredanne <pombredanne@gmail.com>
|
Philippe Ombredanne <pombredanne@gmail.com>
|
||||||
Pi Delport <pjdelport@gmail.com>
|
Pi Delport <pjdelport@gmail.com>
|
||||||
|
|
40
NEWS.rst
40
NEWS.rst
|
@ -7,6 +7,46 @@
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
19.0.3 (2019-02-20)
|
||||||
|
===================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- Fix an ``IndexError`` crash when a legacy build of a wheel fails. (`#6252 <https://github.com/pypa/pip/issues/6252>`_)
|
||||||
|
- Fix a regression introduced in 19.0.2 where the filename in a RECORD file
|
||||||
|
of an installed file would not be updated when installing a wheel. (`#6266 <https://github.com/pypa/pip/issues/6266>`_)
|
||||||
|
|
||||||
|
|
||||||
|
19.0.2 (2019-02-09)
|
||||||
|
===================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- Fix a crash where PEP 517-based builds using ``--no-cache-dir`` would fail in
|
||||||
|
some circumstances with an ``AssertionError`` due to not finalizing a build
|
||||||
|
directory internally. (`#6197 <https://github.com/pypa/pip/issues/6197>`_)
|
||||||
|
- Provide a better error message if attempting an editable install of a
|
||||||
|
directory with a ``pyproject.toml`` but no ``setup.py``. (`#6170 <https://github.com/pypa/pip/issues/6170>`_)
|
||||||
|
- The implicit default backend used for projects that provide a ``pyproject.toml``
|
||||||
|
file without explicitly specifying ``build-backend`` now behaves more like direct
|
||||||
|
execution of ``setup.py``, and hence should restore compatibility with projects
|
||||||
|
that were unable to be installed with ``pip`` 19.0. This raised the minimum
|
||||||
|
required version of ``setuptools`` for such builds to 40.8.0. (`#6163 <https://github.com/pypa/pip/issues/6163>`_)
|
||||||
|
- Allow ``RECORD`` lines with more than three elements, and display a warning. (`#6165 <https://github.com/pypa/pip/issues/6165>`_)
|
||||||
|
- ``AdjacentTempDirectory`` fails on unwritable directory instead of locking up the uninstall command. (`#6169 <https://github.com/pypa/pip/issues/6169>`_)
|
||||||
|
- Make failed uninstalls roll back more reliably and better at avoiding naming conflicts. (`#6194 <https://github.com/pypa/pip/issues/6194>`_)
|
||||||
|
- Ensure the correct wheel file is copied when building PEP 517 distribution is built. (`#6196 <https://github.com/pypa/pip/issues/6196>`_)
|
||||||
|
- The Python 2 end of life warning now only shows on CPython, which is the
|
||||||
|
implementation that has announced end of life plans. (`#6207 <https://github.com/pypa/pip/issues/6207>`_)
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- Re-write README and documentation index (`#5815 <https://github.com/pypa/pip/issues/5815>`_)
|
||||||
|
|
||||||
|
|
||||||
19.0.1 (2019-01-23)
|
19.0.1 (2019-01-23)
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
54
README.rst
54
README.rst
|
@ -1,52 +1,46 @@
|
||||||
pip
|
pip - The Python Package Installer
|
||||||
===
|
==================================
|
||||||
|
|
||||||
The `PyPA recommended`_ tool for installing Python packages.
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/pip.svg
|
.. image:: https://img.shields.io/pypi/v/pip.svg
|
||||||
:target: https://pypi.org/project/pip/
|
:target: https://pypi.org/project/pip/
|
||||||
|
|
||||||
.. image:: https://img.shields.io/travis/pypa/pip/master.svg?label=travis-ci
|
|
||||||
:target: https://travis-ci.org/pypa/pip
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/appveyor/ci/pypa/pip.svg?label=appveyor-ci
|
|
||||||
:target: https://ci.appveyor.com/project/pypa/pip/history
|
|
||||||
|
|
||||||
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
|
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
|
||||||
:target: https://pip.pypa.io/en/latest
|
:target: https://pip.pypa.io/en/latest
|
||||||
|
|
||||||
.. image:: https://dev.azure.com/pypa/pip/_apis/build/status/Linux?branchName=master&label=Windows
|
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
|
||||||
:target: https://dev.azure.com/pypa/pip/_build/latest?definitionId=6
|
|
||||||
|
|
||||||
.. image:: https://dev.azure.com/pypa/pip/_apis/build/status/macOS?branchName=master&label=macOS
|
Please take a look at our documentation for how to install and use pip:
|
||||||
:target: https://dev.azure.com/pypa/pip/_build/latest?definitionId=7
|
|
||||||
|
|
||||||
.. image:: https://dev.azure.com/pypa/pip/_apis/build/status/Linux?branchName=master&label=Linux
|
|
||||||
:target: https://dev.azure.com/pypa/pip/_build/latest?definitionId=4
|
|
||||||
|
|
||||||
* `Installation`_
|
* `Installation`_
|
||||||
* `Documentation`_
|
* `Usage`_
|
||||||
* `Changelog`_
|
* `Release notes`_
|
||||||
* `GitHub Page`_
|
|
||||||
* `Issue Tracking`_
|
If you find bugs, need help, or want to talk to the developers please use our mailing lists or chat rooms:
|
||||||
* `User mailing list`_
|
|
||||||
* `Dev mailing list`_
|
* `Issue tracking`_
|
||||||
|
* `Discourse channel`_
|
||||||
* `User IRC`_
|
* `User IRC`_
|
||||||
|
|
||||||
|
If you want to get involved head over to GitHub to get the source code and feel free to jump on the developer mailing lists and chat rooms:
|
||||||
|
|
||||||
|
* `GitHub page`_
|
||||||
|
* `Dev mailing list`_
|
||||||
* `Dev IRC`_
|
* `Dev IRC`_
|
||||||
|
|
||||||
Code of Conduct
|
Code of Conduct
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Everyone interacting in the pip project's codebases, issue trackers, chat
|
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/
|
.. _package installer: https://packaging.python.org/en/latest/current/
|
||||||
|
.. _Python Package Index: https://pypi.org
|
||||||
.. _Installation: https://pip.pypa.io/en/stable/installing.html
|
.. _Installation: https://pip.pypa.io/en/stable/installing.html
|
||||||
.. _Documentation: https://pip.pypa.io/en/stable/
|
.. _Usage: https://pip.pypa.io/en/stable/
|
||||||
.. _Changelog: https://pip.pypa.io/en/stable/news.html
|
.. _Release notes: https://pip.pypa.io/en/stable/news.html
|
||||||
.. _GitHub Page: https://github.com/pypa/pip
|
.. _GitHub page: https://github.com/pypa/pip
|
||||||
.. _Issue Tracking: https://github.com/pypa/pip/issues
|
.. _Issue tracking: https://github.com/pypa/pip/issues
|
||||||
.. _User mailing list: https://groups.google.com/forum/#!forum/python-virtualenv
|
.. _Discourse channel: https://discuss.python.org/c/packaging
|
||||||
.. _Dev mailing list: https://groups.google.com/forum/#!forum/pypa-dev
|
.. _Dev mailing list: https://groups.google.com/forum/#!forum/pypa-dev
|
||||||
.. _User IRC: https://webchat.freenode.net/?channels=%23pypa
|
.. _User IRC: https://webchat.freenode.net/?channels=%23pypa
|
||||||
.. _Dev IRC: https://webchat.freenode.net/?channels=%23pypa-dev
|
.. _Dev IRC: https://webchat.freenode.net/?channels=%23pypa-dev
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
pip
|
pip - The Python Package Installer
|
||||||
===
|
==================================
|
||||||
|
|
||||||
`User list <https://groups.google.com/forum/#!forum/python-virtualenv>`_ |
|
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
|
||||||
`Dev list <https://groups.google.com/forum/#!forum/pypa-dev>`_ |
|
|
||||||
`GitHub <https://github.com/pypa/pip>`_ |
|
|
||||||
`PyPI <https://pypi.org/project/pip/>`_ |
|
|
||||||
User IRC: #pypa |
|
|
||||||
Dev IRC: #pypa-dev
|
|
||||||
|
|
||||||
The `PyPA recommended <https://packaging.python.org/en/latest/current/>`_ tool
|
Please take a look at our documentation for how to install and use pip:
|
||||||
for installing Python packages.
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
@ -20,3 +14,34 @@ for installing Python packages.
|
||||||
reference/index
|
reference/index
|
||||||
development/index
|
development/index
|
||||||
news
|
news
|
||||||
|
|
||||||
|
If you find bugs, need help, or want to talk to the developers please use our mailing lists or chat rooms:
|
||||||
|
|
||||||
|
* `Issue tracking`_
|
||||||
|
* `Discourse channel`_
|
||||||
|
* `User IRC`_
|
||||||
|
|
||||||
|
If you want to get involved head over to GitHub to get the source code and feel free to jump on the developer mailing lists and chat rooms:
|
||||||
|
|
||||||
|
* `GitHub page`_
|
||||||
|
* `Dev mailing list`_
|
||||||
|
* `Dev IRC`_
|
||||||
|
|
||||||
|
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`_.
|
||||||
|
|
||||||
|
.. _package installer: https://packaging.python.org/en/latest/current/
|
||||||
|
.. _Python Package Index: https://pypi.org
|
||||||
|
.. _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
|
||||||
|
.. _Discourse channel: https://discuss.python.org/c/packaging
|
||||||
|
.. _Dev mailing list: https://groups.google.com/forum/#!forum/pypa-dev
|
||||||
|
.. _User IRC: https://webchat.freenode.net/?channels=%23pypa
|
||||||
|
.. _Dev IRC: https://webchat.freenode.net/?channels=%23pypa-dev
|
||||||
|
.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
|
||||||
|
|
|
@ -145,26 +145,28 @@ explicitly manage the build environment. For such workflows, build isolation
|
||||||
can be problematic. If this is the case, pip provides a
|
can be problematic. If this is the case, pip provides a
|
||||||
``--no-build-isolation`` flag to disable build isolation. Users supplying this
|
``--no-build-isolation`` flag to disable build isolation. Users supplying this
|
||||||
flag are responsible for ensuring the build environment is managed
|
flag are responsible for ensuring the build environment is managed
|
||||||
appropriately.
|
appropriately (including ensuring that all required build dependencies are
|
||||||
|
installed).
|
||||||
|
|
||||||
By default, pip will continue to use the legacy (``setuptools`` based) build
|
By default, pip will continue to use the legacy (direct ``setup.py`` execution
|
||||||
processing for projects that do not have a ``pyproject.toml`` file. Projects
|
based) build processing for projects that do not have a ``pyproject.toml`` file.
|
||||||
with a ``pyproject.toml`` file will use a :pep:`517` backend. Projects with
|
Projects with a ``pyproject.toml`` file will use a :pep:`517` backend. Projects
|
||||||
a ``pyproject.toml`` file, but which don't have a ``build-system`` section,
|
with a ``pyproject.toml`` file, but which don't have a ``build-system`` section,
|
||||||
will be assumed to have the following backend settings::
|
will be assumed to have the following backend settings::
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=40.2.0", "wheel"]
|
requires = ["setuptools>=40.8.0", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta:__legacy__"
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
``setuptools`` 40.2.0 is the first version of setuptools with full
|
``setuptools`` 40.8.0 is the first version of setuptools that offers a
|
||||||
:pep:`517` support.
|
:pep:`517` backend that closely mimics directly executing ``setup.py``.
|
||||||
|
|
||||||
If a project has ``[build-system]``, but no ``build-backend``, pip will use
|
If a project has ``[build-system]``, but no ``build-backend``, pip will also use
|
||||||
``setuptools.build_meta``, but will assume the project requirements include
|
``setuptools.build_meta:__legacy__``, but will expect the project requirements
|
||||||
``setuptools>=40.2.0`` and ``wheel`` (and will report an error if not).
|
to include ``setuptools`` and ``wheel`` (and will report an error if the
|
||||||
|
installed version of ``setuptools`` is not recent enough).
|
||||||
|
|
||||||
If a user wants to explicitly request :pep:`517` handling even though a project
|
If a user wants to explicitly request :pep:`517` handling even though a project
|
||||||
doesn't have a ``pyproject.toml`` file, this can be done using the
|
doesn't have a ``pyproject.toml`` file, this can be done using the
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Allow ``RECORD`` lines with more than three elements, and display a warning.
|
|
|
@ -20,6 +20,7 @@ exclude =
|
||||||
_vendor,
|
_vendor,
|
||||||
data
|
data
|
||||||
select = E,W,F
|
select = E,W,F
|
||||||
|
ignore = W504
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
follow_imports = silent
|
follow_imports = silent
|
||||||
|
|
|
@ -18,8 +18,8 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
from pip._internal.utils.ui import open_spinner
|
from pip._internal.utils.ui import open_spinner
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Tuple, Set, Iterable, Optional, List # noqa: F401
|
from typing import Tuple, Set, Iterable, Optional, List
|
||||||
from pip._internal.index import PackageFinder # noqa: F401
|
from pip._internal.index import PackageFinder
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ class BuildEnvironment(object):
|
||||||
args.append('--')
|
args.append('--')
|
||||||
args.extend(requirements)
|
args.extend(requirements)
|
||||||
with open_spinner(message) as spinner:
|
with open_spinner(message) as spinner:
|
||||||
call_subprocess(args, show_stdout=False, spinner=spinner)
|
call_subprocess(args, spinner=spinner)
|
||||||
|
|
||||||
|
|
||||||
class NoOpBuildEnvironment(BuildEnvironment):
|
class NoOpBuildEnvironment(BuildEnvironment):
|
||||||
|
|
|
@ -16,8 +16,8 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
from pip._internal.wheel import InvalidWheelFilename, Wheel
|
from pip._internal.wheel import InvalidWheelFilename, Wheel
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional, Set, List, Any # noqa: F401
|
from typing import Optional, Set, List, Any
|
||||||
from pip._internal.index import FormatControl # noqa: F401
|
from pip._internal.index import FormatControl
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
||||||
import logging.config
|
import logging.config
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
@ -36,10 +37,10 @@ from pip._internal.utils.outdated import pip_version_check
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional, List, Tuple, Any # noqa: F401
|
from typing import Optional, List, Tuple, Any
|
||||||
from optparse import Values # noqa: F401
|
from optparse import Values
|
||||||
from pip._internal.cache import WheelCache # noqa: F401
|
from pip._internal.cache import WheelCache
|
||||||
from pip._internal.req.req_set import RequirementSet # noqa: F401
|
from pip._internal.req.req_set import RequirementSet
|
||||||
|
|
||||||
__all__ = ['Command']
|
__all__ = ['Command']
|
||||||
|
|
||||||
|
@ -145,14 +146,16 @@ class Command(object):
|
||||||
gone_in='19.2',
|
gone_in='19.2',
|
||||||
)
|
)
|
||||||
elif sys.version_info[:2] == (2, 7):
|
elif sys.version_info[:2] == (2, 7):
|
||||||
deprecated(
|
message = (
|
||||||
"Python 2.7 will reach the end of its life on January 1st, "
|
"A future version of pip will drop support for Python 2.7."
|
||||||
"2020. Please upgrade your Python as Python 2.7 won't be "
|
|
||||||
"maintained after that date. A future version of pip will "
|
|
||||||
"drop support for Python 2.7.",
|
|
||||||
replacement=None,
|
|
||||||
gone_in=None,
|
|
||||||
)
|
)
|
||||||
|
if platform.python_implementation() == "CPython":
|
||||||
|
message = (
|
||||||
|
"Python 2.7 will reach the end of its life on January "
|
||||||
|
"1st, 2020. Please upgrade your Python as Python 2.7 "
|
||||||
|
"won't be maintained after that date. "
|
||||||
|
) + message
|
||||||
|
deprecated(message, replacement=None, gone_in=None)
|
||||||
|
|
||||||
# TODO: Try to get these passing down from the command?
|
# TODO: Try to get these passing down from the command?
|
||||||
# without resorting to os.environ to hold these.
|
# without resorting to os.environ to hold these.
|
||||||
|
|
|
@ -24,9 +24,9 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
from pip._internal.utils.ui import BAR_TYPES
|
from pip._internal.utils.ui import BAR_TYPES
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Any, Callable, Dict, List, Optional, Union # noqa: F401
|
from typing import Any, Callable, Dict, Optional
|
||||||
from optparse import OptionParser, Values # noqa: F401
|
from optparse import OptionParser, Values
|
||||||
from pip._internal.cli.parser import ConfigOptionParser # noqa: F401
|
from pip._internal.cli.parser import ConfigOptionParser
|
||||||
|
|
||||||
|
|
||||||
def raise_option_error(parser, option, msg):
|
def raise_option_error(parser, option, msg):
|
||||||
|
@ -729,7 +729,7 @@ def _merge_hash(option, opt_str, value, parser):
|
||||||
"""Given a value spelled "algo:digest", append the digest to a list
|
"""Given a value spelled "algo:digest", append the digest to a list
|
||||||
pointed to in a dict by the algo name."""
|
pointed to in a dict by the algo name."""
|
||||||
if not parser.values.hashes:
|
if not parser.values.hashes:
|
||||||
parser.values.hashes = {} # type: ignore
|
parser.values.hashes = {}
|
||||||
try:
|
try:
|
||||||
algo, digest = value.split(':', 1)
|
algo, digest = value.split(':', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
|
@ -17,7 +17,7 @@ from pip._internal.utils.misc import get_prog
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Tuple, List # noqa: F401
|
from typing import Tuple, List
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["create_main_parser", "parse_command"]
|
__all__ = ["create_main_parser", "parse_command"]
|
||||||
|
|
|
@ -20,8 +20,8 @@ from pip._internal.commands.wheel import WheelCommand
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import List, Type # noqa: F401
|
from typing import List, Type
|
||||||
from pip._internal.cli.base_command import Command # noqa: F401
|
from pip._internal.cli.base_command import Command
|
||||||
|
|
||||||
commands_order = [
|
commands_order = [
|
||||||
InstallCommand,
|
InstallCommand,
|
||||||
|
|
|
@ -359,7 +359,7 @@ class InstallCommand(RequirementCommand):
|
||||||
# so we fail here.
|
# so we fail here.
|
||||||
if build_failures:
|
if build_failures:
|
||||||
raise InstallationError(
|
raise InstallationError(
|
||||||
"Could not build wheels for {} which use" +
|
"Could not build wheels for {} which use"
|
||||||
" PEP 517 and cannot be installed directly".format(
|
" PEP 517 and cannot be installed directly".format(
|
||||||
", ".join(r.name for r in build_failures)))
|
", ".join(r.name for r in build_failures)))
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import absolute_import
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from email.parser import FeedParser # type: ignore
|
from email.parser import FeedParser
|
||||||
|
|
||||||
from pip._vendor import pkg_resources
|
from pip._vendor import pkg_resources
|
||||||
from pip._vendor.packaging.utils import canonicalize_name
|
from pip._vendor.packaging.utils import canonicalize_name
|
||||||
|
|
|
@ -29,7 +29,7 @@ from pip._internal.utils.misc import ensure_dir, enum
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Any, Dict, Iterable, List, NewType, Optional, Tuple
|
Any, Dict, Iterable, List, NewType, Optional, Tuple
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ class Configuration(object):
|
||||||
ensure_dir(os.path.dirname(fname))
|
ensure_dir(os.path.dirname(fname))
|
||||||
|
|
||||||
with open(fname, "w") as f:
|
with open(fname, "w") as f:
|
||||||
parser.write(f) # type: ignore
|
parser.write(f)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Private routines
|
# Private routines
|
||||||
|
|
|
@ -48,12 +48,12 @@ from pip._internal.utils.ui import DownloadProgressProvider
|
||||||
from pip._internal.vcs import vcs
|
from pip._internal.vcs import vcs
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Optional, Tuple, Dict, IO, Text, Union
|
Optional, Tuple, Dict, IO, Text, Union
|
||||||
)
|
)
|
||||||
from pip._internal.models.link import Link # noqa: F401
|
from pip._internal.models.link import Link
|
||||||
from pip._internal.utils.hashes import Hashes # noqa: F401
|
from pip._internal.utils.hashes import Hashes
|
||||||
from pip._internal.vcs import AuthInfo # noqa: F401
|
from pip._internal.vcs import AuthInfo
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ssl # noqa
|
import ssl # noqa
|
||||||
|
@ -795,7 +795,7 @@ def _copy_dist_from_dir(link_path, location):
|
||||||
logger.info('Running setup.py sdist for %s', link_path)
|
logger.info('Running setup.py sdist for %s', link_path)
|
||||||
|
|
||||||
with indent_log():
|
with indent_log():
|
||||||
call_subprocess(sdist_args, cwd=link_path, show_stdout=False)
|
call_subprocess(sdist_args, cwd=link_path)
|
||||||
|
|
||||||
# unpack sdist into `location`
|
# unpack sdist into `location`
|
||||||
sdist = os.path.join(location, os.listdir(location)[0])
|
sdist = os.path.join(location, os.listdir(location)[0])
|
||||||
|
|
|
@ -8,8 +8,8 @@ from pip._vendor.six import iteritems
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional # noqa: F401
|
from typing import Optional
|
||||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
|
|
||||||
|
|
||||||
class PipError(Exception):
|
class PipError(Exception):
|
||||||
|
|
|
@ -41,15 +41,15 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
from pip._internal.wheel import Wheel
|
from pip._internal.wheel import Wheel
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from logging import Logger # noqa: F401
|
from logging import Logger
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Tuple, Optional, Any, List, Union, Callable, Set, Sequence,
|
Tuple, Optional, Any, List, Union, Callable, Set, Sequence,
|
||||||
Iterable, MutableMapping
|
Iterable, MutableMapping
|
||||||
)
|
)
|
||||||
from pip._vendor.packaging.version import _BaseVersion # noqa: F401
|
from pip._vendor.packaging.version import _BaseVersion
|
||||||
from pip._vendor.requests import Response # noqa: F401
|
from pip._vendor.requests import Response
|
||||||
from pip._internal.req import InstallRequirement # noqa: F401
|
from pip._internal.req import InstallRequirement
|
||||||
from pip._internal.download import PipSession # noqa: F401
|
from pip._internal.download import PipSession
|
||||||
|
|
||||||
SecureOrigin = Tuple[str, str, Optional[str]]
|
SecureOrigin = Tuple[str, str, Optional[str]]
|
||||||
BuildTag = Tuple[Any, ...] # either emply tuple or Tuple[int, str]
|
BuildTag = Tuple[Any, ...] # either emply tuple or Tuple[int, str]
|
||||||
|
@ -227,7 +227,7 @@ def _get_html_page(link, session=None):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = _get_html_response(url, session=session)
|
resp = _get_html_response(url, session=session)
|
||||||
except _NotHTTP as exc:
|
except _NotHTTP:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Skipping page %s because it looks like an archive, and cannot '
|
'Skipping page %s because it looks like an archive, and cannot '
|
||||||
'be checked by HEAD.', link,
|
'be checked by HEAD.', link,
|
||||||
|
|
|
@ -15,7 +15,7 @@ from pip._internal.utils.compat import WINDOWS, expanduser
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Any, Union, Dict, List, Optional # noqa: F401
|
from typing import Any, Union, Dict, List, Optional
|
||||||
|
|
||||||
|
|
||||||
# Application Directories
|
# Application Directories
|
||||||
|
|
|
@ -4,9 +4,9 @@ from pip._internal.utils.models import KeyBasedCompareMixin
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from pip._vendor.packaging.version import _BaseVersion # noqa: F401
|
from pip._vendor.packaging.version import _BaseVersion
|
||||||
from pip._internal.models.link import Link # noqa: F401
|
from pip._internal.models.link import Link
|
||||||
from typing import Any, Union # noqa: F401
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class InstallationCandidate(KeyBasedCompareMixin):
|
class InstallationCandidate(KeyBasedCompareMixin):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from pip._vendor.packaging.utils import canonicalize_name
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional, Set, FrozenSet # noqa: F401
|
from typing import Optional, Set, FrozenSet
|
||||||
|
|
||||||
|
|
||||||
class FormatControl(object):
|
class FormatControl(object):
|
||||||
|
|
|
@ -11,8 +11,8 @@ from pip._internal.utils.models import KeyBasedCompareMixin
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional, Tuple, Union, Text # noqa: F401
|
from typing import Optional, Tuple, Union
|
||||||
from pip._internal.index import HTMLPage # noqa: F401
|
from pip._internal.index import HTMLPage
|
||||||
|
|
||||||
|
|
||||||
class Link(KeyBasedCompareMixin):
|
class Link(KeyBasedCompareMixin):
|
||||||
|
|
|
@ -14,8 +14,8 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Any, Callable, Dict, Optional, Set, Tuple, List
|
Any, Callable, Dict, Optional, Set, Tuple, List
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,11 @@ from pip._internal.utils.misc import (
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Iterator, Optional, List, Container, Set, Dict, Tuple, Iterable, Union
|
Iterator, Optional, List, Container, Set, Dict, Tuple, Iterable, Union
|
||||||
)
|
)
|
||||||
from pip._internal.cache import WheelCache # noqa: F401
|
from pip._internal.cache import WheelCache
|
||||||
from pip._vendor.pkg_resources import ( # noqa: F401
|
from pip._vendor.pkg_resources import (
|
||||||
Distribution, Requirement
|
Distribution, Requirement
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
from pip._internal.vcs import vcs
|
from pip._internal.vcs import vcs
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Any, Optional # noqa: F401
|
from typing import Any, Optional
|
||||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
from pip._internal.index import PackageFinder # noqa: F401
|
from pip._internal.index import PackageFinder
|
||||||
from pip._internal.download import PipSession # noqa: F401
|
from pip._internal.download import PipSession
|
||||||
from pip._internal.req.req_tracker import RequirementTracker # noqa: F401
|
from pip._internal.req.req_tracker import RequirementTracker
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ from pip._internal.utils.compat import get_extension_suffixes
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Tuple, Callable, List, Optional, Union, Dict
|
Tuple, Callable, List, Optional, Union, Dict
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from pip._vendor import pytoml, six
|
from pip._vendor import pytoml, six
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ from pip._internal.exceptions import InstallationError
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Any, Tuple, Optional, List # noqa: F401
|
from typing import Any, Tuple, Optional, List
|
||||||
|
|
||||||
|
|
||||||
def _is_list_of_str(obj):
|
def _is_list_of_str(obj):
|
||||||
|
@ -20,6 +21,17 @@ def _is_list_of_str(obj):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_pyproject_path(setup_py_dir):
|
||||||
|
# type: (str) -> str
|
||||||
|
path = os.path.join(setup_py_dir, 'pyproject.toml')
|
||||||
|
|
||||||
|
# Python2 __file__ should not be unicode
|
||||||
|
if six.PY2 and isinstance(path, six.text_type):
|
||||||
|
path = path.encode(sys.getfilesystemencoding())
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
def load_pyproject_toml(
|
def load_pyproject_toml(
|
||||||
use_pep517, # type: Optional[bool]
|
use_pep517, # type: Optional[bool]
|
||||||
pyproject_toml, # type: str
|
pyproject_toml, # type: str
|
||||||
|
@ -99,11 +111,13 @@ def load_pyproject_toml(
|
||||||
# section, or the user has no pyproject.toml, but has opted in
|
# section, or the user has no pyproject.toml, but has opted in
|
||||||
# explicitly via --use-pep517.
|
# explicitly via --use-pep517.
|
||||||
# In the absence of any explicit backend specification, we
|
# In the absence of any explicit backend specification, we
|
||||||
# assume the setuptools backend, and require wheel and a version
|
# assume the setuptools backend that most closely emulates the
|
||||||
# of setuptools that supports that backend.
|
# traditional direct setup.py execution, and require wheel and
|
||||||
|
# a version of setuptools that supports that backend.
|
||||||
|
|
||||||
build_system = {
|
build_system = {
|
||||||
"requires": ["setuptools>=40.2.0", "wheel"],
|
"requires": ["setuptools>=40.8.0", "wheel"],
|
||||||
"build-backend": "setuptools.build_meta",
|
"build-backend": "setuptools.build_meta:__legacy__",
|
||||||
}
|
}
|
||||||
|
|
||||||
# If we're using PEP 517, we have build system information (either
|
# If we're using PEP 517, we have build system information (either
|
||||||
|
@ -142,7 +156,7 @@ def load_pyproject_toml(
|
||||||
# If the user didn't specify a backend, we assume they want to use
|
# If the user didn't specify a backend, we assume they want to use
|
||||||
# the setuptools backend. But we can't be sure they have included
|
# the setuptools backend. But we can't be sure they have included
|
||||||
# a version of setuptools which supplies the backend, or wheel
|
# a version of setuptools which supplies the backend, or wheel
|
||||||
# (which is neede by the backend) in their requirements. So we
|
# (which is needed by the backend) in their requirements. So we
|
||||||
# make a note to check that those requirements are present once
|
# make a note to check that those requirements are present once
|
||||||
# we have set up the environment.
|
# we have set up the environment.
|
||||||
# This is quite a lot of work to check for a very specific case. But
|
# This is quite a lot of work to check for a very specific case. But
|
||||||
|
@ -151,7 +165,7 @@ def load_pyproject_toml(
|
||||||
# execute setup.py, but never considered needing to mention the build
|
# execute setup.py, but never considered needing to mention the build
|
||||||
# tools themselves. The original PEP 518 code had a similar check (but
|
# tools themselves. The original PEP 518 code had a similar check (but
|
||||||
# implemented in a different way).
|
# implemented in a different way).
|
||||||
backend = "setuptools.build_meta"
|
backend = "setuptools.build_meta:__legacy__"
|
||||||
check = ["setuptools>=40.2.0", "wheel"]
|
check = ["setuptools>=40.8.0", "wheel"]
|
||||||
|
|
||||||
return (requires, backend, check)
|
return (requires, backend, check)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from pip._internal.utils.logging import indent_log
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Any, List, Sequence # noqa: F401
|
from typing import Any, List, Sequence
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"RequirementSet", "InstallRequirement",
|
"RequirementSet", "InstallRequirement",
|
||||||
|
|
|
@ -23,6 +23,7 @@ from pip._internal.download import (
|
||||||
from pip._internal.exceptions import InstallationError
|
from pip._internal.exceptions import InstallationError
|
||||||
from pip._internal.models.index import PyPI, TestPyPI
|
from pip._internal.models.index import PyPI, TestPyPI
|
||||||
from pip._internal.models.link import Link
|
from pip._internal.models.link import Link
|
||||||
|
from pip._internal.pyproject import make_pyproject_path
|
||||||
from pip._internal.req.req_install import InstallRequirement
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
from pip._internal.utils.misc import is_installable_dir
|
from pip._internal.utils.misc import is_installable_dir
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
@ -30,10 +31,10 @@ from pip._internal.vcs import vcs
|
||||||
from pip._internal.wheel import Wheel
|
from pip._internal.wheel import Wheel
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Any, Dict, Optional, Set, Tuple, Union
|
Any, Dict, Optional, Set, Tuple, Union
|
||||||
)
|
)
|
||||||
from pip._internal.cache import WheelCache # noqa: F401
|
from pip._internal.cache import WheelCache
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -77,10 +78,18 @@ def parse_editable(editable_req):
|
||||||
|
|
||||||
if os.path.isdir(url_no_extras):
|
if os.path.isdir(url_no_extras):
|
||||||
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
|
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
|
||||||
raise InstallationError(
|
msg = (
|
||||||
"Directory %r is not installable. File 'setup.py' not found." %
|
'File "setup.py" not found. Directory cannot be installed '
|
||||||
url_no_extras
|
'in editable mode: {}'.format(os.path.abspath(url_no_extras))
|
||||||
)
|
)
|
||||||
|
pyproject_path = make_pyproject_path(url_no_extras)
|
||||||
|
if os.path.isfile(pyproject_path):
|
||||||
|
msg += (
|
||||||
|
'\n(A "pyproject.toml" file was found, but editable '
|
||||||
|
'mode currently requires a setup.py based build.)'
|
||||||
|
)
|
||||||
|
raise InstallationError(msg)
|
||||||
|
|
||||||
# Treating it as code that has already been checked out
|
# Treating it as code that has already been checked out
|
||||||
url_no_extras = path_to_url(url_no_extras)
|
url_no_extras = path_to_url(url_no_extras)
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,13 @@ from pip._internal.req.constructors import (
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Any, Callable, Iterator, List, NoReturn, Optional, Text, Tuple
|
Any, Callable, Iterator, List, NoReturn, Optional, Text, Tuple
|
||||||
)
|
)
|
||||||
from pip._internal.req import InstallRequirement # noqa: F401
|
from pip._internal.req import InstallRequirement
|
||||||
from pip._internal.cache import WheelCache # noqa: F401
|
from pip._internal.cache import WheelCache
|
||||||
from pip._internal.index import PackageFinder # noqa: F401
|
from pip._internal.index import PackageFinder
|
||||||
from pip._internal.download import PipSession # noqa: F401
|
from pip._internal.download import PipSession
|
||||||
|
|
||||||
ReqFileLines = Iterator[Tuple[int, Text]]
|
ReqFileLines = Iterator[Tuple[int, Text]]
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ from pip._internal.locations import (
|
||||||
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
|
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
|
||||||
)
|
)
|
||||||
from pip._internal.models.link import Link
|
from pip._internal.models.link import Link
|
||||||
from pip._internal.pyproject import load_pyproject_toml
|
from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path
|
||||||
from pip._internal.req.req_uninstall import UninstallPathSet
|
from pip._internal.req.req_uninstall import UninstallPathSet
|
||||||
from pip._internal.utils.compat import native_str
|
from pip._internal.utils.compat import native_str
|
||||||
from pip._internal.utils.hashes import Hashes
|
from pip._internal.utils.hashes import Hashes
|
||||||
|
@ -41,15 +41,15 @@ from pip._internal.vcs import vcs
|
||||||
from pip._internal.wheel import move_wheel_files
|
from pip._internal.wheel import move_wheel_files
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Any, Dict, Iterable, List, Mapping, Optional, Sequence, Union
|
Any, Dict, Iterable, List, Mapping, Optional, Sequence, Union
|
||||||
)
|
)
|
||||||
from pip._internal.build_env import BuildEnvironment # noqa: F401
|
from pip._internal.build_env import BuildEnvironment
|
||||||
from pip._internal.cache import WheelCache # noqa: F401
|
from pip._internal.cache import WheelCache
|
||||||
from pip._internal.index import PackageFinder # noqa: F401
|
from pip._internal.index import PackageFinder
|
||||||
from pip._vendor.pkg_resources import Distribution # noqa: F401
|
from pip._vendor.pkg_resources import Distribution
|
||||||
from pip._vendor.packaging.specifiers import SpecifierSet # noqa: F401
|
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||||
from pip._vendor.packaging.markers import Marker # noqa: F401
|
from pip._vendor.packaging.markers import Marker
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -474,13 +474,7 @@ class InstallRequirement(object):
|
||||||
# type: () -> str
|
# type: () -> str
|
||||||
assert self.source_dir, "No source dir for %s" % self
|
assert self.source_dir, "No source dir for %s" % self
|
||||||
|
|
||||||
pp_toml = os.path.join(self.setup_py_dir, 'pyproject.toml')
|
return make_pyproject_path(self.setup_py_dir)
|
||||||
|
|
||||||
# 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 load_pyproject_toml(self):
|
def load_pyproject_toml(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
|
@ -521,7 +515,6 @@ class InstallRequirement(object):
|
||||||
cmd,
|
cmd,
|
||||||
cwd=cwd,
|
cwd=cwd,
|
||||||
extra_environ=extra_environ,
|
extra_environ=extra_environ,
|
||||||
show_stdout=False,
|
|
||||||
spinner=spinner
|
spinner=spinner
|
||||||
)
|
)
|
||||||
self.spin_message = ""
|
self.spin_message = ""
|
||||||
|
@ -619,7 +612,6 @@ class InstallRequirement(object):
|
||||||
call_subprocess(
|
call_subprocess(
|
||||||
egg_info_cmd + egg_base_option,
|
egg_info_cmd + egg_base_option,
|
||||||
cwd=self.setup_py_dir,
|
cwd=self.setup_py_dir,
|
||||||
show_stdout=False,
|
|
||||||
command_desc='python setup.py egg_info')
|
command_desc='python setup.py egg_info')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -772,7 +764,6 @@ class InstallRequirement(object):
|
||||||
list(install_options),
|
list(install_options),
|
||||||
|
|
||||||
cwd=self.setup_py_dir,
|
cwd=self.setup_py_dir,
|
||||||
show_stdout=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.install_succeeded = True
|
self.install_succeeded = True
|
||||||
|
@ -957,7 +948,6 @@ class InstallRequirement(object):
|
||||||
call_subprocess(
|
call_subprocess(
|
||||||
install_args + install_options,
|
install_args + install_options,
|
||||||
cwd=self.setup_py_dir,
|
cwd=self.setup_py_dir,
|
||||||
show_stdout=False,
|
|
||||||
spinner=spinner,
|
spinner=spinner,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
from pip._internal.wheel import Wheel
|
from pip._internal.wheel import Wheel
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Dict, Iterable, List, Optional, Tuple # noqa: F401
|
from typing import Dict, Iterable, List, Optional, Tuple
|
||||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -10,10 +10,10 @@ from pip._internal.utils.temp_dir import TempDirectory
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from types import TracebackType # noqa: F401
|
from types import TracebackType
|
||||||
from typing import Iterator, Optional, Set, Type # noqa: F401
|
from typing import Iterator, Optional, Set, Type
|
||||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
from pip._internal.models.link import Link # noqa: F401
|
from pip._internal.models.link import Link
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -15,16 +15,16 @@ from pip._internal.utils.compat import WINDOWS, cache_from_source, uses_pycache
|
||||||
from pip._internal.utils.logging import indent_log
|
from pip._internal.utils.logging import indent_log
|
||||||
from pip._internal.utils.misc import (
|
from pip._internal.utils.misc import (
|
||||||
FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local,
|
FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local,
|
||||||
normalize_path, renames,
|
normalize_path, renames, rmtree,
|
||||||
)
|
)
|
||||||
from pip._internal.utils.temp_dir import AdjacentTempDirectory
|
from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple
|
Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple
|
||||||
)
|
)
|
||||||
from pip._vendor.pkg_resources import Distribution # noqa: F401
|
from pip._vendor.pkg_resources import Distribution
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ def compress_for_rename(paths):
|
||||||
# If all the files we found are in our remaining set of files to
|
# If all the files we found are in our remaining set of files to
|
||||||
# remove, then remove them from the latter set and add a wildcard
|
# remove, then remove them from the latter set and add a wildcard
|
||||||
# for the directory.
|
# for the directory.
|
||||||
if len(all_files - remaining) == 0:
|
if not (all_files - remaining):
|
||||||
remaining.difference_update(all_files)
|
remaining.difference_update(all_files)
|
||||||
wildcards.add(root + os.sep)
|
wildcards.add(root + os.sep)
|
||||||
|
|
||||||
|
@ -199,6 +199,111 @@ def compress_for_output_listing(paths):
|
||||||
return will_remove, will_skip
|
return will_remove, will_skip
|
||||||
|
|
||||||
|
|
||||||
|
class StashedUninstallPathSet(object):
|
||||||
|
"""A set of file rename operations to stash files while
|
||||||
|
tentatively uninstalling them."""
|
||||||
|
def __init__(self):
|
||||||
|
# Mapping from source file root to [Adjacent]TempDirectory
|
||||||
|
# for files under that directory.
|
||||||
|
self._save_dirs = {}
|
||||||
|
# (old path, new path) tuples for each move that may need
|
||||||
|
# to be undone.
|
||||||
|
self._moves = []
|
||||||
|
|
||||||
|
def _get_directory_stash(self, path):
|
||||||
|
"""Stashes a directory.
|
||||||
|
|
||||||
|
Directories are stashed adjacent to their original location if
|
||||||
|
possible, or else moved/copied into the user's temp dir."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
save_dir = AdjacentTempDirectory(path)
|
||||||
|
save_dir.create()
|
||||||
|
except OSError:
|
||||||
|
save_dir = TempDirectory(kind="uninstall")
|
||||||
|
save_dir.create()
|
||||||
|
self._save_dirs[os.path.normcase(path)] = save_dir
|
||||||
|
|
||||||
|
return save_dir.path
|
||||||
|
|
||||||
|
def _get_file_stash(self, path):
|
||||||
|
"""Stashes a file.
|
||||||
|
|
||||||
|
If no root has been provided, one will be created for the directory
|
||||||
|
in the user's temp directory."""
|
||||||
|
path = os.path.normcase(path)
|
||||||
|
head, old_head = os.path.dirname(path), None
|
||||||
|
save_dir = None
|
||||||
|
|
||||||
|
while head != old_head:
|
||||||
|
try:
|
||||||
|
save_dir = self._save_dirs[head]
|
||||||
|
break
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
head, old_head = os.path.dirname(head), head
|
||||||
|
else:
|
||||||
|
# Did not find any suitable root
|
||||||
|
head = os.path.dirname(path)
|
||||||
|
save_dir = TempDirectory(kind='uninstall')
|
||||||
|
save_dir.create()
|
||||||
|
self._save_dirs[head] = save_dir
|
||||||
|
|
||||||
|
relpath = os.path.relpath(path, head)
|
||||||
|
if relpath and relpath != os.path.curdir:
|
||||||
|
return os.path.join(save_dir.path, relpath)
|
||||||
|
return save_dir.path
|
||||||
|
|
||||||
|
def stash(self, path):
|
||||||
|
"""Stashes the directory or file and returns its new location.
|
||||||
|
"""
|
||||||
|
if os.path.isdir(path):
|
||||||
|
new_path = self._get_directory_stash(path)
|
||||||
|
else:
|
||||||
|
new_path = self._get_file_stash(path)
|
||||||
|
|
||||||
|
self._moves.append((path, new_path))
|
||||||
|
if os.path.isdir(path) and os.path.isdir(new_path):
|
||||||
|
# If we're moving a directory, we need to
|
||||||
|
# remove the destination first or else it will be
|
||||||
|
# moved to inside the existing directory.
|
||||||
|
# We just created new_path ourselves, so it will
|
||||||
|
# be removable.
|
||||||
|
os.rmdir(new_path)
|
||||||
|
renames(path, new_path)
|
||||||
|
return new_path
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
"""Commits the uninstall by removing stashed files."""
|
||||||
|
for _, save_dir in self._save_dirs.items():
|
||||||
|
save_dir.cleanup()
|
||||||
|
self._moves = []
|
||||||
|
self._save_dirs = {}
|
||||||
|
|
||||||
|
def rollback(self):
|
||||||
|
"""Undoes the uninstall by moving stashed files back."""
|
||||||
|
for p in self._moves:
|
||||||
|
logging.info("Moving to %s\n from %s", *p)
|
||||||
|
|
||||||
|
for new_path, path in self._moves:
|
||||||
|
try:
|
||||||
|
logger.debug('Replacing %s from %s', new_path, path)
|
||||||
|
if os.path.isfile(new_path):
|
||||||
|
os.unlink(new_path)
|
||||||
|
elif os.path.isdir(new_path):
|
||||||
|
rmtree(new_path)
|
||||||
|
renames(path, new_path)
|
||||||
|
except OSError as ex:
|
||||||
|
logger.error("Failed to restore %s", new_path)
|
||||||
|
logger.debug("Exception: %s", ex)
|
||||||
|
|
||||||
|
self.commit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_rollback(self):
|
||||||
|
return bool(self._moves)
|
||||||
|
|
||||||
|
|
||||||
class UninstallPathSet(object):
|
class UninstallPathSet(object):
|
||||||
"""A set of file paths to be removed in the uninstallation of a
|
"""A set of file paths to be removed in the uninstallation of a
|
||||||
requirement."""
|
requirement."""
|
||||||
|
@ -208,8 +313,7 @@ class UninstallPathSet(object):
|
||||||
self._refuse = set() # type: Set[str]
|
self._refuse = set() # type: Set[str]
|
||||||
self.pth = {} # type: Dict[str, UninstallPthEntries]
|
self.pth = {} # type: Dict[str, UninstallPthEntries]
|
||||||
self.dist = dist
|
self.dist = dist
|
||||||
self._save_dirs = [] # type: List[AdjacentTempDirectory]
|
self._moved_paths = StashedUninstallPathSet()
|
||||||
self._moved_paths = [] # type: List[Tuple[str, str]]
|
|
||||||
|
|
||||||
def _permitted(self, path):
|
def _permitted(self, path):
|
||||||
# type: (str) -> bool
|
# type: (str) -> bool
|
||||||
|
@ -250,20 +354,6 @@ class UninstallPathSet(object):
|
||||||
else:
|
else:
|
||||||
self._refuse.add(pth_file)
|
self._refuse.add(pth_file)
|
||||||
|
|
||||||
def _stash(self, path):
|
|
||||||
# type: (str) -> str
|
|
||||||
best = None
|
|
||||||
for save_dir in self._save_dirs:
|
|
||||||
if not path.startswith(save_dir.original + os.sep):
|
|
||||||
continue
|
|
||||||
if not best or len(save_dir.original) > len(best.original):
|
|
||||||
best = save_dir
|
|
||||||
if best is None:
|
|
||||||
best = AdjacentTempDirectory(os.path.dirname(path))
|
|
||||||
best.create()
|
|
||||||
self._save_dirs.append(best)
|
|
||||||
return os.path.join(best.path, os.path.relpath(path, best.original))
|
|
||||||
|
|
||||||
def remove(self, auto_confirm=False, verbose=False):
|
def remove(self, auto_confirm=False, verbose=False):
|
||||||
# type: (bool, bool) -> None
|
# type: (bool, bool) -> None
|
||||||
"""Remove paths in ``self.paths`` with confirmation (unless
|
"""Remove paths in ``self.paths`` with confirmation (unless
|
||||||
|
@ -283,11 +373,14 @@ class UninstallPathSet(object):
|
||||||
|
|
||||||
with indent_log():
|
with indent_log():
|
||||||
if auto_confirm or self._allowed_to_proceed(verbose):
|
if auto_confirm or self._allowed_to_proceed(verbose):
|
||||||
for path in sorted(compact(compress_for_rename(self.paths))):
|
moved = self._moved_paths
|
||||||
new_path = self._stash(path)
|
|
||||||
|
for_rename = compress_for_rename(self.paths)
|
||||||
|
|
||||||
|
for path in sorted(compact(for_rename)):
|
||||||
|
moved.stash(path)
|
||||||
logger.debug('Removing file or directory %s', path)
|
logger.debug('Removing file or directory %s', path)
|
||||||
self._moved_paths.append((path, new_path))
|
|
||||||
renames(path, new_path)
|
|
||||||
for pth in self.pth.values():
|
for pth in self.pth.values():
|
||||||
pth.remove()
|
pth.remove()
|
||||||
|
|
||||||
|
@ -327,25 +420,21 @@ class UninstallPathSet(object):
|
||||||
def rollback(self):
|
def rollback(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
"""Rollback the changes previously made by remove()."""
|
"""Rollback the changes previously made by remove()."""
|
||||||
if not self._save_dirs:
|
if not self._moved_paths.can_rollback:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Can't roll back %s; was not uninstalled",
|
"Can't roll back %s; was not uninstalled",
|
||||||
self.dist.project_name,
|
self.dist.project_name,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
logger.info('Rolling back uninstall of %s', self.dist.project_name)
|
logger.info('Rolling back uninstall of %s', self.dist.project_name)
|
||||||
for path, tmp_path in self._moved_paths:
|
self._moved_paths.rollback()
|
||||||
logger.debug('Replacing %s', path)
|
|
||||||
renames(tmp_path, path)
|
|
||||||
for pth in self.pth.values():
|
for pth in self.pth.values():
|
||||||
pth.rollback()
|
pth.rollback()
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
"""Remove temporary save dir: rollback will no longer be possible."""
|
"""Remove temporary save dir: rollback will no longer be possible."""
|
||||||
for save_dir in self._save_dirs:
|
self._moved_paths.commit()
|
||||||
save_dir.cleanup()
|
|
||||||
self._moved_paths = []
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dist(cls, dist):
|
def from_dist(cls, dist):
|
||||||
|
|
|
@ -25,15 +25,15 @@ from pip._internal.utils.packaging import check_dist_requires_python
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional, DefaultDict, List, Set # noqa: F401
|
from typing import Optional, DefaultDict, List, Set
|
||||||
from pip._internal.download import PipSession # noqa: F401
|
from pip._internal.download import PipSession
|
||||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
from pip._internal.index import PackageFinder # noqa: F401
|
from pip._internal.index import PackageFinder
|
||||||
from pip._internal.req.req_set import RequirementSet # noqa: F401
|
from pip._internal.req.req_set import RequirementSet
|
||||||
from pip._internal.operations.prepare import ( # noqa: F401
|
from pip._internal.operations.prepare import (
|
||||||
DistAbstraction, RequirementPreparer
|
DistAbstraction, RequirementPreparer
|
||||||
)
|
)
|
||||||
from pip._internal.cache import WheelCache # noqa: F401
|
from pip._internal.cache import WheelCache
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,7 @@ from pip._internal.utils.compat import WINDOWS, expanduser
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import List
|
||||||
List, Union
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def user_cache_dir(appname):
|
def user_cache_dir(appname):
|
||||||
|
|
|
@ -14,7 +14,7 @@ from pip._vendor.six import text_type
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Tuple, Text # noqa: F401
|
from typing import Tuple, Text
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
|
@ -12,7 +12,7 @@ from pip import __version__ as current_version
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Any, Optional # noqa: F401
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
class PipDeprecationWarning(Warning):
|
class PipDeprecationWarning(Warning):
|
||||||
|
|
|
@ -6,7 +6,7 @@ import sys
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import List, Tuple, Text # noqa: F401
|
from typing import List, Tuple, Text
|
||||||
|
|
||||||
BOMS = [
|
BOMS = [
|
||||||
(codecs.BOM_UTF8, 'utf8'),
|
(codecs.BOM_UTF8, 'utf8'),
|
||||||
|
|
|
@ -7,7 +7,7 @@ import warnings
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional, Tuple # noqa: F401
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
def glibc_version_string():
|
def glibc_version_string():
|
||||||
|
|
|
@ -11,14 +11,14 @@ from pip._internal.utils.misc import read_chunks
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Dict, List, BinaryIO, NoReturn, Iterator
|
Dict, List, BinaryIO, NoReturn, Iterator
|
||||||
)
|
)
|
||||||
from pip._vendor.six import PY3
|
from pip._vendor.six import PY3
|
||||||
if PY3:
|
if PY3:
|
||||||
from hashlib import _Hash # noqa: F401
|
from hashlib import _Hash
|
||||||
else:
|
else:
|
||||||
from hashlib import _hash as _Hash # noqa: F401
|
from hashlib import _hash as _Hash
|
||||||
|
|
||||||
|
|
||||||
# The recommended hash algo of the moment. Change this whenever the state of
|
# The recommended hash algo of the moment. Change this whenever the state of
|
||||||
|
|
|
@ -43,13 +43,13 @@ else:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Optional, Tuple, Iterable, List, Match, Union, Any, Mapping, Text,
|
Optional, Tuple, Iterable, List, Match, Union, Any, Mapping, Text,
|
||||||
AnyStr, Container
|
AnyStr, Container
|
||||||
)
|
)
|
||||||
from pip._vendor.pkg_resources import Distribution # noqa: F401
|
from pip._vendor.pkg_resources import Distribution
|
||||||
from pip._internal.models.link import Link # noqa: F401
|
from pip._internal.models.link import Link
|
||||||
from pip._internal.utils.ui import SpinnerInterface # noqa: F401
|
from pip._internal.utils.ui import SpinnerInterface
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['rmtree', 'display_path', 'backup_dir',
|
__all__ = ['rmtree', 'display_path', 'backup_dir',
|
||||||
|
@ -650,7 +650,7 @@ def unpack_file(
|
||||||
|
|
||||||
def call_subprocess(
|
def call_subprocess(
|
||||||
cmd, # type: List[str]
|
cmd, # type: List[str]
|
||||||
show_stdout=True, # type: bool
|
show_stdout=False, # type: bool
|
||||||
cwd=None, # type: Optional[str]
|
cwd=None, # type: Optional[str]
|
||||||
on_returncode='raise', # type: str
|
on_returncode='raise', # type: str
|
||||||
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
|
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
|
||||||
|
@ -677,13 +677,13 @@ def call_subprocess(
|
||||||
#
|
#
|
||||||
# The obvious thing that affects output is the show_stdout=
|
# The obvious thing that affects output is the show_stdout=
|
||||||
# kwarg. show_stdout=True means, let the subprocess write directly to our
|
# kwarg. show_stdout=True means, let the subprocess write directly to our
|
||||||
# stdout. Even though it is nominally the default, it is almost never used
|
# stdout. It is almost never used
|
||||||
# inside pip (and should not be used in new code without a very good
|
# inside pip (and should not be used in new code without a very good
|
||||||
# reason); as of 2016-02-22 it is only used in a few places inside the VCS
|
# reason); as of 2016-02-22 it is only used in a few places inside the VCS
|
||||||
# wrapper code. Ideally we should get rid of it entirely, because it
|
# wrapper code. Ideally we should get rid of it entirely, because it
|
||||||
# creates a lot of complexity here for a rarely used feature.
|
# creates a lot of complexity here for a rarely used feature.
|
||||||
#
|
#
|
||||||
# Most places in pip set show_stdout=False. What this means is:
|
# Most places in pip use show_stdout=False. What this means is:
|
||||||
# - We connect the child stdout to a pipe, which we read.
|
# - We connect the child stdout to a pipe, which we read.
|
||||||
# - By default, we hide the output but show a spinner -- unless the
|
# - By default, we hide the output but show a spinner -- unless the
|
||||||
# subprocess exits with an error, in which case we show the output.
|
# subprocess exits with an error, in which case we show the output.
|
||||||
|
|
|
@ -16,9 +16,9 @@ from pip._internal.utils.misc import ensure_dir, get_installed_version
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
import optparse # noqa: F401
|
import optparse
|
||||||
from typing import Any, Dict # noqa: F401
|
from typing import Any, Dict
|
||||||
from pip._internal.download import PipSession # noqa: F401
|
from pip._internal.download import PipSession
|
||||||
|
|
||||||
|
|
||||||
SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
|
|
@ -12,9 +12,9 @@ from pip._internal.utils.misc import display_path
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Optional # noqa: F401
|
from typing import Optional
|
||||||
from email.message import Message # noqa: F401
|
from email.message import Message
|
||||||
from pip._vendor.pkg_resources import Distribution # noqa: F401
|
from pip._vendor.pkg_resources import Distribution
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import errno
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
|
@ -98,7 +99,11 @@ class AdjacentTempDirectory(TempDirectory):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# The characters that may be used to name the temp directory
|
# The characters that may be used to name the temp directory
|
||||||
LEADING_CHARS = "-~.+=%0123456789"
|
# We always prepend a ~ and then rotate through these until
|
||||||
|
# a usable name is found.
|
||||||
|
# pkg_resources raises a different error for .dist-info folder
|
||||||
|
# with leading '-' and invalid metadata
|
||||||
|
LEADING_CHARS = "-~.=%0123456789"
|
||||||
|
|
||||||
def __init__(self, original, delete=None):
|
def __init__(self, original, delete=None):
|
||||||
super(AdjacentTempDirectory, self).__init__(delete=delete)
|
super(AdjacentTempDirectory, self).__init__(delete=delete)
|
||||||
|
@ -114,11 +119,17 @@ class AdjacentTempDirectory(TempDirectory):
|
||||||
package).
|
package).
|
||||||
"""
|
"""
|
||||||
for i in range(1, len(name)):
|
for i in range(1, len(name)):
|
||||||
if name[i] in cls.LEADING_CHARS:
|
for candidate in itertools.combinations_with_replacement(
|
||||||
continue
|
cls.LEADING_CHARS, i - 1):
|
||||||
|
new_name = '~' + ''.join(candidate) + name[i:]
|
||||||
|
if new_name != name:
|
||||||
|
yield new_name
|
||||||
|
|
||||||
|
# If we make it this far, we will have to make a longer name
|
||||||
|
for i in range(len(cls.LEADING_CHARS)):
|
||||||
for candidate in itertools.combinations_with_replacement(
|
for candidate in itertools.combinations_with_replacement(
|
||||||
cls.LEADING_CHARS, i):
|
cls.LEADING_CHARS, i):
|
||||||
new_name = ''.join(candidate) + name[i:]
|
new_name = '~' + ''.join(candidate) + name
|
||||||
if new_name != name:
|
if new_name != name:
|
||||||
yield new_name
|
yield new_name
|
||||||
|
|
||||||
|
@ -128,8 +139,10 @@ class AdjacentTempDirectory(TempDirectory):
|
||||||
path = os.path.join(root, candidate)
|
path = os.path.join(root, candidate)
|
||||||
try:
|
try:
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
except OSError:
|
except OSError as ex:
|
||||||
pass
|
# Continue if the name exists already
|
||||||
|
if ex.errno != errno.EEXIST:
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
self.path = os.path.realpath(path)
|
self.path = os.path.realpath(path)
|
||||||
break
|
break
|
||||||
|
|
|
@ -21,7 +21,7 @@ In pip, all static-typing related imports should be guarded as follows:
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ... # noqa: F401
|
from typing import ...
|
||||||
|
|
||||||
Ref: https://github.com/python/mypy/issues/3216
|
Ref: https://github.com/python/mypy/issues/3216
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -21,7 +21,7 @@ from pip._internal.utils.misc import format_size
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import Any, Iterator, IO # noqa: F401
|
from typing import Any, Iterator, IO
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pip._vendor import colorama
|
from pip._vendor import colorama
|
||||||
|
|
|
@ -16,10 +16,10 @@ from pip._internal.utils.misc import (
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Any, Dict, Iterable, List, Mapping, Optional, Text, Tuple, Type
|
Any, Dict, Iterable, List, Mapping, Optional, Text, Tuple, Type
|
||||||
)
|
)
|
||||||
from pip._internal.utils.ui import SpinnerInterface # noqa: F401
|
from pip._internal.utils.ui import SpinnerInterface
|
||||||
|
|
||||||
AuthInfo = Tuple[Optional[str], Optional[str]]
|
AuthInfo = Tuple[Optional[str], Optional[str]]
|
||||||
|
|
||||||
|
|
|
@ -41,19 +41,18 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
from pip._internal.utils.ui import open_spinner
|
from pip._internal.utils.ui import open_spinner
|
||||||
|
|
||||||
if MYPY_CHECK_RUNNING:
|
if MYPY_CHECK_RUNNING:
|
||||||
from typing import ( # noqa: F401
|
from typing import (
|
||||||
Dict, List, Optional, Sequence, Mapping, Tuple, IO, Text, Any,
|
Dict, List, Optional, Sequence, Mapping, Tuple, IO, Text, Any, Iterable
|
||||||
Union, Iterable
|
|
||||||
)
|
)
|
||||||
from pip._vendor.packaging.requirements import Requirement # noqa: F401
|
from pip._vendor.packaging.requirements import Requirement
|
||||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
from pip._internal.download import PipSession # noqa: F401
|
from pip._internal.download import PipSession
|
||||||
from pip._internal.index import PackageFinder # noqa: F401
|
from pip._internal.index import FormatControl, PackageFinder
|
||||||
from pip._internal.operations.prepare import ( # noqa: F401
|
from pip._internal.operations.prepare import (
|
||||||
RequirementPreparer
|
RequirementPreparer
|
||||||
)
|
)
|
||||||
from pip._internal.cache import WheelCache # noqa: F401
|
from pip._internal.cache import WheelCache
|
||||||
from pip._internal.pep425tags import Pep425Tag # noqa: F401
|
from pip._internal.pep425tags import Pep425Tag
|
||||||
|
|
||||||
InstalledCSVRow = Tuple[str, ...]
|
InstalledCSVRow = Tuple[str, ...]
|
||||||
|
|
||||||
|
@ -212,12 +211,12 @@ def message_about_scripts_not_on_PATH(scripts):
|
||||||
# Format a message
|
# Format a message
|
||||||
msg_lines = []
|
msg_lines = []
|
||||||
for parent_dir, scripts in warn_for.items():
|
for parent_dir, scripts in warn_for.items():
|
||||||
scripts = sorted(scripts)
|
sorted_scripts = sorted(scripts) # type: List[str]
|
||||||
if len(scripts) == 1:
|
if len(sorted_scripts) == 1:
|
||||||
start_text = "script {} is".format(scripts[0])
|
start_text = "script {} is".format(sorted_scripts[0])
|
||||||
else:
|
else:
|
||||||
start_text = "scripts {} are".format(
|
start_text = "scripts {} are".format(
|
||||||
", ".join(scripts[:-1]) + " and " + scripts[-1]
|
", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1]
|
||||||
)
|
)
|
||||||
|
|
||||||
msg_lines.append(
|
msg_lines.append(
|
||||||
|
@ -267,16 +266,23 @@ def get_csv_rows_for_installed(
|
||||||
lib_dir, # type: str
|
lib_dir, # type: str
|
||||||
):
|
):
|
||||||
# type: (...) -> List[InstalledCSVRow]
|
# type: (...) -> List[InstalledCSVRow]
|
||||||
|
"""
|
||||||
|
:param installed: A map from archive RECORD path to installation RECORD
|
||||||
|
path.
|
||||||
|
"""
|
||||||
installed_rows = [] # type: List[InstalledCSVRow]
|
installed_rows = [] # type: List[InstalledCSVRow]
|
||||||
for row in old_csv_rows:
|
for row in old_csv_rows:
|
||||||
if len(row) > 3:
|
if len(row) > 3:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'RECORD line has more than three elements: {}'.format(row)
|
'RECORD line has more than three elements: {}'.format(row)
|
||||||
)
|
)
|
||||||
fpath = row[0]
|
# Make a copy because we are mutating the row.
|
||||||
fpath = installed.pop(fpath, fpath)
|
row = list(row)
|
||||||
if fpath in changed:
|
old_path = row[0]
|
||||||
digest, length = rehash(fpath)
|
new_path = installed.pop(old_path, old_path)
|
||||||
|
row[0] = new_path
|
||||||
|
if new_path in changed:
|
||||||
|
digest, length = rehash(new_path)
|
||||||
row[1] = digest
|
row[1] = digest
|
||||||
row[2] = length
|
row[2] = length
|
||||||
installed_rows.append(tuple(row))
|
installed_rows.append(tuple(row))
|
||||||
|
@ -725,6 +731,117 @@ def _contains_egg_info(
|
||||||
return bool(_egg_info_re.search(s))
|
return bool(_egg_info_re.search(s))
|
||||||
|
|
||||||
|
|
||||||
|
def should_use_ephemeral_cache(
|
||||||
|
req, # type: InstallRequirement
|
||||||
|
format_control, # type: FormatControl
|
||||||
|
autobuilding, # type: bool
|
||||||
|
cache_available # type: bool
|
||||||
|
):
|
||||||
|
# type: (...) -> Optional[bool]
|
||||||
|
"""
|
||||||
|
Return whether to build an InstallRequirement object using the
|
||||||
|
ephemeral cache.
|
||||||
|
|
||||||
|
:param cache_available: whether a cache directory is available for the
|
||||||
|
autobuilding=True case.
|
||||||
|
|
||||||
|
:return: True or False to build the requirement with ephem_cache=True
|
||||||
|
or False, respectively; or None not to build the requirement.
|
||||||
|
"""
|
||||||
|
if req.constraint:
|
||||||
|
return None
|
||||||
|
if req.is_wheel:
|
||||||
|
if not autobuilding:
|
||||||
|
logger.info(
|
||||||
|
'Skipping %s, due to already being wheel.', req.name,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
if not autobuilding:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if req.editable or not req.source_dir:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if req.link and not req.link.is_artifact:
|
||||||
|
# VCS checkout. Build wheel just for this run.
|
||||||
|
return True
|
||||||
|
|
||||||
|
if "binary" not in format_control.get_allowed_formats(
|
||||||
|
canonicalize_name(req.name)):
|
||||||
|
logger.info(
|
||||||
|
"Skipping bdist_wheel for %s, due to binaries "
|
||||||
|
"being disabled for it.", req.name,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
link = req.link
|
||||||
|
base, ext = link.splitext()
|
||||||
|
if cache_available and _contains_egg_info(base):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Otherwise, build the wheel just for this run using the ephemeral
|
||||||
|
# cache since we are either in the case of e.g. a local directory, or
|
||||||
|
# no cache directory is available to use.
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def format_command(
|
||||||
|
command_args, # type: List[str]
|
||||||
|
command_output, # type: str
|
||||||
|
):
|
||||||
|
# type: (...) -> str
|
||||||
|
"""
|
||||||
|
Format command information for logging.
|
||||||
|
"""
|
||||||
|
text = 'Command arguments: {}\n'.format(command_args)
|
||||||
|
|
||||||
|
if not command_output:
|
||||||
|
text += 'Command output: None'
|
||||||
|
elif logger.getEffectiveLevel() > logging.DEBUG:
|
||||||
|
text += 'Command output: [use --verbose to show]'
|
||||||
|
else:
|
||||||
|
if not command_output.endswith('\n'):
|
||||||
|
command_output += '\n'
|
||||||
|
text += (
|
||||||
|
'Command output:\n{}'
|
||||||
|
'-----------------------------------------'
|
||||||
|
).format(command_output)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def get_legacy_build_wheel_path(
|
||||||
|
names, # type: List[str]
|
||||||
|
temp_dir, # type: str
|
||||||
|
req, # type: InstallRequirement
|
||||||
|
command_args, # type: List[str]
|
||||||
|
command_output, # type: str
|
||||||
|
):
|
||||||
|
# type: (...) -> Optional[str]
|
||||||
|
"""
|
||||||
|
Return the path to the wheel in the temporary build directory.
|
||||||
|
"""
|
||||||
|
# Sort for determinism.
|
||||||
|
names = sorted(names)
|
||||||
|
if not names:
|
||||||
|
msg = (
|
||||||
|
'Legacy build of wheel for {!r} created no files.\n'
|
||||||
|
).format(req.name)
|
||||||
|
msg += format_command(command_args, command_output)
|
||||||
|
logger.warning(msg)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(names) > 1:
|
||||||
|
msg = (
|
||||||
|
'Legacy build of wheel for {!r} created more than one file.\n'
|
||||||
|
'Filenames (choosing first): {}\n'
|
||||||
|
).format(req.name, names)
|
||||||
|
msg += format_command(command_args, command_output)
|
||||||
|
logger.warning(msg)
|
||||||
|
|
||||||
|
return os.path.join(temp_dir, names[0])
|
||||||
|
|
||||||
|
|
||||||
class WheelBuilder(object):
|
class WheelBuilder(object):
|
||||||
"""Build wheels from a RequirementSet."""
|
"""Build wheels from a RequirementSet."""
|
||||||
|
|
||||||
|
@ -764,15 +881,14 @@ class WheelBuilder(object):
|
||||||
builder = self._build_one_pep517
|
builder = self._build_one_pep517
|
||||||
else:
|
else:
|
||||||
builder = self._build_one_legacy
|
builder = self._build_one_legacy
|
||||||
if builder(req, temp_dir.path, python_tag=python_tag):
|
wheel_path = builder(req, temp_dir.path, python_tag=python_tag)
|
||||||
|
if wheel_path is not None:
|
||||||
|
wheel_name = os.path.basename(wheel_path)
|
||||||
|
dest_path = os.path.join(output_dir, wheel_name)
|
||||||
try:
|
try:
|
||||||
wheel_name = os.listdir(temp_dir.path)[0]
|
shutil.move(wheel_path, dest_path)
|
||||||
wheel_path = os.path.join(output_dir, wheel_name)
|
|
||||||
shutil.move(
|
|
||||||
os.path.join(temp_dir.path, wheel_name), wheel_path
|
|
||||||
)
|
|
||||||
logger.info('Stored in directory: %s', output_dir)
|
logger.info('Stored in directory: %s', output_dir)
|
||||||
return wheel_path
|
return dest_path
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# Ignore return, we can't do anything else useful.
|
# Ignore return, we can't do anything else useful.
|
||||||
|
@ -790,11 +906,15 @@ class WheelBuilder(object):
|
||||||
] + list(self.global_options)
|
] + list(self.global_options)
|
||||||
|
|
||||||
def _build_one_pep517(self, req, tempd, python_tag=None):
|
def _build_one_pep517(self, req, tempd, python_tag=None):
|
||||||
|
"""Build one InstallRequirement using the PEP 517 build process.
|
||||||
|
|
||||||
|
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||||
|
"""
|
||||||
assert req.metadata_directory is not None
|
assert req.metadata_directory is not None
|
||||||
try:
|
try:
|
||||||
req.spin_message = 'Building wheel for %s (PEP 517)' % (req.name,)
|
req.spin_message = 'Building wheel for %s (PEP 517)' % (req.name,)
|
||||||
logger.debug('Destination directory: %s', tempd)
|
logger.debug('Destination directory: %s', tempd)
|
||||||
wheelname = req.pep517_backend.build_wheel(
|
wheel_name = req.pep517_backend.build_wheel(
|
||||||
tempd,
|
tempd,
|
||||||
metadata_directory=req.metadata_directory
|
metadata_directory=req.metadata_directory
|
||||||
)
|
)
|
||||||
|
@ -802,17 +922,23 @@ class WheelBuilder(object):
|
||||||
# General PEP 517 backends don't necessarily support
|
# General PEP 517 backends don't necessarily support
|
||||||
# a "--python-tag" option, so we rename the wheel
|
# a "--python-tag" option, so we rename the wheel
|
||||||
# file directly.
|
# file directly.
|
||||||
newname = replace_python_tag(wheelname, python_tag)
|
new_name = replace_python_tag(wheel_name, python_tag)
|
||||||
os.rename(
|
os.rename(
|
||||||
os.path.join(tempd, wheelname),
|
os.path.join(tempd, wheel_name),
|
||||||
os.path.join(tempd, newname)
|
os.path.join(tempd, new_name)
|
||||||
)
|
)
|
||||||
return True
|
# Reassign to simplify the return at the end of function
|
||||||
|
wheel_name = new_name
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error('Failed building wheel for %s', req.name)
|
logger.error('Failed building wheel for %s', req.name)
|
||||||
return False
|
return None
|
||||||
|
return os.path.join(tempd, wheel_name)
|
||||||
|
|
||||||
def _build_one_legacy(self, req, tempd, python_tag=None):
|
def _build_one_legacy(self, req, tempd, python_tag=None):
|
||||||
|
"""Build one InstallRequirement using the "legacy" build process.
|
||||||
|
|
||||||
|
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||||
|
"""
|
||||||
base_args = self._base_setup_args(req)
|
base_args = self._base_setup_args(req)
|
||||||
|
|
||||||
spin_message = 'Building wheel for %s (setup.py)' % (req.name,)
|
spin_message = 'Building wheel for %s (setup.py)' % (req.name,)
|
||||||
|
@ -825,13 +951,21 @@ class WheelBuilder(object):
|
||||||
wheel_args += ["--python-tag", python_tag]
|
wheel_args += ["--python-tag", python_tag]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
call_subprocess(wheel_args, cwd=req.setup_py_dir,
|
output = call_subprocess(wheel_args, cwd=req.setup_py_dir,
|
||||||
show_stdout=False, spinner=spinner)
|
spinner=spinner)
|
||||||
return True
|
|
||||||
except Exception:
|
except Exception:
|
||||||
spinner.finish("error")
|
spinner.finish("error")
|
||||||
logger.error('Failed building wheel for %s', req.name)
|
logger.error('Failed building wheel for %s', req.name)
|
||||||
return False
|
return None
|
||||||
|
names = os.listdir(tempd)
|
||||||
|
wheel_path = get_legacy_build_wheel_path(
|
||||||
|
names=names,
|
||||||
|
temp_dir=tempd,
|
||||||
|
req=req,
|
||||||
|
command_args=wheel_args,
|
||||||
|
command_output=output,
|
||||||
|
)
|
||||||
|
return wheel_path
|
||||||
|
|
||||||
def _clean_one(self, req):
|
def _clean_one(self, req):
|
||||||
base_args = self._base_setup_args(req)
|
base_args = self._base_setup_args(req)
|
||||||
|
@ -839,7 +973,7 @@ class WheelBuilder(object):
|
||||||
logger.info('Running setup.py clean for %s', req.name)
|
logger.info('Running setup.py clean for %s', req.name)
|
||||||
clean_args = base_args + ['clean', '--all']
|
clean_args = base_args + ['clean', '--all']
|
||||||
try:
|
try:
|
||||||
call_subprocess(clean_args, cwd=req.source_dir, show_stdout=False)
|
call_subprocess(clean_args, cwd=req.source_dir)
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error('Failed cleaning build dir for %s', req.name)
|
logger.error('Failed cleaning build dir for %s', req.name)
|
||||||
|
@ -858,40 +992,20 @@ class WheelBuilder(object):
|
||||||
newly built wheel, in preparation for installation.
|
newly built wheel, in preparation for installation.
|
||||||
:return: True if all the wheels built correctly.
|
:return: True if all the wheels built correctly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
buildset = []
|
buildset = []
|
||||||
format_control = self.finder.format_control
|
format_control = self.finder.format_control
|
||||||
|
# Whether a cache directory is available for autobuilding=True.
|
||||||
|
cache_available = bool(self._wheel_dir or self.wheel_cache.cache_dir)
|
||||||
|
|
||||||
for req in requirements:
|
for req in requirements:
|
||||||
if req.constraint:
|
ephem_cache = should_use_ephemeral_cache(
|
||||||
|
req, format_control=format_control, autobuilding=autobuilding,
|
||||||
|
cache_available=cache_available,
|
||||||
|
)
|
||||||
|
if ephem_cache is None:
|
||||||
continue
|
continue
|
||||||
if req.is_wheel:
|
|
||||||
if not autobuilding:
|
buildset.append((req, ephem_cache))
|
||||||
logger.info(
|
|
||||||
'Skipping %s, due to already being wheel.', req.name,
|
|
||||||
)
|
|
||||||
elif autobuilding and req.editable:
|
|
||||||
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 not _contains_egg_info(base):
|
|
||||||
# E.g. local directory. Build wheel just for this run.
|
|
||||||
ephem_cache = True
|
|
||||||
if "binary" not in format_control.get_allowed_formats(
|
|
||||||
canonicalize_name(req.name)):
|
|
||||||
logger.info(
|
|
||||||
"Skipping bdist_wheel for %s, due to binaries "
|
|
||||||
"being disabled for it.", req.name,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
buildset.append((req, ephem_cache))
|
|
||||||
|
|
||||||
if not buildset:
|
if not buildset:
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -348,5 +348,5 @@ def in_memory_pip():
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def deprecated_python():
|
def deprecated_python():
|
||||||
"""Used to indicate wheither pip deprecated this python version"""
|
"""Used to indicate whether pip deprecated this python version"""
|
||||||
return sys.version_info[:2] in [(3, 4), (2, 7)]
|
return sys.version_info[:2] in [(3, 4), (2, 7)]
|
||||||
|
|
|
@ -62,7 +62,7 @@ def test_pep518_refuses_conflicting_requires(script, data):
|
||||||
result.returncode != 0 and
|
result.returncode != 0 and
|
||||||
('Some build dependencies for %s conflict with PEP 517/518 supported '
|
('Some build dependencies for %s conflict with PEP 517/518 supported '
|
||||||
'requirements: setuptools==1.0 is incompatible with '
|
'requirements: setuptools==1.0 is incompatible with '
|
||||||
'setuptools>=40.2.0.' % path_to_url(project_dir)) in result.stderr
|
'setuptools>=40.8.0.' % path_to_url(project_dir)) in result.stderr
|
||||||
), str(result)
|
), str(result)
|
||||||
|
|
||||||
|
|
||||||
|
@ -491,13 +491,41 @@ def test_install_from_local_directory_with_no_setup_py(script, data):
|
||||||
assert "Neither 'setup.py' nor 'pyproject.toml' found." in result.stderr
|
assert "Neither 'setup.py' nor 'pyproject.toml' found." in result.stderr
|
||||||
|
|
||||||
|
|
||||||
def test_editable_install_from_local_directory_with_no_setup_py(script, data):
|
def test_editable_install__local_dir_no_setup_py(
|
||||||
|
script, data, deprecated_python):
|
||||||
"""
|
"""
|
||||||
Test installing from a local directory with no 'setup.py'.
|
Test installing in editable mode from a local directory with no setup.py.
|
||||||
"""
|
"""
|
||||||
result = script.pip('install', '-e', data.root, expect_error=True)
|
result = script.pip('install', '-e', data.root, expect_error=True)
|
||||||
assert not result.files_created
|
assert not result.files_created
|
||||||
assert "is not installable. File 'setup.py' not found." in result.stderr
|
|
||||||
|
msg = result.stderr
|
||||||
|
if deprecated_python:
|
||||||
|
assert 'File "setup.py" not found. ' in msg
|
||||||
|
else:
|
||||||
|
assert msg.startswith('File "setup.py" not found. ')
|
||||||
|
assert 'pyproject.toml' not in msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_editable_install__local_dir_no_setup_py_with_pyproject(
|
||||||
|
script, deprecated_python):
|
||||||
|
"""
|
||||||
|
Test installing in editable mode from a local directory with no setup.py
|
||||||
|
but that does have pyproject.toml.
|
||||||
|
"""
|
||||||
|
local_dir = script.scratch_path.join('temp').mkdir()
|
||||||
|
pyproject_path = local_dir.join('pyproject.toml')
|
||||||
|
pyproject_path.write('')
|
||||||
|
|
||||||
|
result = script.pip('install', '-e', local_dir, expect_error=True)
|
||||||
|
assert not result.files_created
|
||||||
|
|
||||||
|
msg = result.stderr
|
||||||
|
if deprecated_python:
|
||||||
|
assert 'File "setup.py" not found. ' in msg
|
||||||
|
else:
|
||||||
|
assert msg.startswith('File "setup.py" not found. ')
|
||||||
|
assert 'A "pyproject.toml" file was found' in msg
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif("sys.version_info >= (3,4)")
|
@pytest.mark.skipif("sys.version_info >= (3,4)")
|
||||||
|
|
|
@ -123,3 +123,78 @@ def test_pep517_install_with_no_cache_dir(script, tmpdir, data):
|
||||||
project_dir,
|
project_dir,
|
||||||
)
|
)
|
||||||
result.assert_installed('project', editable=False)
|
result.assert_installed('project', editable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def make_pyproject_with_setup(tmpdir, build_system=True, set_backend=True):
|
||||||
|
project_dir = (tmpdir / 'project').mkdir()
|
||||||
|
setup_script = (
|
||||||
|
'from setuptools import setup\n'
|
||||||
|
)
|
||||||
|
expect_script_dir_on_path = True
|
||||||
|
if build_system:
|
||||||
|
buildsys = {
|
||||||
|
'requires': ['setuptools', 'wheel'],
|
||||||
|
}
|
||||||
|
if set_backend:
|
||||||
|
buildsys['build-backend'] = 'setuptools.build_meta'
|
||||||
|
expect_script_dir_on_path = False
|
||||||
|
project_data = pytoml.dumps({'build-system': buildsys})
|
||||||
|
else:
|
||||||
|
project_data = ''
|
||||||
|
|
||||||
|
if expect_script_dir_on_path:
|
||||||
|
setup_script += (
|
||||||
|
'from pep517_test import __version__\n'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
setup_script += (
|
||||||
|
'try:\n'
|
||||||
|
' import pep517_test\n'
|
||||||
|
'except ImportError:\n'
|
||||||
|
' pass\n'
|
||||||
|
'else:\n'
|
||||||
|
' raise RuntimeError("Source dir incorrectly on sys.path")\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
setup_script += (
|
||||||
|
'setup(name="pep517_test", version="0.1", packages=["pep517_test"])'
|
||||||
|
)
|
||||||
|
|
||||||
|
project_dir.join('pyproject.toml').write(project_data)
|
||||||
|
project_dir.join('setup.py').write(setup_script)
|
||||||
|
package_dir = (project_dir / "pep517_test").mkdir()
|
||||||
|
package_dir.join('__init__.py').write('__version__ = "0.1"')
|
||||||
|
return project_dir, "pep517_test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_build_system_section(script, tmpdir, data, common_wheels):
|
||||||
|
"""Check builds with setup.py, pyproject.toml, but no build-system section.
|
||||||
|
"""
|
||||||
|
project_dir, name = make_pyproject_with_setup(tmpdir, build_system=False)
|
||||||
|
result = script.pip(
|
||||||
|
'install', '--no-cache-dir', '--no-index', '-f', common_wheels,
|
||||||
|
project_dir,
|
||||||
|
)
|
||||||
|
result.assert_installed(name, editable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_build_backend_entry(script, tmpdir, data, common_wheels):
|
||||||
|
"""Check builds with setup.py, pyproject.toml, but no build-backend entry.
|
||||||
|
"""
|
||||||
|
project_dir, name = make_pyproject_with_setup(tmpdir, set_backend=False)
|
||||||
|
result = script.pip(
|
||||||
|
'install', '--no-cache-dir', '--no-index', '-f', common_wheels,
|
||||||
|
project_dir,
|
||||||
|
)
|
||||||
|
result.assert_installed(name, editable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_explicit_setuptools_backend(script, tmpdir, data, common_wheels):
|
||||||
|
"""Check builds with setup.py, pyproject.toml, and a build-backend entry.
|
||||||
|
"""
|
||||||
|
project_dir, name = make_pyproject_with_setup(tmpdir)
|
||||||
|
result = script.pip(
|
||||||
|
'install', '--no-cache-dir', '--no-index', '-f', common_wheels,
|
||||||
|
project_dir,
|
||||||
|
)
|
||||||
|
result.assert_installed(name, editable=False)
|
||||||
|
|
|
@ -14,6 +14,13 @@ def auto_with_wheel(with_wheel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def add_files_to_dist_directory(folder):
|
||||||
|
(folder / 'dist').makedirs()
|
||||||
|
(folder / 'dist' / 'a_name-0.0.1.tar.gz').write("hello")
|
||||||
|
# Not adding a wheel file since that confuses setuptools' backend.
|
||||||
|
# (folder / 'dist' / 'a_name-0.0.1-py2.py3-none-any.whl').write("hello")
|
||||||
|
|
||||||
|
|
||||||
def test_wheel_exit_status_code_when_no_requirements(script):
|
def test_wheel_exit_status_code_when_no_requirements(script):
|
||||||
"""
|
"""
|
||||||
Test wheel exit status code when no requirements specified
|
Test wheel exit status code when no requirements specified
|
||||||
|
@ -210,3 +217,31 @@ def test_pip_wheel_with_user_set_in_config(script, data, common_wheels):
|
||||||
'--no-index', '-f', common_wheels
|
'--no-index', '-f', common_wheels
|
||||||
)
|
)
|
||||||
assert "Successfully built withpyproject" in result.stdout, result.stdout
|
assert "Successfully built withpyproject" in result.stdout, result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_pep517_wheels_are_not_confused_with_other_files(script, tmpdir, data):
|
||||||
|
"""Check correct wheels are copied. (#6196)
|
||||||
|
"""
|
||||||
|
pkg_to_wheel = data.src / 'withpyproject'
|
||||||
|
add_files_to_dist_directory(pkg_to_wheel)
|
||||||
|
|
||||||
|
result = script.pip('wheel', pkg_to_wheel, '-w', script.scratch_path)
|
||||||
|
assert "Installing build dependencies" in result.stdout, result.stdout
|
||||||
|
|
||||||
|
wheel_file_name = 'withpyproject-0.0.1-py%s-none-any.whl' % pyversion[0]
|
||||||
|
wheel_file_path = script.scratch / wheel_file_name
|
||||||
|
assert wheel_file_path in result.files_created, result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_legacy_wheels_are_not_confused_with_other_files(script, tmpdir, data):
|
||||||
|
"""Check correct wheels are copied. (#6196)
|
||||||
|
"""
|
||||||
|
pkg_to_wheel = data.src / 'simplewheel-1.0'
|
||||||
|
add_files_to_dist_directory(pkg_to_wheel)
|
||||||
|
|
||||||
|
result = script.pip('wheel', pkg_to_wheel, '-w', script.scratch_path)
|
||||||
|
assert "Installing build dependencies" not in result.stdout, result.stdout
|
||||||
|
|
||||||
|
wheel_file_name = 'simplewheel-1.0-py%s-none-any.whl' % pyversion[0]
|
||||||
|
wheel_file_path = script.scratch / wheel_file_name
|
||||||
|
assert wheel_file_path in result.files_created, result.stdout
|
||||||
|
|
|
@ -21,6 +21,10 @@ pyversion = sys.version[:3]
|
||||||
pyversion_tuple = sys.version_info
|
pyversion_tuple = sys.version_info
|
||||||
|
|
||||||
|
|
||||||
|
def assert_paths_equal(actual, expected):
|
||||||
|
os.path.normpath(actual) == os.path.normpath(expected)
|
||||||
|
|
||||||
|
|
||||||
def path_to_url(path):
|
def path_to_url(path):
|
||||||
"""
|
"""
|
||||||
Convert a path to URI. The path will be made absolute and
|
Convert a path to URI. The path will be made absolute and
|
||||||
|
|
|
@ -10,10 +10,22 @@ import textwrap
|
||||||
import pip._internal.configuration
|
import pip._internal.configuration
|
||||||
from pip._internal.utils.misc import ensure_dir
|
from pip._internal.utils.misc import ensure_dir
|
||||||
|
|
||||||
# This is so that tests don't need to import pip.configuration
|
# This is so that tests don't need to import pip._internal.configuration.
|
||||||
kinds = pip._internal.configuration.kinds
|
kinds = pip._internal.configuration.kinds
|
||||||
|
|
||||||
|
|
||||||
|
def reset_os_environ(old_environ):
|
||||||
|
"""
|
||||||
|
Reset os.environ while preserving the same underlying mapping.
|
||||||
|
"""
|
||||||
|
# Preserving the same mapping is preferable to assigning a new mapping
|
||||||
|
# because the latter has interfered with test isolation by, for example,
|
||||||
|
# preventing time.tzset() from working in subsequent tests after
|
||||||
|
# changing os.environ['TZ'] in those tests.
|
||||||
|
os.environ.clear()
|
||||||
|
os.environ.update(old_environ)
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationMixin(object):
|
class ConfigurationMixin(object):
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
@ -28,7 +40,7 @@ class ConfigurationMixin(object):
|
||||||
for fname in self._files_to_clear:
|
for fname in self._files_to_clear:
|
||||||
fname.stop()
|
fname.stop()
|
||||||
|
|
||||||
os.environ = self._old_environ
|
reset_os_environ(self._old_environ)
|
||||||
|
|
||||||
def patch_configuration(self, variant, di):
|
def patch_configuration(self, variant, di):
|
||||||
old = self.configuration._load_config_files
|
old = self.configuration._load_config_files
|
||||||
|
|
|
@ -6,6 +6,7 @@ import os
|
||||||
from pip._internal.cli import cmdoptions
|
from pip._internal.cli import cmdoptions
|
||||||
from pip._internal.cli.base_command import Command
|
from pip._internal.cli.base_command import Command
|
||||||
from pip._internal.commands import commands_dict
|
from pip._internal.commands import commands_dict
|
||||||
|
from tests.lib.configuration_helpers import reset_os_environ
|
||||||
|
|
||||||
|
|
||||||
class FakeCommand(Command):
|
class FakeCommand(Command):
|
||||||
|
@ -28,5 +29,5 @@ class AddFakeCommandMixin(object):
|
||||||
commands_dict[FakeCommand.name] = FakeCommand
|
commands_dict[FakeCommand.name] = FakeCommand
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
os.environ = self.environ_before
|
reset_os_environ(self.environ_before)
|
||||||
commands_dict.pop(FakeCommand.name)
|
commands_dict.pop(FakeCommand.name)
|
||||||
|
|
|
@ -185,11 +185,11 @@ class TestUserDataDir:
|
||||||
monkeypatch.setattr(sys, "platform", "darwin")
|
monkeypatch.setattr(sys, "platform", "darwin")
|
||||||
|
|
||||||
if os.path.isdir('/home/test/Library/Application Support/'):
|
if os.path.isdir('/home/test/Library/Application Support/'):
|
||||||
assert (appdirs.user_data_dir("pip") ==
|
assert (appdirs.user_data_dir("pip") ==
|
||||||
"/home/test/Library/Application Support/pip")
|
"/home/test/Library/Application Support/pip")
|
||||||
else:
|
else:
|
||||||
assert (appdirs.user_data_dir("pip") ==
|
assert (appdirs.user_data_dir("pip") ==
|
||||||
"/home/test/.config/pip")
|
"/home/test/.config/pip")
|
||||||
|
|
||||||
def test_user_data_dir_linux(self, monkeypatch):
|
def test_user_data_dir_linux(self, monkeypatch):
|
||||||
monkeypatch.setattr(appdirs, "WINDOWS", False)
|
monkeypatch.setattr(appdirs, "WINDOWS", False)
|
||||||
|
@ -267,11 +267,11 @@ class TestUserConfigDir:
|
||||||
monkeypatch.setattr(sys, "platform", "darwin")
|
monkeypatch.setattr(sys, "platform", "darwin")
|
||||||
|
|
||||||
if os.path.isdir('/home/test/Library/Application Support/'):
|
if os.path.isdir('/home/test/Library/Application Support/'):
|
||||||
assert (appdirs.user_data_dir("pip") ==
|
assert (appdirs.user_data_dir("pip") ==
|
||||||
"/home/test/Library/Application Support/pip")
|
"/home/test/Library/Application Support/pip")
|
||||||
else:
|
else:
|
||||||
assert (appdirs.user_data_dir("pip") ==
|
assert (appdirs.user_data_dir("pip") ==
|
||||||
"/home/test/.config/pip")
|
"/home/test/.config/pip")
|
||||||
|
|
||||||
def test_user_config_dir_linux(self, monkeypatch):
|
def test_user_config_dir_linux(self, monkeypatch):
|
||||||
monkeypatch.setattr(appdirs, "WINDOWS", False)
|
monkeypatch.setattr(appdirs, "WINDOWS", False)
|
||||||
|
|
|
@ -82,9 +82,7 @@ class Test_base_command_logging(object):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.old_time = time.time
|
self.old_time = time.time
|
||||||
time.time = lambda: 1547704837.4
|
time.time = lambda: 1547704837.4
|
||||||
# Robustify the tests below to the ambient timezone by setting it
|
self.old_tz = os.environ.get('TZ')
|
||||||
# explicitly here.
|
|
||||||
self.old_tz = getattr(os.environ, 'TZ', None)
|
|
||||||
os.environ['TZ'] = 'UTC'
|
os.environ['TZ'] = 'UTC'
|
||||||
# time.tzset() is not implemented on some platforms (notably, Windows).
|
# time.tzset() is not implemented on some platforms (notably, Windows).
|
||||||
if hasattr(time, 'tzset'):
|
if hasattr(time, 'tzset'):
|
||||||
|
|
|
@ -34,9 +34,7 @@ class TestIndentingFormatter(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
# Robustify the tests below to the ambient timezone by setting it
|
self.old_tz = os.environ.get('TZ')
|
||||||
# explicitly here.
|
|
||||||
self.old_tz = getattr(os.environ, 'TZ', None)
|
|
||||||
os.environ['TZ'] = 'UTC'
|
os.environ['TZ'] = 'UTC'
|
||||||
# time.tzset() is not implemented on some platforms (notably, Windows).
|
# time.tzset() is not implemented on some platforms (notably, Windows).
|
||||||
if hasattr(time, 'tzset'):
|
if hasattr(time, 'tzset'):
|
||||||
|
|
|
@ -5,8 +5,8 @@ from mock import Mock
|
||||||
|
|
||||||
import pip._internal.req.req_uninstall
|
import pip._internal.req.req_uninstall
|
||||||
from pip._internal.req.req_uninstall import (
|
from pip._internal.req.req_uninstall import (
|
||||||
UninstallPathSet, compact, compress_for_output_listing,
|
StashedUninstallPathSet, UninstallPathSet, compact,
|
||||||
compress_for_rename, uninstallation_paths,
|
compress_for_output_listing, compress_for_rename, uninstallation_paths,
|
||||||
)
|
)
|
||||||
from tests.lib import create_file
|
from tests.lib import create_file
|
||||||
|
|
||||||
|
@ -175,3 +175,96 @@ class TestUninstallPathSet(object):
|
||||||
ups.add(path1)
|
ups.add(path1)
|
||||||
ups.add(path2)
|
ups.add(path2)
|
||||||
assert ups.paths == {path1}
|
assert ups.paths == {path1}
|
||||||
|
|
||||||
|
|
||||||
|
class TestStashedUninstallPathSet(object):
|
||||||
|
WALK_RESULT = [
|
||||||
|
("A", ["B", "C"], ["a.py"]),
|
||||||
|
("A/B", ["D"], ["b.py"]),
|
||||||
|
("A/B/D", [], ["c.py"]),
|
||||||
|
("A/C", [], ["d.py", "e.py"]),
|
||||||
|
("A/E", ["F"], ["f.py"]),
|
||||||
|
("A/E/F", [], []),
|
||||||
|
("A/G", ["H"], ["g.py"]),
|
||||||
|
("A/G/H", [], ["h.py"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mock_walk(cls, root):
|
||||||
|
for dirname, subdirs, files in cls.WALK_RESULT:
|
||||||
|
dirname = os.path.sep.join(dirname.split("/"))
|
||||||
|
if dirname.startswith(root):
|
||||||
|
yield dirname[len(root) + 1:], subdirs, files
|
||||||
|
|
||||||
|
def test_compress_for_rename(self, monkeypatch):
|
||||||
|
paths = [os.path.sep.join(p.split("/")) for p in [
|
||||||
|
"A/B/b.py",
|
||||||
|
"A/B/D/c.py",
|
||||||
|
"A/C/d.py",
|
||||||
|
"A/E/f.py",
|
||||||
|
"A/G/g.py",
|
||||||
|
]]
|
||||||
|
|
||||||
|
expected_paths = [os.path.sep.join(p.split("/")) for p in [
|
||||||
|
"A/B/", # selected everything below A/B
|
||||||
|
"A/C/d.py", # did not select everything below A/C
|
||||||
|
"A/E/", # only empty folders remain under A/E
|
||||||
|
"A/G/g.py", # non-empty folder remains under A/G
|
||||||
|
]]
|
||||||
|
|
||||||
|
monkeypatch.setattr('os.walk', self.mock_walk)
|
||||||
|
|
||||||
|
actual_paths = compress_for_rename(paths)
|
||||||
|
assert set(expected_paths) == set(actual_paths)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_stash(cls, tmpdir, paths):
|
||||||
|
for dirname, subdirs, files in cls.WALK_RESULT:
|
||||||
|
root = os.path.join(tmpdir, *dirname.split("/"))
|
||||||
|
if not os.path.exists(root):
|
||||||
|
os.mkdir(root)
|
||||||
|
for d in subdirs:
|
||||||
|
os.mkdir(os.path.join(root, d))
|
||||||
|
for f in files:
|
||||||
|
with open(os.path.join(root, f), "wb"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
pathset = StashedUninstallPathSet()
|
||||||
|
|
||||||
|
paths = [os.path.join(tmpdir, *p.split('/')) for p in paths]
|
||||||
|
stashed_paths = [(p, pathset.stash(p)) for p in paths]
|
||||||
|
|
||||||
|
return pathset, stashed_paths
|
||||||
|
|
||||||
|
def test_stash(self, tmpdir):
|
||||||
|
pathset, stashed_paths = self.make_stash(tmpdir, [
|
||||||
|
"A/B/", "A/C/d.py", "A/E/", "A/G/g.py",
|
||||||
|
])
|
||||||
|
|
||||||
|
for old_path, new_path in stashed_paths:
|
||||||
|
assert not os.path.exists(old_path)
|
||||||
|
assert os.path.exists(new_path)
|
||||||
|
|
||||||
|
assert stashed_paths == pathset._moves
|
||||||
|
|
||||||
|
def test_commit(self, tmpdir):
|
||||||
|
pathset, stashed_paths = self.make_stash(tmpdir, [
|
||||||
|
"A/B/", "A/C/d.py", "A/E/", "A/G/g.py",
|
||||||
|
])
|
||||||
|
|
||||||
|
pathset.commit()
|
||||||
|
|
||||||
|
for old_path, new_path in stashed_paths:
|
||||||
|
assert not os.path.exists(old_path)
|
||||||
|
assert not os.path.exists(new_path)
|
||||||
|
|
||||||
|
def test_rollback(self, tmpdir):
|
||||||
|
pathset, stashed_paths = self.make_stash(tmpdir, [
|
||||||
|
"A/B/", "A/C/d.py", "A/E/", "A/G/g.py",
|
||||||
|
])
|
||||||
|
|
||||||
|
pathset.rollback()
|
||||||
|
|
||||||
|
for old_path, new_path in stashed_paths:
|
||||||
|
assert os.path.exists(old_path)
|
||||||
|
assert not os.path.exists(new_path)
|
||||||
|
|
|
@ -553,6 +553,10 @@ class TestTempDirectory(object):
|
||||||
"ABC.dist-info",
|
"ABC.dist-info",
|
||||||
"_+-",
|
"_+-",
|
||||||
"_package",
|
"_package",
|
||||||
|
"A......B",
|
||||||
|
"AB",
|
||||||
|
"A",
|
||||||
|
"2",
|
||||||
])
|
])
|
||||||
def test_adjacent_directory_names(self, name):
|
def test_adjacent_directory_names(self, name):
|
||||||
def names():
|
def names():
|
||||||
|
@ -566,15 +570,82 @@ class TestTempDirectory(object):
|
||||||
# result that works, provided there are many of those
|
# result that works, provided there are many of those
|
||||||
# and that shorter names result in totally unique sets,
|
# and that shorter names result in totally unique sets,
|
||||||
# it's okay to skip part of the test.)
|
# it's okay to skip part of the test.)
|
||||||
some_names = list(itertools.islice(names(), 10000))
|
some_names = list(itertools.islice(names(), 1000))
|
||||||
assert len(some_names) == len(set(some_names))
|
# We should always get at least 1000 names
|
||||||
|
assert len(some_names) == 1000
|
||||||
|
|
||||||
# Ensure original name does not appear
|
# Ensure original name does not appear early in the set
|
||||||
assert not any(n == name for n in names())
|
assert name not in some_names
|
||||||
|
|
||||||
# Check the first group are correct
|
if len(name) > 2:
|
||||||
assert all(x == y for x, y in
|
# Names should be at least 90% unique (given the infinite
|
||||||
zip(some_names, [c + name[1:] for c in chars]))
|
# range of inputs, and the possibility that generated names
|
||||||
|
# may already exist on disk anyway, this is a much cheaper
|
||||||
|
# criteria to enforce than complete uniqueness).
|
||||||
|
assert len(some_names) > 0.9 * len(set(some_names))
|
||||||
|
|
||||||
|
# Ensure the first few names are the same length as the original
|
||||||
|
same_len = list(itertools.takewhile(
|
||||||
|
lambda x: len(x) == len(name),
|
||||||
|
some_names
|
||||||
|
))
|
||||||
|
assert len(same_len) > 10
|
||||||
|
|
||||||
|
# Check the first group are correct
|
||||||
|
expected_names = ['~' + name[1:]]
|
||||||
|
expected_names.extend('~' + c + name[2:] for c in chars)
|
||||||
|
for x, y in zip(some_names, expected_names):
|
||||||
|
assert x == y
|
||||||
|
|
||||||
|
else:
|
||||||
|
# All names are going to be longer than our original
|
||||||
|
assert min(len(x) for x in some_names) > 1
|
||||||
|
|
||||||
|
# All names are going to be unqiue
|
||||||
|
assert len(some_names) == len(set(some_names))
|
||||||
|
|
||||||
|
if len(name) == 2:
|
||||||
|
# All but the first name are going to end with our original
|
||||||
|
assert all(x.endswith(name) for x in some_names[1:])
|
||||||
|
else:
|
||||||
|
# All names are going to end with our original
|
||||||
|
assert all(x.endswith(name) for x in some_names)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", [
|
||||||
|
"A",
|
||||||
|
"ABC",
|
||||||
|
"ABC.dist-info",
|
||||||
|
"_+-",
|
||||||
|
"_package",
|
||||||
|
])
|
||||||
|
def test_adjacent_directory_exists(self, name, tmpdir):
|
||||||
|
block_name, expect_name = itertools.islice(
|
||||||
|
AdjacentTempDirectory._generate_names(name), 2)
|
||||||
|
|
||||||
|
original = os.path.join(tmpdir, name)
|
||||||
|
blocker = os.path.join(tmpdir, block_name)
|
||||||
|
|
||||||
|
ensure_dir(original)
|
||||||
|
ensure_dir(blocker)
|
||||||
|
|
||||||
|
with AdjacentTempDirectory(original) as atmp_dir:
|
||||||
|
assert expect_name == os.path.split(atmp_dir.path)[1]
|
||||||
|
|
||||||
|
def test_adjacent_directory_permission_error(self, monkeypatch):
|
||||||
|
name = "ABC"
|
||||||
|
|
||||||
|
def raising_mkdir(*args, **kwargs):
|
||||||
|
raise OSError("Unknown OSError")
|
||||||
|
|
||||||
|
with TempDirectory() as tmp_dir:
|
||||||
|
original = os.path.join(tmp_dir.path, name)
|
||||||
|
|
||||||
|
ensure_dir(original)
|
||||||
|
monkeypatch.setattr("os.mkdir", raising_mkdir)
|
||||||
|
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
with AdjacentTempDirectory(original):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestGlibc(object):
|
class TestGlibc(object):
|
||||||
|
@ -653,16 +724,27 @@ class TestGetProg(object):
|
||||||
assert get_prog() == expected
|
assert get_prog() == expected
|
||||||
|
|
||||||
|
|
||||||
def test_call_subprocess_works_okay_when_just_given_nothing():
|
def test_call_subprocess_works__no_keyword_arguments():
|
||||||
try:
|
result = call_subprocess(
|
||||||
call_subprocess([sys.executable, '-c', 'print("Hello")'])
|
[sys.executable, '-c', 'print("Hello")'],
|
||||||
except Exception:
|
)
|
||||||
assert False, "Expected subprocess call to succeed"
|
assert result.rstrip() == 'Hello'
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_subprocess_works__show_stdout_true():
|
||||||
|
result = call_subprocess(
|
||||||
|
[sys.executable, '-c', 'print("Hello")'],
|
||||||
|
show_stdout=True,
|
||||||
|
)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
def test_call_subprocess_closes_stdin():
|
def test_call_subprocess_closes_stdin():
|
||||||
with pytest.raises(InstallationError):
|
with pytest.raises(InstallationError):
|
||||||
call_subprocess([sys.executable, '-c', 'input()'])
|
call_subprocess(
|
||||||
|
[sys.executable, '-c', 'input()'],
|
||||||
|
show_stdout=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('args, expected', [
|
@pytest.mark.parametrize('args, expected', [
|
||||||
|
|
|
@ -10,9 +10,12 @@ from pip._vendor.packaging.requirements import Requirement
|
||||||
|
|
||||||
from pip._internal import pep425tags, wheel
|
from pip._internal import pep425tags, wheel
|
||||||
from pip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel
|
from pip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel
|
||||||
|
from pip._internal.index import FormatControl
|
||||||
|
from pip._internal.models.link import Link
|
||||||
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
from pip._internal.utils.compat import WINDOWS
|
from pip._internal.utils.compat import WINDOWS
|
||||||
from pip._internal.utils.misc import unpack_file
|
from pip._internal.utils.misc import unpack_file
|
||||||
from tests.lib import DATA_DIR
|
from tests.lib import DATA_DIR, assert_paths_equal
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -35,6 +38,162 @@ def test_contains_egg_info(s, expected):
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def make_test_install_req(base_name=None):
|
||||||
|
"""
|
||||||
|
Return an InstallRequirement object for testing purposes.
|
||||||
|
"""
|
||||||
|
if base_name is None:
|
||||||
|
base_name = 'pendulum-2.0.4'
|
||||||
|
|
||||||
|
req = Requirement('pendulum')
|
||||||
|
link_url = (
|
||||||
|
'https://files.pythonhosted.org/packages/aa/{base_name}.tar.gz'
|
||||||
|
'#sha256=cf535d36c063575d4752af36df928882b2e0e31541b4482c97d637527'
|
||||||
|
'85f9fcb'
|
||||||
|
).format(base_name=base_name)
|
||||||
|
link = Link(
|
||||||
|
url=link_url,
|
||||||
|
comes_from='https://pypi.org/simple/pendulum/',
|
||||||
|
requires_python='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
|
||||||
|
)
|
||||||
|
req = InstallRequirement(
|
||||||
|
req=req,
|
||||||
|
comes_from=None,
|
||||||
|
constraint=False,
|
||||||
|
editable=False,
|
||||||
|
link=link,
|
||||||
|
source_dir='/tmp/pip-install-9py5m2z1/pendulum',
|
||||||
|
)
|
||||||
|
|
||||||
|
return req
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"base_name, autobuilding, cache_available, expected",
|
||||||
|
[
|
||||||
|
('pendulum-2.0.4', False, False, False),
|
||||||
|
# The following cases test autobuilding=True.
|
||||||
|
# Test _contains_egg_info() returning True.
|
||||||
|
('pendulum-2.0.4', True, True, False),
|
||||||
|
('pendulum-2.0.4', True, False, True),
|
||||||
|
# Test _contains_egg_info() returning False.
|
||||||
|
('pendulum', True, True, True),
|
||||||
|
('pendulum', True, False, True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_should_use_ephemeral_cache__issue_6197(
|
||||||
|
base_name, autobuilding, cache_available, expected,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Regression test for: https://github.com/pypa/pip/issues/6197
|
||||||
|
"""
|
||||||
|
req = make_test_install_req(base_name=base_name)
|
||||||
|
assert not req.is_wheel
|
||||||
|
assert req.link.is_artifact
|
||||||
|
|
||||||
|
format_control = FormatControl()
|
||||||
|
ephem_cache = wheel.should_use_ephemeral_cache(
|
||||||
|
req, format_control=format_control, autobuilding=autobuilding,
|
||||||
|
cache_available=cache_available,
|
||||||
|
)
|
||||||
|
assert ephem_cache is expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_command__INFO(caplog):
|
||||||
|
|
||||||
|
caplog.set_level(logging.INFO)
|
||||||
|
actual = wheel.format_command(
|
||||||
|
command_args=['arg1', 'arg2'],
|
||||||
|
command_output='output line 1\noutput line 2\n',
|
||||||
|
)
|
||||||
|
assert actual.splitlines() == [
|
||||||
|
"Command arguments: ['arg1', 'arg2']",
|
||||||
|
'Command output: [use --verbose to show]',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command_output', [
|
||||||
|
# Test trailing newline.
|
||||||
|
'output line 1\noutput line 2\n',
|
||||||
|
# Test no trailing newline.
|
||||||
|
'output line 1\noutput line 2',
|
||||||
|
])
|
||||||
|
def test_format_command__DEBUG(caplog, command_output):
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
|
actual = wheel.format_command(
|
||||||
|
command_args=['arg1', 'arg2'],
|
||||||
|
command_output=command_output,
|
||||||
|
)
|
||||||
|
assert actual.splitlines() == [
|
||||||
|
"Command arguments: ['arg1', 'arg2']",
|
||||||
|
'Command output:',
|
||||||
|
'output line 1',
|
||||||
|
'output line 2',
|
||||||
|
'-----------------------------------------',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('log_level', ['DEBUG', 'INFO'])
|
||||||
|
def test_format_command__empty_output(caplog, log_level):
|
||||||
|
caplog.set_level(log_level)
|
||||||
|
actual = wheel.format_command(
|
||||||
|
command_args=['arg1', 'arg2'],
|
||||||
|
command_output='',
|
||||||
|
)
|
||||||
|
assert actual.splitlines() == [
|
||||||
|
"Command arguments: ['arg1', 'arg2']",
|
||||||
|
'Command output: None',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def call_get_legacy_build_wheel_path(caplog, names):
|
||||||
|
req = make_test_install_req()
|
||||||
|
wheel_path = wheel.get_legacy_build_wheel_path(
|
||||||
|
names=names,
|
||||||
|
temp_dir='/tmp/abcd',
|
||||||
|
req=req,
|
||||||
|
command_args=['arg1', 'arg2'],
|
||||||
|
command_output='output line 1\noutput line 2\n',
|
||||||
|
)
|
||||||
|
return wheel_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_legacy_build_wheel_path(caplog):
|
||||||
|
actual = call_get_legacy_build_wheel_path(caplog, names=['name'])
|
||||||
|
assert_paths_equal(actual, '/tmp/abcd/name')
|
||||||
|
assert not caplog.records
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_legacy_build_wheel_path__no_names(caplog):
|
||||||
|
actual = call_get_legacy_build_wheel_path(caplog, names=[])
|
||||||
|
assert actual is None
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
record = caplog.records[0]
|
||||||
|
assert record.levelname == 'WARNING'
|
||||||
|
assert record.message.splitlines() == [
|
||||||
|
"Legacy build of wheel for 'pendulum' created no files.",
|
||||||
|
"Command arguments: ['arg1', 'arg2']",
|
||||||
|
'Command output: [use --verbose to show]',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_legacy_build_wheel_path__multiple_names(caplog):
|
||||||
|
# Deliberately pass the names in non-sorted order.
|
||||||
|
actual = call_get_legacy_build_wheel_path(
|
||||||
|
caplog, names=['name2', 'name1'],
|
||||||
|
)
|
||||||
|
assert_paths_equal(actual, '/tmp/abcd/name1')
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
record = caplog.records[0]
|
||||||
|
assert record.levelname == 'WARNING'
|
||||||
|
assert record.message.splitlines() == [
|
||||||
|
"Legacy build of wheel for 'pendulum' created more than one file.",
|
||||||
|
"Filenames (choosing first): ['name1', 'name2']",
|
||||||
|
"Command arguments: ['arg1', 'arg2']",
|
||||||
|
'Command output: [use --verbose to show]',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("console_scripts",
|
@pytest.mark.parametrize("console_scripts",
|
||||||
["pip = pip._internal.main:pip",
|
["pip = pip._internal.main:pip",
|
||||||
"pip:pip = pip._internal.main:pip"])
|
"pip:pip = pip._internal.main:pip"])
|
||||||
|
@ -82,7 +241,9 @@ def call_get_csv_rows_for_installed(tmpdir, text):
|
||||||
path = tmpdir.join('temp.txt')
|
path = tmpdir.join('temp.txt')
|
||||||
path.write(text)
|
path.write(text)
|
||||||
|
|
||||||
installed = {}
|
# Test that an installed file appearing in RECORD has its filename
|
||||||
|
# updated in the new RECORD file.
|
||||||
|
installed = {'a': 'z'}
|
||||||
changed = set()
|
changed = set()
|
||||||
generated = []
|
generated = []
|
||||||
lib_dir = '/lib/dir'
|
lib_dir = '/lib/dir'
|
||||||
|
@ -104,7 +265,7 @@ def test_get_csv_rows_for_installed(tmpdir, caplog):
|
||||||
outrows = call_get_csv_rows_for_installed(tmpdir, text)
|
outrows = call_get_csv_rows_for_installed(tmpdir, text)
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
('a', 'b', 'c'),
|
('z', 'b', 'c'),
|
||||||
('d', 'e', 'f'),
|
('d', 'e', 'f'),
|
||||||
]
|
]
|
||||||
assert outrows == expected
|
assert outrows == expected
|
||||||
|
@ -121,7 +282,7 @@ def test_get_csv_rows_for_installed__long_lines(tmpdir, caplog):
|
||||||
outrows = call_get_csv_rows_for_installed(tmpdir, text)
|
outrows = call_get_csv_rows_for_installed(tmpdir, text)
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
('a', 'b', 'c', 'd'),
|
('z', 'b', 'c', 'd'),
|
||||||
('e', 'f', 'g'),
|
('e', 'f', 'g'),
|
||||||
('h', 'i', 'j', 'k'),
|
('h', 'i', 'j', 'k'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
flake8 == 3.5.0
|
flake8 == 3.7.6
|
||||||
isort == 4.3.4
|
isort == 4.3.4
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
mypy == 0.650
|
mypy == 0.670
|
||||||
|
|
|
@ -1,2 +1,9 @@
|
||||||
setuptools
|
# Create local setuptools wheel files for testing by:
|
||||||
|
# 1. Cloning setuptools and checking out the branch of interest
|
||||||
|
# 2. Running `python3 bootstrap.py` in that directory
|
||||||
|
# 3. Running `python3 -m pip wheel --no-cache -w /tmp/setuptools_build_meta_legacy/ .`
|
||||||
|
# 4. Replacing the `setuptools` entry below with a `file:///...` URL
|
||||||
|
# (Adjust artifact directory used based on preference and operating system)
|
||||||
|
|
||||||
|
setuptools >= 40.8.0
|
||||||
wheel
|
wheel
|
||||||
|
|
Loading…
Reference in a new issue