| 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 | |
| Build libmoq_ffi ourselves, for the codecs the release leaves out eab184b nandi 9d ago | 85 | def error_variants(path): |
| 86 | """The MoqError variant table, read from the same generated bindings. |
| 87 | |
| 88 | Hand-maintaining this is a trap: the variants are not appended to, they are |
| 89 | INSERTED into. Turning moq-ffi\'s audio/video features on adds Audio and |
| 90 | Video at 5 and 6 and shifts every later variant down two, so a stale table |
| 91 | does not report an unknown variant -- it reports a confidently wrong name |
| 92 | for a real one. |
| 93 | """ |
| 94 | src = open(path).read() |
| 95 | i = src.index("class _UniffiFfiConverterTypeMoqError") |
| 96 | blk = src[i:] |
| 97 | blk = blk[:blk.index("\nclass ", 10)] |
| 98 | pairs = re.findall(r"if variant == (\d+):\s*\n\s*return MoqError\.(\w+)\(", blk) |
| 99 | def kebab(n): |
| 100 | return re.sub(r"(?<!^)(?=[A-Z])", "-", n).lower() |
| 101 | return [(int(n), kebab(name)) for n, name in pairs] |
| 102 | |
| 103 | |
| Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago | 104 | if __name__ == "__main__": |
| 105 | rows, unknown, total = main(sys.argv[1]) |
| 106 | print(f";; {len(rows)} of {total} entry points bound", file=sys.stderr) |
| 107 | if unknown: |
| 108 | print(";; UNMAPPED (left unbound rather than guessed):", file=sys.stderr) |
| 109 | for t, c in unknown.most_common(): |
| 110 | print(f";; {t} x{c}", file=sys.stderr) |
| Build libmoq_ffi ourselves, for the codecs the release leaves out eab184b nandi 9d ago | 111 | variants = error_variants(sys.argv[1]) |
| 112 | print(";; MoqError, as UniFFI numbers it in THIS object. Generated with the") |
| 113 | print(";; entry points above, and for the same reason: the variants are") |
| 114 | print(";; inserted into rather than appended to, so a table written by hand") |
| 115 | print(";; against one build names the wrong error in the next.") |
| 116 | print("(def moq-error-variants") |
| 117 | print(" {" + "\n ".join("%d :%s" % (n, k) for n, k in variants) + "})") |
| 118 | print() |
| Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 nandi 9d ago | 119 | for n, a, r in rows: |
| 120 | # Anchored, longest-first: "uniffi_moq_ffi_checksum_" must not be |
| 121 | # eaten by the "ffi_moq_ffi_" that also matches inside it. |
| 122 | for pre in ("uniffi_moq_ffi_fn_", "uniffi_moq_ffi_checksum_", |
| 123 | "ffi_moq_ffi_", "uniffi_moq_ffi_"): |
| 124 | if n.startswith(pre): |
| 125 | stem = n[len(pre):] |
| 126 | if pre.endswith("checksum_"): |
| 127 | stem = "checksum-" + stem |
| 128 | break |
| 129 | else: |
| 130 | stem = n |
| 131 | jolt_name = stem.replace("_", "-") |
| 132 | argv = " ".join(a) if a else "" |
| 133 | print(f'(ffi/defcfn {jolt_name} "{n}" [{argv}] {r})') |