open-standards/wai/quickstart/libwai_interface.py
Transaction Science 5af0c378c6 wai.h: the header holds the C interface alone
build.rs handed cbindgen the whole crate, so the committed header carried every public
constant of every module as a macro (F, S and A; CREATE and DELETE several times over) and an
opaque typedef for each type one belongs to (Fx, Isa, Plant, PowerSource, Reason, Voice).
It now hands it the four files of the C interface, src/ffi.rs, ffi_codecs.rs, ffi_neural.rs
and ffi_sink.rs, and nothing else: the header declares the wai_* functions, the WAI_*
constants and the wai_*_t types, and none of its wai_*/WAI_* declarations changed. The 102
removals wai breaking reports are acknowledged in the change's entry.

wai/quickstart/libwai_interface.py holds the committed header to that on every change (a step
of the repository checks): every macro WAI_*, every type wai_*, a declaration for each export
of the four files and no other. The release's libwai build runs it too, with the library's
symbols.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 18:21:14 -05:00

103 lines
4.4 KiB
Python
Executable file

#!/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:]))