chore: import upstream snapshot with attribution
This commit is contained in:
Executable
+395
@@ -0,0 +1,395 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
#######################################################################
|
||||
# build_android.sh — cross-compile zvec for Android arm64-v8a,
|
||||
# then run all C/C++ unit tests inside an emulator.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/build_android.sh [api_level] [build_type]
|
||||
#
|
||||
# Examples:
|
||||
# ./scripts/build_android.sh # defaults: API 36, Release
|
||||
# ./scripts/build_android.sh 36 Debug
|
||||
#######################################################################
|
||||
|
||||
CURRENT_DIR=$(pwd)
|
||||
ABI="arm64-v8a"
|
||||
API_LEVEL=${1:-35}
|
||||
BUILD_TYPE=${2:-"Release"}
|
||||
CORE_COUNT=$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)
|
||||
|
||||
# ── Android SDK paths (set later, after host protoc build) ───────────
|
||||
ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}
|
||||
ANDROID_NDK_HOME=${ANDROID_NDK_HOME:-$(ls -d "$ANDROID_SDK_ROOT/ndk/"* 2>/dev/null | sort -V | tail -1)}
|
||||
|
||||
echo "============================================================"
|
||||
echo " Android Cross Build & Test"
|
||||
echo "============================================================"
|
||||
echo " ABI : $ABI"
|
||||
echo " API Level : $API_LEVEL"
|
||||
echo " Build Type : $BUILD_TYPE"
|
||||
echo " NDK : $ANDROID_NDK_HOME"
|
||||
echo " CPU cores : $CORE_COUNT"
|
||||
echo "============================================================"
|
||||
|
||||
if [ ! -d "$ANDROID_NDK_HOME" ]; then
|
||||
echo "ERROR: ANDROID_NDK_HOME not found at $ANDROID_NDK_HOME"
|
||||
echo "Please install the NDK via Android Studio or sdkmanager."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Step 1: build host protoc (using HOST compiler, NOT NDK) ─────────
|
||||
echo ""
|
||||
echo ">>> Step 1: Building protoc for host..."
|
||||
HOST_BUILD_DIR="build_host"
|
||||
|
||||
git submodule foreach --recursive 'git stash --include-untracked' 2>/dev/null || true
|
||||
|
||||
if [ ! -f "$CURRENT_DIR/$HOST_BUILD_DIR/bin/protoc" ]; then
|
||||
# Explicitly avoid NDK toolchain for host build
|
||||
cmake -S . -B "$HOST_BUILD_DIR" \
|
||||
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="" \
|
||||
-G Ninja
|
||||
cmake --build "$HOST_BUILD_DIR" --target protoc -j"$CORE_COUNT"
|
||||
else
|
||||
echo " (cached — skipping)"
|
||||
fi
|
||||
PROTOC_EXECUTABLE=$CURRENT_DIR/$HOST_BUILD_DIR/bin/protoc
|
||||
echo ">>> Step 1: Done (protoc=$PROTOC_EXECUTABLE)"
|
||||
|
||||
# ── Now export Android env vars for cross-compilation ────────────────
|
||||
export ANDROID_SDK_ROOT
|
||||
export ANDROID_HOME=$ANDROID_SDK_ROOT
|
||||
export ANDROID_NDK_HOME
|
||||
export CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
|
||||
|
||||
export PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin
|
||||
export PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools
|
||||
export PATH=$PATH:$ANDROID_SDK_ROOT/emulator
|
||||
export PATH=$PATH:$ANDROID_NDK_HOME
|
||||
|
||||
# ── Step 2: cross-compile zvec + tests for Android ───────────────────
|
||||
echo ""
|
||||
echo ">>> Step 2: Cross-compiling zvec for Android ($ABI, API $API_LEVEL)..."
|
||||
|
||||
# reset thirdparty so the cross toolchain can patch cleanly
|
||||
git submodule foreach --recursive 'git stash --include-untracked' 2>/dev/null || true
|
||||
|
||||
BUILD_DIR="build_android_${ABI}"
|
||||
|
||||
# Force CMake reconfigure to pick up any bazel.cmake changes
|
||||
rm -f "$BUILD_DIR/CMakeCache.txt"
|
||||
|
||||
cmake -S . -B "$BUILD_DIR" -G Ninja \
|
||||
-DANDROID_NDK="$ANDROID_NDK_HOME" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$CMAKE_TOOLCHAIN_FILE" \
|
||||
-DANDROID_ABI="$ABI" \
|
||||
-DANDROID_NATIVE_API_LEVEL="$API_LEVEL" \
|
||||
-DANDROID_STL="c++_static" \
|
||||
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
||||
-DBUILD_PYTHON_BINDINGS=OFF \
|
||||
-DBUILD_TOOLS=OFF \
|
||||
-DENABLE_NATIVE=OFF \
|
||||
-DAUTO_DETECT_ARCH=OFF \
|
||||
-DCMAKE_INSTALL_PREFIX="$BUILD_DIR/install" \
|
||||
-DGLOBAL_CC_PROTOBUF_PROTOC="$PROTOC_EXECUTABLE"
|
||||
|
||||
echo " Building library..."
|
||||
cmake --build "$BUILD_DIR" -j"$CORE_COUNT"
|
||||
|
||||
# Discover test target names from ctest metadata (before building)
|
||||
echo " Discovering test targets from ctest..."
|
||||
TEST_NAMES=()
|
||||
while IFS= read -r line; do
|
||||
name=$(echo "$line" | sed -n 's/.*Test[[:space:]]*#[0-9]*:[[:space:]]*//p')
|
||||
if [ -n "$name" ]; then
|
||||
TEST_NAMES+=("$name")
|
||||
fi
|
||||
done < <(cd "$BUILD_DIR" && ctest --show-only 2>/dev/null || true)
|
||||
|
||||
# Fallback: find *_test targets from CMake cache
|
||||
if [ ${#TEST_NAMES[@]} -eq 0 ]; then
|
||||
echo " ctest listing unavailable, scanning ninja targets..."
|
||||
while IFS= read -r line; do
|
||||
name=$(echo "$line" | sed -n 's/^\([a-zA-Z0-9_]*_test\): .*/\1/p')
|
||||
if [ -n "$name" ]; then
|
||||
TEST_NAMES+=("$name")
|
||||
fi
|
||||
done < <(ninja -C "$BUILD_DIR" -t targets all 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
echo " Found ${#TEST_NAMES[@]} test targets."
|
||||
if [ ${#TEST_NAMES[@]} -eq 0 ]; then
|
||||
echo "WARNING: No test targets found. Skipping emulator step."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build all test executables at once (without running ctest)
|
||||
echo " Building ${#TEST_NAMES[@]} test executables..."
|
||||
ninja -C "$BUILD_DIR" -j"$CORE_COUNT" "${TEST_NAMES[@]}"
|
||||
|
||||
echo ">>> Step 2: Done"
|
||||
|
||||
# ── Step 3: collect test binaries ────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> Step 3: Collecting test binaries..."
|
||||
|
||||
TEST_BINS=()
|
||||
for name in "${TEST_NAMES[@]}"; do
|
||||
bin_path=$(find "$BUILD_DIR" -name "$name" -type f -perm +111 2>/dev/null | head -1)
|
||||
if [ -n "$bin_path" ]; then
|
||||
TEST_BINS+=("$bin_path")
|
||||
else
|
||||
echo " WARNING: binary not found for target '$name'"
|
||||
fi
|
||||
done
|
||||
|
||||
echo " Found ${#TEST_BINS[@]} test binaries."
|
||||
if [ ${#TEST_BINS[@]} -eq 0 ]; then
|
||||
echo "WARNING: No test binaries found. Skipping emulator step."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Step 4: start emulator ───────────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> Step 4: Starting Android emulator..."
|
||||
|
||||
EMULATOR_BIN="$ANDROID_SDK_ROOT/emulator/emulator"
|
||||
ADB_BIN="$ANDROID_SDK_ROOT/platform-tools/adb"
|
||||
AVD_NAME="zvec_test_avd"
|
||||
|
||||
# Find a usable system image
|
||||
SYS_IMG=""
|
||||
for candidate in \
|
||||
"$ANDROID_SDK_ROOT/system-images/android-${API_LEVEL}/default/arm64-v8a" \
|
||||
"$ANDROID_SDK_ROOT/system-images/android-${API_LEVEL}/google_apis/arm64-v8a" \
|
||||
"$ANDROID_SDK_ROOT/system-images/android-${API_LEVEL}/google_apis_playstore/arm64-v8a"; do
|
||||
if [ -d "$candidate" ]; then
|
||||
SYS_IMG="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# If the exact API level isn't found, try any available arm64-v8a image
|
||||
if [ -z "$SYS_IMG" ]; then
|
||||
echo " No system image for API $API_LEVEL, searching for any arm64-v8a image..."
|
||||
SYS_IMG=$(find "$ANDROID_SDK_ROOT/system-images" -type d -name "arm64-v8a" 2>/dev/null | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$SYS_IMG" ]; then
|
||||
echo "ERROR: No arm64-v8a system image found."
|
||||
echo "Install one via: sdkmanager 'system-images;android-${API_LEVEL};default;arm64-v8a'"
|
||||
exit 1
|
||||
fi
|
||||
echo " Using system image: $SYS_IMG"
|
||||
|
||||
# Create AVD without avdmanager (write INI files directly)
|
||||
AVD_DIR="$HOME/.android/avd/${AVD_NAME}.avd"
|
||||
AVD_INI="$HOME/.android/avd/${AVD_NAME}.ini"
|
||||
|
||||
cleanup_emulator() {
|
||||
echo ""
|
||||
echo ">>> Cleaning up emulator..."
|
||||
$ADB_BIN emu kill 2>/dev/null || true
|
||||
sleep 2
|
||||
# Kill any lingering emulator processes for our AVD
|
||||
pkill -f "emulator.*${AVD_NAME}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup_emulator EXIT
|
||||
|
||||
mkdir -p "$AVD_DIR"
|
||||
|
||||
# Write the top-level .ini
|
||||
cat > "$AVD_INI" << EOF
|
||||
avd.ini.encoding=UTF-8
|
||||
path=${AVD_DIR}
|
||||
path.rel=avd/${AVD_NAME}.avd
|
||||
target=android-${API_LEVEL}
|
||||
EOF
|
||||
|
||||
# Write the AVD config
|
||||
cat > "$AVD_DIR/config.ini" << EOF
|
||||
AvdId=${AVD_NAME}
|
||||
PlayStore.enabled=false
|
||||
abi.type=arm64-v8a
|
||||
avd.ini.displayname=${AVD_NAME}
|
||||
avd.ini.encoding=UTF-8
|
||||
disk.dataPartition.size=8G
|
||||
hw.accelerator.isConfigured=true
|
||||
hw.cpu.arch=arm64
|
||||
hw.cpu.ncore=4
|
||||
hw.lcd.density=420
|
||||
hw.lcd.height=1920
|
||||
hw.lcd.width=1080
|
||||
hw.ramSize=4096
|
||||
image.sysdir.1=${SYS_IMG}/
|
||||
tag.display=Default
|
||||
tag.id=default
|
||||
EOF
|
||||
|
||||
echo " Created AVD: $AVD_NAME"
|
||||
|
||||
# Kill any existing emulator
|
||||
$ADB_BIN emu kill 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Start emulator
|
||||
echo " Launching emulator..."
|
||||
$EMULATOR_BIN -avd "$AVD_NAME" \
|
||||
-no-window -no-audio -no-boot-anim \
|
||||
-gpu swiftshader_indirect \
|
||||
-netdelay none -netspeed full \
|
||||
-memory 4096 \
|
||||
-no-snapshot \
|
||||
-wipe-data \
|
||||
2>&1 | sed 's/^/ [emulator] /' &
|
||||
EMULATOR_PID=$!
|
||||
|
||||
# Wait for the device to become reachable
|
||||
echo " Waiting for device..."
|
||||
$ADB_BIN wait-for-device
|
||||
|
||||
# Wait for boot_completed
|
||||
echo " Waiting for boot to complete..."
|
||||
BOOT_TIMEOUT=300
|
||||
BOOT_ELAPSED=0
|
||||
while true; do
|
||||
BOOTED=$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null | tr -d '\r\n ' || true)
|
||||
if [ "$BOOTED" = "1" ]; then
|
||||
echo " Emulator is ready! (took ${BOOT_ELAPSED}s)"
|
||||
break
|
||||
fi
|
||||
if [ $BOOT_ELAPSED -ge $BOOT_TIMEOUT ]; then
|
||||
echo "ERROR: Emulator failed to boot within ${BOOT_TIMEOUT}s"
|
||||
exit 1
|
||||
fi
|
||||
sleep 3
|
||||
BOOT_ELAPSED=$((BOOT_ELAPSED + 3))
|
||||
done
|
||||
|
||||
# Print device info
|
||||
echo ""
|
||||
echo " Device ABI : $($ADB_BIN shell getprop ro.product.cpu.abi | tr -d '\r')"
|
||||
echo " ABI list : $($ADB_BIN shell getprop ro.product.cpu.abilist | tr -d '\r')"
|
||||
echo " CPU info :"
|
||||
$ADB_BIN shell 'cat /proc/cpuinfo | grep -E "^(Features|flags|processor)"' 2>/dev/null | head -4 | sed 's/^/ /'
|
||||
|
||||
# ── Step 5: run tests on emulator ────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> Step 5: Running ${#TEST_BINS[@]} unit tests on emulator..."
|
||||
|
||||
DEVICE_TEST_DIR="/data/local/tmp/zvec_tests"
|
||||
DEVICE_LIB_DIR="$DEVICE_TEST_DIR/lib"
|
||||
$ADB_BIN shell "mkdir -p $DEVICE_TEST_DIR $DEVICE_LIB_DIR" 2>/dev/null
|
||||
|
||||
# Push all shared libraries that tests may depend on
|
||||
echo " Pushing shared libraries..."
|
||||
SO_COUNT=0
|
||||
while IFS= read -r so_file; do
|
||||
$ADB_BIN push "$so_file" "$DEVICE_LIB_DIR/$(basename "$so_file")" > /dev/null 2>&1
|
||||
SO_COUNT=$((SO_COUNT + 1))
|
||||
done < <(find "$BUILD_DIR/lib" -name "*.so" -type f 2>/dev/null)
|
||||
echo " Pushed $SO_COUNT shared libraries."
|
||||
|
||||
# Push non-test helper binaries (e.g. data_generator, collection_optimizer)
|
||||
# These are needed by crash_recovery tests which fork+exec them at runtime.
|
||||
echo " Pushing helper binaries..."
|
||||
HELPER_COUNT=0
|
||||
for helper_name in data_generator collection_optimizer; do
|
||||
helper_path=$(find "$BUILD_DIR" -name "$helper_name" -type f -perm +111 ! -name "*_test" 2>/dev/null | head -1)
|
||||
if [ -n "$helper_path" ]; then
|
||||
$ADB_BIN push "$helper_path" "$DEVICE_TEST_DIR/$helper_name" > /dev/null 2>&1
|
||||
$ADB_BIN shell "chmod 755 $DEVICE_TEST_DIR/$helper_name" 2>/dev/null
|
||||
HELPER_COUNT=$((HELPER_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
echo " Pushed $HELPER_COUNT helper binaries."
|
||||
|
||||
TOTAL=${#TEST_BINS[@]}
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
FAILED_NAMES=()
|
||||
IDX=0
|
||||
|
||||
for test_bin in "${TEST_BINS[@]}"; do
|
||||
IDX=$((IDX + 1))
|
||||
test_name=$(basename "$test_bin")
|
||||
device_path="$DEVICE_TEST_DIR/$test_name"
|
||||
# Give each test its own working directory to avoid name collisions
|
||||
# (e.g. flat_builder_test binary vs flat_builder_test/ directory)
|
||||
WORK_DIR="$DEVICE_TEST_DIR/workdir_${test_name}"
|
||||
|
||||
echo ""
|
||||
echo "────────────────────────────────────────"
|
||||
echo " [$IDX/$TOTAL] $test_name"
|
||||
echo "────────────────────────────────────────"
|
||||
|
||||
set +e
|
||||
# Create isolated working directory
|
||||
$ADB_BIN shell "mkdir -p $WORK_DIR" 2>/dev/null
|
||||
|
||||
# Copy helper binaries into working directory so crash_recovery tests
|
||||
# (which fork+exec data_generator / collection_optimizer) can find them.
|
||||
$ADB_BIN shell "for h in $DEVICE_TEST_DIR/data_generator $DEVICE_TEST_DIR/collection_optimizer; do [ -f \$h ] && cp \$h $WORK_DIR/; done" 2>/dev/null
|
||||
|
||||
# Push binary
|
||||
$ADB_BIN push "$test_bin" "$device_path" > /dev/null 2>&1
|
||||
$ADB_BIN shell "chmod 755 $device_path" 2>/dev/null
|
||||
|
||||
# Run test from its own working directory with LD_LIBRARY_PATH
|
||||
OUTPUT=$($ADB_BIN shell "cd $WORK_DIR && LD_LIBRARY_PATH=$DEVICE_LIB_DIR $device_path 2>&1; echo EXIT_CODE=\$?" 2>&1)
|
||||
|
||||
# Extract exit code from the output
|
||||
EXIT_CODE=$(echo "$OUTPUT" | grep -o 'EXIT_CODE=[0-9]*' | tail -1 | cut -d= -f2)
|
||||
set -e
|
||||
|
||||
# Print test output (without the EXIT_CODE line)
|
||||
echo "$OUTPUT" | grep -v 'EXIT_CODE=' | sed 's/^/ /'
|
||||
|
||||
if [ "$EXIT_CODE" = "0" ]; then
|
||||
echo " >>> PASSED"
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
# Detect "segfault-on-exit" pattern: all gtest assertions passed but
|
||||
# process crashed during teardown (common with cross-compiled gmock tests).
|
||||
GTEST_PASSED_LINE=$(echo "$OUTPUT" | grep '\[ PASSED \]' | tail -1)
|
||||
GTEST_FAILED_LINE=$(echo "$OUTPUT" | grep '\[ FAILED \]' | head -1)
|
||||
if [ -n "$GTEST_PASSED_LINE" ] && [ -z "$GTEST_FAILED_LINE" ] && \
|
||||
{ [ "$EXIT_CODE" = "139" ] || [ "$EXIT_CODE" = "134" ] || [ "$EXIT_CODE" = "135" ]; }; then
|
||||
echo " >>> PASSED (with crash-on-exit, exit code: $EXIT_CODE — ignored)"
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
echo " >>> FAILED (exit code: $EXIT_CODE)"
|
||||
FAILED=$((FAILED + 1))
|
||||
FAILED_NAMES+=("$test_name")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clean up binary and working directory to reclaim disk space
|
||||
$ADB_BIN shell "rm -rf $device_path $WORK_DIR" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " Test Summary"
|
||||
echo "============================================================"
|
||||
echo " Total : $TOTAL"
|
||||
echo " Passed : $PASSED"
|
||||
echo " Failed : $FAILED"
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo ""
|
||||
echo " Failed tests:"
|
||||
for name in "${FAILED_NAMES[@]}"; do
|
||||
echo " - $name"
|
||||
done
|
||||
fi
|
||||
echo "============================================================"
|
||||
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All tests passed!"
|
||||
Reference in New Issue
Block a user