mirror of
https://github.com/libsdl-org/SDL.git
synced 2024-11-23 02:43:30 +08:00
android: add python script checking Android JNI bindings
This commit is contained in:
parent
cf4049cfde
commit
2070adb262
1
.github/workflows/generic.yml
vendored
1
.github/workflows/generic.yml
vendored
@ -357,6 +357,7 @@ jobs:
|
||||
run: |
|
||||
set -e
|
||||
build-scripts/test-versioning.sh
|
||||
python build-scripts/check_android_jni.py
|
||||
- name: 'Upload binary package'
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ always() && matrix.platform.artifact != '' && (steps.package.outcome == 'success' || steps.cpactions.outcome == 'success') && (matrix.platform.enable-artifacts || steps.tests.outcome == 'failure') }}
|
||||
|
172
build-scripts/check_android_jni.py
Executable file
172
build-scripts/check_android_jni.py
Executable file
@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
SDL_ANDROID_C = ROOT / "src/core/android/SDL_android.c"
|
||||
METHOD_SOURCE_PATHS = (
|
||||
SDL_ANDROID_C,
|
||||
ROOT / "src/hidapi/android/hid.cpp",
|
||||
)
|
||||
JAVA_ROOT = ROOT / "android-project/app/src/main/java"
|
||||
|
||||
|
||||
BASIC_TYPE_SPEC_LUT = {
|
||||
"char": "C",
|
||||
"byte": "B",
|
||||
"short": "S",
|
||||
"int": "I",
|
||||
"long": "J",
|
||||
"float": "F",
|
||||
"double": "D",
|
||||
"void": "V",
|
||||
"boolean": "Z",
|
||||
"Object": "Ljava/lang/Object;",
|
||||
"String": "Ljava/lang/String;",
|
||||
}
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class JniType:
|
||||
typ: str
|
||||
array: int
|
||||
|
||||
|
||||
def java_type_to_jni_spec_internal(type_str: str) -> tuple[int, str]:
|
||||
for basic_type_str, basic_type_spec in BASIC_TYPE_SPEC_LUT.items():
|
||||
if type_str.startswith(basic_type_str):
|
||||
return len(basic_type_str), basic_type_spec
|
||||
raise ValueError(f"Don't know how to convert {repr(type_str)} to its equivalent jni spec")
|
||||
|
||||
|
||||
def java_type_to_jni_spec(type_str: str) -> str:
|
||||
end, type_spec = java_type_to_jni_spec_internal(type_str)
|
||||
suffix_str = type_str[end:]
|
||||
assert(all(c in "[] \t" for c in suffix_str))
|
||||
suffix_str = "".join(filter(lambda v: v in "[]", suffix_str))
|
||||
assert len(suffix_str) % 2 == 0
|
||||
array_spec = "[" * (len(suffix_str) // 2)
|
||||
return array_spec + type_spec
|
||||
|
||||
|
||||
def java_method_to_jni_spec(ret: str, args: list[str]) -> str:
|
||||
return "(" + "".join(java_type_to_jni_spec(a) for a in args) +")" + java_type_to_jni_spec(ret)
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class JniMethodBinding:
|
||||
name: str
|
||||
spec: str
|
||||
|
||||
|
||||
def collect_jni_bindings_from_c() -> dict[str, set[JniMethodBinding]]:
|
||||
bindings = {}
|
||||
|
||||
sdl_android_text = SDL_ANDROID_C.read_text()
|
||||
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):
|
||||
kls = m["class"]
|
||||
table = m["table"]
|
||||
methods = set()
|
||||
in_struct = False
|
||||
for method_source_path in METHOD_SOURCE_PATHS:
|
||||
method_source = method_source_path.read_text()
|
||||
for line in method_source.splitlines(keepends=False):
|
||||
if re.match(f"(static )?JNINativeMethod {table}" + r"\[([0-9]+)?\] = \{", line):
|
||||
in_struct = True
|
||||
continue
|
||||
if in_struct:
|
||||
if re.match(r"\};", line):
|
||||
in_struct = False
|
||||
break
|
||||
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)
|
||||
assert n, f"'{line}' does not match regex"
|
||||
methods.add(JniMethodBinding(name=n["method"], spec=n["spec"]))
|
||||
continue
|
||||
if methods:
|
||||
break
|
||||
if methods:
|
||||
break
|
||||
assert methods, f"Could not find methods for {kls} (table={table})"
|
||||
|
||||
assert not in_struct
|
||||
|
||||
assert kls not in bindings, f"{kls} must be unique in C sources"
|
||||
bindings[kls] = methods
|
||||
return bindings
|
||||
|
||||
def collect_jni_bindings_from_java() -> dict[str, set[JniMethodBinding]]:
|
||||
bindings = {}
|
||||
|
||||
for root, _, files in os.walk(JAVA_ROOT):
|
||||
for file in files:
|
||||
file_path = pathlib.Path(root) / file
|
||||
java_text = file_path.read_text()
|
||||
methods = set()
|
||||
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):
|
||||
name = m["method"]
|
||||
ret = m["ret"]
|
||||
args = []
|
||||
args_str = m["args"].strip()
|
||||
if args_str:
|
||||
for a_s in args_str.split(","):
|
||||
atype_str, _ = a_s.strip().rsplit(" ")
|
||||
args.append(atype_str.strip())
|
||||
|
||||
spec = java_method_to_jni_spec(ret=ret, args=args)
|
||||
methods.add(JniMethodBinding(name=name, spec=spec))
|
||||
if methods:
|
||||
relative_java_path = file_path.relative_to(JAVA_ROOT)
|
||||
relative_java_path_without_suffix = relative_java_path.with_suffix("")
|
||||
kls = "/".join(relative_java_path_without_suffix.parts)
|
||||
assert kls not in bindings, f"{kls} must be unique in JAVA sources"
|
||||
bindings[kls] = methods
|
||||
return bindings
|
||||
|
||||
|
||||
def print_error(*args):
|
||||
print("ERROR:", *args)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False, description="Verify Android JNI bindings")
|
||||
args = parser.parse_args()
|
||||
|
||||
bindings_from_c = collect_jni_bindings_from_c()
|
||||
bindings_from_java = collect_jni_bindings_from_java()
|
||||
|
||||
all_ok = bindings_from_c == bindings_from_java
|
||||
if all_ok:
|
||||
print("OK")
|
||||
else:
|
||||
print("NOT OK")
|
||||
kls_c = set(bindings_from_c.keys())
|
||||
kls_java = set(bindings_from_java.keys())
|
||||
if kls_c != kls_java:
|
||||
only_c = kls_c - kls_java
|
||||
for c in only_c:
|
||||
print_error(f"Missing class in JAVA sources: {c}")
|
||||
only_java = kls_java - kls_c
|
||||
for c in only_java:
|
||||
print_error(f"Missing class in C sources: {c}")
|
||||
|
||||
klasses = kls_c.union(kls_java)
|
||||
for kls in klasses:
|
||||
m_c = bindings_from_c.get(kls)
|
||||
m_j = bindings_from_java.get(kls)
|
||||
if m_c and m_j and m_c != m_j:
|
||||
m_only_c = m_c - m_j
|
||||
for c in m_only_c:
|
||||
print_error(f"{kls}: Binding only in C source: {c.name} {c.spec}")
|
||||
m_only_j = m_j - m_c
|
||||
for c in m_only_j:
|
||||
print_error(f"{kls}: Binding only in JAVA source: {c.name} {c.spec}")
|
||||
|
||||
return 0 if all_ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
Loading…
Reference in New Issue
Block a user