Skip to content

Commit 06d0cde

Browse files
davidhewittmessense
authored andcommitted
py-limited-api: introduce 'auto' mode
1 parent c82e0f3 commit 06d0cde

File tree

5 files changed

+68
-53
lines changed

5 files changed

+68
-53
lines changed

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ classifiers =
2626
[options]
2727
packages = setuptools_rust
2828
zip_safe = True
29-
install_requires = setuptools>=46.1; semantic_version>=2.6.0; toml>=0.9.0
29+
install_requires = setuptools>=46.1; semantic_version>=2.6.0; toml>=0.9.0; typing_extensions>=3.7.4.3
3030
setup_requires = setuptools>=46.1; setuptools_scm[toml]>=3.4.3
3131
python_requires = >=3.6
3232

setuptools_rust/build.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
DistutilsExecError,
1111
DistutilsFileError,
1212
)
13+
from distutils.sysconfig import get_config_var
14+
from setuptools.command.build_ext import get_abi3_suffix
1315
from subprocess import check_output
1416

1517
from .command import RustCommand
1618
from .extension import Binding, RustExtension, Strip
17-
from .utils import rust_features, get_rust_target_info
19+
from .utils import binding_features, get_rust_target_info
1820

1921

2022
class build_rust(RustCommand):
@@ -136,13 +138,11 @@ def build_extension(self, ext: RustExtension, target_triple=None):
136138
f"can't find Rust extension project file: {ext.path}"
137139
)
138140

139-
features = set(ext.features)
140-
features.update(rust_features(binding=ext.binding))
141-
if ext.py_limited_api and ext.binding == Binding.PyO3 and platform.python_implementation() != 'PyPy':
142-
# Pass pyo3/abi3-pyXX feature to pyo3 automatically
143-
# py_limited_api: cpXX, remove `cp` prefix
144-
python_version = ext.py_limited_api[2:]
145-
features.add(f"pyo3/abi3-py{python_version}")
141+
bdist_wheel = self.get_finalized_command('bdist_wheel')
142+
features = {
143+
*ext.features,
144+
*binding_features(ext, py_limited_api=bdist_wheel.py_limited_api)
145+
}
146146

147147
debug_build = ext.debug if ext.debug is not None else self.inplace
148148
debug_build = self.debug if self.debug is not None else debug_build
@@ -339,18 +339,26 @@ def install_extension(self, ext: RustExtension, dylib_paths):
339339
mode |= (mode & 0o444) >> 2 # copy R bits to X
340340
os.chmod(ext_path, mode)
341341

342-
def get_dylib_ext_path(self, ext, target_fname):
342+
def get_dylib_ext_path(
343+
self,
344+
ext: RustExtension,
345+
target_fname: str
346+
) -> str:
343347
build_ext = self.get_finalized_command("build_ext")
344-
# Technically it's supposed to contain a
345-
# `setuptools.Extension`, but in practice the only attribute it
346-
# checks is `ext.py_limited_api`.
347-
modpath = target_fname.split('.')[-1]
348-
assert modpath not in build_ext.ext_map
349-
build_ext.ext_map[modpath] = ext
350-
try:
351-
return build_ext.get_ext_fullpath(target_fname)
352-
finally:
353-
del build_ext.ext_map[modpath]
348+
bdist_wheel = self.get_finalized_command("bdist_wheel")
349+
350+
filename = build_ext.get_ext_fullpath(target_fname)
351+
352+
if (
353+
(ext.py_limited_api == "auto" and bdist_wheel.py_limited_api)
354+
or (ext.py_limited_api)
355+
):
356+
abi3_suffix = get_abi3_suffix()
357+
if abi3_suffix is not None:
358+
so_ext = get_config_var('EXT_SUFFIX')
359+
filename = filename[:-len(so_ext)] + get_abi3_suffix()
360+
361+
return filename
354362

355363
@staticmethod
356364
def create_universal2_binary(output_path, input_paths):

setuptools_rust/extension.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from distutils.errors import DistutilsSetupError
44
from enum import IntEnum, auto
55
from typing import Dict, List, Optional, Union
6+
from typing_extensions import Literal
67

78
import semantic_version
89

@@ -72,10 +73,23 @@ class RustExtension:
7273
optional: if it is true, a build failure in the extension will not
7374
abort the build process, but instead simply not install the failing
7475
extension.
75-
py_limited_api: Same as `py_limited_api` on
76-
`setuptools.Extension`. Note that if you set this to True, your extension
77-
must pass the appropriate feature flags to pyo3 (ensuring that `abi3`
78-
feature is enabled).
76+
py_limited_api: Similar to ``py_limited_api`` on
77+
``setuptools.Extension``, this controls whether the built extension
78+
should be considered compatible with the PEP 384 "limited API".
79+
80+
- ``'auto'``: the ``--py-limited-api`` option of
81+
``setup.py bdist_wheel`` will control whether the extension is
82+
built as a limited api extension. The corresponding
83+
``pyo3/abi3-pyXY`` feature will be set accordingly.
84+
This is the recommended setting, as it allows
85+
``python setup.py install`` to build a version-specific extension
86+
for best performance.
87+
88+
- ``True``: the extension is assumed to be compatible with the
89+
limited abi. You must ensure this is the case (e.g. by setting
90+
the ``pyo3/abi3`` feature).
91+
92+
- ``False``: the extension is version-specific.
7993
"""
8094

8195
def __init__(
@@ -93,7 +107,7 @@ def __init__(
93107
script: bool = False,
94108
native: bool = False,
95109
optional: bool = False,
96-
py_limited_api: bool = False,
110+
py_limited_api: Union[bool, Literal["auto"]] = "auto",
97111
):
98112
if isinstance(target, dict):
99113
name = "; ".join("%s=%s" % (key, val) for key, val in target.items())
@@ -114,9 +128,6 @@ def __init__(
114128
self.native = native
115129
self.optional = optional
116130
self.py_limited_api = py_limited_api
117-
# We pass this over to setuptools in one place, and it wants this
118-
# attribute to exist.
119-
self._links_to_dynamic = False
120131

121132
if features is None:
122133
features = []

setuptools_rust/setuptools_ext.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ class bdist_wheel_rust_extension(bdist_wheel_base_class):
162162
def finalize_options(self):
163163
scripts = []
164164
for ext in self.distribution.rust_extensions:
165-
ext.py_limited_api = self.py_limited_api
166165
scripts.extend(ext.entry_points())
167166

168167
if scripts:

setuptools_rust/utils.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,32 @@
1-
import sys
21
import subprocess
32
from distutils.errors import DistutilsPlatformError
3+
from typing import Set, Union
4+
from typing_extensions import Literal
45

56
import semantic_version
67

7-
from .extension import Binding
8+
from .extension import Binding, RustExtension
89

910

10-
def rust_features(ext=True, binding=Binding.PyO3):
11-
version = sys.version_info
12-
13-
if binding in (Binding.NoBinding, Binding.Exec):
14-
return ()
15-
elif binding is Binding.PyO3:
16-
if version >= (3, 6):
17-
if ext:
18-
return {"pyo3/extension-module"}
19-
else:
20-
return {}
21-
else:
22-
raise DistutilsPlatformError(f"unsupported python version: {sys.version}")
23-
elif binding is Binding.RustCPython:
24-
if (3, 3) < version:
25-
if ext:
26-
return {"cpython/python3-sys", "cpython/extension-module"}
27-
else:
28-
return {"cpython/python3-sys"}
29-
else:
30-
raise DistutilsPlatformError(f"unsupported python version: {sys.version}")
11+
def binding_features(
12+
ext: RustExtension,
13+
py_limited_api: Union[Literal["cp36", "cp37", "cp38", "cp39"], bool],
14+
) -> Set[str]:
15+
if ext.binding in (Binding.NoBinding, Binding.Exec):
16+
return set()
17+
elif ext.binding is Binding.PyO3:
18+
features = {"pyo3/extension-module"}
19+
if ext.py_limited_api == "auto":
20+
if isinstance(py_limited_api, str):
21+
python_version = py_limited_api[2:]
22+
features.add(f"pyo3/abi3-py{python_version}")
23+
elif py_limited_api:
24+
features.add(f"pyo3/abi3")
25+
return features
26+
elif ext.binding is Binding.RustCPython:
27+
return {"cpython/python3-sys", "cpython/extension-module"}
3128
else:
32-
raise DistutilsPlatformError(f"unknown Rust binding: '{binding}'")
29+
raise DistutilsPlatformError(f"unknown Rust binding: '{ext.binding}'")
3330

3431

3532
def get_rust_version(min_version=None):

0 commit comments

Comments
 (0)