From da56985499306ebb3c08e3ff6416ba5162c47d91 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 21 Aug 2026 12:12:42 +0200 Subject: [PATCH] Check every JNI symbol, not just the first one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The android-check job failed on a tree that is perfectly correct: android.rs exports three `Java_..._MainActivity_*` symbols, the check compared all three against a single expected name and called it a mismatch. Two bugs, both from assuming one native: - SYM collected every export while EXPECTED was built from one method, so the comparison was three lines against one. - The Kotlin side matched `external fun name()` on empty parens, which quietly skipped nativeSetActivity and nativeBluetoothStateChanged — parameters do not appear in the symbol name, so stop at the paren. Now both sides are collected into sets and diffed, so a native declared in Kotlin with no Rust half fails as loudly as the reverse. Verified against the real files: passes as-is, and fails with a readable diff for a Kotlin-only native and for a renamed Rust export. Co-Authored-By: Claude Opus 5 (1M context) --- .gitea/workflows/build-and-test.yml | 39 ++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/.gitea/workflows/build-and-test.yml b/.gitea/workflows/build-and-test.yml index 50d9f8d..bf129dc 100644 --- a/.gitea/workflows/build-and-test.yml +++ b/.gitea/workflows/build-and-test.yml @@ -138,7 +138,7 @@ jobs: # Cheap invariants that a compiler cannot see. Both failures are silent: # the app builds, installs, launches, and then finds no trainer. - - name: Check the Android source layout and the JNI symbol + - name: Check the Android source layout and the JNI symbols run: | set -e # gen/ is generated and must stay untracked, or a `tauri android init` @@ -152,20 +152,35 @@ jobs: KT=src-tauri/android/src/main/java/paris/tourolle/bikecontrol/MainActivity.kt [ -f "$KT" ] || { echo "❌ $KT is missing"; exit 1; } - # The JNI symbol in android.rs is matched by the *runtime*, by name. - # Rename the Kotlin package or the method and nothing fails to - # compile — btleplug simply never gets initialised. - SYM=$(sed -n 's/.*pub extern "system" fn \(Java_[A-Za-z0-9_]*\).*/\1/p' src-tauri/src/android.rs) + # JNI symbols are matched by the *runtime*, by name. Rename the + # Kotlin package, add a native without its Rust half, or drop a Rust + # export, and nothing fails to compile — the method simply is not + # there when Java calls it. So compare the two sets, both ways. + # + # Sets, not a single pair: there are three natives now, and matching + # `external fun name()` on empty parens missed the two that take + # arguments. Parameters do not appear in the symbol name, so stop at + # the opening paren. + TMP="${RUNNER_TEMP:-/tmp}" PKG=$(sed -n 's/^package \(.*\)$/\1/p' "$KT" | tr -d '\r') - METHOD=$(sed -n 's/.*external fun \([A-Za-z0-9_]*\)().*/\1/p' "$KT") - EXPECTED="Java_$(echo "$PKG" | tr '.' '_')_MainActivity_$METHOD" - if [ "$SYM" != "$EXPECTED" ]; then - echo "❌ JNI symbol mismatch:" - echo " android.rs exports: $SYM" - echo " MainActivity needs: $EXPECTED" + PREFIX="Java_$(echo "$PKG" | tr '.' '_')_MainActivity_" + + sed -n 's/.*pub extern "system" fn \(Java_[A-Za-z0-9_]*\).*/\1/p' \ + src-tauri/src/android.rs | sort -u > "$TMP/jni-rust" + sed -n "s/.*external fun \([A-Za-z0-9_]*\)(.*/$PREFIX\1/p" \ + "$KT" | sort -u > "$TMP/jni-kotlin" + + [ -s "$TMP/jni-rust" ] || { echo "❌ No JNI exports found in android.rs"; exit 1; } + + if ! diff -u "$TMP/jni-kotlin" "$TMP/jni-rust" > "$TMP/jni-diff"; then + echo "❌ JNI symbols differ between MainActivity.kt and android.rs:" + echo " - declared in MainActivity.kt, not exported by android.rs" + echo " + exported by android.rs, not declared in MainActivity.kt" + tail -n +4 "$TMP/jni-diff" exit 1 fi - echo "✅ JNI symbol $SYM matches $PKG.MainActivity.$METHOD" + echo "✅ $(wc -l < "$TMP/jni-rust") JNI symbols match $PKG.MainActivity:" + sed 's/^/ /' "$TMP/jni-rust" - name: Cargo check (aarch64-linux-android) run: |