#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$SCRIPT_DIR" TMP_DIR="$PROJECT_DIR/.tmp-android-build" SDK_DIR="$PROJECT_DIR/android-sdk" JDK_DIR="$PROJECT_DIR/jdk" STATE_FILE="$PROJECT_DIR/.setup-build-environment.state" CMDLINE_TOOLS_DIR="$SDK_DIR/cmdline-tools" CMDLINE_TOOLS_LATEST_DIR="$CMDLINE_TOOLS_DIR/latest" WRAPPER_JAR_PATH="$PROJECT_DIR/gradle/wrapper/gradle-wrapper.jar" LOCAL_PROPERTIES_PATH="$PROJECT_DIR/local.properties" ANDROID_CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" GRADLE_DIST_URL="https://services.gradle.org/distributions/gradle-8.7-bin.zip" JDK_DIST_URL="https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jdk/hotspot/normal/eclipse" ANDROID_NDK_PACKAGE="ndk;27.2.12479018" ANDROID_CMAKE_PACKAGE="cmake;3.22.1" ANDROID_NDK_DIR="$SDK_DIR/ndk/27.2.12479018" ANDROID_CMAKE_DIR="$SDK_DIR/cmake/3.22.1" STATE_TTL_SECONDS=86400 log() { printf '\n[%s] %s\n' "setup" "$1" } require_tool() { if ! command -v "$1" >/dev/null 2>&1; then echo "Missing required tool: $1" >&2 exit 1 fi } ensure_dir() { mkdir -p "$1" } current_epoch() { date +%s } have_recent_success_marker() { if [ ! -f "$STATE_FILE" ]; then return 1 fi local recorded_epoch recorded_epoch="$(cat "$STATE_FILE" 2>/dev/null || true)" if ! printf '%s' "$recorded_epoch" | grep -Eq '^[0-9]+$'; then return 1 fi local now now="$(current_epoch)" [ $((now - recorded_epoch)) -lt "$STATE_TTL_SECONDS" ] } mark_successful_check() { current_epoch > "$STATE_FILE" } auto_commit_if_needed() { if ! command -v git >/dev/null 2>&1; then log "Git is not available; skipping automatic commit" return fi if ! git -C "$PROJECT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then log "Project is not inside a git work tree; skipping automatic commit" return fi git -C "$PROJECT_DIR" add -A if git -C "$PROJECT_DIR" diff --cached --quiet; then log "No source changes to commit after successful build" return fi local changed_files changed_files="$(git -C "$PROJECT_DIR" diff --cached --name-only)" local -a summary_parts=() if printf '%s\n' "$changed_files" | grep -q '^app/src/main/java/'; then summary_parts+=("update app gameplay/UI") fi if printf '%s\n' "$changed_files" | grep -Eq '^(SetupBuildEnvironment\.sh|gradle\.properties|gradle/|gradlew|gradlew\.bat|build\.gradle\.kts|settings\.gradle\.kts|app/build\.gradle\.kts)'; then summary_parts+=("improve build setup") fi if printf '%s\n' "$changed_files" | grep -Eq '^(README\.md|\.gitignore)$'; then summary_parts+=("update docs/config") fi if [ ${#summary_parts[@]} -eq 0 ]; then summary_parts+=("project updates") fi local commit_message commit_message="Auto-commit after successful build: $(printf '%s, ' "${summary_parts[@]}" | sed 's/, $//')" log "Creating git commit for the latest successful build" git -C "$PROJECT_DIR" commit -m "$commit_message" -m "Changed files:\n$changed_files" } download_if_missing() { local url="$1" local output="$2" if [ -f "$output" ]; then log "Using existing download: ${output#$PROJECT_DIR/}" else log "Downloading $(basename "$output")" curl -L "$url" -o "$output" fi } ensure_cmdline_tools() { if [ -x "$CMDLINE_TOOLS_LATEST_DIR/bin/sdkmanager" ]; then log "Android command-line tools already present" return fi ensure_dir "$CMDLINE_TOOLS_DIR" ensure_dir "$TMP_DIR" local zip_path="$TMP_DIR/commandlinetools.zip" download_if_missing "$ANDROID_CMDLINE_TOOLS_URL" "$zip_path" log "Extracting Android command-line tools" rm -rf "$CMDLINE_TOOLS_LATEST_DIR" unzip -q -o "$zip_path" -d "$CMDLINE_TOOLS_DIR" if [ -d "$CMDLINE_TOOLS_DIR/cmdline-tools" ]; then mv "$CMDLINE_TOOLS_DIR/cmdline-tools" "$CMDLINE_TOOLS_LATEST_DIR" fi } ensure_wrapper_jar() { if [ -f "$WRAPPER_JAR_PATH" ]; then log "Gradle wrapper jar already present" return fi ensure_dir "$TMP_DIR" local zip_path="$TMP_DIR/gradle-8.7-bin.zip" local gradle_extract_dir="$TMP_DIR/gradle-8.7" local plugin_wrapper_jar="$gradle_extract_dir/lib/plugins/gradle-wrapper-8.7.jar" download_if_missing "$GRADLE_DIST_URL" "$zip_path" log "Extracting Gradle wrapper jar" rm -rf "$gradle_extract_dir" unzip -q -o "$zip_path" -d "$TMP_DIR" python - <&2 exit 1 fi tar -xzf "$archive_path" -C "$extract_dir" local extracted_root extracted_root="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)" if [ -z "$extracted_root" ]; then echo "Unable to locate extracted JDK directory" >&2 exit 1 fi mv "$extracted_root" "$JDK_DIR" } setup_java_env() { export JAVA_HOME="$JDK_DIR" export PATH="$JAVA_HOME/bin:$PATH" } write_local_properties() { log "Writing local.properties" cat > "$LOCAL_PROPERTIES_PATH" </dev/null local license_status=$? set -o pipefail set -e if [ "$license_status" -ne 0 ]; then echo "sdkmanager --licenses failed with exit code $license_status" >&2 exit "$license_status" fi log "Installing required Android SDK packages" "$CMDLINE_TOOLS_LATEST_DIR/bin/sdkmanager" --sdk_root="$SDK_DIR" \ "platform-tools" \ "platforms;android-34" \ "build-tools;34.0.0" \ "$ANDROID_NDK_PACKAGE" \ "$ANDROID_CMAKE_PACKAGE" mark_successful_check } maybe_build() { local mode="${1:-}" local gradle_task="" local artifact_label="" local artifact_source="" local artifact_target="" case "$mode" in --build) gradle_task="assembleDebug" artifact_label="debug APK" artifact_source="$PROJECT_DIR/app/build/outputs/apk/debug/app-debug.apk" artifact_target="$PROJECT_DIR/app/build/outputs/apk/debug/githug-android-debug.apk" ;; --build-aab) gradle_task="bundleDebug" artifact_label="debug AAB" artifact_source="$PROJECT_DIR/app/build/outputs/bundle/debug/app-debug.aab" artifact_target="$PROJECT_DIR/app/build/outputs/bundle/debug/githug-android-debug.aab" ;; *) return ;; esac setup_env bump_android_version log "Building $artifact_label with --no-daemon" "$PROJECT_DIR/gradlew" --no-daemon "$gradle_task" if [ -f "$artifact_source" ]; then log "Renaming $(basename "$artifact_source") to $(basename "$artifact_target")" mv -f "$artifact_source" "$artifact_target" else log "Expected build output not found for rename: ${artifact_source#$PROJECT_DIR/}" fi auto_commit_if_needed log "Stopping any Gradle daemons just in case" "$PROJECT_DIR/gradlew" --stop >/dev/null 2>&1 || true } print_usage() { cat <&2 print_usage >&2 exit 1 ;; esac } main() { validate_args "${1:-}" require_tool curl require_tool unzip require_tool tar require_tool git require_tool perl require_tool make require_tool clang require_tool pkg-config require_tool readelf ensure_dir "$TMP_DIR" ensure_dir "$SDK_DIR" ensure_jdk17 ensure_cmdline_tools ensure_wrapper_jar write_local_properties ensure_sdk_packages maybe_build "${1:-}" log "Environment ready" log "Project dir: $PROJECT_DIR" log "JDK dir: $JDK_DIR" log "SDK dir: $SDK_DIR" log "NDK dir: $ANDROID_NDK_DIR" log "CMake dir: $ANDROID_CMAKE_DIR" log "To cross-compile Git for Android arm64-v8a: bash ./CrossCompileGitForAndroid.sh" log "To build without a background Gradle daemon: ./gradlew --no-daemon assembleDebug" log "To setup and build in one step: bash ./SetupBuildEnvironment.sh --build" log "To setup and build a debug AAB in one step: bash ./SetupBuildEnvironment.sh --build-aab" } main "$@"