Skip to content

Commit 934d688

Browse files
authored
Merge pull request #137 from messense/py-limited-api
Pass pyo3/abi3-pyXX feature to pyo3 automatically
2 parents 956e41c + f6ff544 commit 934d688

File tree

7 files changed

+71
-53
lines changed

7 files changed

+71
-53
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ jobs:
137137
cd examples/rust_with_cffi/
138138
python --version
139139
pip install wheel
140-
python setup.py bdist_wheel --py-limited-api=cp35
140+
python setup.py bdist_wheel --py-limited-api=cp36
141141
ls -la dist/
142142
143143
# Now we switch to a differnet Python version and ensure we can install

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44
### Added
55
- Add `--target` command line option for specifying target triple. [#136](https://github.com/PyO3/setuptools-rust/pull/136)
6+
- Add new default `"auto"` setting for `RustExtension.py_limited_api`. [#137](https://github.com/PyO3/setuptools-rust/pull/137)
67
- Support very verbose cargo build.rs output. [#140](https://github.com/PyO3/setuptools-rust/pull/140)
78

89
### Removed

examples/rust_with_cffi/setup.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@
1919
],
2020
packages=["rust_with_cffi"],
2121
rust_extensions=[
22-
RustExtension(
23-
"rust_with_cffi.rust",
24-
py_limited_api=True,
25-
features=[] if platform.python_implementation() == 'PyPy' else ["pyo3/abi3"]
26-
),
22+
RustExtension("rust_with_cffi.rust", py_limited_api="auto"),
2723
],
2824
cffi_modules=["cffi_module.py:ffi"],
2925
install_requires=["cffi"],

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 & 14 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):
@@ -138,8 +140,11 @@ def build_extension(self, ext: RustExtension, target_triple=None):
138140
f"can't find Rust extension project file: {ext.path}"
139141
)
140142

141-
features = set(ext.features)
142-
features.update(rust_features(binding=ext.binding))
143+
bdist_wheel = self.get_finalized_command('bdist_wheel')
144+
features = {
145+
*ext.features,
146+
*binding_features(ext, py_limited_api=bdist_wheel.py_limited_api)
147+
}
143148

144149
debug_build = ext.debug if ext.debug is not None else self.inplace
145150
debug_build = self.debug if self.debug is not None else debug_build
@@ -340,18 +345,26 @@ def install_extension(self, ext: RustExtension, dylib_paths):
340345
mode |= (mode & 0o444) >> 2 # copy R bits to X
341346
os.chmod(ext_path, mode)
342347

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

356369
@staticmethod
357370
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/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)