https://github.com/python/cpython/commit/67e7ed68395be14cdf8a221f71b5d82227a9b10b
commit: 67e7ed68395be14cdf8a221f71b5d82227a9b10b
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: ambv <[email protected]>
date: 2026-10-21T23:49:11Z
summary:

[3.15] gh-159998: Fix `libpython.dynamic` in build-details.json for framework 
builds (GH-160005) (#160145)

In framework builds, `libpython.dynamic` now joins `PYTHONFRAMEWORKPREFIX` with 
`LDLIBRARY` instead of `LIBDIR` with `LDLIBRARY`, so it points at the framework 
library that is actually installed. Other builds are unchanged.
(cherry picked from commit 13c758d73070ade5884026c52e335ff2a98c6b97)

Co-authored-by: Itamar Oren <[email protected]>

files:
A Misc/NEWS.d/next/Build/2026-10-20-23-00-00.gh-issue-159998.FwDyn1.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 b6f4b6fe26379b..3d5e4685efe871 100644
--- a/Lib/test/test_build_details.py
+++ b/Lib/test/test_build_details.py
@@ -191,6 +191,26 @@ def test_libpython_static(self):
         with open(value['static'], 'rb') as f:
             self.assertEqual(f.read(8), b'!<arch>\n')
 
+    @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"
+    )
+    @unittest.skipIf(
+        sys.platform == 'cygwin',
+        "Cygwin installs the DLL in BINDIR and the import library in LIBPL, "
+        "not in LIBDIR"
+    )
+    def test_libpython_dynamic(self):
+        value = self.data.get('libpython', {})
+        if 'dynamic' not in value:
+            self.skipTest("No dynamic libpython")
+
+        if sysconfig._installation_is_relocated():
+            self.skipTest("Installation is relocated")
+
+        self.assertTrue(os.path.isfile(value['dynamic']))
+
 
 @unittest.skipIf(
     generate_build_details is None,
@@ -303,8 +323,14 @@ def test_framework(self):
         # 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')
+            STATIC_LIBPYTHON=1, PYTHONFRAMEWORK='Python',
+            PYTHONFRAMEWORKPREFIX='/Library/Frameworks')
         self.assertNotIn('static', libpython)
+        # The dynamic library is the framework binary, which is installed at
+        # PYTHONFRAMEWORKPREFIX/LDLIBRARY.
+        self.assertEqual(
+            libpython['dynamic'],
+            '/Library/Frameworks/Python.framework/Versions/3.X/Python')
 
 
 if __name__ == '__main__':
diff --git 
a/Misc/NEWS.d/next/Build/2026-10-20-23-00-00.gh-issue-159998.FwDyn1.rst 
b/Misc/NEWS.d/next/Build/2026-10-20-23-00-00.gh-issue-159998.FwDyn1.rst
new file mode 100644
index 00000000000000..33a5c6414936e7
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2026-10-20-23-00-00.gh-issue-159998.FwDyn1.rst
@@ -0,0 +1,3 @@
+Fix ``libpython.dynamic`` in the :pep:`739` ``build-details.json`` file for
+macOS and iOS framework builds. It now points at the framework's library
+instead of a nonexistent path under :file:`lib/`.
diff --git a/Tools/build/generate-build-details.py 
b/Tools/build/generate-build-details.py
index 738c51026b77cc..c03321dab64e86 100644
--- a/Tools/build/generate-build-details.py
+++ b/Tools/build/generate-build-details.py
@@ -93,6 +93,13 @@ def generate_data(schema_version: str) -> 
collections.defaultdict[str, Any]:
         # 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
+        # In framework builds, LDLIBRARY is the path of the framework library
+        # relative to PYTHONFRAMEWORKPREFIX (e.g.
+        # Python.framework/Versions/3.X/Python), not a file in LIBDIR.
+        if sysconfig.get_config_var('PYTHONFRAMEWORK'):
+            dynamic_library_dir = 
sysconfig.get_config_var('PYTHONFRAMEWORKPREFIX')
+        else:
+            dynamic_library_dir = 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.
@@ -101,6 +108,7 @@ def generate_data(schema_version: str) -> 
collections.defaultdict[str, Any]:
         has_dynamic_library = hasattr(sys, 'dllhandle')
         has_static_library = not has_dynamic_library
         static_library_dir = LIBDIR
+        dynamic_library_dir = LIBDIR
     else:
         raise NotADirectoryError(f'Unknown platform: {os.name}')
 
@@ -121,7 +129,7 @@ def generate_data(schema_version: str) -> 
collections.defaultdict[str, Any]:
                 data['abi']['stable_abi_suffix'] = suffix
                 break
 
-        data['libpython']['dynamic'] = os.path.join(LIBDIR, LDLIBRARY)
+        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.
         if PY3LIBRARY:

_______________________________________________
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]

Reply via email to