Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/5351.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use target sysconfig data for stable-ABI extension filenames.
19 changes: 14 additions & 5 deletions setuptools/command/build_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import sys
import textwrap
from collections.abc import Iterator
from importlib.machinery import EXTENSION_SUFFIXES
from importlib.util import cache_from_source as _compiled_file_name
from pathlib import Path
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -78,10 +77,20 @@ def _customize_compiler_for_shlib(compiler):

def get_abi3_suffix():
"""Return the file extension for an abi3-compliant Extension()"""
for suffix in EXTENSION_SUFFIXES:
if '.abi3' in suffix or suffix == '.pyd': # Unix
return suffix
return None
suffix = get_config_var('EXT_SUFFIX')
if not isinstance(suffix, str) or get_config_var('HAVE_DYNAMIC_LOADING') == 0:
return None
if suffix.endswith('.pyd') and not suffix.startswith('_d'):
return '.pyd'
if not suffix.startswith('.cpython-'):
return None
if get_config_var('DYNLOADFILE') == 'dynload_hpux.o':
return None

abi = '.abi3t' if get_config_var('Py_GIL_DISABLED') else '.abi3'
platform = get_config_var('SOABI_PLATFORM')
_, ext = os.path.splitext(suffix)
return f'{abi}-{platform}{ext}' if platform else f'{abi}{ext}'


class build_ext(_build_ext):
Expand Down
27 changes: 27 additions & 0 deletions setuptools/tests/test_bdist_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import setuptools
from setuptools.command.bdist_wheel import bdist_wheel, get_abi_tag
from setuptools.command.build_ext import get_abi3_suffix
from setuptools.dist import Distribution
from setuptools.warnings import SetuptoolsDeprecationWarning

Expand Down Expand Up @@ -468,6 +469,32 @@ def test_limited_abi(monkeypatch, tmp_path, tmp_path_factory):
bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run()


@pytest.mark.skipif(
'abi3extension-dist' not in EXAMPLES or not get_abi3_suffix(),
reason='requires a GIL-enabled build with stable-ABI suffix support',
)
@pytest.mark.parametrize('skip_build', [False, True])
def test_abi3_filename_consistent(monkeypatch, tmp_path, tmp_path_factory, skip_build):
"""Wheel creation must not change the build command's output filenames."""
from distutils.sysconfig import get_config_vars

monkeypatch.chdir(mkexample(tmp_path_factory, 'abi3extension-dist'))
monkeypatch.setitem(get_config_vars(), 'SOABI_PLATFORM', 'target-platform')
ext = os.path.splitext(get_config_vars()['EXT_SUFFIX'])[1]
cmd = bdist_wheel_cmd(
py_limited_api='cp32', dist_dir=str(tmp_path), skip_build=skip_build
)
build_ext = cmd.get_finalized_command('build_ext')
outputs = build_ext.get_outputs()
if skip_build:
cmd.run_command('build_ext')
cmd.run()
assert build_ext.get_outputs() == outputs
wheel = next(tmp_path.glob('*.whl'))
with ZipFile(wheel) as archive:
assert f'extension.abi3-target-platform{ext}' in archive.namelist()


def test_build_from_readonly_tree(dummy_dist, monkeypatch, tmp_path):
basedir = str(tmp_path.joinpath("dummy"))
shutil.copytree(str(dummy_dist), basedir)
Expand Down
93 changes: 92 additions & 1 deletion setuptools/tests/test_build_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,80 @@
from .textwrap import DALS

import distutils.command.build_ext as orig
from distutils.sysconfig import get_config_var
from distutils.sysconfig import get_config_var, get_config_vars

IS_PYPY = '__pypy__' in sys.builtin_module_names


@pytest.mark.parametrize(
('config', 'expected'),
[
({}, '.abi3.so'),
({'SOABI_PLATFORM': ''}, '.abi3.so'),
({'SOABI_PLATFORM': 0}, '.abi3.so'),
({'SOABI_PLATFORM': 'aarch64-linux-gnu'}, '.abi3-aarch64-linux-gnu.so'),
({'SOABI_PLATFORM': 'arm-linux-gnueabihf'}, '.abi3-arm-linux-gnueabihf.so'),
({'SOABI_PLATFORM': 'x86_64-linux-musl'}, '.abi3-x86_64-linux-musl.so'),
({'SOABI_PLATFORM': 'darwin'}, '.abi3-darwin.so'),
({'SOABI_PLATFORM': 'iphonesimulator'}, '.abi3-iphonesimulator.so'),
(
{
'EXT_SUFFIX': '.cpython-315-target-platform.ext',
'SOABI_PLATFORM': 'target-platform',
},
'.abi3-target-platform.ext',
),
({'EXT_SUFFIX': '.cpython-315-target-platform.ext'}, '.abi3.ext'),
(
{
'EXT_SUFFIX': '.cpython-315t-aarch64-linux-gnu.so',
'SOABI_PLATFORM': 'aarch64-linux-gnu',
'Py_GIL_DISABLED': 1,
},
'.abi3t-aarch64-linux-gnu.so',
),
({'Py_GIL_DISABLED': 1}, '.abi3t.so'),
({'Py_GIL_DISABLED': 1, 'SOABI_PLATFORM': 0}, '.abi3t.so'),
({'Py_GIL_DISABLED': 1, 'VERSION': None}, '.abi3t.so'),
({'Py_GIL_DISABLED': 1, 'VERSION': '3.14'}, '.abi3t.so'),
({'Py_GIL_DISABLED': 1, 'VERSION': '3.13'}, '.abi3t.so'),
({'EXT_SUFFIX': '.cp315-win_amd64.pyd'}, '.pyd'),
({'EXT_SUFFIX': '.cp315-win_arm64.pyd'}, '.pyd'),
({'EXT_SUFFIX': '.cp310-win32.pyd'}, '.pyd'),
({'EXT_SUFFIX': '.cp315t-win_amd64.pyd', 'Py_GIL_DISABLED': 1}, '.pyd'),
({'EXT_SUFFIX': '.pyd'}, '.pyd'),
({'EXT_SUFFIX': '_d.cp315-win_amd64.pyd'}, None),
({'EXT_SUFFIX': '_d.pyd'}, None),
({'EXT_SUFFIX': '.pypy310-pp73-x86_64-linux-gnu.so'}, None),
({'EXT_SUFFIX': '.pypy310-pp73-win_amd64.pyd'}, '.pyd'),
({'EXT_SUFFIX': '.dll'}, None),
(
{'EXT_SUFFIX': '.cpython-312-x86_64-cygwin.dll', 'MACHDEP': 'cygwin'},
'.abi3.dll',
),
({'EXT_SUFFIX': '.cpython-315.sl', 'DYNLOADFILE': 'dynload_hpux.o'}, None),
({'EXT_SUFFIX': '.cpython-315.so', 'DYNLOADFILE': 'dynload_hpux.o'}, None),
({'EXT_SUFFIX': '.so'}, None),
({'EXT_SUFFIX': ''}, None),
({'EXT_SUFFIX': None}, None),
({'HAVE_DYNAMIC_LOADING': 0}, None),
],
)
def test_abi3_suffix(monkeypatch, config, expected):
config_vars = {
'EXT_SUFFIX': '.cpython-315-aarch64-linux-gnu.so',
'SOABI_PLATFORM': None,
'Py_GIL_DISABLED': 0,
'MACHDEP': 'linux',
'HAVE_DYNAMIC_LOADING': 1,
'DYNLOADFILE': 'dynload_shlib.o',
}
config_vars.update(config)
for name, value in config_vars.items():
monkeypatch.setitem(get_config_vars(), name, value)
assert get_abi3_suffix() == expected


class TestBuildExt:
def test_get_ext_filename(self):
"""
Expand Down Expand Up @@ -56,6 +125,28 @@ def test_abi3_filename(self):
else:
assert 'abi3' in res

@pytest.mark.parametrize('abi_flags', ['', 't'])
def test_abi3_filename_cross(self, monkeypatch, abi_flags):
suffix = f'.cpython-315{abi_flags}-aarch64-linux-gnu.so'
monkeypatch.setitem(get_config_vars(), 'EXT_SUFFIX', suffix)
monkeypatch.setitem(get_config_vars(), 'SOABI_PLATFORM', 'aarch64-linux-gnu')
monkeypatch.setitem(get_config_vars(), 'Py_GIL_DISABLED', bool(abi_flags))
monkeypatch.setitem(get_config_vars(), 'MACHDEP', 'linux')
monkeypatch.setitem(get_config_vars(), 'HAVE_DYNAMIC_LOADING', 1)
monkeypatch.setitem(get_config_vars(), 'DYNLOADFILE', 'dynload_shlib.o')
extensions = [
Extension('spam.eggs', ['eggs.c'], py_limited_api=True),
Extension('spam.normal', ['normal.c']),
]
cmd = build_ext(Distribution(dict(ext_modules=extensions)))
cmd.finalize_options()
assert cmd.get_ext_filename('spam.eggs') == os.path.join(
'spam', f'eggs.abi3{abi_flags}-aarch64-linux-gnu.so'
)
assert cmd.get_ext_filename('spam.normal') == os.path.join(
'spam', 'normal' + suffix
)

def test_ext_suffix_override(self):
"""
SETUPTOOLS_EXT_SUFFIX variable always overrides
Expand Down
Loading