Skip to content

Commit

Permalink
Automatically set compiler flags to target PEP 384 Python limited API
Browse files Browse the repository at this point in the history
PEP 384 introduced a Python limited API that allows you to build one
wheel that targets all Python versions on a given platform.

Setuptools supports opting in to building wheels for the Python
limited API by adding the following lines to ``setup.cfg``:

    [bdist_wheel]
    py_limited_api = cp36  # replace with desired min Python version

However, there are two needed steps that Setuptools does not do
automatically:

1. Pass the `py_limited_api=True` keyword argument to each `Extension`
2. Set the `Py_LIMITED_API` preprocessor macro to the version hex
   value for the desired minimum Python version

This patch teaches `get_extension()` to do those two missing steps
automatically if the limited API is enabled in the ``setup.cfg`` file.
  • Loading branch information
lpsinger committed Feb 10, 2021
1 parent 51636f3 commit 6547724
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 1 deletion.
17 changes: 17 additions & 0 deletions docs/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,20 @@ To use this, you should modify your ``setup.py`` file to use
Note that if you use this, extension-helpers will also we create a
``packagename.compiler_version`` submodule that contain information about the
compilers used.

Python limited API
------------------

Your package may opt in to the :pep:`384` Python Limited API so that a single
binary wheel works with many different versions of Python on the same platform.
To opt in to the Python Limited API, add the following standard setuptools
option to your project's ``setup.cfg`` file::

[bdist_wheel]
py_limited_api = cp36

Here, ``36`` denotes API compatibility with Python >= 3.6. Replace with the
lowest major and minor version number that you wish to support.

The ``get_extensions()`` functions will automatically detect this option and
add the necessary compiler flags to build your extension modules.
46 changes: 45 additions & 1 deletion extension_helpers/_setup_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import os
import re
import sys
import shutil
import subprocess
Expand All @@ -15,11 +16,36 @@
from setuptools import find_packages
from setuptools.config import read_configuration

from ._distutils_helpers import get_compiler
from ._distutils_helpers import get_compiler, get_distutils_option
from ._utils import import_file, walk_skip_hidden

__all__ = ['get_extensions', 'pkg_config']

_PEP_384_WARNING = """\
Not targeting PEP 384 limited API. You can build one binary wheel for each \
platform to support all Python versions by adding the following to your \
project's setup.cfg file:
[bdist_wheel]
py_limited_api = cp36
Replace '36' with the lowest Python major and minor version that you wish to \
support.
"""


def _abi_to_version_info(abi):
match = re.fullmatch(r'^cp(\d)(\d)$', abi)
if match is None:
return None

Check warning on line 40 in extension_helpers/_setup_helpers.py

View check run for this annotation

Codecov / codecov/patch

extension_helpers/_setup_helpers.py#L38-L40

Added lines #L38 - L40 were not covered by tests
else:
return int(match[1]), int(match[2])

Check warning on line 42 in extension_helpers/_setup_helpers.py

View check run for this annotation

Codecov / codecov/patch

extension_helpers/_setup_helpers.py#L42

Added line #L42 was not covered by tests


def _version_info_to_version_hex(major=0, minor=0, micro=0,
releaselevel=0, serial=0):
return f'0x{major:0>2}{minor:0>2}{micro:0>2}{releaselevel:0>2}{serial:0>2}'

Check warning on line 47 in extension_helpers/_setup_helpers.py

View check run for this annotation

Codecov / codecov/patch

extension_helpers/_setup_helpers.py#L47

Added line #L47 was not covered by tests


def get_extensions(srcdir='.'):
"""
Expand Down Expand Up @@ -85,6 +111,24 @@ def get_extensions(srcdir='.'):
[os.path.join(main_package_dir, '_compiler.c')])
ext_modules.append(ext)

use_limited_api = False
abi = get_distutils_option('py_limited_api', ['bdist_wheel'])
if abi:
version_info = _abi_to_version_info(abi)
if version_info:
use_limited_api = True

Check warning on line 119 in extension_helpers/_setup_helpers.py

View check run for this annotation

Codecov / codecov/patch

extension_helpers/_setup_helpers.py#L117-L119

Added lines #L117 - L119 were not covered by tests

if use_limited_api:
log.info(

Check warning on line 122 in extension_helpers/_setup_helpers.py

View check run for this annotation

Codecov / codecov/patch

extension_helpers/_setup_helpers.py#L122

Added line #L122 was not covered by tests
'Targeting PEP 384 limited API supporting Python >= %d.%d',
*version_info)
version_hex = _version_info_to_version_hex(*version_info)
for ext in ext_modules:
ext.py_limited_api = True
ext.define_macros.append(('Py_LIMITED_API', version_hex))

Check warning on line 128 in extension_helpers/_setup_helpers.py

View check run for this annotation

Codecov / codecov/patch

extension_helpers/_setup_helpers.py#L125-L128

Added lines #L125 - L128 were not covered by tests
else:
log.warn(_PEP_384_WARNING)

return ext_modules


Expand Down

0 comments on commit 6547724

Please sign in to comment.