113 lines
4.1 KiB
Bash
113 lines
4.1 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# Assert that R8 left the names JNI resolves at runtime alone.
|
||
|
|
#
|
||
|
|
# iperf_jni.c looks up the callback methods by name with GetMethodID. R8 renames
|
||
|
|
# by default, and a renamed callback does not fail the build — it produces an
|
||
|
|
# APK that aborts on the first callback of every test:
|
||
|
|
#
|
||
|
|
# NoSuchMethodError: no non-static method "Lp0;.onOutput(Ljava/lang/String;)V"
|
||
|
|
#
|
||
|
|
# That shipped once. Building the release APK is not enough to catch it, because
|
||
|
|
# the build succeeds; this reads R8's own mapping file and checks the names
|
||
|
|
# survived. Run after `flutter build apk --release`.
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
MAPPING="${1:-build/app/outputs/mapping/release/mapping.txt}"
|
||
|
|
APK="${2:-build/app/outputs/flutter-apk/app-release.apk}"
|
||
|
|
|
||
|
|
if [[ ! -f "$MAPPING" ]]; then
|
||
|
|
echo "FAIL: no R8 mapping at $MAPPING"
|
||
|
|
echo " Run 'flutter build apk --release' first. If the release build"
|
||
|
|
echo " no longer minifies, this check is meaningless — say so"
|
||
|
|
echo " explicitly rather than deleting it."
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
failed=0
|
||
|
|
|
||
|
|
# A class kept by name maps to itself: "a.b.C -> a.b.C:".
|
||
|
|
require_class_kept() {
|
||
|
|
local fqcn="$1"
|
||
|
|
if grep -qF -- "$fqcn -> $fqcn:" "$MAPPING"; then
|
||
|
|
echo " ok class $fqcn"
|
||
|
|
else
|
||
|
|
echo " FAIL class $fqcn was renamed or removed"
|
||
|
|
grep -F -- "$fqcn ->" "$MAPPING" | head -3 | sed 's/^/ got: /' || true
|
||
|
|
failed=1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# A method kept by name ends with "-> <name>".
|
||
|
|
require_method_kept() {
|
||
|
|
local signature="$1" name="$2"
|
||
|
|
if grep -qE -- "$signature.*-> ${name}\$" "$MAPPING"; then
|
||
|
|
echo " ok method $name"
|
||
|
|
else
|
||
|
|
echo " FAIL method $name was renamed"
|
||
|
|
grep -E -- "$signature" "$MAPPING" | head -3 | sed 's/^/ got: /' || true
|
||
|
|
failed=1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "Checking JNI-visible names in $MAPPING"
|
||
|
|
|
||
|
|
require_class_kept 'com.novoyuuparosk.iperf3gui.IperfRunner'
|
||
|
|
require_class_kept 'com.novoyuuparosk.iperf3gui.IperfRunner$IperfCallback'
|
||
|
|
require_class_kept 'com.novoyuuparosk.iperf3gui.IperfRunner$MainThreadCallback'
|
||
|
|
|
||
|
|
# Resolved by iperf_jni.c on the concrete callback object's class.
|
||
|
|
require_method_kept 'void onOutput\(java\.lang\.String\)' 'onOutput'
|
||
|
|
require_method_kept 'void onError\(java\.lang\.String\)' 'onError'
|
||
|
|
require_method_kept 'void onComplete\(\)' 'onComplete'
|
||
|
|
|
||
|
|
# Native methods are NOT checked against mapping.txt: R8 only emits entries for
|
||
|
|
# members it has line-number info or a rename for, and a native method has no
|
||
|
|
# body, so it is absent whether or not it was renamed. Absence would prove
|
||
|
|
# nothing. Check the DEX string pool instead — a surviving name is interned
|
||
|
|
# there, a renamed one is not.
|
||
|
|
check_dex_names() {
|
||
|
|
local apk="$1"
|
||
|
|
shift
|
||
|
|
if [[ ! -f "$apk" ]]; then
|
||
|
|
echo " skip DEX name check: no APK at $apk"
|
||
|
|
return
|
||
|
|
fi
|
||
|
|
local tmp
|
||
|
|
tmp="$(mktemp -d)"
|
||
|
|
trap 'rm -rf "$tmp"' RETURN
|
||
|
|
if ! unzip -o -q "$apk" 'classes*.dex' -d "$tmp" 2>/dev/null; then
|
||
|
|
echo " FAIL could not extract classes*.dex from $apk"
|
||
|
|
failed=1
|
||
|
|
return
|
||
|
|
fi
|
||
|
|
# Dump the string pool once to a file, then grep the file. Never
|
||
|
|
# `... | strings | grep -q`: grep -q exits at the first match, SIGPIPEs
|
||
|
|
# strings, and `set -o pipefail` reports the whole pipeline as failed — so
|
||
|
|
# a name found early "fails" and one found late passes. That flakiness cost
|
||
|
|
# an afternoon; leave the intermediate file in place.
|
||
|
|
cat "$tmp"/classes*.dex | strings > "$tmp/dex-strings.txt"
|
||
|
|
|
||
|
|
local name
|
||
|
|
for name in "$@"; do
|
||
|
|
if grep -qxF -- "$name" "$tmp/dex-strings.txt"; then
|
||
|
|
echo " ok native $name"
|
||
|
|
else
|
||
|
|
echo " FAIL native $name is absent from the DEX string pool"
|
||
|
|
failed=1
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
check_dex_names "${2:-build/app/outputs/flutter-apk/app-release.apk}" \
|
||
|
|
runIperfLive forceStopIperfTest setTempDir isTestRunning
|
||
|
|
|
||
|
|
if [[ $failed -ne 0 ]]; then
|
||
|
|
echo
|
||
|
|
echo "R8 renamed something JNI resolves by name. The release APK will abort"
|
||
|
|
echo "on the first callback. Fix android/app/proguard-rules.pro."
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "All JNI-visible names survived minification."
|