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
25 changes: 18 additions & 7 deletions distutils/command/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

import os
import re
import sys
import sysconfig
from collections.abc import Callable
Expand Down Expand Up @@ -77,16 +78,18 @@ def finalize_options(self) -> None: # noqa: C901
"using './configure --help' on your platform)"
)

plat_specifier = f".{self.plat_name}-{sys.implementation.cache_tag}"
soabi = sysconfig.get_config_var('SOABI')
plat_specifier = f".{self.plat_name}-{soabi or sys.implementation.cache_tag}"

# Python 3.13+ with --disable-gil shouldn't share build directories
if sysconfig.get_config_var('Py_GIL_DISABLED'):
if not soabi and sysconfig.get_config_var('Py_GIL_DISABLED'):
plat_specifier += 't'

# Make it so Python 2.x and Python 2.x with --with-pydebug don't
# share the same build directories. Doing so confuses the build
# process for C modules
if hasattr(sys, 'gettotalrefcount'):
# SOABI does not distinguish debug builds on Windows.
py_debug = sysconfig.get_config_var('Py_DEBUG')
if py_debug is None:
py_debug = hasattr(sys, 'gettotalrefcount')
if py_debug:
plat_specifier += '-pydebug'

# 'build_purelib' and 'build_platlib' just default to 'lib' and
Expand All @@ -111,9 +114,17 @@ def finalize_options(self) -> None: # noqa: C901
if self.build_temp is None:
self.build_temp = os.path.join(self.build_base, 'temp' + plat_specifier)
if self.build_scripts is None:
version = re.fullmatch(
r'(\d)\.?([0-9]+)', str(sysconfig.get_config_var('VERSION'))
)
python_version = (
f'{version[1]}.{version[2]}'
if version
else f'{sys.version_info.major}.{sys.version_info.minor}'
)
self.build_scripts = os.path.join(
self.build_base,
f'scripts-{sys.version_info.major}.{sys.version_info.minor}',
f'scripts-{python_version}',
)

if self.executable is None and sys.executable:
Expand Down
122 changes: 116 additions & 6 deletions distutils/tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import os
import sys
from distutils.command.build import build
from distutils.extension import Extension
from distutils.tests import support
from sysconfig import get_config_var, get_platform
from sysconfig import get_config_var, get_config_vars, get_platform

import pytest


class TestBuild(support.TempdirManager):
Expand All @@ -20,11 +23,10 @@ def test_finalize_options(self):
wanted = os.path.join(cmd.build_base, 'lib')
assert cmd.build_purelib == wanted

# build_platlib is 'build/lib.platform-cache_tag[-pydebug]'
# examples:
# build/lib.macosx-10.3-i386-cpython39
plat_spec = f'.{cmd.plat_name}-{sys.implementation.cache_tag}'
if get_config_var('Py_GIL_DISABLED'):
# build_platlib is 'build/lib.platform-soabi[-pydebug]'
soabi = get_config_var('SOABI')
plat_spec = f'.{cmd.plat_name}-{soabi or sys.implementation.cache_tag}'
if not soabi and get_config_var('Py_GIL_DISABLED'):
plat_spec += 't'
if hasattr(sys, 'gettotalrefcount'):
assert cmd.build_platlib.endswith('-pydebug')
Expand All @@ -47,3 +49,111 @@ def test_finalize_options(self):

# executable is os.path.normpath(sys.executable)
assert cmd.executable == os.path.normpath(sys.executable)

@pytest.mark.parametrize(
('soabi', 'gil_disabled', 'debug'),
[
('cpython-313-aarch64-linux-gnu', 0, 0),
('cpython-314-aarch64-linux-gnu', 0, 0),
('cpython-314d-aarch64-linux-gnu', 0, 1),
('cpython-314t-aarch64-linux-gnu', 1, 0),
('cpython-314td-aarch64-linux-gnu', 1, 1),
('cp314-win_arm64', 0, 0),
('pypy311-pp80-aarch64-linux-gnu', 0, 0),
('graalpy231-310-native-aarch64-linux', 0, 0),
],
)
def test_target_abi(self, monkeypatch, soabi, gil_disabled, debug):
monkeypatch.setitem(get_config_vars(), 'SOABI', soabi)
monkeypatch.setitem(get_config_vars(), 'Py_GIL_DISABLED', gil_disabled)
monkeypatch.setitem(get_config_vars(), 'Py_DEBUG', debug)
_pkg_dir, dist = self.create_dist(ext_modules=[Extension('foo', ['foo.c'])])
cmd = build(dist)
cmd.finalize_options()

tag = soabi + ('-pydebug' if debug else '')
assert cmd.build_platlib == os.path.join('build', f'lib.{cmd.plat_name}-{tag}')
assert cmd.build_temp == os.path.join('build', f'temp.{cmd.plat_name}-{tag}')
assert cmd.build_lib == cmd.build_platlib

@pytest.mark.parametrize('debug', [0, 1])
@pytest.mark.parametrize('native_debug', [False, True])
def test_target_debug(self, monkeypatch, debug, native_debug):
monkeypatch.setitem(get_config_vars(), 'SOABI', 'cp314-win_arm64')
monkeypatch.setitem(get_config_vars(), 'Py_DEBUG', debug)
if native_debug:
monkeypatch.setattr(sys, 'gettotalrefcount', lambda: 0, raising=False)
else:
monkeypatch.delattr(sys, 'gettotalrefcount', raising=False)
_pkg_dir, dist = self.create_dist()
cmd = build(dist)
cmd.finalize_options()

tag = 'cp314-win_arm64' + ('-pydebug' if debug else '')
assert cmd.build_platlib == os.path.join('build', f'lib.{cmd.plat_name}-{tag}')
assert cmd.build_temp == os.path.join('build', f'temp.{cmd.plat_name}-{tag}')

@pytest.mark.parametrize(
('version', 'expected'),
[('3.13', '3.13'), ('3.15', '3.15'), ('315', '3.15'), (315, '3.15')],
)
def test_target_version(self, monkeypatch, version, expected):
monkeypatch.setitem(get_config_vars(), 'VERSION', version)
_pkg_dir, dist = self.create_dist()
cmd = build(dist)
cmd.finalize_options()

assert cmd.build_scripts == os.path.join('build', f'scripts-{expected}')

@pytest.mark.parametrize('version', [None, '', 'invalid'])
def test_missing_version(self, monkeypatch, version):
monkeypatch.setitem(get_config_vars(), 'VERSION', version)
_pkg_dir, dist = self.create_dist()
cmd = build(dist)
cmd.finalize_options()

expected = f'{sys.version_info.major}.{sys.version_info.minor}'
assert cmd.build_scripts == os.path.join('build', f'scripts-{expected}')

@pytest.mark.parametrize('debug', [None, 0, 1])
@pytest.mark.parametrize('native_debug', [False, True])
@pytest.mark.parametrize('gil_disabled', [0, 1])
def test_missing_soabi(self, monkeypatch, debug, native_debug, gil_disabled):
monkeypatch.setitem(get_config_vars(), 'SOABI', None)
monkeypatch.setitem(get_config_vars(), 'Py_DEBUG', debug)
monkeypatch.setitem(get_config_vars(), 'Py_GIL_DISABLED', gil_disabled)
if native_debug:
monkeypatch.setattr(sys, 'gettotalrefcount', lambda: 0, raising=False)
else:
monkeypatch.delattr(sys, 'gettotalrefcount', raising=False)
_pkg_dir, dist = self.create_dist()
cmd = build(dist)
cmd.finalize_options()

tag = str(sys.implementation.cache_tag)
if gil_disabled:
tag += 't'
expected_debug = debug if debug is not None else native_debug
if expected_debug:
tag += '-pydebug'
assert cmd.build_platlib == os.path.join('build', f'lib.{cmd.plat_name}-{tag}')
assert cmd.build_temp == os.path.join('build', f'temp.{cmd.plat_name}-{tag}')

def test_directory_overrides(self, monkeypatch):
monkeypatch.setitem(get_config_vars(), 'SOABI', 'cpython-315-aarch64-linux-gnu')
monkeypatch.setitem(get_config_vars(), 'VERSION', '3.15')
_pkg_dir, dist = self.create_dist()
cmd = build(dist)
directories = {
'build_purelib': 'custom-purelib',
'build_platlib': 'custom-platlib',
'build_lib': 'custom-lib',
'build_temp': 'custom-temp',
'build_scripts': 'custom-scripts',
}
for name, value in directories.items():
setattr(cmd, name, value)
cmd.finalize_options()

for name, value in directories.items():
assert getattr(cmd, name) == value
1 change: 1 addition & 0 deletions newsfragments/+target-build-directories.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use the configured Python's SOABI and debug setting for platform-specific build directories, and its version for the script build directory. Default platform-specific directory names now include SOABI when available instead of the running interpreter's cache tag. (pypa/distutils#435)
Loading