python/required-symbols/collect_symbols.py
2026-07-01 14:43:13 +02:00

67 lines
2.2 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Collect undefined non-boring external symbols from compiled Python extension modules
and libpython shared libraries.
Usage: collect_symbols.py <path> [<path>...]
Each path may be a directory (scanned recursively for *.cpython-*.so) or a file
(scanned directly; intended for libpython*.so.* passed explicitly from the caller).
Symbols are unioned across all variants of each module (regular/debug/freethreading),
and the result is printed as JSON to stdout.
"""
import json
import re
import subprocess
import sys
from pathlib import Path
BORING = re.compile(
r"@GLIBC_" # glibc versioned symbols (stable by definition)
r"|@GCC_" # GCC built-ins
r"|^_?Py[A-Za-z_]" # Python C API (resolved from libpython at runtime)
r"|^__" # C runtime internals
r"|^_ITM_" # Intel transactional memory
)
def module_name(so: Path) -> str:
# _ssl.cpython-314td-x86_64-linux-gnu.so → _ssl
if m := re.match(r"(.+?)\.cpython-", so.name):
return m.group(1)
# libpython3.14.so.1.0 → libpython3.14
return re.sub(r"\.so.*$", "", so.name)
def external_symbols(so: Path) -> list[str]:
result = subprocess.run(["nm", "-D", str(so)], capture_output=True, text=True)
return sorted(
parts[-1]
for line in result.stdout.splitlines()
if len(parts := line.split()) >= 2
and parts[-2] == "U"
and not BORING.search(parts[-1])
)
modules: dict[str, set[str]] = {}
for path_arg in sys.argv[1:]:
p = Path(path_arg)
if p.is_file():
print(f"Scanning: {p.name}", file=sys.stderr)
if syms := external_symbols(p):
modules.setdefault(module_name(p), set()).update(syms)
else:
print(f"Scanning: {path_arg}", file=sys.stderr)
for so in sorted(p.rglob("*.cpython-*.so")):
if so.is_symlink():
continue
if syms := external_symbols(so):
modules.setdefault(module_name(so), set()).update(syms)
total_symbols = sum(len(v) for v in modules.values())
print(f"Found {len(modules)} modules, {total_symbols} tracked symbols", file=sys.stderr)
json.dump({k: sorted(v) for k, v in sorted(modules.items())}, sys.stdout, indent=2)
print()