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.
diff --git a/Lib/test/test_build_details.py b/Lib/test/test_build_details.py
index 30d9c21..b6f4b6f 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)
 
 
+@unittest.skipIf(
+    generate_build_details is None,
+    "Failed to import generate-build-details"
+)
+@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX')
+@unittest.skipIf(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 0000000..1fb6a51
--- /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 8272635..738c510 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: