Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start warning on non compliant pyproject.toml files and rejecting incorrect ones #5512

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/5416.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Start refusing to install packages with non PEP-518 compliant pyproject.toml
1 change: 1 addition & 0 deletions news/5512.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Start refusing to install packages with non PEP-518 compliant pyproject.toml
42 changes: 22 additions & 20 deletions src/pip/_internal/operations/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,31 +95,33 @@ def dist(self, finder):
def prep_for_dist(self, finder, build_isolation):
# Before calling "setup.py egg_info", we need to set-up the build
# environment.
build_requirements, isolate = self.req.get_pep_518_info()
should_isolate = build_isolation and isolate

minimum_requirements = ('setuptools', 'wheel')
missing_requirements = set(minimum_requirements) - set(
pkg_resources.Requirement(r).key
for r in build_requirements
)
if missing_requirements:
def format_reqs(rs):
return ' and '.join(map(repr, sorted(rs)))
logger.warning(
"Missing build time requirements in pyproject.toml for %s: "
"%s.", self.req, format_reqs(missing_requirements)
)
logger.warning(
"This version of pip does not implement PEP 517 so it cannot "
"build a wheel without %s.", format_reqs(minimum_requirements)
)
build_requirements = self.req.get_pep_518_info()
should_isolate = build_isolation and build_requirements is not None

if should_isolate:
# Haven't implemented PEP 517 yet, so spew a warning about it if
# build-requirements don't include setuptools and wheel.
missing_requirements = {'setuptools', 'wheel'} - {
pkg_resources.Requirement(r).key for r in build_requirements
}
if missing_requirements:
logger.warning(
"Missing build requirements in pyproject.toml for %s.",
self.req,
)
logger.warning(
"This version of pip does not implement PEP 517 so it "
"cannot build a wheel without %s.",
" and ".join(map(repr, sorted(missing_requirements)))
)

# Isolate in a BuildEnvironment and install the build-time
# requirements.
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
finder, build_requirements,
"Installing build dependencies")
"Installing build dependencies"
)

self.req.run_egg_info()
self.req.assert_source_matches_version()
Expand Down
59 changes: 46 additions & 13 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
)
from pip._internal.req.req_uninstall import UninstallPathSet
from pip._internal.utils.deprecation import RemovedInPip11Warning
from pip._internal.utils.deprecation import (
RemovedInPip11Warning, RemovedInPip12Warning,
)
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
Expand Down Expand Up @@ -560,20 +562,51 @@ def pyproject_toml(self):
return pp_toml

def get_pep_518_info(self):
"""Get a list of the packages required to build the project, if any,
and a flag indicating whether pyproject.toml is present, indicating
that the build should be isolated.
"""Get PEP 518 build-time requirements.

Build requirements can be specified in a pyproject.toml, as described
in PEP 518. If this file exists but doesn't specify build
requirements, pip will default to installing setuptools and wheel.
Returns the list of the packages required to build the project,
specified as per PEP 518 within the package. If `pyproject.toml` is not
present, returns None to signify not using the same.
"""
if os.path.isfile(self.pyproject_toml):
with io.open(self.pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)
build_sys = pp_toml.get('build-system', {})
return (build_sys.get('requires', ['setuptools', 'wheel']), True)
return (['setuptools', 'wheel'], False)
if not os.path.isfile(self.pyproject_toml):
return None

with io.open(self.pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)

# Extract the build requirements
requires = pp_toml.get("build-system", {}).get("requires", None)

template = (
"%s does not comply with PEP 518 since pyproject.toml "
"does not contain a valid '[build-system].requires' key: %s"
)

if requires is None:
logging.warn(template, self, "it is missing.")
warnings.warn(
"Future versions of pip will reject packages with "
"pyproject.toml files that do not comply with PEP 518.",
RemovedInPip12Warning,
)

# NOTE: Currently allowing projects to skip this key so that they
# can transition to a PEP 518 compliant pyproject.toml or
# push to update the PEP.
# Come pip 19.0, bring this to compliance with PEP 518.
return None
else:
# Error out if it's not a list of strings
is_list_of_str = isinstance(requires, list) and all(
isinstance(req, six.string_types) for req in requires
)
if not is_list_of_str:
raise InstallationError(
template % (self, "it is not a list of strings.")
)

# If control flow reaches here, we're good to go.
return requires

def run_egg_info(self):
assert self.source_dir
Expand Down
1 change: 1 addition & 0 deletions tests/data/src/pep518_invalid_requires/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include pyproject.toml
1 change: 1 addition & 0 deletions tests/data/src/pep518_invalid_requires/pep518.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#dummy
2 changes: 2 additions & 0 deletions tests/data/src/pep518_invalid_requires/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build-system]
requires = [1, 2, 3] # not a list of strings
8 changes: 8 additions & 0 deletions tests/data/src/pep518_invalid_requires/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python
from setuptools import setup

setup(
name='pep518_invalid_requires',
version='1.0.0',
py_modules=['pep518'],
)
1 change: 1 addition & 0 deletions tests/data/src/pep518_missing_requires/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include pyproject.toml
1 change: 1 addition & 0 deletions tests/data/src/pep518_missing_requires/pep518.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#dummy
Empty file.
8 changes: 8 additions & 0 deletions tests/data/src/pep518_missing_requires/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python
from setuptools import setup

setup(
name='pep518_missing_requires',
version='1.0.0',
py_modules=['pep518'],
)
22 changes: 22 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,28 @@ def test_pep518_uses_build_env(script, data, common_wheels, command, variant):
)


def test_pep518_refuses_invalid_requires(script, data, common_wheels):
result = script.pip(
'install', '-f', common_wheels,
data.src.join("pep518_invalid_requires"),
expect_error=True
)
assert result.returncode == 1
assert "does not comply with PEP 518" in result.stderr


def test_pep518_allows_but_warns_missing_requires(script, data, common_wheels):
result = script.pip(
'install', '-f', common_wheels,
data.src.join("pep518_missing_requires"),
expect_stderr=True
)
assert "does not comply with PEP 518" in result.stderr
assert "DEPRECATION" in result.stderr
assert result.returncode == 0
assert result.files_created


def test_pep518_with_user_pip(script, virtualenv, pip_src,
data, common_wheels):
virtualenv.system_site_packages = True
Expand Down