mirror of
https://github.com/Z3Prover/z3
synced 2026-08-02 12:13:25 +00:00
Load versioned libz3 soname in Python bindings on Linux (#10290)
The generated Python bindings loaded `libz3.so` without a soversion,
which could bind to an ABI-incompatible shared library when multiple Z3
versions are present. This change makes Linux bindings prefer the
versioned soname while leaving other platforms on the existing lookup
path.
- **Python loader generation**
- Thread a `z3py_soversion` parameter through `scripts/update_api.py`.
- Generate `_lib_name` as `libz3.so.<major>.<minor>` on Linux, and keep
`libz3.<ext>` elsewhere.
- Reuse `_lib_name` consistently for directory probing, system fallback,
and error messages.
- **Build-system wiring**
- Pass the current `SOVERSION` from the CMake Python bindings build on
Linux.
- Pass the same value through the `mk_make`/`mk_util.py` generation path
so both build systems emit the same loader behavior.
- **In-tree Python bindings layout**
- Add the matching `libz3.so.<major>.<minor>` link in `build/python/` on
Linux so the generated bindings work directly from the build tree.
- **Regression coverage**
- Add a focused script-level test for the generated loader preamble to
check:
- Linux uses the versioned soname when provided.
- Non-versioned fallback remains available when no soversion is
supplied.
Example of the generated Linux loader behavior:
```python
_sover = '5.0'
_ext = 'so'
_lib_name = 'libz3.%s.%s' % (_ext, _sover) if sys.platform.startswith('linux') and _sover else 'libz3.%s' % _ext
_lib = ctypes.CDLL(_lib_name)
```
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes #7518
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nikolaj@cs.stanford.edu>
This commit is contained in:
parent
3631d1b85c
commit
5af999eb4f
4 changed files with 116 additions and 22 deletions
|
|
@ -1849,12 +1849,15 @@ def write_core_py_post(core_py):
|
|||
core_py.write("""
|
||||
# Clean up
|
||||
del _lib
|
||||
del _lib_name
|
||||
del _lib_names
|
||||
del _default_dirs
|
||||
del _all_dirs
|
||||
del _ext
|
||||
del _sover
|
||||
""")
|
||||
|
||||
def write_core_py_preamble(core_py):
|
||||
def write_core_py_preamble(core_py, z3py_soversion=None):
|
||||
core_py.write(
|
||||
"""
|
||||
# Automatically generated file
|
||||
|
|
@ -1871,7 +1874,14 @@ from .z3consts import *
|
|||
|
||||
_file_manager = contextlib.ExitStack()
|
||||
atexit.register(_file_manager.close)
|
||||
""")
|
||||
core_py.write(f"_sover = {z3py_soversion!r}\n")
|
||||
core_py.write(
|
||||
"""
|
||||
_ext = 'dll' if sys.platform in ('win32', 'cygwin') else 'dylib' if sys.platform == 'darwin' else 'so'
|
||||
_lib_name = 'libz3.%s.%s' % (_ext, _sover) if sys.platform.startswith('linux') and _sover else 'libz3.%s' % _ext
|
||||
# On Linux with a soversion, also try the unversioned name as a fallback (e.g. build directories)
|
||||
_lib_names = ['libz3.%s.%s' % (_ext, _sover), 'libz3.%s' % _ext] if sys.platform.startswith('linux') and _sover else ['libz3.%s' % _ext]
|
||||
_lib = None
|
||||
_z3_lib_resource = importlib_resources.files('z3').joinpath('lib')
|
||||
_z3_lib_resource_path = _file_manager.enter_context(
|
||||
|
|
@ -1902,39 +1912,44 @@ for v in ('Z3_LIBRARY_PATH', 'PATH', 'PYTHONPATH'):
|
|||
_all_dirs.extend(lds)
|
||||
|
||||
_failures = []
|
||||
for d in _all_dirs:
|
||||
try:
|
||||
d = os.path.realpath(d)
|
||||
if os.path.isdir(d):
|
||||
d = os.path.join(d, 'libz3.%s' % _ext)
|
||||
if os.path.isfile(d):
|
||||
_lib = ctypes.CDLL(d)
|
||||
break
|
||||
except Exception as e:
|
||||
_failures += [e]
|
||||
pass
|
||||
for _name in _lib_names:
|
||||
for d in _all_dirs:
|
||||
try:
|
||||
d = os.path.realpath(d)
|
||||
if os.path.isdir(d):
|
||||
d = os.path.join(d, _name)
|
||||
if os.path.isfile(d):
|
||||
_lib = ctypes.CDLL(d)
|
||||
break
|
||||
except Exception as e:
|
||||
_failures += [e]
|
||||
pass
|
||||
if _lib is not None:
|
||||
break
|
||||
|
||||
if _lib is None:
|
||||
# If all else failed, ask the system to find it.
|
||||
try:
|
||||
_lib = ctypes.CDLL('libz3.%s' % _ext)
|
||||
except Exception as e:
|
||||
_failures += [e]
|
||||
pass
|
||||
for _name in _lib_names:
|
||||
try:
|
||||
_lib = ctypes.CDLL(_name)
|
||||
break
|
||||
except Exception as e:
|
||||
_failures += [e]
|
||||
pass
|
||||
|
||||
if _lib is None:
|
||||
print("Could not find libz3.%s; consider adding the directory containing it to" % _ext)
|
||||
print("Could not find %s; consider adding the directory containing it to" % ' or '.join(_lib_names))
|
||||
print(" - your system's PATH environment variable,")
|
||||
print(" - the Z3_LIBRARY_PATH environment variable, or ")
|
||||
print(" - to the custom Z3_LIB_DIRS Python-builtin before importing the z3 module, e.g. via")
|
||||
if sys.version < '3':
|
||||
print(" import __builtin__")
|
||||
print(" __builtin__.Z3_LIB_DIRS = [ '/path/to/z3/lib/dir' ] # directory containing libz3.%s" % _ext)
|
||||
print(" __builtin__.Z3_LIB_DIRS = [ '/path/to/z3/lib/dir' ] # directory containing %s" % _lib_name)
|
||||
else:
|
||||
print(" import builtins")
|
||||
print(" builtins.Z3_LIB_DIRS = [ '/path/to/z3/lib/dir' ] # directory containing libz3.%s" % _ext)
|
||||
print(" builtins.Z3_LIB_DIRS = [ '/path/to/z3/lib/dir' ] # directory containing %s" % _lib_name)
|
||||
print(_failures)
|
||||
raise Z3Exception("libz3.%s not found." % _ext)
|
||||
raise Z3Exception("%s not found." % _lib_name)
|
||||
|
||||
|
||||
if sys.version < '3':
|
||||
|
|
@ -2000,6 +2015,7 @@ core_py = None
|
|||
def generate_files(api_files,
|
||||
api_output_dir=None,
|
||||
z3py_output_dir=None,
|
||||
z3py_soversion=None,
|
||||
dotnet_output_dir=None,
|
||||
java_input_dir=None,
|
||||
java_output_dir=None,
|
||||
|
|
@ -2057,7 +2073,7 @@ def generate_files(api_files,
|
|||
write_log_h_preamble(log_h)
|
||||
write_log_c_preamble(log_c)
|
||||
write_exe_c_preamble(exe_c)
|
||||
write_core_py_preamble(core_py)
|
||||
write_core_py_preamble(core_py, z3py_soversion)
|
||||
|
||||
# FIXME: these functions are awful
|
||||
apiTypes.def_Types(api_files)
|
||||
|
|
@ -2100,6 +2116,10 @@ def main(args):
|
|||
dest="z3py_output_dir",
|
||||
default=None,
|
||||
help="Directory to emit z3py files. If not specified no files are emitted.")
|
||||
parser.add_argument("--z3py-soversion",
|
||||
dest="z3py_soversion",
|
||||
default=None,
|
||||
help="SOVERSION for loading libz3 on supported platforms.")
|
||||
parser.add_argument("--dotnet-output-dir",
|
||||
dest="dotnet_output_dir",
|
||||
default=None,
|
||||
|
|
@ -2147,6 +2167,7 @@ def main(args):
|
|||
generate_files(api_files=pargs.api_files,
|
||||
api_output_dir=pargs.api_output_dir,
|
||||
z3py_output_dir=pargs.z3py_output_dir,
|
||||
z3py_soversion=pargs.z3py_soversion,
|
||||
dotnet_output_dir=pargs.dotnet_output_dir,
|
||||
java_input_dir=pargs.java_input_dir,
|
||||
java_output_dir=pargs.java_output_dir,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue