check-symbols.py (3545B)
1 #!/usr/bin/env python3 2 3 import difflib 4 import os 5 import re 6 import shutil 7 import subprocess 8 import sys 9 10 os.environ["LC_ALL"] = "C" # otherwise 'nm' prints in wrong order 11 12 srcdir = sys.argv[1] 13 base_srcdir = sys.argv[2] 14 builddir = sys.argv[3] 15 16 IGNORED_SYMBOLS = [ 17 "_fini", 18 "_init", 19 "_fdata", 20 "_ftext", 21 "_fbss", 22 "__bss_start", 23 "__bss_start__", 24 "__bss_end__", 25 "_edata", 26 "_end", 27 "_bss_end__", 28 "__end__", 29 "__gcov_.*", 30 "llvm_.*", 31 "flush_fn_list", 32 "writeout_fn_list", 33 "mangle_path", 34 "lprofDirMode", 35 "reset_fn_list", 36 ] 37 38 # Rust 39 IGNORED_SYMBOLS += [ 40 "rust_eh_personality", 41 "_.*3std9panicking11EMPTY_PANIC.*", # 'std.*::panicking::EMPTY_PANIC::.*' 42 "_.*3std3sys3pal4unix4args3imp15ARGV_INIT_ARRAY.*", # 'std.*::sys::pal::unix::args::imp::ARGV_INIT_ARRAY::.*' 43 "_.*3std3sys4args4unix3imp15ARGV_INIT_ARRAY.*", # std.*::sys::args::unix::imp::ARGV_INIT_ARRAY::.* 44 "_.*17compiler_builtins.*", # 'compiler_builtins.*::.*' 45 ".*__rustc.*", # '.*__rustc.*' # Eg. _RNvCsgSLETaxrkfn_7___rustc17___rust_probestack 46 '_hb_harfrust_.*_rs', 47 ] 48 49 IGNORED_SYMBOLS = "|".join(IGNORED_SYMBOLS) 50 51 nm = os.getenv("NM", shutil.which("nm")) 52 if not nm: 53 print("check-symbols.py: 'nm' not found; skipping test") 54 sys.exit(77) 55 56 tested = False 57 stat = 0 58 59 for soname in [ 60 "harfbuzz", 61 "harfbuzz-subset", 62 "harfbuzz-icu", 63 "harfbuzz-gobject", 64 "harfbuzz-cairo", 65 ]: 66 for suffix in ["so", "dylib"]: 67 so = os.path.join(builddir, "lib%s.%s" % (soname, suffix)) 68 if not os.path.exists(so): 69 continue 70 71 # On macOS, C symbols are prefixed with _ 72 symprefix = "_" if suffix == "dylib" else "" 73 74 EXPORTED_SYMBOLS = [ 75 s.split()[2] 76 for s in re.findall( 77 r"^.+ [BCDGIRSTu] .+$", 78 subprocess.check_output(nm.split() + [so]).decode("utf-8"), 79 re.MULTILINE, 80 ) 81 if not re.match(r".* %s(%s)\b" % (symprefix, IGNORED_SYMBOLS), s) 82 ] 83 84 prefix = ( 85 (symprefix + os.path.basename(so)) 86 .replace("libharfbuzz", "hb") 87 .replace("-", "_") 88 .split(".")[0] 89 ) 90 91 print("Checking that %s does not expose internal symbols" % so) 92 suspicious_symbols = [ 93 x for x in EXPORTED_SYMBOLS if not re.match(r"^%s(_|$)" % prefix, x) 94 ] 95 if suspicious_symbols: 96 print("Ouch, internal symbols exposed:", suspicious_symbols) 97 stat = 1 98 99 def_path = os.path.join(builddir, soname + ".def") 100 if not os.path.exists(def_path): 101 print("'%s' not found; skipping" % def_path) 102 else: 103 print("Checking that %s has the same symbol list as %s" % (so, def_path)) 104 with open(def_path, "r", encoding="utf-8") as f: 105 def_file = f.read() 106 diff_result = list( 107 difflib.context_diff( 108 def_file.splitlines(), 109 ["EXPORTS"] 110 + [re.sub("^%shb" % symprefix, "hb", x) for x in EXPORTED_SYMBOLS] 111 + 112 # cheat: copy the last line from the def file! 113 [def_file.splitlines()[-1]], 114 ) 115 ) 116 117 if diff_result: 118 print("\n".join(diff_result)) 119 stat = 1 120 121 tested = True 122 123 if not tested: 124 print("check-symbols.py: no shared libraries found; skipping test") 125 sys.exit(77) 126 127 sys.exit(stat)