From df866b339d01ad8da1e0b73a855a5e83849cc3d2 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Wed, 21 Oct 2026 16:10:54 -0700 Subject: [PATCH] gh-159998: Fix `libpython.dynamic` in build-details.json for framework builds (GH-160005) 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 --- Lib/test/test_build_details.py | 28 ++++++++++++++++++- ...-10-20-23-00-00.gh-issue-159998.FwDyn1.rst | 3 ++ Tools/build/generate-build-details.py | 10 ++++++- 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2026-10-20-23-00-00.gh-issue-159998.FwDyn1.rst 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'!\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: