summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/build-scripts/check_android_jni.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/contrib/SDL-3.2.20/build-scripts/check_android_jni.py')
-rwxr-xr-xsrc/contrib/SDL-3.2.20/build-scripts/check_android_jni.py172
1 files changed, 172 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/build-scripts/check_android_jni.py b/src/contrib/SDL-3.2.20/build-scripts/check_android_jni.py
new file mode 100755
index 0000000..9e519fb
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/build-scripts/check_android_jni.py
@@ -0,0 +1,172 @@
1#!/usr/bin/env python
2
3import argparse
4import dataclasses
5import os
6import pathlib
7import re
8
9ROOT = pathlib.Path(__file__).resolve().parents[1]
10SDL_ANDROID_C = ROOT / "src/core/android/SDL_android.c"
11METHOD_SOURCE_PATHS = (
12 SDL_ANDROID_C,
13 ROOT / "src/hidapi/android/hid.cpp",
14)
15JAVA_ROOT = ROOT / "android-project/app/src/main/java"
16
17
18BASIC_TYPE_SPEC_LUT = {
19 "char": "C",
20 "byte": "B",
21 "short": "S",
22 "int": "I",
23 "long": "J",
24 "float": "F",
25 "double": "D",
26 "void": "V",
27 "boolean": "Z",
28 "Object": "Ljava/lang/Object;",
29 "String": "Ljava/lang/String;",
30}
31
32
33@dataclasses.dataclass(frozen=True)
34class JniType:
35 typ: str
36 array: int
37
38
39def java_type_to_jni_spec_internal(type_str: str) -> tuple[int, str]:
40 for basic_type_str, basic_type_spec in BASIC_TYPE_SPEC_LUT.items():
41 if type_str.startswith(basic_type_str):
42 return len(basic_type_str), basic_type_spec
43 raise ValueError(f"Don't know how to convert {repr(type_str)} to its equivalent jni spec")
44
45
46def java_type_to_jni_spec(type_str: str) -> str:
47 end, type_spec = java_type_to_jni_spec_internal(type_str)
48 suffix_str = type_str[end:]
49 assert(all(c in "[] \t" for c in suffix_str))
50 suffix_str = "".join(filter(lambda v: v in "[]", suffix_str))
51 assert len(suffix_str) % 2 == 0
52 array_spec = "[" * (len(suffix_str) // 2)
53 return array_spec + type_spec
54
55
56def java_method_to_jni_spec(ret: str, args: list[str]) -> str:
57 return "(" + "".join(java_type_to_jni_spec(a) for a in args) +")" + java_type_to_jni_spec(ret)
58
59
60@dataclasses.dataclass(frozen=True)
61class JniMethodBinding:
62 name: str
63 spec: str
64
65
66def collect_jni_bindings_from_c() -> dict[str, set[JniMethodBinding]]:
67 bindings = {}
68
69 sdl_android_text = SDL_ANDROID_C.read_text()
70 for m in re.finditer(r"""register_methods\((?:[A-Za-z0-9]+),\s*"(?P<class>[a-zA-Z0-9_/]+)",\s*(?P<table>[a-zA-Z0-9_]+),\s*SDL_arraysize\((?P=table)\)\)""", sdl_android_text):
71 kls = m["class"]
72 table = m["table"]
73 methods = set()
74 in_struct = False
75 for method_source_path in METHOD_SOURCE_PATHS:
76 method_source = method_source_path.read_text()
77 for line in method_source.splitlines(keepends=False):
78 if re.match(f"(static )?JNINativeMethod {table}" + r"\[([0-9]+)?\] = \{", line):
79 in_struct = True
80 continue
81 if in_struct:
82 if re.match(r"\};", line):
83 in_struct = False
84 break
85 n = re.match(r"""\s*\{\s*"(?P<method>[a-zA-Z0-9_]+)"\s*,\s*"(?P<spec>[()A-Za-z0-9_/;[]+)"\s*,\s*(\(void\*\))?(HID|SDL)[_A-Z]*_JAVA_[_A-Z]*INTERFACE[_A-Z]*\((?P=method)\)\s*\},?""", line)
86 assert n, f"'{line}' does not match regex"
87 methods.add(JniMethodBinding(name=n["method"], spec=n["spec"]))
88 continue
89 if methods:
90 break
91 if methods:
92 break
93 assert methods, f"Could not find methods for {kls} (table={table})"
94
95 assert not in_struct
96
97 assert kls not in bindings, f"{kls} must be unique in C sources"
98 bindings[kls] = methods
99 return bindings
100
101def collect_jni_bindings_from_java() -> dict[str, set[JniMethodBinding]]:
102 bindings = {}
103
104 for root, _, files in os.walk(JAVA_ROOT):
105 for file in files:
106 file_path = pathlib.Path(root) / file
107 java_text = file_path.read_text()
108 methods = set()
109 for m in re.finditer(r"(?:(?:public|private)\s+)?(?:static\s+)?native\s+(?P<ret>[A-Za-z0-9_]+)\s+(?P<method>[a-zA-Z0-9_]+)\s*\(\s*(?P<args>[^)]*)\);", java_text):
110 name = m["method"]
111 ret = m["ret"]
112 args = []
113 args_str = m["args"].strip()
114 if args_str:
115 for a_s in args_str.split(","):
116 atype_str, _ = a_s.strip().rsplit(" ")
117 args.append(atype_str.strip())
118
119 spec = java_method_to_jni_spec(ret=ret, args=args)
120 methods.add(JniMethodBinding(name=name, spec=spec))
121 if methods:
122 relative_java_path = file_path.relative_to(JAVA_ROOT)
123 relative_java_path_without_suffix = relative_java_path.with_suffix("")
124 kls = "/".join(relative_java_path_without_suffix.parts)
125 assert kls not in bindings, f"{kls} must be unique in JAVA sources"
126 bindings[kls] = methods
127 return bindings
128
129
130def print_error(*args):
131 print("ERROR:", *args)
132
133
134def main():
135 parser = argparse.ArgumentParser(allow_abbrev=False, description="Verify Android JNI bindings")
136 args = parser.parse_args()
137
138 bindings_from_c = collect_jni_bindings_from_c()
139 bindings_from_java = collect_jni_bindings_from_java()
140
141 all_ok = bindings_from_c == bindings_from_java
142 if all_ok:
143 print("OK")
144 else:
145 print("NOT OK")
146 kls_c = set(bindings_from_c.keys())
147 kls_java = set(bindings_from_java.keys())
148 if kls_c != kls_java:
149 only_c = kls_c - kls_java
150 for c in only_c:
151 print_error(f"Missing class in JAVA sources: {c}")
152 only_java = kls_java - kls_c
153 for c in only_java:
154 print_error(f"Missing class in C sources: {c}")
155
156 klasses = kls_c.union(kls_java)
157 for kls in klasses:
158 m_c = bindings_from_c.get(kls)
159 m_j = bindings_from_java.get(kls)
160 if m_c and m_j and m_c != m_j:
161 m_only_c = m_c - m_j
162 for c in m_only_c:
163 print_error(f"{kls}: Binding only in C source: {c.name} {c.spec}")
164 m_only_j = m_j - m_c
165 for c in m_only_j:
166 print_error(f"{kls}: Binding only in JAVA source: {c.name} {c.spec}")
167
168 return 0 if all_ok else 1
169
170
171if __name__ == "__main__":
172 raise SystemExit(main())