Skip to content

Commit e41b38b

Browse files
authored
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.
1 parent 34fe59b commit e41b38b

3 files changed

Lines changed: 89 additions & 2 deletions

File tree

‎Lib/test/test_build_details.py‎

Lines changed: 74 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,23 @@ 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+
if sysconfig._installation_is_relocated():
188+
self.skipTest("Installation is relocated")
189+
190+
# The path must name an installed static library (an 'ar' archive).
191+
with open(value['static'], 'rb') as f:
192+
self.assertEqual(f.read(8), b'!<arch>\n')
193+
176194

177195
@unittest.skipIf(
178196
generate_build_details is None,
@@ -233,5 +251,61 @@ def test_round_trip(self):
233251
self.assertEqual(data_abs_path[part][key], abs_rel_path)
234252

235253

254+
@unittest.skipIf(
255+
generate_build_details is None,
256+
"Failed to import generate-build-details"
257+
)
258+
@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX')
259+
@unittest.skipIf(is_wasm32, 'Feature not available on WebAssembly builds')
260+
class BuildDetailsLibpythonTests(unittest.TestCase):
261+
LIBDIR = '/prefix/lib'
262+
LIBPL = '/prefix/lib/python3.X/config-3.X'
263+
264+
def generate_libpython(self, **config_vars):
265+
"""Generate the libpython section with overridden config vars."""
266+
config_vars = dict(LIBDIR=self.LIBDIR, LIBPL=self.LIBPL,
267+
LIBRARY='libpython3.X.a', **config_vars)
268+
get_config_var = sysconfig.get_config_var
269+
def patched_get_config_var(name):
270+
if name in config_vars:
271+
return config_vars[name]
272+
return get_config_var(name)
273+
with mock.patch('sysconfig.get_config_var', patched_get_config_var):
274+
data = generate_build_details.generate_data(schema_version='1.0')
275+
return json.loads(json.dumps(data)).get('libpython', {})
276+
277+
def test_static_only(self):
278+
libpython = self.generate_libpython(
279+
LDLIBRARY='libpython3.X.a', STATIC_LIBPYTHON=1,
280+
PYTHONFRAMEWORK='')
281+
self.assertNotIn('dynamic', libpython)
282+
self.assertEqual(libpython['static'],
283+
os.path.join(self.LIBDIR, 'libpython3.X.a'))
284+
285+
def test_shared_and_static(self):
286+
# The static library is only installed into LIBPL, not LIBDIR.
287+
libpython = self.generate_libpython(
288+
LDLIBRARY='libpython3.X.so', STATIC_LIBPYTHON=1,
289+
PYTHONFRAMEWORK='')
290+
self.assertEqual(libpython['dynamic'],
291+
os.path.join(self.LIBDIR, 'libpython3.X.so'))
292+
self.assertEqual(libpython['static'],
293+
os.path.join(self.LIBPL, 'libpython3.X.a'))
294+
295+
def test_shared_without_static(self):
296+
libpython = self.generate_libpython(
297+
LDLIBRARY='libpython3.X.so', STATIC_LIBPYTHON=0,
298+
PYTHONFRAMEWORK='')
299+
self.assertNotIn('static', libpython)
300+
301+
def test_framework(self):
302+
# Framework builds don't install the static library. LIBPL contains
303+
# a libpython3.X.a symlink to the framework's shared library instead.
304+
libpython = self.generate_libpython(
305+
LDLIBRARY='Python.framework/Versions/3.X/Python',
306+
STATIC_LIBPYTHON=1, PYTHONFRAMEWORK='Python')
307+
self.assertNotIn('static', libpython)
308+
309+
236310
if __name__ == '__main__':
237311
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)