diff --git a/docs/using.rst b/docs/using.rst index b7ddb7b..2075091 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -91,5 +91,16 @@ You can also set this option in ``pyproject.toml``, using:: although note that this option is not formally documented/supported by the Python packaging infrastructure and may change in future. -The ``get_extensions()`` functions will automatically detect this option and +Alternatively, if you use setuptools 65.4 or later, you can dynamically opt in +to limited API builds by setting the ``EXTENSION_HELPERS_PY_LIMITED_API`` +environment variable, e.g.:: + + EXTENSION_HELPERS_PY_LIMITED_API='cp311' python -m build + +If you define ``py_limited_api`` in ``setup.cfg``, you can use +``EXTENSION_HELPERS_PY_LIMITED_API`` to opt **out** of the limited API builds +by setting ``EXTENSION_HELPERS_PY_LIMITED_API`` to an empty string. There is however +no way to opt out if you use ``py-limited-api`` in ``pyproject.toml``. + +The ``get_extensions()`` functions will automatically detect these options and add the necessary compiler flags to build your extension modules. diff --git a/extension_helpers/_utils.py b/extension_helpers/_utils.py index edb3120..f784a17 100644 --- a/extension_helpers/_utils.py +++ b/extension_helpers/_utils.py @@ -3,6 +3,7 @@ import os import re import sys +import tempfile from configparser import ConfigParser from importlib import machinery as import_machinery from importlib.util import module_from_spec, spec_from_file_location @@ -154,6 +155,21 @@ def get_limited_api_option(srcdir): for the py_limited_api setting """ + py_limited_api = os.environ.get("EXTENSION_HELPERS_PY_LIMITED_API") + + if py_limited_api is not None: + + if "DIST_EXTRA_CONFIG" in os.environ: + raise ValueError( + "Cannot use EXTENSION_HELPERS_PY_LIMITED_API if DIST_EXTRA_CONFIG is already defined" + ) + + dist_extra_config_filename = tempfile.mktemp() + with open(dist_extra_config_filename, "w") as f: + f.write(f"[bdist_wheel]\npy_limited_api={py_limited_api}") + os.environ["DIST_EXTRA_CONFIG"] = dist_extra_config_filename + return py_limited_api + srcdir = Path(srcdir) setup_cfg = srcdir / "setup.cfg" diff --git a/extension_helpers/tests/test_setup_helpers.py b/extension_helpers/tests/test_setup_helpers.py index 2802943..cf15b7a 100644 --- a/extension_helpers/tests/test_setup_helpers.py +++ b/extension_helpers/tests/test_setup_helpers.py @@ -469,9 +469,15 @@ def test(): @pytest.mark.parametrize("config", ("setup.cfg", "pyproject.toml")) +@pytest.mark.parametrize("envvar", (False, True)) @pytest.mark.parametrize("limited_api", (None, "cp310")) @pytest.mark.parametrize("extension_type", ("c", "pyx", "both")) -def test_limited_api(tmp_path, config, limited_api, extension_type): +def test_limited_api(tmp_path, config, envvar, limited_api, extension_type): + + if sys.version_info < (3, 11): + pytest.skip( + "This test requires setuptools>=65.4 which is only available for Python 3.11 and later" + ) package = _extension_test_package( tmp_path, extension_type=extension_type, include_numpy=True, include_setup_py=False @@ -493,8 +499,12 @@ def test_limited_api(tmp_path, config, limited_api, extension_type): """ ) - if limited_api: + if limited_api and not envvar: setup_cfg += f"\n[bdist_wheel]\npy_limited_api={limited_api}" + elif envvar: + # Make sure if we are using the environment variable that it takes + # precedence over this setting (this only works for setup.cfg) + setup_cfg += "\n[bdist_wheel]\npy_limited_api=cp35" (package / "setup.cfg").write_text(setup_cfg) @@ -524,7 +534,7 @@ def test_limited_api(tmp_path, config, limited_api, extension_type): build-backend = 'setuptools.build_meta' [project] - name = "hehlpers_test_package" + name = "helpers_test_package" version = "0.1" [tool.setuptools.packages] @@ -535,13 +545,23 @@ def test_limited_api(tmp_path, config, limited_api, extension_type): """ ) - if limited_api: + if limited_api and not envvar: pyproject_toml += f'\n[tool.distutils.bdist_wheel]\npy-limited-api = "{limited_api}"' (package / "pyproject.toml").write_text(pyproject_toml) + env = os.environ.copy() + + if envvar: + if limited_api: + env["EXTENSION_HELPERS_PY_LIMITED_API"] = limited_api + else: + env["EXTENSION_HELPERS_PY_LIMITED_API"] = "" + with chdir(package): - subprocess.run([sys.executable, "-m", "build", "--wheel", "--no-isolation"], check=True) + subprocess.run( + [sys.executable, "-m", "build", "--wheel", "--no-isolation"], env=env, check=True + ) wheels = os.listdir(package / "dist")