diff --git a/.github/workflows/lib_compat_test.py b/.github/workflows/lib_compat_test.py index 10460cc..d1dd918 100755 --- a/.github/workflows/lib_compat_test.py +++ b/.github/workflows/lib_compat_test.py @@ -9,67 +9,81 @@ import subprocess import sys parser = argparse.ArgumentParser() -parser.add_argument('binary') -parser.add_argument('--lib', action='append', default=[], - help="Whitelist a library, e.g. GLIBC_2.18 or GLIBC") -parser.add_argument('--sym', action='append', default=[], - help="Whitelist a symbol, e.g. crc32") +parser.add_argument("binary") +parser.add_argument( + "--lib", + action="append", + default=[], + help="Whitelist a library, e.g. GLIBC_2.18 or GLIBC", +) +parser.add_argument( + "--sym", action="append", default=[], help="Whitelist a symbol, e.g. crc32" +) args = parser.parse_args() # Parses GLIBC_2.3 into ("GLIBC", [2,3]) # Parses GLIBC into ("GLIBC", None) def parse_version(version): - parts = version.rsplit('_', 1) - if len(parts) == 1: - return (version, None) - try: - return (parts[0], [int(p) for p in parts[1].split('.')]) - except ValueError: - return (version, None) + parts = version.rsplit("_", 1) + if len(parts) == 1: + return (version, None) + try: + return (parts[0], [int(p) for p in parts[1].split(".")]) + except ValueError: + return (version, None) + lib_versions = dict([parse_version(v) for v in args.lib]) # Determines whether all symbols with version 'lib' are acceptable. # A versioned library is name_x.y.z by convention. def accept_lib(lib): - (lib, ver) = parse_version(lib) - if not lib in lib_versions: # Non-whitelisted library. - return False - if lib_versions[lib] is None: # Unrestricted version - return True - if ver is None: # Lib has non-numeric version, library restricts version. - return False - return ver <= lib_versions[lib] + (lib, ver) = parse_version(lib) + if not lib in lib_versions: # Non-whitelisted library. + return False + if lib_versions[lib] is None: # Unrestricted version + return True + if ver is None: # Lib has non-numeric version, library restricts version. + return False + return ver <= lib_versions[lib] + # Determines whether an optionally-versioned symbol is acceptable. # A versioned symbol is symbol@version as output by nm. def accept_symbol(sym): - if sym in args.sym: - return True - split = sym.split('@', 1) - return (split[0] in args.sym) or (len(split) == 2 and accept_lib(split[1])) + if sym in args.sym: + return True + split = sym.split("@", 1) + return (split[0] in args.sym) or (len(split) == 2 and accept_lib(split[1])) + # Run nm to find the undefined symbols, and check whether each is acceptable. -nm = subprocess.run(['nm', '-uD', '--with-symbol-version', args.binary], - stdout=subprocess.PIPE, text=True) +nm = subprocess.run( + ["nm", "-uD", "--with-symbol-version", args.binary], + stdout=subprocess.PIPE, + text=True, +) nm.check_returncode() status = 0 for line in nm.stdout.splitlines(): - # line = " U foo@GLIBC_2.3" - parts = line.split() - if len(parts) != 2: - print("Unparseable nm output: ", line, file=sys.stderr) - status = 2 - continue - if parts[0] == 'w': # Weak-undefined symbol, not actually required. - continue - if not accept_symbol(parts[1]): - print(parts[1]) - status = 1 + # line = " U foo@GLIBC_2.3" + parts = line.split() + if len(parts) != 2: + print("Unparseable nm output: ", line, file=sys.stderr) + status = 2 + continue + if parts[0] == "w": # Weak-undefined symbol, not actually required. + continue + if not accept_symbol(parts[1]): + print(parts[1]) + status = 1 if status == 1: - print("Binary depends on disallowed symbols above. Use some combination of:\n" + print( + "Binary depends on disallowed symbols above. Use some combination of:\n" " - relax the whitelist by adding --lib and --sym flags to this test\n" " - force older symbol versions by updating lib_compat.h\n" " - avoid dynamic dependencies by changing CMake configuration\n" - " - remove bad dependencies from the code", file=sys.stderr) + " - remove bad dependencies from the code", + file=sys.stderr, + ) sys.exit(status)