diff --git a/news/5627.bugfix b/news/5627.bugfix new file mode 100644 index 000000000..d26364d8b --- /dev/null +++ b/news/5627.bugfix @@ -0,0 +1 @@ +Disallow packages with ``pyproject.toml`` files that have an empty build-system table. diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index b9b6ab3a0..462c80aa1 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -564,35 +564,40 @@ class InstallRequirement(object): specified as per PEP 518 within the package. If `pyproject.toml` is not present, returns None to signify not using the same. """ + # If pyproject.toml does not exist, don't do anything. if not os.path.isfile(self.pyproject_toml): return None + error_template = ( + "{package} has a pyproject.toml file that does not comply " + "with PEP 518: {reason}" + ) + 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) - - if requires is None: - # We isolate on the presence of the pyproject.toml file. - # If build-system.requires is not specified, treat it as if it was - # specified as ["setuptools", "wheel"] + # If there is no build-system table, just use setuptools and wheel. + if "build-system" not in pp_toml: return ["setuptools", "wheel"] - 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: - template = ( - "{} does not comply with PEP 518 since pyproject.toml " - "does not contain a valid build-system.requires key: {}" - ) - raise InstallationError( - template.format(self, "it is not a list of strings.") - ) - # If control flow reaches here, we're good to go. + # Specifying the build-system table but not the requires key is invalid + build_system = pp_toml["build-system"] + if "requires" not in build_system: + raise InstallationError( + error_template.format(package=self, reason=( + "it has a 'build-system' table but not " + "'build-system.requires' which is mandatory in the table" + )) + ) + + # Error out if it's not a list of strings + requires = build_system["requires"] + if not _is_list_of_str(requires): + raise InstallationError(error_template.format( + package=self, + reason="'build-system.requires' is not a list of strings.", + )) + return requires def run_egg_info(self): @@ -1128,3 +1133,10 @@ def deduce_helpful_msg(req): else: msg += " File '%s' does not exist." % (req) return msg + + +def _is_list_of_str(obj): + return ( + isinstance(obj, list) and + all(isinstance(item, six.string_types) for item in obj) + ) diff --git a/tests/data/src/pep518_invalid_build_system/MANIFEST.in b/tests/data/src/pep518_invalid_build_system/MANIFEST.in new file mode 100644 index 000000000..bec201fc8 --- /dev/null +++ b/tests/data/src/pep518_invalid_build_system/MANIFEST.in @@ -0,0 +1 @@ +include pyproject.toml diff --git a/tests/data/src/pep518_invalid_build_system/pep518.py b/tests/data/src/pep518_invalid_build_system/pep518.py new file mode 100644 index 000000000..7986d1137 --- /dev/null +++ b/tests/data/src/pep518_invalid_build_system/pep518.py @@ -0,0 +1 @@ +#dummy diff --git a/tests/data/src/pep518_invalid_build_system/pyproject.toml b/tests/data/src/pep518_invalid_build_system/pyproject.toml new file mode 100644 index 000000000..86fcebfa8 --- /dev/null +++ b/tests/data/src/pep518_invalid_build_system/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +# This table is intentionally empty. diff --git a/tests/data/src/pep518_invalid_build_system/setup.py b/tests/data/src/pep518_invalid_build_system/setup.py new file mode 100644 index 000000000..ba23cf24a --- /dev/null +++ b/tests/data/src/pep518_invalid_build_system/setup.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +from setuptools import setup + +setup( + name='pep518_invalid_build_system', + version='1.0.0', + py_modules=['pep518'], +) diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 9ccaf9e8c..5767cdd37 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -47,6 +47,16 @@ def test_pep518_refuses_invalid_requires(script, data, common_wheels): assert "does not comply with PEP 518" in result.stderr +def test_pep518_refuses_invalid_build_system(script, data, common_wheels): + result = script.pip( + 'install', '-f', common_wheels, + data.src.join("pep518_invalid_build_system"), + expect_error=True + ) + assert result.returncode == 1 + assert "does not comply with PEP 518" in result.stderr + + def test_pep518_allows_missing_requires(script, data, common_wheels): result = script.pip( 'install', '-f', common_wheels,