diff --git a/test cases/unit/125 python extension/build-details.json b/test cases/unit/125 python extension/build-details.json new file mode 100644 index 000000000000..f89e247f8166 --- /dev/null +++ b/test cases/unit/125 python extension/build-details.json @@ -0,0 +1,51 @@ +{ + "schema_version": "1.0", + "base_interpreter": "/usr/bin/python", + "base_prefix": "/usr", + "platform": "linux-x86_64", + "language": { + "version": "3.14", + "version_info": { + "major": 3, + "minor": 14, + "micro": 0, + "releaselevel": "alpha", + "serial": 0 + } + }, + "implementation": { + "name": "cpython", + "version": { + "major": 3, + "minor": 14, + "micro": 0, + "releaselevel": "alpha", + "serial": 0 + }, + "hexversion": 51249312, + "cache_tag": "cpython-314", + "_multiarch": "x86_64-linux-gnu" + }, + "abi": { + "flags": ["t", "d"], + "extension_suffix": ".cpython-314-x86_64-linux-gnu.so", + "stable_abi_suffix": ".abi3.so" + }, + "suffixes": { + "source": [".py"], + "bytecode": [".pyc"], + "optimized_bytecode": [".pyc"], + "debug_bytecode": [".pyc"], + "extensions": [".cpython-314-x86_64-linux-gnu.so", ".abi3.so", ".so"] + }, + "libpython": { + "dynamic": "/usr/lib/libpython3.14.so.1.0", + "dynamic_stableabi": "/usr/lib/libpython3.so", + "static": "/usr/lib/python3.14/config-3.14-x86_64-linux-gnu/libpython3.14.a", + "link_extensions": true + }, + "c_api": { + "headers": "/usr/include/python3.14", + "pkgconfig_path": "/usr/lib/pkgconfig" + } +} diff --git a/test cases/unit/125 python extension/foo.c b/test cases/unit/125 python extension/foo.c new file mode 100644 index 000000000000..0b39d704399b --- /dev/null +++ b/test cases/unit/125 python extension/foo.c @@ -0,0 +1,31 @@ +#define PY_SSIZE_T_CLEAN +#include + + +static PyObject * +bar_impl(PyObject *self) +{ + return Py_None; +} + + +static PyMethodDef foo_methods[] = { + {"bar", bar_impl, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL} /* sentinel */ +}; + + +static struct PyModuleDef foo_module = { + PyModuleDef_HEAD_INIT, + "foo", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + foo_methods, /* m_methods */ +}; + + +PyMODINIT_FUNC +PyInit_foo(void) +{ + return PyModule_Create(&foo_module); +} diff --git a/test cases/unit/125 python extension/meson.build b/test cases/unit/125 python extension/meson.build new file mode 100644 index 000000000000..c9885d99a934 --- /dev/null +++ b/test cases/unit/125 python extension/meson.build @@ -0,0 +1,15 @@ +project('python extension', 'c') + +py = import('python').find_installation('') + +py.extension_module( + 'foo', 'foo.c', + install: true, +) + +py.extension_module( + 'foo_stable', 'foo.c', + install: true, + limited_api: '3.2', +) + diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 96576b0ee888..e90221b49c0a 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -12,6 +12,7 @@ import pickle import zipfile, tarfile import sys +import sysconfig from unittest import mock, SkipTest, skipIf, skipUnless from contextlib import contextmanager from glob import glob @@ -2929,6 +2930,85 @@ def test_pkg_config_libdir(self): self.wipe() self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) + @skipIf(is_windows(), 'POSIX only') + def test_python_build_config_extensions(self): + testdir = os.path.join(self.unit_test_dir, + '125 python extension') + + VERSION_INFO_KEYS = ('major', 'minor', 'micro', 'releaselevel', 'serial') + EXTENSION_SUFFIX = '.extension-suffix.so' + STABLE_ABI_SUFFIX = '.stable-abi-suffix.so' + + python_build_config = { + 'schema_version': '1.0', + 'base_interpreter': sys.executable, + 'base_prefix': '/usr', + 'platform': sysconfig.get_platform(), + 'language': { + 'version': sysconfig.get_python_version(), + 'version_info': {key: getattr(sys.version_info, key) for key in VERSION_INFO_KEYS} + }, + 'implementation': { + attr: ( + getattr(sys.implementation, attr) + if attr != 'version' else + {key: getattr(sys.implementation.version, key) for key in VERSION_INFO_KEYS} + ) + for attr in dir(sys.implementation) + if not attr.startswith('__') + }, + 'abi': { + 'flags': [], + 'extension_suffix': EXTENSION_SUFFIX, + 'stable_abi_suffix': STABLE_ABI_SUFFIX, + }, + 'suffixes': { + 'source': ['.py'], + 'bytecode': ['.pyc'], + 'optimized_bytecode': ['.pyc'], + 'debug_bytecode': ['.pyc'], + 'extensions': [EXTENSION_SUFFIX, STABLE_ABI_SUFFIX, '.so'], + }, + 'libpython': { + 'dynamic': sysconfig.get_config_var('LDLIBRARY'), + 'dynamic_stableabi': sysconfig.get_config_var('PY3LIBRARY'), + 'static': sysconfig.get_config_var('LIBRARY'), + 'link_extensions': True, + }, + 'c_api': { + 'headers': sysconfig.get_path('include'), + 'pkgconfig_path': sysconfig.get_config_var('LIBPC'), + } + } + with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as python_build_config_file: + json.dump(python_build_config, fp=python_build_config_file) + + with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as cross_file: + cross_file.write( + textwrap.dedent(f''' + [properties] + python_build_config = '{python_build_config_file}' + '''.strip()) + ) + cross_file.flush() + + intro_installed_file = os.path.join(self.builddir, 'meson-info', 'intro-installed.json') + expected_files = [ + os.path.join(self.builddir, 'foo' + EXTENSION_SUFFIX), + os.path.join(self.builddir, 'foo_stable' + STABLE_ABI_SUFFIX), + ] + + for extra_args in ( + ['--python.build-config', python_build_config_file.name], + ['--cross-file', cross_file.name], + ): + with self.subTest(extra_args=extra_args): + self.init(testdir, extra_args=extra_args) + with open(intro_installed_file) as f: + intro_installed = json.load(f) + print(intro_installed.keys()) + self.assertEqual(expected_files, list(intro_installed)) + def __reconfigure(self): # Set an older version to force a reconfigure from scratch filename = os.path.join(self.privatedir, 'coredata.dat')