Read the Android API level out of the ELF, not out of file(1)
Build and test / Desktop (Linux) (push) Successful in 2h8m13s
Build and test / Layer separation (push) Successful in 1m3s
🐳 Android image / Build and push (push) Successful in 2s
Build and test / android-image (push) Successful in 2s
Traceability / Requirement traces (push) Successful in 41s
Build and test / Android (aarch64) (push) Successful in 1h0m55s

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) <noreply@anthropic.com>
This commit is contained in:
2026-08-28 22:28:57 +02:00
co-authored by Claude Opus 5
parent e997be8c72
commit 0bba882fb1
+22 -4
View File
@@ -272,12 +272,30 @@ jobs:
exit 1 exit 1
fi fi
echo "checking $SO" echo "checking $SO"
file "$SO" # `file` is kept for the log — it names the NDK that built this — but
API=$(file "$SO" | sed -n 's/.*for Android \([0-9]*\).*/\1/p') # the check no longer depends on it.
if [ -z "$API" ] || [ "$API" != "$MIN_API" ]; then file "$SO" || true
echo "FAIL: linked for Android '${API:-unknown}', expected $MIN_API" # 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 exit 1
fi 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 # The APK itself, so a run leaves something installable behind rather
# than only the knowledge that it would have linked. The assembly is # than only the knowledge that it would have linked. The assembly is