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

Merge pull request #6449 from cjerdonek/restore-pyproject-toml-19.0.3

Restore pyproject.toml handling to its version 19.0.3 state
This commit is contained in:
Chris Jerdonek 2019-04-29 13:33:33 -07:00 committed by GitHub
commit f8732ac06d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 459 deletions

View file

@ -1,7 +1,2 @@
Allow ``--no-use-pep517`` to be used as a work-around when installing a Restore ``pyproject.toml`` handling to how it was with pip 19.0.3 to prevent
project in editable mode, even when `PEP 517 the need to add ``--no-use-pep517`` when installing in editable mode.
<https://www.python.org/dev/peps/pep-0517/>`_ mandates
``pyproject.toml``-style processing (i.e. when the project has a
``pyproject.toml`` file as well as a ``"build-backend"`` key for the
``[build-system]`` table). Since this option conflicts with the PEP 517 spec,
this mode of operation is officially unsupported.

View file

@ -136,11 +136,7 @@ class IsSDist(DistAbstraction):
# 2. Set up the build environment # 2. Set up the build environment
self.req.load_pyproject_toml() self.req.load_pyproject_toml()
should_isolate = self.req.use_pep517 and build_isolation
should_isolate = (
(self.req.use_pep517 or self.req.pyproject_requires) and
build_isolation
)
if should_isolate: if should_isolate:
# Isolate in a BuildEnvironment and install the build-time # Isolate in a BuildEnvironment and install the build-time
@ -167,12 +163,10 @@ class IsSDist(DistAbstraction):
" and ".join(map(repr, sorted(missing))) " and ".join(map(repr, sorted(missing)))
) )
if self.req.use_pep517: # Install any extra build dependencies that the backend requests.
# If we're using PEP 517, then install any extra build # This must be done in a second pass, as the pyproject.toml
# dependencies that the backend requested. This must be # dependencies must be installed before we can call the backend.
# done in a second pass, as the pyproject.toml dependencies self.install_backend_dependencies(finder=finder)
# must be installed before we can call the backend.
self.install_backend_dependencies(finder=finder)
self.req.prepare_metadata() self.req.prepare_metadata()
self.req.assert_source_matches_version() self.req.assert_source_matches_version()

View file

@ -1,7 +1,6 @@
from __future__ import absolute_import from __future__ import absolute_import
import io import io
import logging
import os import os
import sys import sys
@ -11,12 +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, Dict, List, Optional, Tuple from typing import Any, Tuple, Optional, List
Pep517Data = Tuple[str, List[str]]
logger = logging.getLogger(__name__)
def _is_list_of_str(obj): def _is_list_of_str(obj):
@ -38,42 +32,100 @@ def make_pyproject_path(setup_py_dir):
return path return path
def read_pyproject_toml(path): def load_pyproject_toml(
# type: (str) -> Optional[Dict[str, str]] use_pep517, # type: Optional[bool]
pyproject_toml, # type: str
setup_py, # type: str
req_name # type: str
):
# type: (...) -> Optional[Tuple[List[str], str, List[str]]]
"""Load the pyproject.toml file.
Parameters:
use_pep517 - Has the user requested PEP 517 processing? None
means the user hasn't explicitly specified.
pyproject_toml - Location of the project's pyproject.toml file
setup_py - Location of the project's setup.py file
req_name - The name of the requirement we're processing (for
error reporting)
Returns:
None if we should use the legacy code path, otherwise a tuple
(
requirements from pyproject.toml,
name of PEP 517 backend,
requirements we should check are installed after setting
up the build environment
)
""" """
Read a project's pyproject.toml file. has_pyproject = os.path.isfile(pyproject_toml)
has_setup = os.path.isfile(setup_py)
:param path: The path to the pyproject.toml file. if has_pyproject:
with io.open(pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)
build_system = pp_toml.get("build-system")
else:
build_system = None
:return: The "build_system" value specified in the project's # The following cases must use PEP 517
pyproject.toml file. # We check for use_pep517 being non-None and falsey because that means
""" # the user explicitly requested --no-use-pep517. The value 0 as
with io.open(path, encoding="utf-8") as f: # opposed to False can occur when the value is provided via an
pp_toml = pytoml.load(f) # environment variable or config file option (due to the quirk of
build_system = pp_toml.get("build-system") # strtobool() returning an integer in pip's configuration code).
if has_pyproject and not has_setup:
if use_pep517 is not None and not use_pep517:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project does not have a setup.py"
)
use_pep517 = True
elif build_system and "build-backend" in build_system:
if use_pep517 is not None and not use_pep517:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project specifies a build backend of {} "
"in pyproject.toml".format(
build_system["build-backend"]
)
)
use_pep517 = True
return build_system # If we haven't worked out whether to use PEP 517 yet,
# and the user hasn't explicitly stated a preference,
# we do so if the project has a pyproject.toml file.
elif use_pep517 is None:
use_pep517 = has_pyproject
# At this point, we know whether we're going to use PEP 517.
assert use_pep517 is not None
def make_editable_error(req_name, reason): # If we're using the legacy code path, there is nothing further
""" # for us to do here.
:param req_name: the name of the requirement. if not use_pep517:
:param reason: the reason the requirement is being processed as
pyproject.toml-style.
"""
message = (
'Error installing {!r}: editable mode is not supported for '
'pyproject.toml-style projects. This project is being processed '
'as pyproject.toml-style because {}. '
'See PEP 517 for the relevant specification.'
).format(req_name, reason)
return InstallationError(message)
def get_build_system_requires(build_system, req_name):
if build_system is None:
return None return None
if build_system is None:
# Either the user has a pyproject.toml with no build-system
# section, or the user has no pyproject.toml, but has opted in
# explicitly via --use-pep517.
# In the absence of any explicit backend specification, we
# assume the setuptools backend that most closely emulates the
# traditional direct setup.py execution, and require wheel and
# a version of setuptools that supports that backend.
build_system = {
"requires": ["setuptools>=40.8.0", "wheel"],
"build-backend": "setuptools.build_meta:__legacy__",
}
# If we're using PEP 517, we have build system information (either
# from pyproject.toml, or defaulted by the code above).
# Note that at this point, we do not know if the user has actually
# specified a backend, though.
assert build_system is not None
# Ensure that the build-system section in pyproject.toml conforms # Ensure that the build-system section in pyproject.toml conforms
# to PEP 518. # to PEP 518.
error_template = ( error_template = (
@ -98,150 +150,6 @@ def get_build_system_requires(build_system, req_name):
reason="'build-system.requires' is not a list of strings.", reason="'build-system.requires' is not a list of strings.",
)) ))
return requires
def resolve_pyproject_toml(
build_system, # type: Optional[Dict[str, Any]]
has_pyproject, # type: bool
has_setup, # type: bool
use_pep517, # type: Optional[bool]
editable, # type: bool
req_name, # type: str
):
# type: (...) -> Tuple[Optional[List[str]], Optional[Pep517Data]]
"""
Return how a pyproject.toml file's contents should be interpreted.
:param build_system: the "build_system" value specified in a project's
pyproject.toml file, or None if the project either doesn't have the
file or does but the file doesn't have a [build-system] table.
:param has_pyproject: whether the project has a pyproject.toml file.
:param has_setup: whether the project has a setup.py file.
:param use_pep517: whether the user requested PEP 517 processing. None
means the user didn't explicitly specify.
:param editable: whether editable mode was requested for the requirement.
:param req_name: the name of the requirement we're processing (for
error reporting).
:return: a tuple (requires, pep517_data), where `requires` is the list
of build requirements from pyproject.toml (or else None). The value
`pep517_data` is None if `use_pep517` is False. Otherwise, it is the
tuple (backend, check), where `backend` is the name of the PEP 517
backend and `check` is the list of requirements we should check are
installed after setting up the build environment.
"""
# The following cases must use PEP 517
# We check for use_pep517 being non-None and falsey because that means
# the user explicitly requested --no-use-pep517. The value 0 as
# opposed to False can occur when the value is provided via an
# environment variable or config file option (due to the quirk of
# strtobool() returning an integer in pip's configuration code).
if editable and use_pep517:
raise make_editable_error(
req_name, 'PEP 517 processing was explicitly requested'
)
elif has_pyproject and not has_setup:
if use_pep517 is not None and not use_pep517:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project does not have a setup.py"
)
if editable:
raise make_editable_error(
req_name, 'it has a pyproject.toml file and no setup.py'
)
use_pep517 = True
elif build_system and "build-backend" in build_system:
if editable:
if use_pep517 is None:
message = (
'Error installing {!r}: editable mode is not supported '
'for pyproject.toml-style projects. '
'This project is pyproject.toml-style because it has a '
'pyproject.toml file and a "build-backend" key for the '
'[build-system] table, but editable mode is undefined '
'for pyproject.toml-style projects. '
'Since the project has a setup.py, you may pass '
'--no-use-pep517 to opt out of pyproject.toml-style '
'processing. However, this is an unsupported combination. '
'See PEP 517 for details on pyproject.toml-style projects.'
).format(req_name)
raise InstallationError(message)
# The case of `editable and use_pep517` being true was already
# handled above.
assert not use_pep517
message = (
'Installing {!r} in editable mode, which is not supported '
'for pyproject.toml-style projects: '
'this project is pyproject.toml-style because it has a '
'pyproject.toml file and a "build-backend" key for the '
'[build-system] table, but editable mode is undefined '
'for pyproject.toml-style projects. '
'See PEP 517 for details on pyproject.toml-style projects.'
).format(req_name)
logger.warning(message)
elif use_pep517 is not None and not use_pep517:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project specifies a build backend of {} "
"in pyproject.toml".format(
build_system["build-backend"]
)
)
else:
use_pep517 = True
# If we haven't worked out whether to use PEP 517 yet, and the user
# hasn't explicitly stated a preference, we do so if the project has
# a pyproject.toml file (provided editable mode wasn't requested).
elif use_pep517 is None:
if has_pyproject and editable:
message = (
'Error installing {!r}: editable mode is not supported for '
'pyproject.toml-style projects. pip is processing this '
'project as pyproject.toml-style because it has a '
'pyproject.toml file. Since the project has a setup.py and '
'the pyproject.toml has no "build-backend" key for the '
'[build-system] table, you may pass --no-use-pep517 to opt '
'out of pyproject.toml-style processing. '
'See PEP 517 for details on pyproject.toml-style projects.'
).format(req_name)
raise InstallationError(message)
use_pep517 = has_pyproject
# At this point, we know whether we're going to use PEP 517.
assert use_pep517 is not None
requires = get_build_system_requires(build_system, req_name=req_name)
# If we're using the legacy code path, there is nothing further
# for us to do here.
if not use_pep517:
return (requires, None)
if build_system is None:
# Either the user has a pyproject.toml with no build-system
# section, or the user has no pyproject.toml, but has opted in
# explicitly via --use-pep517.
# In the absence of any explicit backend specification, we
# assume the setuptools backend that most closely emulates the
# traditional direct setup.py execution, and require wheel and
# a version of setuptools that supports that backend.
requires = ["setuptools>=40.8.0", "wheel"]
build_system = {
"build-backend": "setuptools.build_meta:__legacy__",
}
# If we're using PEP 517, we have build system information (either
# from pyproject.toml, or defaulted by the code above).
# Note that at this point, we do not know if the user has actually
# specified a backend, though.
assert build_system is not None
backend = build_system.get("build-backend") backend = build_system.get("build-backend")
check = [] # type: List[str] check = [] # type: List[str]
if backend is None: if backend is None:
@ -260,50 +168,4 @@ def resolve_pyproject_toml(
backend = "setuptools.build_meta:__legacy__" backend = "setuptools.build_meta:__legacy__"
check = ["setuptools>=40.8.0", "wheel"] check = ["setuptools>=40.8.0", "wheel"]
return (requires, (backend, check)) return (requires, backend, check)
def load_pyproject_toml(
use_pep517, # type: Optional[bool]
editable, # type: bool
pyproject_toml, # type: str
setup_py, # type: str
req_name # type: str
):
# type: (...) -> Tuple[Optional[List[str]], Optional[Pep517Data]]
"""Load the pyproject.toml file.
Parameters:
use_pep517 - Has the user requested PEP 517 processing? None
means the user hasn't explicitly specified.
editable - Whether editable mode was requested for the requirement.
pyproject_toml - Location of the project's pyproject.toml file
setup_py - Location of the project's setup.py file
req_name - The name of the requirement we're processing (for
error reporting)
Returns: (requires, pep_517_data)
requires: requirements from pyproject.toml (can be None).
pep_517_data: None if we should use the legacy code path, otherwise:
(
name of PEP 517 backend,
requirements we should check are installed after setting up
the build environment
)
"""
has_pyproject = os.path.isfile(pyproject_toml)
has_setup = os.path.isfile(setup_py)
if has_pyproject:
build_system = read_pyproject_toml(pyproject_toml)
else:
build_system = None
return resolve_pyproject_toml(
build_system=build_system,
has_pyproject=has_pyproject,
has_setup=has_setup,
use_pep517=use_pep517,
editable=editable,
req_name=req_name,
)

View file

@ -485,22 +485,20 @@ class InstallRequirement(object):
use_pep517 attribute can be used to determine whether we should use_pep517 attribute can be used to determine whether we should
follow the PEP 517 or legacy (setup.py) code path. follow the PEP 517 or legacy (setup.py) code path.
""" """
requires, pep517_data = load_pyproject_toml( pep517_data = load_pyproject_toml(
self.use_pep517, self.use_pep517,
self.editable,
self.pyproject_toml, self.pyproject_toml,
self.setup_py, self.setup_py,
str(self) str(self)
) )
use_pep517 = bool(pep517_data) if pep517_data is None:
self.use_pep517 = False
self.use_pep517 = use_pep517 else:
self.pyproject_requires = requires self.use_pep517 = True
requires, backend, check = pep517_data
if use_pep517:
backend, check = pep517_data
self.requirements_to_check = check self.requirements_to_check = check
self.pyproject_requires = requires
self.pep517_backend = Pep517HookCaller(self.setup_py_dir, backend) self.pep517_backend = Pep517HookCaller(self.setup_py_dir, backend)
# Use a custom function to call subprocesses # Use a custom function to call subprocesses

View file

@ -302,12 +302,8 @@ def test_constraints_local_editable_install_pep518(script, data):
to_install = data.src.join("pep518-3.0") to_install = data.src.join("pep518-3.0")
script.pip('download', 'setuptools', 'wheel', '-d', data.packages) script.pip('download', 'setuptools', 'wheel', '-d', data.packages)
# --no-use-pep517 has to be passed since a pyproject.toml file is
# present but PEP 517 doesn't support editable mode.
script.pip( script.pip(
'install', '--no-use-pep517', '--no-index', '-f', data.find_links, 'install', '--no-index', '-f', data.find_links, '-e', to_install)
'-e', to_install,
)
def test_constraints_local_install_causes_error(script, data): def test_constraints_local_install_causes_error(script, data):

View file

@ -1,210 +1,9 @@
from contextlib import contextmanager
import pytest import pytest
from pip._internal.exceptions import InstallationError from pip._internal.exceptions import InstallationError
from pip._internal.pyproject import resolve_pyproject_toml
from pip._internal.req import InstallRequirement from pip._internal.req import InstallRequirement
@contextmanager
def assert_error_startswith(exc_type, expected_start, expected_substr):
with pytest.raises(InstallationError) as excinfo:
yield
err_args = excinfo.value.args
assert len(err_args) == 1
msg = err_args[0]
assert msg.startswith(expected_start), 'full message: {}'.format(msg)
assert expected_substr in msg, 'full message: {}'.format(msg)
def test_resolve_pyproject_toml__pep_517_optional():
"""
Test resolve_pyproject_toml() when has_pyproject=True but the source
tree isn't pyproject.toml-style per PEP 517.
"""
actual = resolve_pyproject_toml(
build_system=None,
has_pyproject=True,
has_setup=True,
use_pep517=None,
editable=False,
req_name='my-package',
)
expected = (
['setuptools>=40.8.0', 'wheel'],
('setuptools.build_meta:__legacy__', []),
)
assert actual == expected
def test_resolve_pyproject_toml__editable_with_build_system_requires():
"""
Test a case of editable=True when build-system.requires are returned.
"""
actual = resolve_pyproject_toml(
build_system={'requires': ['package-a', 'package=b']},
has_pyproject=True,
has_setup=True,
use_pep517=False,
editable=True,
req_name='my-package',
)
expected = (['package-a', 'package=b'], None)
assert actual == expected
def test_resolve_pyproject_toml__editable_without_use_pep517_false():
"""
Test passing editable=True when PEP 517 processing is optional, but
use_pep517=False hasn't been passed.
"""
expected_start = (
"Error installing 'my-package': editable mode is not supported"
)
expected_substr = (
'you may pass --no-use-pep517 to opt out of pyproject.toml-style '
'processing'
)
with assert_error_startswith(
InstallationError, expected_start, expected_substr=expected_substr,
):
resolve_pyproject_toml(
build_system=None,
has_pyproject=True,
has_setup=True,
use_pep517=None,
editable=True,
req_name='my-package',
)
@pytest.mark.parametrize(
'has_pyproject, has_setup, use_pep517, build_system, expected_err', [
# Test pyproject.toml with no setup.py.
(True, False, None, None, 'has a pyproject.toml file and no setup.py'),
# Test explicitly requesting PEP 517 processing.
(True, True, True, None,
'PEP 517 processing was explicitly requested'),
]
)
def test_resolve_pyproject_toml__editable_and_pep_517_required(
has_pyproject, has_setup, use_pep517, build_system, expected_err,
):
"""
Test that passing editable=True raises an error if PEP 517 processing
is required.
"""
expected_start = (
"Error installing 'my-package': editable mode is not supported"
)
with assert_error_startswith(
InstallationError, expected_start, expected_substr=expected_err,
):
resolve_pyproject_toml(
build_system=build_system,
has_pyproject=has_pyproject,
has_setup=has_setup,
use_pep517=use_pep517,
editable=True,
req_name='my-package',
)
def test_resolve_pyproject_toml__editable_build_backend_use_pep517_none():
"""
Test editable=True with "build-backend" and use_pep517=None.
"""
expected_start = (
"Error installing 'my-package': editable mode is not supported "
)
expected_substr = 'you may pass --no-use-pep517 to opt out'
with assert_error_startswith(
InstallationError, expected_start, expected_substr=expected_substr,
):
resolve_pyproject_toml(
build_system={'requires': ['my-package'], 'build-backend': 'foo'},
has_pyproject=True,
has_setup=True,
use_pep517=None,
editable=True,
req_name='my-package',
)
def test_resolve_pyproject_toml__editable_build_backend_use_pep517_false(
caplog
):
"""
Test editable=True with "build-backend" and use_pep517=False.
"""
resolve_pyproject_toml(
build_system={'requires': ['my-package'], 'build-backend': 'foo'},
has_pyproject=True,
has_setup=True,
use_pep517=False,
editable=True,
req_name='my-package',
)
records = caplog.records
assert len(records) == 1
record = records[0]
assert record.levelname == 'WARNING'
expected_start = (
"Installing 'my-package' in editable mode, which is not supported "
)
assert record.message.startswith(expected_start), (
'full message: {}'.format(record.message)
)
@pytest.mark.parametrize(
'has_pyproject, has_setup, use_pep517, editable, build_system, '
'expected_err', [
# Test editable=False with no build-system.requires.
(True, False, None, False, {},
"it has a 'build-system' table but not 'build-system.requires'"),
# Test editable=True with no build-system.requires (we also
# need to pass use_pep517=False).
(True, True, False, True, {},
"it has a 'build-system' table but not 'build-system.requires'"),
# Test editable=False with a bad build-system.requires value.
(True, False, None, False, {'requires': 'foo'},
"'build-system.requires' is not a list of strings"),
# Test editable=True with a bad build-system.requires value (we also
# need to pass use_pep517=False).
(True, True, False, True, {'requires': 'foo'},
"'build-system.requires' is not a list of strings"),
]
)
def test_resolve_pyproject_toml__bad_build_system_section(
has_pyproject, has_setup, use_pep517, editable, build_system,
expected_err,
):
"""
Test a pyproject.toml build-system section that doesn't conform to
PEP 518.
"""
expected_start = (
'my-package has a pyproject.toml file that does not comply with '
'PEP 518:'
)
with assert_error_startswith(
InstallationError, expected_start, expected_substr=expected_err,
):
resolve_pyproject_toml(
build_system=build_system,
has_pyproject=has_pyproject,
has_setup=has_setup,
use_pep517=use_pep517,
editable=editable,
req_name='my-package',
)
@pytest.mark.parametrize(('source', 'expected'), [ @pytest.mark.parametrize(('source', 'expected'), [
("pep517_setup_and_pyproject", True), ("pep517_setup_and_pyproject", True),
("pep517_setup_only", False), ("pep517_setup_only", False),