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