From 0bba882fb1ce61c95c4aa4485fe4036fef741211 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 28 Aug 2026 22:28:57 +0200 Subject: [PATCH] Read the Android API level out of the ELF, not out of file(1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every push has failed the Android job for weeks — back through fourteen runs — with "FAIL: linked for Android 'unknown', expected 28", on a .so that was linked perfectly correctly at API 28 the whole time. The check ran `file` on the linked object and pulled the level out of its description with a regex. `file` only prints "for Android 28" when its magic database is new enough to decode `.note.android.ident`, and this image's is not — it stops at "dynamically linked, not stripped". The regex matched nothing, and `${API:-unknown}` turned that silence into a confident-looking failure about the artefact rather than about the tool inspecting it. The API level is the first word of that note, little-endian, so it is read straight out of the ELF with `readelf` — which is in the image via build-essential, and cannot go out of date the way a magic database can. An absent note is now its own message rather than being folded into the mismatch case, since "nothing states an API level" and "states the wrong one" are different faults. `file` stays in the image and in the log: it names the NDK that built the object, which is worth having when this does go wrong. Nothing depends on it. Verified inside the real container against the linked .so: the note reads 1c 00 00 00, and the step prints "OK: linked for Android 28". Co-Authored-By: Claude Opus 5 (1M context) --- .gitea/workflows/build-and-test.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build-and-test.yml b/.gitea/workflows/build-and-test.yml index 8d1f8c2..5350cd3 100644 --- a/.gitea/workflows/build-and-test.yml +++ b/.gitea/workflows/build-and-test.yml @@ -272,12 +272,30 @@ jobs: exit 1 fi echo "checking $SO" - file "$SO" - API=$(file "$SO" | sed -n 's/.*for Android \([0-9]*\).*/\1/p') - if [ -z "$API" ] || [ "$API" != "$MIN_API" ]; then - echo "FAIL: linked for Android '${API:-unknown}', expected $MIN_API" + # `file` is kept for the log — it names the NDK that built this — but + # the check no longer depends on it. + file "$SO" || true + # The API level is the first word of the `.note.android.ident` ELF + # note, little-endian. Read the note rather than asking `file` for it: + # `file` only prints "for Android 28" when its magic database is new + # enough to decode that note, and this image's is not. The parse then + # produced nothing, `${API:-unknown}` reported "unknown", and every + # push failed here for weeks on a .so that was linked perfectly + # correctly. A note read straight out of the ELF cannot go stale that + # way. + readelf -n "$SO" | sed -n '/android.ident/,+3p' + HEX=$(readelf -n "$SO" 2>/dev/null \ + | awk '/description data:/ { print $6 $5 $4 $3; exit }') + if [ -z "$HEX" ]; then + echo "FAIL: no .note.android.ident in $SO — nothing states an API level" exit 1 fi + API=$(( 0x$HEX )) + if [ "$API" != "$MIN_API" ]; then + echo "FAIL: linked for Android $API, expected $MIN_API" + exit 1 + fi + echo "OK: linked for Android $API" # The APK itself, so a run leaves something installable behind rather # than only the knowledge that it would have linked. The assembly is