Files
Michał Pierzchała e3880f10c8 fix(android-helper): build with the pinned build-tools version, fail on CI when unset (#2568)
build-android-helper.sh compiled with whichever build-tools directory was newest on the
image while every lane that builds a helper installs exactly build-tools;36.0.0. That
selection feeds d8 and aapt2, so a newer package on the runner changed the helper's
bytecode and resources rather than just its packaging, and nothing said so.

The script now takes the version as an input, its last positional or
AGENT_DEVICE_ANDROID_BUILD_TOOLS, resolves it under $SDK_ROOT/build-tools, and checks the
four tools the build actually runs instead of aapt2 alone. An unpinned build is a hard
failure on CI; locally the newest-installed fallback stays and is announced on stderr.

Each lane that builds a helper declares the version it installs and interpolates that same
value into its sdkmanager line and the build environment, so an install and a build inside
one lane cannot drift. setup-android-replay-host exports it to the packaging gate and names
it in both helper cache keys, which are keyed on APK bytes; size.yml and
release-android-snapshot-helper.yml declare it at job level. The fixture-repack cluster
keeps its own pin: a different command family that ships no helper APK.

Closes #2527
2026-09-14 07:35:00 +02:00

191 lines
5.6 KiB
Bash
Executable File

#!/bin/sh
set -eu
HELPER="${AGENT_DEVICE_ANDROID_HELPER:-}"
if [ -z "$HELPER" ]; then
HELPER="${1:-}"
[ "$#" -ge 1 ] && shift
fi
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
echo "Usage: AGENT_DEVICE_ANDROID_HELPER=<snapshot|ime> $0 <version> <output-dir> [build-tools-version]" >&2
echo " or: $0 <snapshot|ime> <version> <output-dir> [build-tools-version]" >&2
echo "The version also comes from AGENT_DEVICE_ANDROID_BUILD_TOOLS." >&2
exit 1
fi
VERSION="$1"
OUTPUT_DIR="$2"
BUILD_TOOLS_VERSION="${3:-${AGENT_DEVICE_ANDROID_BUILD_TOOLS:-}}"
PROJECT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
MIN_SDK=23
TARGET_SDK=36
KEYSTORE="$PROJECT_DIR/android/snapshot-helper/debug.keystore"
# Per-helper config: HELPER_DIR/PACKAGE_NAME/APK name always differ; RUN_TEST_CLASS is
# only set for the snapshot helper (it has a unit test to compile+run); RESOURCE_DIR is
# only set for the ime helper (it has a res/ dir that needs an aapt2 compile pass).
case "$HELPER" in
snapshot)
HELPER_DIR="$PROJECT_DIR/android/snapshot-helper"
PACKAGE_NAME="com.callstack.agentdevice.snapshothelper"
RUN_TEST_CLASS="com.callstack.agentdevice.snapshothelper.SnapshotHelperTestSuite"
RESOURCE_DIR=""
;;
ime)
HELPER_DIR="$PROJECT_DIR/android/ime-helper"
PACKAGE_NAME="com.callstack.agentdevice.imehelper"
RUN_TEST_CLASS=""
RESOURCE_DIR="$PROJECT_DIR/android/ime-helper/res"
;;
*)
echo "Unknown Android helper: '$HELPER' (expected 'snapshot' or 'ime')" >&2
exit 1
;;
esac
APK_BASENAME="agent-device-android-$HELPER-helper-$VERSION.apk"
SDK_ROOT="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [ -z "$SDK_ROOT" ] || [ ! -d "$SDK_ROOT" ]; then
echo "ANDROID_HOME or ANDROID_SDK_ROOT must point to an Android SDK" >&2
exit 1
fi
ANDROID_JAR="$SDK_ROOT/platforms/android-$TARGET_SDK/android.jar"
if [ ! -f "$ANDROID_JAR" ]; then
echo "Missing Android platform jar: $ANDROID_JAR" >&2
exit 1
fi
# d8 and aapt2 come from build-tools, so the version decides the compiled bytecode and resources,
# not only packaging. CI names the version it installed; only a local build may take the newest.
if [ -z "$BUILD_TOOLS_VERSION" ]; then
if [ "${CI:-}" = "true" ]; then
echo "AGENT_DEVICE_ANDROID_BUILD_TOOLS must name a build-tools version under $SDK_ROOT/build-tools" >&2
exit 1
fi
NEWEST_BUILD_TOOLS_DIR="$(
find "$SDK_ROOT/build-tools" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort -V | tail -n 1
)"
if [ -z "$NEWEST_BUILD_TOOLS_DIR" ]; then
echo "No Android build tools installed under $SDK_ROOT/build-tools" >&2
exit 1
fi
BUILD_TOOLS_VERSION="${NEWEST_BUILD_TOOLS_DIR##*/}"
echo "No build-tools version requested; newest installed is $BUILD_TOOLS_VERSION" >&2
fi
case "$BUILD_TOOLS_VERSION" in
*/* | *[[:space:]]*)
echo "Not an Android build-tools version: '$BUILD_TOOLS_VERSION'" >&2
exit 1
;;
esac
BUILD_TOOLS_DIR="$SDK_ROOT/build-tools/$BUILD_TOOLS_VERSION"
if [ ! -d "$BUILD_TOOLS_DIR" ]; then
echo "No Android build tools $BUILD_TOOLS_VERSION under $SDK_ROOT/build-tools" >&2
exit 1
fi
for BUILD_TOOL in aapt2 d8 zipalign apksigner; do
if [ ! -x "$BUILD_TOOLS_DIR/$BUILD_TOOL" ]; then
echo "Incomplete Android build tools: no $BUILD_TOOL in $BUILD_TOOLS_DIR" >&2
exit 1
fi
done
VERSION_CODE="$(
printf '%s\n' "$VERSION" | awk -F. '
/^[0-9]+[.][0-9]+[.][0-9]+$/ {
print ($1 * 1000000) + ($2 * 1000) + $3
next
}
{ print 1 }
'
)"
BUILD_DIR="$HELPER_DIR/build"
CLASSES_DIR="$BUILD_DIR/classes"
TEST_CLASSES_DIR="$BUILD_DIR/test-classes"
DEX_DIR="$BUILD_DIR/dex"
RES_COMPILED_DIR="$BUILD_DIR/res-compiled"
UNSIGNED_APK="$BUILD_DIR/helper-unsigned.apk"
ALIGNED_APK="$BUILD_DIR/helper-aligned.apk"
APK_PATH="$OUTPUT_DIR/$APK_BASENAME"
rm -rf "$BUILD_DIR"
mkdir -p "$CLASSES_DIR" "$TEST_CLASSES_DIR" "$DEX_DIR" "$RES_COMPILED_DIR" "$OUTPUT_DIR"
javac \
--release 11 \
-classpath "$ANDROID_JAR" \
-d "$CLASSES_DIR" \
$(find "$HELPER_DIR/src/main/java" -name '*.java' | sort)
if [ -n "$RUN_TEST_CLASS" ]; then
javac \
--release 11 \
-classpath "$ANDROID_JAR:$CLASSES_DIR" \
-d "$TEST_CLASSES_DIR" \
$(find "$HELPER_DIR/src/test/java" -name '*.java' | sort)
java \
-classpath "$ANDROID_JAR:$CLASSES_DIR:$TEST_CLASSES_DIR" \
"$RUN_TEST_CLASS"
fi
"$BUILD_TOOLS_DIR/d8" \
--min-api "$MIN_SDK" \
--classpath "$ANDROID_JAR" \
--output "$DEX_DIR" \
$(find "$CLASSES_DIR" -name '*.class' | sort)
if [ -n "$RESOURCE_DIR" ]; then
"$BUILD_TOOLS_DIR/aapt2" compile \
--dir "$RESOURCE_DIR" \
-o "$RES_COMPILED_DIR"
"$BUILD_TOOLS_DIR/aapt2" link \
--manifest "$HELPER_DIR/AndroidManifest.xml" \
-I "$ANDROID_JAR" \
--min-sdk-version "$MIN_SDK" \
--target-sdk-version "$TARGET_SDK" \
--version-code "$VERSION_CODE" \
--version-name "$VERSION" \
-R "$RES_COMPILED_DIR"/*.flat \
-o "$UNSIGNED_APK"
else
"$BUILD_TOOLS_DIR/aapt2" link \
--manifest "$HELPER_DIR/AndroidManifest.xml" \
-I "$ANDROID_JAR" \
--min-sdk-version "$MIN_SDK" \
--target-sdk-version "$TARGET_SDK" \
--version-code "$VERSION_CODE" \
--version-name "$VERSION" \
-o "$UNSIGNED_APK"
fi
zip -q -j "$UNSIGNED_APK" "$DEX_DIR/classes.dex"
"$BUILD_TOOLS_DIR/zipalign" -f 4 "$UNSIGNED_APK" "$ALIGNED_APK"
if [ ! -f "$KEYSTORE" ]; then
echo "Missing Android helper signing keystore: $KEYSTORE" >&2
exit 1
fi
"$BUILD_TOOLS_DIR/apksigner" sign \
--ks "$KEYSTORE" \
--ks-pass pass:android \
--key-pass pass:android \
--out "$APK_PATH" \
"$ALIGNED_APK"
"$BUILD_TOOLS_DIR/apksigner" verify --min-sdk-version "$MIN_SDK" "$APK_PATH"
printf 'apk=%s\n' "$APK_PATH"
printf 'package=%s\n' "$PACKAGE_NAME"
printf 'version_code=%s\n' "$VERSION_CODE"