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]>
103 lines
4.4 KiB
Python
Executable file
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:]))
|