#!/bin/sh # Post-link floor check for libpunktfunk_android.so: every undefined dynamic symbol must be # defined in the NDK's minSdk-level stub libraries, for every built ABI. # # Why this exists: a Rust cdylib links fine with dangling undefined symbols (lld only rejects them # under --no-undefined, which rustc does not pass), so a hard import of an above-floor NDK entry # point sails through `cargo ndk --platform 28` and only explodes at `System.loadLibrary` on every # device below the symbol's introduction level — 0.9.0 shipped exactly this # (AMediaCodec_setOnFrameRenderedCallback, API 33) and bricked the client on all pre-Android-13 # devices. Above-floor entry points must be dlsym-resolved at runtime instead (see # decode::try_set_frame_rate / decode::install_render_callback / adpf). This script makes the # violation a build failure. Wired into clients/android/kit/build.gradle.kts after each cargo-ndk # build (local + CI). # # Usage: check-android-jni-imports.sh set -eu NDK=$1 JNILIBS=$2 API=$3 # Host-agnostic prebuilt dir (linux-x86_64 on CI and the dev box, darwin-* on a Mac). PREBUILT=$(echo "$NDK"/toolchains/llvm/prebuilt/*) NM="$PREBUILT/bin/llvm-nm" [ -x "$NM" ] || { echo "check-android-jni-imports: llvm-nm not found under $NDK" >&2; exit 1; } triple_for_abi() { case "$1" in arm64-v8a) echo aarch64-linux-android ;; armeabi-v7a) echo arm-linux-androideabi ;; x86_64) echo x86_64-linux-android ;; x86) echo i686-linux-android ;; *) echo "" ;; esac } TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT checked=0 fail=0 for so in "$JNILIBS"/*/libpunktfunk_android.so; do [ -f "$so" ] || continue abi=$(basename "$(dirname "$so")") triple=$(triple_for_abi "$abi") if [ -z "$triple" ]; then echo "check-android-jni-imports: unknown ABI dir '$abi' — extend triple_for_abi" >&2 exit 1 fi stubs="$PREBUILT/sysroot/usr/lib/$triple/$API" [ -d "$stubs" ] || { echo "check-android-jni-imports: no stub dir $stubs" >&2; exit 1; } # Strong undefined imports only ('U'; weak 'w'/'v' resolve to null and are fine), and the # stubs' exports, both with the @VERSION / @@VERSION suffix stripped. "$NM" -D --undefined-only "$so" | awk '$1 == "U" { print $2 }' | sed 's/@.*//' | sort -u \ > "$TMP/undef" for stub in "$stubs"/*.so; do "$NM" -D --defined-only "$stub" 2>/dev/null | awk '{ print $NF }' | sed 's/@.*//' done | sort -u > "$TMP/defined" missing=$(comm -23 "$TMP/undef" "$TMP/defined") if [ -n "$missing" ]; then echo "check-android-jni-imports: $abi libpunktfunk_android.so imports symbols absent from" >&2 echo "the API-$API sysroot — System.loadLibrary would fail on devices at the minSdk floor." >&2 echo "dlsym-resolve these at runtime instead of hard-linking them:" >&2 echo "$missing" | sed 's/^/ /' >&2 fail=1 fi checked=$((checked + 1)) done [ "$checked" -gt 0 ] || { echo "check-android-jni-imports: no libpunktfunk_android.so under $JNILIBS" >&2; exit 1; } [ "$fail" -eq 0 ] && echo "check-android-jni-imports: $checked ABI(s) clean at the API-$API floor" exit "$fail"