diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 5b32ef7..33bdc60 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -84,6 +84,11 @@ jobs: run: tools/ci/fetch-openssl-statics.sh # picks up $OPENSSL_ANDROID_ROOT from the image - name: Gradle assemble + lint run: ./gradlew --no-daemon assembleDebug assembleRelease lint + - name: Check 16 KB .so alignment + run: | + for apk in app/build/outputs/apk/*/*.apk; do + tools/ci/check-so-alignment.sh "$apk" + done - uses: actions/upload-artifact@v4 with: name: apk diff --git a/app/src/main/jni/Application.mk b/app/src/main/jni/Application.mk index 31afe2e..08db648 100755 --- a/app/src/main/jni/Application.mk +++ b/app/src/main/jni/Application.mk @@ -7,3 +7,6 @@ # at API 24+. APP_ABI := arm64-v8a x86_64 APP_PLATFORM := android-24 + +# Play requires 16 KB-aligned LOAD segments at targetSdk 35+; r26d's lld still defaults to 4 KB. +APP_LDFLAGS := -Wl,-z,max-page-size=16384 diff --git a/tools/ci/check-so-alignment.sh b/tools/ci/check-so-alignment.sh new file mode 100755 index 0000000..52ad62c --- /dev/null +++ b/tools/ci/check-so-alignment.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# +# Fail if any packaged .so has a LOAD segment aligned below 16 KB. Play rejects those +# at targetSdk 35+, and nothing in a normal build says so — the regression is silent. +set -euo pipefail + +APK="${1:?usage: $0 }" +WANT=16384 + +# The toolchain image carries the NDK but no binutils, so fall back to its llvm-readelf. +READELF="$(command -v llvm-readelf || command -v readelf || true)" +if [ -z "$READELF" ]; then + for cand in "${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-/nonexistent}}"/toolchains/llvm/prebuilt/*/bin/llvm-readelf; do + [ -x "$cand" ] && READELF="$cand" && break + done +fi +[ -n "$READELF" ] || { + echo "check-so-alignment: no readelf/llvm-readelf found" >&2 + exit 1 +} + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +mkdir -p "$tmp/lib" +# 11 is "nothing matched", which the count check below reports in its own words. +rc=0 +unzip -q -o "$APK" 'lib/*' -d "$tmp" || rc=$? +if [ "$rc" -ne 0 ] && [ "$rc" -ne 11 ]; then + echo "check-so-alignment: cannot extract $APK (unzip rc=$rc)" >&2 + exit 1 +fi + +# Cross-check the walk against the archive listing. find's exit status is invisible to set -e +# from inside a process substitution, so a truncated walk would silently check a subset and +# still report success; comparing counts asserts completeness, not merely non-emptiness. +# unzip -Z1 exits 11 when the pattern matches nothing, which is the likely regression here +# (a build shipping no native libs at all), so name that case rather than blame the archive. +listed="$(unzip -Z1 "$APK" 'lib/*.so' | wc -l)" || { + echo "check-so-alignment: no lib/*.so in $APK, or it is unreadable" >&2 + exit 1 +} +mapfile -t sos < <(find "$tmp/lib" -type f -name '*.so' | sort) +if [ "${#sos[@]}" -ne "$listed" ]; then + echo "check-so-alignment: $APK lists $listed .so, walked ${#sos[@]}" >&2 + exit 1 +fi + +rc=0 +for so in "${sos[@]}"; do + name="${so#"$tmp"/}" + aligns="$("$READELF" -lW "$so" | awk '$1 == "LOAD" { print $NF }')" + [ -n "$aligns" ] || { + echo "check-so-alignment: no LOAD segments in $name" >&2 + exit 1 + } + for a in $aligns; do + if [ "$((a))" -lt "$WANT" ]; then + echo "FAIL $name: LOAD p_align $a < $WANT" + rc=1 + fi + done +done + +[ "$rc" -eq 0 ] && echo "16 KB aligned: ${#sos[@]} .so in $(basename "$APK")" +exit "$rc"