Repository: astropy/extension-helpers

Issue:
Support for `pyproject.toml` configuration
In this https://github.com/astropy/astropy/pull/14361#issuecomment-1419210239, it was requested that `extension-helpers` support something like:

```ini
[extension-helpers]
use_extension_helpers = true
```

configurations, but in the `pyproject.toml` instead of the `setup.cfg`. This is so that projects like `astropy` can move towards adopting [PEP621](https://peps.python.org/pep-0621/) (storing all project metadata in `pyproject.toml` instead of across the `setup.cfg` and `setup.py` files).

Discussion:
WilliamJamieson: Do we really need to support python 3.7? I'll remove my walrus...
codecov[bot]: # [Codecov](https://codecov.io/gh/astropy/extension-helpers/pull/48?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) Report
> Merging [#48](https://codecov.io/gh/astropy/extension-helpers/pull/48?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) (c3fff89) into [main](https://codecov.io/gh/astropy/extension-helpers/commit/28d24b527c5b56b92bb58e6ac7ac551d1eadc524?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) (28d24b5) will **decrease** coverage by `9.55%`.
> The diff coverage is `0.00%`.

```diff
@@            Coverage Diff             @@
##             main      #48      +/-   ##
==========================================
- Coverage   79.86%   70.32%   -9.55%     
==========================================
  Files           4        4              
  Lines         298      310      +12     
==========================================
- Hits          238      218      -20     
- Misses         60       92      +32     
```


| [Impacted Files](https://codecov.io/gh/astropy/extension-helpers/pull/48?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) | Coverage Δ | |
|---|---|---|
| [extension\_helpers/\_\_init\_\_.py](https://codecov.io/gh/astropy/extension-helpers/pull/48?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy#diff-ZXh0ZW5zaW9uX2hlbHBlcnMvX19pbml0X18ucHk=) | `24.00% <0.00%> (-22.16%)` | :arrow_down: |
| [extension\_helpers/\_utils.py](https://codecov.io/gh/astropy/extension-helpers/pull/48?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy#diff-ZXh0ZW5zaW9uX2hlbHBlcnMvX3V0aWxzLnB5) | `74.00% <0.00%> (-18.00%)` | :arrow_down: |
| [extension\_helpers/\_openmp\_helpers.py](https://codecov.io/gh/astropy/extension-helpers/pull/48?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy#diff-ZXh0ZW5zaW9uX2hlbHBlcnMvX29wZW5tcF9oZWxwZXJzLnB5) | `81.75% <0.00%> (-6.57%)` | :arrow_down: |
| [extension\_helpers/\_setup\_helpers.py](https://codecov.io/gh/astropy/extension-helpers/pull/48?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy#diff-ZXh0ZW5zaW9uX2hlbHBlcnMvX3NldHVwX2hlbHBlcnMucHk=) | `64.28% <0.00%> (-2.05%)` | :arrow_down: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy)

WilliamJamieson: The `py36` job should probably be dropped see issue #49
WilliamJamieson: I don't think the `py38-test-linuxgcc-conda` and `py39-downstream` CI failures are related. Looks like something has changed since the last time they were run.
WilliamJamieson: @astrofrog I added `tomli` as a requirement in 29be02d; however, I can easily make it an optional requirement say `extension-helpers[toml]` or something? Let me know what you prefer.
larrybradley: It would be nice to get this in a new `extension-helpers` release.  Why has this PR stalled?

@astrofrog, @WilliamJamieson - Are changes need beyond a rebase on https://github.com/astropy/extension-helpers/pull/50 ?
WilliamJamieson: I forgot all about this. I'll see if I can fix it on Monday. 

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 0df9e73..6d63f25 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,8 @@
   recommends the ``-Qopenmp`` flag rather than ``-fopenmp`` for greater
   performance.
 
+* Add support for enabling extension-helpers from pyproject.toml. [#48]
+
 1.0.0 (2022-03-16)
 ------------------
 
diff --git a/docs/using.rst b/docs/using.rst
index b6002e0..6e29ea8 100644
--- a/docs/using.rst
+++ b/docs/using.rst
@@ -45,3 +45,13 @@ It is also possible to enable extension-helpers in ``setup.cfg`` instead of
 
     [extension-helpers]
     use_extension_helpers = true
+
+Moreover, one can also enable extension-helpers in ``pyproject.toml`` by adding
+the following configuration to the ``pyproject.toml`` file::
+
+    [tool.extension-helpers]
+    use_extension_helpers = true
+
+.. note::
+  For backwards compatibility, the setting of ``use_extension_helpers`` in
+  ``setup.cfg`` will override any setting of it in ``pyproject.toml``.
diff --git a/extension_helpers/__init__.py b/extension_helpers/__init__.py
index 98f7953..d6323fc 100644
--- a/extension_helpers/__init__.py
+++ b/extension_helpers/__init__.py
@@ -11,11 +11,31 @@ def _finalize_distribution_hook(distribution):
     Entry point for setuptools which allows extension-helpers to be enabled
     from setup.cfg without the need for setup.py.
     """
+    import os
+    from pathlib import Path
+
+    import tomli
+
     config_files = distribution.find_config_files()
     if len(config_files) == 0:
         return
+
     cfg = ConfigParser()
     cfg.read(config_files[0])
-    if (cfg.has_option("extension-helpers", "use_extension_helpers") and
-            cfg.get("extension-helpers", "use_extension_helpers").lower() == 'true'):
-        distribution.ext_modules = get_extensions()
+    found_config = False
+    if cfg.has_option("extension-helpers", "use_extension_helpers"):
+        found_config = True
+
+        if cfg.get("extension-helpers", "use_extension_helpers").lower() == 'true':
+            distribution.ext_modules = get_extensions()
+
+    pyproject = Path(distribution.src_root or os.curdir, "pyproject.toml")
+    if pyproject.exists() and not found_config:
+        with pyproject.open("rb") as f:
+            pyproject_cfg = tomli.load(f)
+            if ('tool' in pyproject_cfg and
+                    'extension-helpers' in pyproject_cfg['tool'] and
+                    'use_extension_helpers' in pyproject_cfg['tool']['extension-helpers'] and
+                    pyproject_cfg['tool']['extension-helpers']['use_extension_helpers']):
+
+                distribution.ext_modules = get_extensions()
diff --git a/setup.cfg b/setup.cfg
index 5769b70..93c9b8f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,6 +26,7 @@ python_requires = >=3.7
 packages = find:
 install_requires =
     setuptools>=40.2
+    tomli>=1.0.0
 
 [options.package_data]
 extension_helpers = src/compiler.c


Test patch:
diff --git a/extension_helpers/tests/test_setup_helpers.py b/extension_helpers/tests/test_setup_helpers.py
index 05fc7ab..8eeea0c 100644
--- a/extension_helpers/tests/test_setup_helpers.py
+++ b/extension_helpers/tests/test_setup_helpers.py
@@ -184,7 +184,8 @@ def test_compiler_module(capsys, c_extension_test_package):
 
 
 @pytest.mark.parametrize('use_extension_helpers', [None, False, True])
-def test_no_setup_py(tmpdir, use_extension_helpers):
+@pytest.mark.parametrize('pyproject_use_helpers', [None, False, True])
+def test_no_setup_py(tmpdir, use_extension_helpers, pyproject_use_helpers):
     """
     Test that makes sure that extension-helpers can be enabled without a
     setup.py file.
@@ -242,12 +243,23 @@ def test_no_setup_py(tmpdir, use_extension_helpers):
             use_extension_helpers = {str(use_extension_helpers).lower()}
         """))
 
-    test_pkg.join('pyproject.toml').write(dedent("""\
-        [build-system]
-        requires = ["setuptools>=43.0.0",
-                    "wheel"]
-        build-backend = 'setuptools.build_meta'
-    """))
+    if pyproject_use_helpers is None:
+        test_pkg.join('pyproject.toml').write(dedent("""\
+            [build-system]
+            requires = ["setuptools>=43.0.0",
+                        "wheel"]
+            build-backend = 'setuptools.build_meta'
+        """))
+    else:
+        test_pkg.join('pyproject.toml').write(dedent(f"""\
+            [build-system]
+            requires = ["setuptools>=43.0.0",
+                        "wheel"]
+            build-backend = 'setuptools.build_meta'
+
+            [tool.extension-helpers]
+            use_extension_helpers = {str(pyproject_use_helpers).lower()}
+        """))
 
     install_temp = test_pkg.mkdir('install_temp')
 
@@ -267,7 +279,7 @@ def test_no_setup_py(tmpdir, use_extension_helpers):
 
         importlib.import_module(package_name)
 
-        if use_extension_helpers:
+        if use_extension_helpers or (use_extension_helpers is None and pyproject_use_helpers):
             compiler_version_mod = importlib.import_module(package_name + '.compiler_version')
             assert compiler_version_mod.compiler != 'unknown'
         else: