open-standards/wai/wai-sink-android/scripts/build-native.sh
Transaction Science c6abab6581 wai-sink-android: read a tool's output whole before searching it (#488)
The AAR check failed in CI with "the AAR has no jni/arm64-v8a/libwai.so"
although the libraries were built and packed: under pipefail,
`unzip -l | grep -q` fails whenever grep stops at its first match before
unzip has written the rest (SIGPIPE). On the AAR the Studio built, that
pipeline fails 7 times in 300, and the listing read first 0 in 300. The
workflow's AAR check reads the listing once and prints it on a failure;
build-native.sh reads nm's and readelf's output whole too, where the same
race could report an export or a NEEDED entry missing, or let a LOAD
segment's misalignment through.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 18:43:13 -04:00

115 lines
5.9 KiB
Shell
Executable file

#!/usr/bin/env bash
# Build libwai (wai-rs, feature `native_sink`, no codec library) and the JNI layer over its C
# interface (jni/wai_sink_jni.c, against include/wai.h).
#
# scripts/build-native.sh android the AAR's ABIs, with the pinned NDK:
# sink/build/jniLibs/<abi>/{libwai.so,libwai_sink_jni.so}
# scripts/build-native.sh host this machine, for the JVM tests: core/build/native/
#
# WAI_SINK_ABIS chooses the ABIs (default "arm64-v8a armeabi-v7a x86_64"). The NDK is
# $ANDROID_NDK_HOME, or the pinned version under the SDK; its revision is checked. The Rust
# targets need their standard library (`rustup target add aarch64-linux-android
# armv7-linux-androideabi x86_64-linux-android`). Every library is linked for 16 KiB pages, and
# the script checks that it was, and that the JNI layer exports every method WaiNative declares.
set -euo pipefail
. "$(dirname "${BASH_SOURCE[0]}")/env.sh"
what="${1:-}"
TARGET_DIR="${CARGO_TARGET_DIR:-$WAI_RS_DIR/target}"
FEATURES=(--no-default-features --features native_sink)
JNI_SRC="$WAI_SINK_DIR/jni/wai_sink_jni.c"
INC="$WAI_RS_DIR/include"
# The methods WaiNative declares, each a symbol the JNI layer must export.
METHODS="abiVersion nowUs open free info receiptName playlist playlistParent objectVerdict event observe energy attach takeCmcd end verifyBundle"
cargo_lib() { cargo build --release --locked "${FEATURES[@]}" --lib --manifest-path "$WAI_RS_DIR/Cargo.toml" "$@"; }
# Each tool's output is read whole before it is searched: under pipefail, `tool | grep -q` fails
# whenever grep stops at its first match before the tool has written the rest (SIGPIPE), which
# would report a symbol or an entry missing that is there.
check_exports() { # <nm> <library>
local nm="$1" lib="$2" missing="" syms
syms="$("$nm" -D --defined-only "$lib" 2>/dev/null || true)"
for m in $METHODS JNI_OnLoad; do
case $m in JNI_OnLoad) s=JNI_OnLoad ;; *) s=Java_wai_sink_WaiNative_$m ;; esac
grep -qE "[[:space:]]_?$s\$" <<<"$syms" || missing="$missing $s"
done
[ -z "$missing" ] || die "$lib does not export:$missing"
}
android() {
[ -n "${ANDROID_HOME:-}" ] || die "no Android SDK: set ANDROID_HOME"
local ndk="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/$WAI_SINK_NDK_VERSION}"
[ -d "$ndk" ] || die "no NDK at $ndk (the pinned revision is $WAI_SINK_NDK_VERSION)"
local rev
rev="$(sed -n 's/^Pkg.Revision *= *//p' "$ndk/source.properties")"
[ "$rev" = "$WAI_SINK_NDK_VERSION" ] || die "the NDK at $ndk is $rev, not the pinned $WAI_SINK_NDK_VERSION"
local tag
case "$(uname -s)" in Darwin) tag=darwin-x86_64 ;; Linux) tag=linux-x86_64 ;; *) die "no NDK host for $(uname -s)" ;; esac
local bin="$ndk/toolchains/llvm/prebuilt/$tag/bin"
for abi in ${WAI_SINK_ABIS:-arm64-v8a armeabi-v7a x86_64}; do
local triple clang
case "$abi" in
arm64-v8a) triple=aarch64-linux-android; clang=aarch64-linux-android$WAI_SINK_MIN_API-clang ;;
armeabi-v7a) triple=armv7-linux-androideabi; clang=armv7a-linux-androideabi$WAI_SINK_MIN_API-clang ;;
x86_64) triple=x86_64-linux-android; clang=x86_64-linux-android$WAI_SINK_MIN_API-clang ;;
*) die "unknown ABI $abi" ;;
esac
local up lo
up="$(echo "$triple" | tr 'a-z-' 'A-Z_')"
lo="$(echo "$triple" | tr '-' '_')"
# The NDK's clang links libwai for the platform; a dependency that compiles C (cc-rs) uses it
# too. 16 KiB pages: what current devices require of an application's native code.
(
export "CARGO_TARGET_${up}_LINKER=$bin/$clang" \
"CARGO_TARGET_${up}_RUSTFLAGS=-C link-arg=-Wl,-z,max-page-size=16384" \
"CC_${lo}=$bin/$clang" "AR_${lo}=$bin/llvm-ar"
cargo_lib --target "$triple"
)
local out="$WAI_SINK_DIR/sink/build/jniLibs/$abi"
rm -rf "$out" && mkdir -p "$out"
cp "$TARGET_DIR/$triple/release/libwai.so" "$out/libwai.so"
"$bin/$clang" -shared -fPIC -O2 -Wall -Wextra -Werror -I"$INC" "$JNI_SRC" -L"$out" -lwai \
-Wl,--no-undefined -Wl,-z,max-page-size=16384 -o "$out/libwai_sink_jni.so"
"$bin/llvm-strip" --strip-unneeded "$out/libwai.so" "$out/libwai_sink_jni.so"
check_exports "$bin/llvm-nm" "$out/libwai_sink_jni.so"
deps="$("$bin/llvm-readelf" -d "$out/libwai_sink_jni.so")"
grep -q 'NEEDED.*\[libwai.so\]' <<<"$deps" || die "$abi: the JNI layer does not name libwai.so"
if [ "$abi" != armeabi-v7a ]; then
for so in "$out"/*.so; do
loads="$("$bin/llvm-readelf" -lW "$so" | awk '$1=="LOAD"{print $NF}')"
[ -n "$loads" ] || die "$abi: $(basename "$so") has no LOAD segment"
grep -qv '^0x4000$' <<<"$loads" && die "$abi: $(basename "$so") has a LOAD segment not aligned to 16 KiB"
done
fi
echo "built $abi: $(cd "$out" && ls -l *.so | awk '{print $NF" "$5}' | tr '\n' ' ')"
done
}
host() {
[ -n "${JAVA_HOME:-}" ] && [ -f "$JAVA_HOME/include/jni.h" ] || die "no JDK with jni.h: set JAVA_HOME"
cargo_lib
local out="$WAI_SINK_DIR/core/build/native"
rm -rf "$out" && mkdir -p "$out"
local cc="${CC:-cc}"
case "$(uname -s)" in
Darwin)
cp "$TARGET_DIR/release/libwai.dylib" "$out/libwai.dylib"
install_name_tool -id @rpath/libwai.dylib "$out/libwai.dylib"
"$cc" -dynamiclib -fPIC -O2 -Wall -Wextra -Werror -I"$INC" -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" \
"$JNI_SRC" -L"$out" -lwai -Wl,-rpath,@loader_path -o "$out/libwai_sink_jni.dylib"
check_exports nm "$out/libwai_sink_jni.dylib" ;;
Linux)
cp "$TARGET_DIR/release/libwai.so" "$out/libwai.so"
"$cc" -shared -fPIC -O2 -Wall -Wextra -Werror -I"$INC" -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
"$JNI_SRC" -L"$out" -lwai -Wl,--no-undefined -Wl,-rpath,'$ORIGIN' -o "$out/libwai_sink_jni.so"
check_exports nm "$out/libwai_sink_jni.so" ;;
*) die "no host build for $(uname -s)" ;;
esac
echo "built host: $(ls "$out" | tr '\n' ' ')"
}
case "$what" in
android) android ;;
host) host ;;
*) die "usage: build-native.sh android|host" ;;
esac