diff options
| author | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-03-22 11:07:12 +0000 |
|---|---|---|
| committer | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-03-22 11:07:12 +0000 |
| commit | fbfc92b6d896db536469fab594064f9f3eb81204 (patch) | |
| tree | 63e9166fc363fee969acd45fcdb193b05178f5d2 | |
| parent | e267806d3764ca84eb5a5a384f9e617a53a3811d (diff) | |
| download | python-setuptools-git-fbfc92b6d896db536469fab594064f9f3eb81204.tar.gz | |
Ignore ext-modules for auto-discovery with pyproject.toml metadata
| -rw-r--r-- | setuptools/config/pyprojecttoml.py | 2 | ||||
| -rw-r--r-- | setuptools/discovery.py | 27 | ||||
| -rw-r--r-- | setuptools/tests/test_config_discovery.py | 124 |
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): |
