https://github.com/python/cpython/commit/e41b38b7cdcc901a13ec25d149d4cf2db03fe547
commit: e41b38b7cdcc901a13ec25d149d4cf2db03fe547
branch: main
author: Itamar Oren <[email protected]>
committer: itamaro <[email protected]>
date: 2026-10-19T17:50:16-07:00
summary:
gh-159759: Fix libpython.static in PEP 739 build-details.json (#159766)
When a shared library is also built, point `libpython.static` at `LIBPL`,
the only place `make install` puts the static library, and don't list it for
framework builds, which don't install one. Static-only builds keep the
existing `LIBDIR` value, which is installed in that configuration.
files:
A Misc/NEWS.d/next/Build/2026-10-18-12-00-00.gh-issue-159759.LibStA.rst
M Lib/test/test_build_details.py
M Tools/build/generate-build-details.py
diff --git a/Lib/test/test_build_details.py b/Lib/test/test_build_details.py
index 30d9c213077ab75..b6f4b6fe26379bb 100644
--- a/Lib/test/test_build_details.py
+++ b/Lib/test/test_build_details.py
@@ -7,6 +7,7 @@
import string
import unittest
from pathlib import Path
+from unittest import mock
from test.support import is_android, is_apple_mobile, is_wasm32
@@ -173,6 +174,23 @@ def test_c_api(self):
version = sysconfig.get_config_var('VERSION')
self.assertTrue(os.path.exists(os.path.join(value['pkgconfig_path'],
f'python-{version}.pc')))
+ @needs_installed_python
+ @unittest.skipIf(
+ is_android or is_apple_mobile,
+ "Android and iOS run tests via a custom testbed method that doesn't
ship libpython"
+ )
+ def test_libpython_static(self):
+ value = self.data.get('libpython', {})
+ if 'static' not in value:
+ self.skipTest("No static libpython")
+
+ if sysconfig._installation_is_relocated():
+ self.skipTest("Installation is relocated")
+
+ # The path must name an installed static library (an 'ar' archive).
+ with open(value['static'], 'rb') as f:
+ self.assertEqual(f.read(8), b'!<arch>\n')
+
@unittest.skipIf(
generate_build_details is None,
@@ -233,5 +251,61 @@ def test_round_trip(self):
self.assertEqual(data_abs_path[part][key], abs_rel_path)
[email protected](
+ generate_build_details is None,
+ "Failed to import generate-build-details"
+)
[email protected](os.name != 'posix', 'Feature only implemented on POSIX')
[email protected](is_wasm32, 'Feature not available on WebAssembly builds')
+class BuildDetailsLibpythonTests(unittest.TestCase):
+ LIBDIR = '/prefix/lib'
+ LIBPL = '/prefix/lib/python3.X/config-3.X'
+
+ 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', {})
+
+ def test_static_only(self):
+ libpython = self.generate_libpython(
+ LDLIBRARY='libpython3.X.a', STATIC_LIBPYTHON=1,
+ PYTHONFRAMEWORK='')
+ self.assertNotIn('dynamic', libpython)
+ self.assertEqual(libpython['static'],
+ os.path.join(self.LIBDIR, 'libpython3.X.a'))
+
+ def test_shared_and_static(self):
+ # The static library is only installed into LIBPL, not LIBDIR.
+ libpython = self.generate_libpython(
+ LDLIBRARY='libpython3.X.so', STATIC_LIBPYTHON=1,
+ PYTHONFRAMEWORK='')
+ self.assertEqual(libpython['dynamic'],
+ os.path.join(self.LIBDIR, 'libpython3.X.so'))
+ self.assertEqual(libpython['static'],
+ os.path.join(self.LIBPL, 'libpython3.X.a'))
+
+ def test_shared_without_static(self):
+ libpython = self.generate_libpython(
+ LDLIBRARY='libpython3.X.so', STATIC_LIBPYTHON=0,
+ PYTHONFRAMEWORK='')
+ self.assertNotIn('static', libpython)
+
+ def test_framework(self):
+ # Framework builds don't install the static library. LIBPL contains
+ # a libpython3.X.a symlink to the framework's shared library instead.
+ libpython = self.generate_libpython(
+ LDLIBRARY='Python.framework/Versions/3.X/Python',
+ STATIC_LIBPYTHON=1, PYTHONFRAMEWORK='Python')
+ self.assertNotIn('static', libpython)
+
+
if __name__ == '__main__':
unittest.main()
diff --git
a/Misc/NEWS.d/next/Build/2026-10-18-12-00-00.gh-issue-159759.LibStA.rst
b/Misc/NEWS.d/next/Build/2026-10-18-12-00-00.gh-issue-159759.LibStA.rst
new file mode 100644
index 000000000000000..1fb6a51fded6dcb
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2026-10-18-12-00-00.gh-issue-159759.LibStA.rst
@@ -0,0 +1,4 @@
+Fix the ``libpython.static`` path in :pep:`739` ``build-details.json``, which
+pointed to a file that is not installed when building with
+:option:`--enable-shared`, and no longer list a static library for framework
+builds, which do not install one.
diff --git a/Tools/build/generate-build-details.py
b/Tools/build/generate-build-details.py
index 8272635bc627d6a..738c51026b77cc8 100644
--- a/Tools/build/generate-build-details.py
+++ b/Tools/build/generate-build-details.py
@@ -72,6 +72,7 @@ def generate_data(schema_version: str) ->
collections.defaultdict[str, Any]:
data['suffixes']['extensions'] = importlib.machinery.EXTENSION_SUFFIXES
LIBDIR = sysconfig.get_config_var('LIBDIR')
+ LIBPL = sysconfig.get_config_var('LIBPL')
LDLIBRARY = sysconfig.get_config_var('LDLIBRARY')
LIBRARY = sysconfig.get_config_var('LIBRARY')
PY3LIBRARY = sysconfig.get_config_var('PY3LIBRARY')
@@ -84,7 +85,14 @@ def generate_data(schema_version: str) ->
collections.defaultdict[str, Any]:
# dynamic library if enabled, otherwise it's the static library.
# If LIBRARY != LDLIBRARY, support for the dynamic library is enabled.
has_dynamic_library = LDLIBRARY != LIBRARY
- has_static_library = sysconfig.get_config_var('STATIC_LIBPYTHON')
+ # Framework builds don't install the static library (see libainstall).
+ has_static_library = (
+ sysconfig.get_config_var('STATIC_LIBPYTHON')
+ and not sysconfig.get_config_var('PYTHONFRAMEWORK')
+ )
+ # The static library is only installed into LIBDIR when it is also
+ # LDLIBRARY; otherwise it is only installed into LIBPL.
+ static_library_dir = LIBPL if has_dynamic_library else LIBDIR
elif os.name == 'nt':
# Windows can only use a dynamic library or a static library.
# If it's using a dynamic library, sys.dllhandle will be set.
@@ -92,6 +100,7 @@ def generate_data(schema_version: str) ->
collections.defaultdict[str, Any]:
# More context: https://github.com/python/cpython/issues/110234
has_dynamic_library = hasattr(sys, 'dllhandle')
has_static_library = not has_dynamic_library
+ static_library_dir = LIBDIR
else:
raise NotADirectoryError(f'Unknown platform: {os.name}')
@@ -123,7 +132,7 @@ def generate_data(schema_version: str) ->
collections.defaultdict[str, Any]:
data['libpython']['link_extensions'] = bool(LIBPYTHON)
if has_static_library:
- data['libpython']['static'] = os.path.join(LIBDIR, LIBRARY)
+ data['libpython']['static'] = os.path.join(static_library_dir, LIBRARY)
data['c_api']['headers'] = INCLUDEPY
if LIBPC:
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]