#!/usr/bin/env python3 """libwai_interface.py: wai.h declares WAI's C interface and nothing else, and libwai.a defines the part of it its features hold. libwai_interface.py [--root DIR] [--nm FILE] The C interface is the `#[unsafe(no_mangle)] extern "C" fn`s of wai/wai-rs/src/ffi.rs, ffi_codecs.rs, ffi_neural.rs and ffi_sink.rs, the four files build.rs hands cbindgen. The committed header, wai/wai-rs/include/wai.h, must - name every macro WAI_* (the include guard WAI_H aside), and every struct, enum, union and typedef wai_*: a constant or a type of another module of the crate has no place in it; - declare a function for each export of the four files, and no other function. With --nm, FILE is `nm -g --defined-only libwai.a`, the library built with the feature native_sink (wai/quickstart/build-libwai.sh): it must define exactly the wai_* functions of ffi.rs and ffi_sink.rs, the envelope's and the native sink's; the codec wrappers (ffi_codecs.rs) and the neural decoders (ffi_neural.rs) need libraries it does not hold. Exit 0 when it all holds, 1 with one line a finding otherwise. Standard library only; Python 3.8. """ import os import re import sys FILES = ("ffi.rs", "ffi_codecs.rs", "ffi_neural.rs", "ffi_sink.rs") SHIPPED = ("ffi.rs", "ffi_sink.rs") def exported(path): """The `#[unsafe(no_mangle)] ... extern "C" fn` names of one file.""" names, armed = set(), False for line in open(path, encoding="utf-8"): if "no_mangle" in line: armed = True m = re.search(r'extern "C" fn (\w+)', line) if m and armed: names.add(m.group(1)) armed = False return names def main(argv): root, nm = ".", None i = 0 while i < len(argv): if argv[i] == "--root" and i + 1 < len(argv): root, i = argv[i + 1], i + 2 elif argv[i] == "--nm" and i + 1 < len(argv): nm, i = argv[i + 1], i + 2 else: print("usage: libwai_interface.py [--root DIR] [--nm FILE]", file=sys.stderr) return 2 rs = os.path.join(root, "wai", "wai-rs") files = {f: exported(os.path.join(rs, "src", f)) for f in FILES} interface = set().union(*files.values()) shipped = set().union(*(files[f] for f in SHIPPED)) text = open(os.path.join(rs, "include", "wai.h"), encoding="utf-8").read() text = re.sub(r"/\*.*?\*/", " ", text, flags=re.S) text = re.sub(r"//[^\n]*", " ", text) bad = [] for name in re.findall(r"^\s*#\s*define\s+(\w+)", text, re.M): if name != "WAI_H" and not name.startswith("WAI_"): bad.append(f"wai.h defines the macro {name}, which is not WAI's") for name in re.findall(r"\b(?:struct|enum|union)\s+(\w+)", text): if not name.startswith("wai_"): bad.append(f"wai.h declares the type {name}, which is not WAI's") for name in re.findall(r"\btypedef\b[^;{]*?(\w+)\s*;", text): if not name.startswith("wai_"): bad.append(f"wai.h declares the typedef {name}, which is not WAI's") body = re.sub(r"^\s*#.*$", " ", text, flags=re.M).replace('extern "C" {', " ") declared = set(re.findall(r"\b(\w+)\s*\([^;{}]*\)\s*;", body)) for name in sorted(declared - interface): bad.append(f"wai.h declares the function {name}, which none of src/{', src/'.join(FILES)} exports") for name in sorted(interface - declared): bad.append(f"wai.h does not declare {name}, which the C interface exports: commit the header a build regenerates") if nm is not None: defined = set() for line in open(nm, encoding="utf-8"): parts = line.split() if len(parts) >= 3 and parts[1] == "T" and parts[2].startswith("wai_"): defined.add(parts[2]) for name in sorted(shipped - defined): bad.append(f"libwai.a does not define {name}") for name in sorted(defined - shipped): bad.append(f"libwai.a defines {name}, which is not of src/{' or src/'.join(SHIPPED)}") if bad: for b in sorted(set(bad)): print(f"libwai_interface: {b}", file=sys.stderr) return 1 line = f"wai.h: {len(declared)} functions, the C interface's, and every name WAI's" if nm is not None: line += (f"; libwai.a defines the {len(shipped)} of the envelope and the native sink, and not the " f"{len(interface - shipped)} of the codec wrappers and the neural decoders") print(line) return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))