Skip to content
Open
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
88 changes: 77 additions & 11 deletions Lib/test/test_build_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ def contents(self):
def test_location(self):
self.assertTrue(os.path.isfile(self.location))

def test_abi(self):
self.assertEqual(self.key('abi.flags'), list(sys.abiflags))
self.assertEqual(self.key('abi.extension_suffix'),
sysconfig.get_config_var('EXT_SUFFIX'))
self.assertIn(self.key('abi.stable_abi_suffix'),
importlib.machinery.EXTENSION_SUFFIXES)

def test_extension_suffixes(self):
self.assertEqual(self.key('suffixes.extensions'),
importlib.machinery.EXTENSION_SUFFIXES)

# Override generic format tests with tests for our specific implemenation.

@needs_installed_python
Expand Down Expand Up @@ -289,6 +300,62 @@ def check_relative_paths(self, data_abs_path, data_rel_path):
self.assertEqual(data_abs_path[part][key], abs_rel_path)


def generate_data(**config_vars):
"""Generate build-details data with overridden config vars."""
get_config_var = sysconfig.get_config_var
def patched_get_config_var(name):
if name in config_vars:
return config_vars[name]
return get_config_var(name)
with mock.patch('sysconfig.get_config_var', patched_get_config_var):
data = generate_build_details.generate_data(schema_version='1.0')
return json.loads(json.dumps(data))


@unittest.skipIf(
generate_build_details is None,
"Failed to import generate-build-details"
)
@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX')
class BuildDetailsTargetTests(unittest.TestCase):
"""Values that must come from the target configuration (gh-136267)."""

def test_native_extension_suffixes(self):
self.assertEqual(generate_build_details.get_extension_suffixes(),
importlib.machinery.EXTENSION_SUFFIXES)

def test_free_threaded_debug(self):
# A GIL-enabled release build Python generating for this target
# used to report its own flags and suffixes.
data = generate_data(
ABIFLAGS='td', MACHDEP='linux', DYNLOADFILE='dynload_shlib.o',
SOABI_PLATFORM='aarch64-linux-gnu',
SOABI='cpython-3XXtd-aarch64-linux-gnu',
ALT_SOABI='cpython-3XXt-aarch64-linux-gnu',
EXT_SUFFIX='.cpython-3XXtd-aarch64-linux-gnu.so',
Py_GIL_DISABLED=1)
self.assertEqual(data['abi']['flags'], ['t', 'd'])
self.assertEqual(data['abi']['extension_suffix'],
'.cpython-3XXtd-aarch64-linux-gnu.so')
self.assertEqual(data['abi']['stable_abi_suffix'],
'.abi3t-aarch64-linux-gnu.so')
self.assertEqual(data['suffixes']['extensions'], [
'.cpython-3XXtd-aarch64-linux-gnu.so',
'.cpython-3XXt-aarch64-linux-gnu.so',
'.abi3t-aarch64-linux-gnu.so',
'.abi3t.so',
'.so',
])

def test_no_dynamic_loading(self):
data = generate_data(MACHDEP='wasi', DYNLOADFILE='dynload_stub.o')
self.assertEqual(data['suffixes']['extensions'], [])
self.assertNotIn('extension_suffix', data['abi'])
self.assertNotIn('stable_abi_suffix', data['abi'])
self.assertFalse(
data['implementation']['supports_isolated_interpreters'])


@unittest.skipIf(
generate_build_details is None,
"Failed to import generate-build-details"
Expand All @@ -299,26 +366,25 @@ class BuildDetailsLibpythonTests(unittest.TestCase):
LIBDIR = '/prefix/lib'
LIBPL = '/prefix/lib/python3.X/config-3.X'

def generate(self, **config_vars):
return generate_data(LIBDIR=self.LIBDIR, LIBPL=self.LIBPL,
LIBRARY='libpython3.X.a', **config_vars)

def generate_libpython(self, **config_vars):
"""Generate the libpython section with overridden config vars."""
config_vars = dict(LIBDIR=self.LIBDIR, LIBPL=self.LIBPL,
LIBRARY='libpython3.X.a', **config_vars)
get_config_var = sysconfig.get_config_var
def patched_get_config_var(name):
if name in config_vars:
return config_vars[name]
return get_config_var(name)
with mock.patch('sysconfig.get_config_var', patched_get_config_var):
data = generate_build_details.generate_data(schema_version='1.0')
return json.loads(json.dumps(data)).get('libpython', {})
return self.generate(**config_vars).get('libpython', {})

def test_static_only(self):
libpython = self.generate_libpython(
data = self.generate(
LDLIBRARY='libpython3.X.a', STATIC_LIBPYTHON=1,
PYTHONFRAMEWORK='')
libpython = data['libpython']
self.assertNotIn('dynamic', libpython)
self.assertEqual(libpython['static'],
os.path.join(self.LIBDIR, 'libpython3.X.a'))
# Extension modules are supported without a shared libpython.
self.assertIn('extension_suffix', data['abi'])
self.assertIn('stable_abi_suffix', data['abi'])

def test_shared_and_static(self):
# The static library is only installed into LIBPL, not LIBDIR.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
``build-details.json`` now describes the interpreter being built rather than
the interpreter running :program:`generate-build-details.py`: ``abi.flags``,
``suffixes.extensions``, ``abi.stable_abi_suffix`` and
``implementation.supports_isolated_interpreters`` are derived from the
target's configuration, which fixes cross builds and builds that use
:option:`--with-build-python`. ``abi.extension_suffix`` and
``abi.stable_abi_suffix`` are now also present when libpython is only built
as a static library.
59 changes: 52 additions & 7 deletions Tools/build/generate-build-details.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,46 @@ def get_dict_key(container: dict[str, Any], key: str) -> dict[str, Any]:
return container


def get_extension_suffixes() -> list[str]:
"""Return the extension module suffixes of the target interpreter.

importlib.machinery.EXTENSION_SUFFIXES describes the running interpreter,
which is not the target under --with-build-python. On POSIX, rebuild the
list from the target configuration instead, following
_PyImport_DynLoadFiletab in Python/dynload_*.c. The two are kept in sync
by test_build_details.BuildDetailsTargetTests.test_native_extension_suffixes.
"""
if os.name != 'posix':
return importlib.machinery.EXTENSION_SUFFIXES

dynloadfile = sysconfig.get_config_var('DYNLOADFILE')
if dynloadfile == 'dynload_stub.o':
return []
if dynloadfile == 'dynload_hpux.o':
return [sysconfig.get_config_var('EXT_SUFFIX'), '.sl']
if dynloadfile != 'dynload_shlib.o':
raise ValueError(f'Unsupported DYNLOADFILE: {dynloadfile!r}')

soabi = sysconfig.get_config_var('SOABI')
if sysconfig.get_config_var('MACHDEP') == 'cygwin':
return [f'.{soabi}.dll', '.dll']

suffixes = [f'.{soabi}.so']
alt_soabi = sysconfig.get_config_var('ALT_SOABI')
if alt_soabi:
suffixes.append(f'.{alt_soabi}.so')
platform = sysconfig.get_config_var('SOABI_PLATFORM')
if not sysconfig.get_config_var('Py_GIL_DISABLED'):
if platform:
suffixes.append(f'.abi3-{platform}.so')
suffixes.append('.abi3.so')
if platform:
suffixes.append(f'.abi3t-{platform}.so')
suffixes.append('.abi3t.so')
suffixes.append('.so')
return suffixes


def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
"""Generate the build-details.json data (PEP 739).

Expand Down Expand Up @@ -62,14 +102,20 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
# Fix cross-compilation
if '_multiarch' in data['implementation']:
data['implementation']['_multiarch'] = sysconfig.get_config_var('MULTIARCH')
if 'supports_isolated_interpreters' in data['implementation']:
# Mirrors Python/sysmodule.c.
data['implementation']['supports_isolated_interpreters'] = (
sysconfig.get_config_var('MACHDEP') not in ('wasi', 'emscripten')
)

data['abi']['flags'] = list(sys.abiflags)
data['abi']['flags'] = list(sysconfig.get_config_var('ABIFLAGS'))

data['suffixes']['source'] = importlib.machinery.SOURCE_SUFFIXES
data['suffixes']['bytecode'] = importlib.machinery.BYTECODE_SUFFIXES
#data['suffixes']['optimized_bytecode'] = importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES
#data['suffixes']['debug_bytecode'] = importlib.machinery.DEBUG_BYTECODE_SUFFIXES
data['suffixes']['extensions'] = importlib.machinery.EXTENSION_SUFFIXES
extension_suffixes = get_extension_suffixes()
data['suffixes']['extensions'] = extension_suffixes

LIBDIR = sysconfig.get_config_var('LIBDIR')
LIBPL = sysconfig.get_config_var('LIBPL')
Expand Down Expand Up @@ -118,17 +164,16 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
# regardless.
#
# We only want to set abi.extension_suffix and stable_abi_suffix if
# extension modules are supported.
if has_dynamic_library:
# extension modules are supported, regardless of how libpython is built.
if extension_suffixes:
data['abi']['extension_suffix'] = sysconfig.get_config_var('EXT_SUFFIX')

# EXTENSION_SUFFIXES has been constant for a long time, and currently we
# don't have a better information source to find the stable ABI suffix.
for suffix in importlib.machinery.EXTENSION_SUFFIXES:
for suffix in extension_suffixes:
if suffix.startswith('.abi'):
data['abi']['stable_abi_suffix'] = suffix
break

if has_dynamic_library:
data['libpython']['dynamic'] = os.path.join(dynamic_library_dir, LDLIBRARY)
# FIXME: Not sure if windows has a different dll for the stable ABI, and
# even if it does, currently we don't have a way to get its name.
Expand Down
Loading