Skip to content

Commit 5f35442

Browse files
itamaroclaude
andcommitted
gh-159759: Fix libpython.static in PEP 739 build-details.json
When a shared library is also built, `make install` only installs the static library into LIBPL, so point libpython.static there instead of LIBDIR. Static-only builds keep the existing LIBDIR path, which is installed in that configuration. Framework builds don't install the static library (LIBPL contains a symlink to the framework's shared library instead), so don't list it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6576fb4 commit 5f35442

3 files changed

Lines changed: 90 additions & 2 deletions

File tree

‎Lib/test/test_build_details.py‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import string
88
import unittest
99
from pathlib import Path
10+
from unittest import mock
1011

1112
from test.support import is_android, is_apple_mobile, is_wasm32
1213

@@ -173,6 +174,24 @@ def test_c_api(self):
173174
version = sysconfig.get_config_var('VERSION')
174175
self.assertTrue(os.path.exists(os.path.join(value['pkgconfig_path'], f'python-{version}.pc')))
175176

177+
@needs_installed_python
178+
@unittest.skipIf(
179+
is_android or is_apple_mobile,
180+
"Android and iOS run tests via a custom testbed method that doesn't ship libpython"
181+
)
182+
def test_libpython_static(self):
183+
value = self.data.get('libpython', {})
184+
if 'static' not in value:
185+
self.skipTest("No static libpython")
186+
187+
# Skip check if installation is relocated
188+
if sysconfig._installation_is_relocated():
189+
self.skipTest("Installation is relocated")
190+
191+
# The path must name an installed static library (an ar archive).
192+
with open(value['static'], 'rb') as f:
193+
self.assertEqual(f.read(8), b'!<arch>\n')
194+
176195

177196
@unittest.skipIf(
178197
generate_build_details is None,
@@ -233,5 +252,61 @@ def test_round_trip(self):
233252
self.assertEqual(data_abs_path[part][key], abs_rel_path)
234253

235254

255+
@unittest.skipIf(
256+
generate_build_details is None,
257+
"Failed to import generate-build-details"
258+
)
259+
@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX right now')
260+
@unittest.skipIf(is_wasm32, 'Feature not available on WebAssembly builds')
261+
class BuildDetailsLibpythonTests(unittest.TestCase):
262+
LIBDIR = '/prefix/lib'
263+
LIBPL = '/prefix/lib/python3.X/config-3.X'
264+
265+
def generate_libpython(self, **config_vars):
266+
"""Generate the libpython section with overridden config vars."""
267+
config_vars = dict(LIBDIR=self.LIBDIR, LIBPL=self.LIBPL,
268+
LIBRARY='libpython3.X.a', **config_vars)
269+
get_config_var = sysconfig.get_config_var
270+
def patched_get_config_var(name):
271+
if name in config_vars:
272+
return config_vars[name]
273+
return get_config_var(name)
274+
with mock.patch('sysconfig.get_config_var', patched_get_config_var):
275+
data = generate_build_details.generate_data(schema_version='1.0')
276+
return json.loads(json.dumps(data)).get('libpython', {})
277+
278+
def test_static_only(self):
279+
libpython = self.generate_libpython(
280+
LDLIBRARY='libpython3.X.a', STATIC_LIBPYTHON=1,
281+
PYTHONFRAMEWORK='')
282+
self.assertNotIn('dynamic', libpython)
283+
self.assertEqual(libpython['static'],
284+
os.path.join(self.LIBDIR, 'libpython3.X.a'))
285+
286+
def test_shared_and_static(self):
287+
# The static library is only installed into LIBPL, not LIBDIR.
288+
libpython = self.generate_libpython(
289+
LDLIBRARY='libpython3.X.so', STATIC_LIBPYTHON=1,
290+
PYTHONFRAMEWORK='')
291+
self.assertEqual(libpython['dynamic'],
292+
os.path.join(self.LIBDIR, 'libpython3.X.so'))
293+
self.assertEqual(libpython['static'],
294+
os.path.join(self.LIBPL, 'libpython3.X.a'))
295+
296+
def test_shared_without_static(self):
297+
libpython = self.generate_libpython(
298+
LDLIBRARY='libpython3.X.so', STATIC_LIBPYTHON=0,
299+
PYTHONFRAMEWORK='')
300+
self.assertNotIn('static', libpython)
301+
302+
def test_framework(self):
303+
# Framework builds don't install the static library. LIBPL contains
304+
# a libpython3.X.a symlink to the framework's shared library instead.
305+
libpython = self.generate_libpython(
306+
LDLIBRARY='Python.framework/Versions/3.X/Python',
307+
STATIC_LIBPYTHON=1, PYTHONFRAMEWORK='Python')
308+
self.assertNotIn('static', libpython)
309+
310+
236311
if __name__ == '__main__':
237312
unittest.main()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix the ``libpython.static`` path in :pep:`739` ``build-details.json``, which
2+
pointed to a file that is not installed when building with
3+
:option:`--enable-shared`, and no longer list a static library for framework
4+
builds, which do not install one.

‎Tools/build/generate-build-details.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
7272
data['suffixes']['extensions'] = importlib.machinery.EXTENSION_SUFFIXES
7373

7474
LIBDIR = sysconfig.get_config_var('LIBDIR')
75+
LIBPL = sysconfig.get_config_var('LIBPL')
7576
LDLIBRARY = sysconfig.get_config_var('LDLIBRARY')
7677
LIBRARY = sysconfig.get_config_var('LIBRARY')
7778
PY3LIBRARY = sysconfig.get_config_var('PY3LIBRARY')
@@ -84,14 +85,22 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
8485
# dynamic library if enabled, otherwise it's the static library.
8586
# If LIBRARY != LDLIBRARY, support for the dynamic library is enabled.
8687
has_dynamic_library = LDLIBRARY != LIBRARY
87-
has_static_library = sysconfig.get_config_var('STATIC_LIBPYTHON')
88+
# Framework builds don't install the static library (see libainstall).
89+
has_static_library = (
90+
sysconfig.get_config_var('STATIC_LIBPYTHON')
91+
and not sysconfig.get_config_var('PYTHONFRAMEWORK')
92+
)
93+
# The static library is only installed into LIBDIR when it is also
94+
# LDLIBRARY; otherwise it is only installed into LIBPL.
95+
static_library_dir = LIBPL if has_dynamic_library else LIBDIR
8896
elif os.name == 'nt':
8997
# Windows can only use a dynamic library or a static library.
9098
# If it's using a dynamic library, sys.dllhandle will be set.
9199
# Static builds on Windows are not really well supported, though.
92100
# More context: https://github.com/python/cpython/issues/110234
93101
has_dynamic_library = hasattr(sys, 'dllhandle')
94102
has_static_library = not has_dynamic_library
103+
static_library_dir = LIBDIR
95104
else:
96105
raise NotADirectoryError(f'Unknown platform: {os.name}')
97106

@@ -123,7 +132,7 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
123132
data['libpython']['link_extensions'] = bool(LIBPYTHON)
124133

125134
if has_static_library:
126-
data['libpython']['static'] = os.path.join(LIBDIR, LIBRARY)
135+
data['libpython']['static'] = os.path.join(static_library_dir, LIBRARY)
127136

128137
data['c_api']['headers'] = INCLUDEPY
129138
if LIBPC:

0 commit comments

Comments
 (0)