summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnderson Bravalheri <andersonbravalheri@gmail.com>2022-03-22 11:07:12 +0000
committerAnderson Bravalheri <andersonbravalheri@gmail.com>2022-03-22 11:07:12 +0000
commitfbfc92b6d896db536469fab594064f9f3eb81204 (patch)
tree63e9166fc363fee969acd45fcdb193b05178f5d2
parente267806d3764ca84eb5a5a384f9e617a53a3811d (diff)
downloadpython-setuptools-git-fbfc92b6d896db536469fab594064f9f3eb81204.tar.gz
Ignore ext-modules for auto-discovery with pyproject.toml metadata
-rw-r--r--setuptools/config/pyprojecttoml.py2
-rw-r--r--setuptools/discovery.py27
-rw-r--r--setuptools/tests/test_config_discovery.py124
3 files changed, 100 insertions, 53 deletions
diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py
index 834d5a35..9a7c9fe6 100644
--- a/setuptools/config/pyprojecttoml.py
+++ b/setuptools/config/pyprojecttoml.py
@@ -184,6 +184,8 @@ class _EnsurePackagesDiscovered(_expand.EnsurePackagesDiscovered):
package_dir.update(dist.package_dir or {})
dist.package_dir = package_dir # needs to be the same object
+ dist.set_defaults._ignore_ext_modules() # pyproject.toml-specific behaviour
+
# Set `py_modules` and `packages` in dist to short-circuit auto-discovery,
# but avoid overwriting empty lists purposefully set by users.
if dist.py_modules is None:
diff --git a/setuptools/discovery.py b/setuptools/discovery.py
index b444cc57..00d9065a 100644
--- a/setuptools/discovery.py
+++ b/setuptools/discovery.py
@@ -270,11 +270,24 @@ class ConfigDiscovery:
self.dist = distribution
self._called = False
self._disabled = False
+ self._skip_ext_modules = False
def _disable(self):
"""Internal API to disable automatic discovery"""
self._disabled = True
+ def _ignore_ext_modules(self):
+ """Internal API to disregard ext_modules.
+
+ Normally auto-discovery would not be triggered if ``ext_modules`` are set
+ (this is done for backward compatibility with existing packages relying on
+ ``setup.py`` or ``setup.cfg``). However, ``setuptools`` can call this function
+ to ignore given ``ext_modules`` and proceed with the auto-discovery if
+ ``packages`` and ``py_modules`` are not given (e.g. when using pyproject.toml
+ metadata).
+ """
+ self._skip_ext_modules = True
+
@property
def _root_dir(self) -> _Path:
# The best is to wait until `src_root` is set in dist, before using _root_dir.
@@ -286,7 +299,7 @@ class ConfigDiscovery:
return {}
return self.dist.package_dir
- def __call__(self, force=False, name=True):
+ def __call__(self, force=False, name=True, ignore_ext_modules=False):
"""Automatically discover missing configuration fields
and modifies the given ``distribution`` object in-place.
@@ -301,22 +314,24 @@ class ConfigDiscovery:
# Avoid overhead of multiple calls
return
- self._analyse_package_layout()
+ self._analyse_package_layout(ignore_ext_modules)
if name:
self.analyse_name() # depends on ``packages`` and ``py_modules``
self._called = True
- def _explicitly_specified(self) -> bool:
+ def _explicitly_specified(self, ignore_ext_modules: bool) -> bool:
"""``True`` if the user has specified some form of package/module listing"""
+ ignore_ext_modules = ignore_ext_modules or self._skip_ext_modules
+ ext_modules = not (self.dist.ext_modules is None or ignore_ext_modules)
return (
self.dist.packages is not None
or self.dist.py_modules is not None
- or self.dist.ext_modules is not None
+ or ext_modules
)
- def _analyse_package_layout(self) -> bool:
- if self._explicitly_specified():
+ def _analyse_package_layout(self, ignore_ext_modules: bool) -> bool:
+ if self._explicitly_specified(ignore_ext_modules):
# For backward compatibility, just try to find modules/packages
# when nothing is given
return True
diff --git a/setuptools/tests/test_config_discovery.py b/setuptools/tests/test_config_discovery.py
index 053a605b..cbfd0188 100644
--- a/setuptools/tests/test_config_discovery.py
+++ b/setuptools/tests/test_config_discovery.py
@@ -2,7 +2,6 @@ import os
import sys
from configparser import ConfigParser
from itertools import product
-from inspect import cleandoc
from setuptools.command.sdist import sdist
from setuptools.dist import Distribution
@@ -316,55 +315,86 @@ def test_discovered_package_dir_with_attr_in_pyproject_config(tmp_path):
assert dist.package_dir == {"": "src"}
-def test_skip_when_extensions_are_provided(tmp_path):
- """Ensure that auto-discovery is not triggered when the project is based on
- C-Extensions only.
- """
- # This example is based on: https://github.com/nucleic/kiwi/tree/1.4.0
- files = [
- "benchmarks/file.py",
- "docs/Makefile",
- "docs/requirements.txt",
- "docs/source/conf.py",
- "proj/header.h",
- "proj/file.py",
- "py/proj.cpp",
- "py/other.cpp",
- "py/file.py",
- "py/py.typed",
- "py/tests/test_proj.py",
- "README.rst",
- ]
- _populate_project_dir(tmp_path, files, {})
+class TestWithCExtension:
+ def _simulate_package_with_extension(self, tmp_path):
+ # This example is based on: https://github.com/nucleic/kiwi/tree/1.4.0
+ files = [
+ "benchmarks/file.py",
+ "docs/Makefile",
+ "docs/requirements.txt",
+ "docs/source/conf.py",
+ "proj/header.h",
+ "proj/file.py",
+ "py/proj.cpp",
+ "py/other.cpp",
+ "py/file.py",
+ "py/py.typed",
+ "py/tests/test_proj.py",
+ "README.rst",
+ ]
+ _populate_project_dir(tmp_path, files, {})
- pyproject = """
- [project]
- name = 'proj'
- version = '42'
- """
- (tmp_path / "pyproject.toml").write_text(cleandoc(pyproject))
+ setup_script = """
+ from setuptools import Extension, setup
+
+ ext_modules = [
+ Extension(
+ "proj",
+ ["py/proj.cpp", "py/other.cpp"],
+ include_dirs=["."],
+ language="c++",
+ ),
+ ]
+ setup(ext_modules=ext_modules)
+ """
+ (tmp_path / "setup.py").write_text(DALS(setup_script))
+
+ def test_skip_discovery_with_setupcfg_metadata(self, tmp_path):
+ """Ensure that auto-discovery is not triggered when the project is based on
+ C-extensions only, for backward compatibility.
+ """
+ self._simulate_package_with_extension(tmp_path)
+
+ pyproject = """
+ [build-system]
+ requires = []
+ build-backend = 'setuptools.build_meta'
+ """
+ (tmp_path / "pyproject.toml").write_text(DALS(pyproject))
- setup_script = """
- from setuptools import Extension, setup
+ setupcfg = """
+ [metadata]
+ name = proj
+ version = 42
+ """
+ (tmp_path / "setup.cfg").write_text(DALS(setupcfg))
- ext_modules = [
- Extension(
- "proj",
- ["py/proj.cpp", "py/other.cpp"],
- include_dirs=["."],
- language="c++",
- ),
- ]
- setup(ext_modules=ext_modules)
- """
- (tmp_path / "setup.py").write_text(cleandoc(setup_script))
- dist = _get_dist(tmp_path, {})
- assert dist.get_name() == "proj"
- assert dist.get_version() == "42"
- assert dist.py_modules is None
- assert dist.packages is None
- assert len(dist.ext_modules) == 1
- assert dist.ext_modules[0].name == "proj"
+ dist = _get_dist(tmp_path, {})
+ assert dist.get_name() == "proj"
+ assert dist.get_version() == "42"
+ assert dist.py_modules is None
+ assert dist.packages is None
+ assert len(dist.ext_modules) == 1
+ assert dist.ext_modules[0].name == "proj"
+
+ def test_dont_skip_discovery_with_pyproject_metadata(self, tmp_path):
+ """When opting-in to pyproject.toml metadata, auto-discovery will be active if
+ the package lists C-extensions, but does not configure py-modules or packages.
+
+ This way we ensure users with complex package layouts that would lead to the
+ discovery of multiple top-level modules/packages see errors and are forced to
+ explicitly set ``packages`` or ``py-modules``.
+ """
+ self._simulate_package_with_extension(tmp_path)
+
+ pyproject = """
+ [project]
+ name = 'proj'
+ version = '42'
+ """
+ (tmp_path / "pyproject.toml").write_text(DALS(pyproject))
+ with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
+ _get_dist(tmp_path, {})
def _populate_project_dir(root, files, options):