|
| 1 | +#!/usr/bin/env bash |
| 2 | +# dist-apk reads the native closure the engine staged (mcpp#634, B5). |
| 3 | +# |
| 4 | +# Five criteria. The first four run `mcpp pack` and read the package with the |
| 5 | +# Android tools; the fifth runs the compiled build program against a fabricated |
| 6 | +# stage, so it holds whichever engine packs. |
| 7 | +# |
| 8 | +# 1. Two packs in a row both carry the dependency's library. Under 0.9.3 the |
| 9 | +# second APK lacked it: the member walked the object's NEEDED entries |
| 10 | +# behind a stamp that outlived the stage the plan wipes, so the walk did |
| 11 | +# not run again. |
| 12 | +# 2. Two `--target` rows give one APK whose native code lists both ABIs, each |
| 13 | +# with the application object, the dependency and `libc++_shared.so`. |
| 14 | +# Under 0.9.3 this pack exited 1: the member did not descend into |
| 15 | +# `lib/<abi>/`. |
| 16 | +# 3. `--format aab` gives an App Bundle that bundletool validates, that |
| 17 | +# jarsigner verifies, and from which bundletool builds a universal APK |
| 18 | +# carrying the same libraries. |
| 19 | +# 4. A refusal reaches the user: `--format apk` for the host row prints the |
| 20 | +# member's reason. Under 0.9.3 the reason went to the build program's |
| 21 | +# stderr, which the engine discards when the program exits 0, and the |
| 22 | +# user read only "no action claimed". |
| 23 | +# 5. A stage manifest without `needs` lines, which every engine below |
| 24 | +# 2026.9.14.2 writes, is refused through `mcpp::warning`, naming that |
| 25 | +# floor. |
| 26 | +# |
| 27 | +# Usage: MCPP=<mcpp 2026.9.14.2+> ./check-apk-closure.sh (run from this directory) |
| 28 | +set -eu |
| 29 | + |
| 30 | +MCPP="${MCPP:-mcpp}" |
| 31 | +fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; } |
| 32 | +# The path of the artifact a pack reports on its `Packed` line. |
| 33 | +packed() { sed -n 's/^ *Packed //p' "$1" | tail -1; } |
| 34 | + |
| 35 | +"$MCPP" self env > mcpp-env.txt |
| 36 | +MCPP_HOME_DIR=$(awk -F'= *' '/^MCPP_HOME/{print $2; exit}' mcpp-env.txt) |
| 37 | +[ -n "$MCPP_HOME_DIR" ] || fail "could not read MCPP_HOME" mcpp-env.txt |
| 38 | +XPKGS="$MCPP_HOME_DIR/registry/data/xpkgs" |
| 39 | + |
| 40 | +# tool <package> <path under the package> : the file in whichever installed |
| 41 | +# version carries it. |
| 42 | +tool() { |
| 43 | + local d |
| 44 | + for d in "$XPKGS/xim-x-$1"/*/; do |
| 45 | + [ -e "$d$2" ] && { echo "$d$2"; return 0; } |
| 46 | + done |
| 47 | + return 1 |
| 48 | +} |
| 49 | + |
| 50 | +# lists <archive> <listing file> : the member names of a zip archive. |
| 51 | +lists() { "$JAR" tf "$1" > "$2"; } |
| 52 | + |
| 53 | +# ── 1. two packs in a row ────────────────────────────────────────────────── |
| 54 | +echo "== 1. two packs in a row carry the dependency's library ==" |
| 55 | +rm -rf target |
| 56 | +for n in 1 2; do |
| 57 | + "$MCPP" pack --target x86_64-linux-android --format apk > "closure-pack$n.log" 2>&1 \ |
| 58 | + || fail "pack $n exited non-zero" "closure-pack$n.log" |
| 59 | + apk=$(packed "closure-pack$n.log") |
| 60 | + [ -f "$apk" ] || fail "pack $n reported no APK" "closure-pack$n.log" |
| 61 | + if [ "$n" = 1 ]; then |
| 62 | + JAR=$(tool jdk-temurin bin/jar) || fail "no jar under $XPKGS/xim-x-jdk-temurin" |
| 63 | + JARSIGNER=$(tool jdk-temurin bin/jarsigner) || fail "no jarsigner under $XPKGS/xim-x-jdk-temurin" |
| 64 | + AAPT2=$(tool android-build-tools aapt2) || fail "no aapt2 under $XPKGS/xim-x-android-build-tools" |
| 65 | + APKSIGNER=$(tool android-build-tools bin/apksigner) || fail "no apksigner under $XPKGS/xim-x-android-build-tools" |
| 66 | + fi |
| 67 | + lists "$apk" "closure-list$n.log" |
| 68 | + for f in lib/x86_64/libapk-consumer-shared.so lib/x86_64/libapk-consumer-dep.so \ |
| 69 | + lib/x86_64/libc++_shared.so; do |
| 70 | + grep -qxF "$f" "closure-list$n.log" || fail "the APK of pack $n does not list $f" "closure-list$n.log" |
| 71 | + done |
| 72 | + echo "ok: pack $n: $(grep -c '^lib/' "closure-list$n.log") native libraries, the dependency's among them" |
| 73 | +done |
| 74 | + |
| 75 | +# ── 2. two triples, one APK ──────────────────────────────────────────────── |
| 76 | +echo "== 2. two triples give one APK listing both ABIs ==" |
| 77 | +"$MCPP" pack --target x86_64-linux-android --target aarch64-linux-android --format apk \ |
| 78 | + > closure-multi.log 2>&1 || fail "the two-triple pack exited non-zero" closure-multi.log |
| 79 | +apk=$(packed closure-multi.log) |
| 80 | +[ -f "$apk" ] || fail "the two-triple pack reported no APK" closure-multi.log |
| 81 | +lists "$apk" closure-multi-list.log |
| 82 | +for abi in x86_64 arm64-v8a; do |
| 83 | + for f in libapk-consumer-shared.so libapk-consumer-dep.so libc++_shared.so; do |
| 84 | + grep -qxF "lib/$abi/$f" closure-multi-list.log \ |
| 85 | + || fail "the two-triple APK does not list lib/$abi/$f" closure-multi-list.log |
| 86 | + done |
| 87 | +done |
| 88 | +"$AAPT2" dump badging "$apk" > closure-badging.log 2>&1 || fail "aapt2 could not read the APK" closure-badging.log |
| 89 | +tr -d '\r' < closure-badging.log | grep -qx "native-code: 'arm64-v8a' 'x86_64'" \ |
| 90 | + || fail "aapt2 does not report native-code 'arm64-v8a' 'x86_64'" closure-badging.log |
| 91 | +"$APKSIGNER" verify "$apk" > closure-verify.log 2>&1 || fail "apksigner does not verify the APK" closure-verify.log |
| 92 | +echo "ok: one signed APK, $(tr -d '\r' < closure-badging.log | grep '^native-code:')" |
| 93 | + |
| 94 | +# ── 3. an App Bundle ─────────────────────────────────────────────────────── |
| 95 | +echo "== 3. --format aab ==" |
| 96 | +"$MCPP" pack --target x86_64-linux-android --format aab > closure-aab.log 2>&1 \ |
| 97 | + || fail "the aab pack exited non-zero" closure-aab.log |
| 98 | +aab=$(packed closure-aab.log) |
| 99 | +case "$aab" in *.aab) ;; *) fail "the aab pack reported '$aab', not an .aab" closure-aab.log ;; esac |
| 100 | +[ -f "$aab" ] || fail "the reported bundle $aab does not exist" closure-aab.log |
| 101 | +BUNDLETOOL=$(tool bundletool bin/bundletool) || fail "no bundletool under $XPKGS/xim-x-bundletool" |
| 102 | +lists "$aab" closure-aab-list.log |
| 103 | +for f in BundleConfig.pb base/manifest/AndroidManifest.xml base/resources.pb \ |
| 104 | + base/lib/x86_64/libapk-consumer-shared.so base/lib/x86_64/libapk-consumer-dep.so \ |
| 105 | + base/lib/x86_64/libc++_shared.so base/assets/mcpp-run.json; do |
| 106 | + grep -qxF "$f" closure-aab-list.log || fail "the bundle does not list $f" closure-aab-list.log |
| 107 | +done |
| 108 | +"$BUNDLETOOL" validate --bundle="$aab" > closure-validate.log 2>&1 \ |
| 109 | + || fail "bundletool validate refused the bundle" closure-validate.log |
| 110 | +"$JARSIGNER" -verify "$aab" > closure-jarsigner.log 2>&1 || fail "jarsigner -verify failed" closure-jarsigner.log |
| 111 | +grep -q '^jar verified' closure-jarsigner.log || fail "jarsigner did not report 'jar verified'" closure-jarsigner.log |
| 112 | +work=$(mktemp -d) |
| 113 | +"$BUNDLETOOL" build-apks --bundle="$aab" --output="$work/universal.apks" --mode=universal \ |
| 114 | + > closure-build-apks.log 2>&1 || fail "bundletool build-apks --mode=universal failed" closure-build-apks.log |
| 115 | +(cd "$work" && "$JAR" xf universal.apks universal.apk) || fail "the .apks set carries no universal.apk" |
| 116 | +lists "$work/universal.apk" closure-universal-list.log |
| 117 | +grep -qxF lib/x86_64/libapk-consumer-dep.so closure-universal-list.log \ |
| 118 | + || fail "the universal APK does not carry the dependency's library" closure-universal-list.log |
| 119 | +echo "ok: the bundle validates, is signed, and yields a universal APK with the dependency's library" |
| 120 | + |
| 121 | +# ── 4. a refusal reaches the user ────────────────────────────────────────── |
| 122 | +echo "== 4. a refusal prints its reason ==" |
| 123 | +rc=0 |
| 124 | +"$MCPP" pack --format apk > closure-refusal.log 2>&1 || rc=$? |
| 125 | +[ "$rc" -ne 0 ] || fail "an APK for the host row was not refused" closure-refusal.log |
| 126 | +grep -q 'mcpp.dist.apk: an APK is an Android format' closure-refusal.log \ |
| 127 | + || fail "the refusal's reason is not in mcpp pack's output (exit $rc)" closure-refusal.log |
| 128 | +echo "ok: exit $rc, and the output names the reason: $(grep -m1 'mcpp.dist.apk:' closure-refusal.log)" |
| 129 | + |
| 130 | +# ── 5. an engine below the floor ─────────────────────────────────────────── |
| 131 | +# |
| 132 | +# The engine's output is not read here, so the build program is run with the |
| 133 | +# environment a pack sets (mcpp's docs/30) and a stage manifest in the form an |
| 134 | +# engine below 2026.9.14.2 writes: header and file list, no `needs` line. |
| 135 | +echo "== 5. a stage manifest without needs lines is refused, naming the floor ==" |
| 136 | +BIN=target/.build-mcpp/build.mcpp.bin |
| 137 | +[ -x "$BIN" ] || fail "no compiled build.mcpp to run" closure-refusal.log |
| 138 | +stage="$work/apk-consumer-shared-0.2.0-x86_64-linux-android" |
| 139 | +mkdir -p "$stage/lib" |
| 140 | +head -c 4096 /dev/zero > "$stage/lib/libapk-consumer-shared.so" |
| 141 | +printf 'closure = not-walked\nreason = the Android closure is not read\n4096 lib/libapk-consumer-shared.so\n' \ |
| 142 | + > "$stage.stage-manifest" |
| 143 | +env -i PATH="$PATH" \ |
| 144 | + MCPP_TARGET_ARCH=x86_64 MCPP_TARGET_OS=linux MCPP_TARGET_ENV=android \ |
| 145 | + MCPP_OUT_DIR="$work/out" MCPP_MANIFEST_DIR="$PWD" \ |
| 146 | + MCPP_PKG_NAME=apk-consumer-shared MCPP_PKG_VERSION=0.2.0 \ |
| 147 | + MCPP_PACK_FORMAT=apk MCPP_PACK_STAGE_DIR="$stage" \ |
| 148 | + "$BIN" > closure-floor.log 2>&1 || true |
| 149 | +if grep -q '^mcpp:action=' closure-floor.log; then |
| 150 | + fail "a stage without needs lines planned actions" closure-floor.log |
| 151 | +fi |
| 152 | +grep '^mcpp:warning=' closure-floor.log | grep -q 'no `needs` line.*2026\.9\.14\.2 or newer' \ |
| 153 | + || fail "the refusal is not a warning naming mcpp 2026.9.14.2" closure-floor.log |
| 154 | +echo "ok: refused, as a warning naming the engine floor" |
| 155 | + |
| 156 | +echo "PASS: dist-apk reads the staged closure, packs two ABIs into one APK, builds an App Bundle, and reports its refusals" |
0 commit comments