| Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago | 1 | #!/usr/bin/env python3 |
| 2 | """Turn uniffi-bindgen's Python output into jolt `defcfn` declarations. |
| 3 | |
| 4 | The Python backend emits, for every entry point in the library, a pair of |
| 5 | lines that together are an exact ABI description: |
| 6 | |
| 7 | _UniffiLib.uniffi_moq_ffi_fn_method_moqclient_connect.argtypes = ( |
| 8 | ctypes.c_uint64, _UniffiRustBuffer, ctypes.POINTER(_UniffiRustCallStatus), |
| 9 | ) |
| 10 | _UniffiLib.uniffi_moq_ffi_fn_method_moqclient_connect.restype = ctypes.c_uint64 |
| 11 | |
| 12 | That is generated from the metadata embedded in the .so itself, so it matches |
| 13 | the object being bound rather than a header shipped alongside it -- which for |
| 14 | this release is a different build (the Apple artifact carries audio/ video, the |
| 15 | Linux and Android ones do not). |
| 16 | """ |
| 17 | import re, sys, collections |
| 18 | |
| 19 | CTYPE = { |
| 20 | "ctypes.c_int8": ":int8", "ctypes.c_uint8": ":uint8", |
| 21 | "ctypes.c_int16": ":int16", "ctypes.c_uint16": ":uint16", |
| 22 | "ctypes.c_int32": ":int32", "ctypes.c_uint32": ":uint32", |
| 23 | "ctypes.c_int64": ":int64", "ctypes.c_uint64": ":uint64", |
| 24 | "ctypes.c_float": ":float", "ctypes.c_double": ":double", |
| 25 | "ctypes.c_void_p": ":pointer", "ctypes.c_size_t": ":uint64", |
| 26 | "None": ":void", |
| 27 | } |
| 28 | # A RustBuffer crosses by value; a status is always a pointer out-param. |
| 29 | RB = "[:by-value [:struct [[:capacity :uint64] [:len :uint64] [:data :pointer]]]]" |
| 30 | STATUS = ":pointer" |
| 31 | |
| 32 | def conv(t): |
| 33 | t = t.strip().rstrip(",").strip() |
| 34 | if not t: |
| 35 | return None |
| 36 | if t in CTYPE: |
| 37 | return CTYPE[t] |
| 38 | if "RustCallStatus" in t: |
| 39 | return STATUS |
| 40 | if "RustBuffer" in t: |
| 41 | return RB |
| 42 | if "ForeignBytes" in t: |
| 43 | return "[:by-value [:struct [[:len :int32] [:data :pointer]]]]" |
| 44 | if t.startswith("ctypes.POINTER"): |
| 45 | return ":pointer" |
| 46 | # A function-pointer typedef (the future continuation) -- jolt lowers an |
| 47 | # ffi/callback to a plain pointer, so that is what the slot takes. |
| 48 | if "callback" in t.lower() or "struct" in t.lower(): |
| 49 | return ":pointer" |
| 50 | return None # unknown -- reported, never guessed |
| 51 | |
| 52 | def split_args(s): |
| 53 | out, depth, cur = [], 0, "" |
| 54 | for ch in s: |
| 55 | if ch == "(": |
| 56 | depth += 1 |
| 57 | elif ch == ")": |
| 58 | depth -= 1 |
| 59 | if ch == "," and depth == 0: |
| 60 | out.append(cur); cur = "" |
| 61 | else: |
| 62 | cur += ch |
| 63 | if cur.strip(): |
| 64 | out.append(cur) |
| 65 | return out |
| 66 | |
| 67 | def main(path): |
| 68 | src = open(path).read() |
| 69 | args = dict(re.findall(r"_UniffiLib\.(\w+)\.argtypes\s*=\s*\(([^)]*(?:\([^)]*\)[^)]*)*)\)", src)) |
| 70 | rets = dict(re.findall(r"_UniffiLib\.(\w+)\.restype\s*=\s*(.+)", src)) |
| 71 | names = sorted(set(args) | set(rets)) |
| 72 | unknown = collections.Counter() |
| 73 | rows = [] |
| 74 | for n in names: |
| 75 | a = [conv(x) for x in split_args(args.get(n, ""))] |
| 76 | r = conv(rets.get(n, "None")) |
| 77 | if None in a or r is None: |
| 78 | for x in split_args(args.get(n, "")) + [rets.get(n, "None")]: |
| 79 | if conv(x) is None: |
| 80 | unknown[x.strip()] += 1 |
| 81 | continue |
| 82 | rows.append((n, a, r)) |
| 83 | return rows, unknown, len(names) |
| 84 | |
| 85 | if __name__ == "__main__": |
| 86 | rows, unknown, total = main(sys.argv[1]) |
| 87 | print(f";; {len(rows)} of {total} entry points bound", file=sys.stderr) |
| 88 | if unknown: |
| 89 | print(";; UNMAPPED (left unbound rather than guessed):", file=sys.stderr) |
| 90 | for t, c in unknown.most_common(): |
| 91 | print(f";; {t} x{c}", file=sys.stderr) |
| 92 | for n, a, r in rows: |
| 93 | # Anchored, longest-first: "uniffi_moq_ffi_checksum_" must not be |
| 94 | # eaten by the "ffi_moq_ffi_" that also matches inside it. |
| 95 | for pre in ("uniffi_moq_ffi_fn_", "uniffi_moq_ffi_checksum_", |
| 96 | "ffi_moq_ffi_", "uniffi_moq_ffi_"): |
| 97 | if n.startswith(pre): |
| 98 | stem = n[len(pre):] |
| 99 | if pre.endswith("checksum_"): |
| 100 | stem = "checksum-" + stem |
| 101 | break |
| 102 | else: |
| 103 | stem = n |
| 104 | jolt_name = stem.replace("_", "-") |
| 105 | argv = " ".join(a) if a else "" |
| 106 | print(f'(ffi/defcfn {jolt_name} "{n}" [{argv}] {r})') |