#!/usr/bin/env python # Script checking that all symbols exported by libpython start with Py or _Py import subprocess import sys import sysconfig def get_exported_symbols(): LIBRARY = sysconfig.get_config_var('LIBRARY') if not LIBRARY: raise Exception("failed to get LIBRARY") args = ('nm', '-p', LIBRARY) print("+ %s" % ' '.join(args)) proc = subprocess.run(args, stdout=subprocess.PIPE, universal_newlines=True) if proc.returncode: sys.stdout.write(proc.stdout) sys.exit(proc.returncode) stdout = proc.stdout.rstrip() if not stdout: raise Exception("command output is empty") return stdout def get_smelly_symbols(stdout): symbols = [] ignored_symtypes = set() for line in stdout.splitlines(): # Split line '0000000000001b80 D PyTextIOWrapper_Type' if not line: continue parts = line.split(maxsplit=2) if len(parts) < 3: continue symtype = parts[1].strip() # Ignore private symbols. # # If lowercase, the symbol is usually local; if uppercase, the symbol # is global (external). There are however a few lowercase symbols that # are shown for special global symbols ("u", "v" and "w"). if symtype.islower() and symtype not in "uvw": ignored_symtypes.add(symtype) continue symbol = parts[-1] if symbol.startswith(('Py', '_Py')): continue symbol = '%s (type: %s)' % (symbol, symtype) symbols.append(symbol) if ignored_symtypes: print("Ignored symbol types: %s" % ', '.join(sorted(ignored_symtypes))) print() return symbols def main(): nm_output = get_exported_symbols() symbols = get_smelly_symbols(nm_output) if not symbols: print("OK: no smelly symbol found") sys.exit(0) symbols.sort() for symbol in symbols: print("Smelly symbol: %s" % symbol) print() print("ERROR: Found %s smelly symbols!" % len(symbols)) sys.exit(1) if __name__ == "__main__": main()