#!/usr/bin/env bash set -Eeuo pipefail APP_NAME="MindMachine" MODULE="app" API_LEVEL="36" BUILD_TOOLS_VERSION="36.0.0" DIST_DIR="dist" COMMIT_MSG_FILE="commit-message.txt" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" export GRADLE_OPTS="${GRADLE_OPTS:-} -Dorg.gradle.daemon=false" usage() { cat <&2; usage; exit 1 ;; esac done CI_VERSION_CODE="${CI_VERSION_CODE:-}" CI_ARTIFACT_TIMESTAMP="${CI_ARTIFACT_TIMESTAMP:-}" need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2 exit 1 } } ensure_java() { need_cmd java local major major="$(java -version 2>&1 | awk -F[\".] '/version/ {print $2}')" if [[ "${major:-0}" -lt 17 ]]; then echo "Java 17+ is required. Found Java ${major:-unknown}." >&2 exit 1 fi } ensure_android_sdk() { if [[ -z "${ANDROID_HOME:-}" ]]; then export ANDROID_HOME="$ROOT_DIR/.android-sdk" fi export ANDROID_SDK_ROOT="$ANDROID_HOME" export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH" if [[ ! -x "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]]; then need_cmd curl need_cmd unzip mkdir -p "$ANDROID_HOME/cmdline-tools" local zip_path="$ANDROID_HOME/cmdline-tools/commandlinetools.zip" curl -L -o "$zip_path" "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" rm -rf "$ANDROID_HOME/cmdline-tools/latest" "$ANDROID_HOME/cmdline-tools/cmdline-tools" unzip -q "$zip_path" -d "$ANDROID_HOME/cmdline-tools" mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" rm "$zip_path" fi yes | sdkmanager --licenses >/dev/null || true sdkmanager \ "platform-tools" \ "platforms;android-$API_LEVEL" \ "build-tools;$BUILD_TOOLS_VERSION" >/dev/null } ensure_gradle_wrapper() { if [[ ! -x "./gradlew" ]]; then if [[ -f "./gradlew" ]]; then chmod +x ./gradlew else echo "Missing Gradle wrapper ./gradlew" >&2 exit 1 fi fi } gradle_no_daemon() { ./gradlew --no-daemon -Dorg.gradle.daemon=false "$@" } prepare_optional_release_signing() { local required=(ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD) local missing=() local key for key in "${required[@]}"; do if [[ -z "${!key:-}" ]]; then missing+=("$key") fi done if [[ ${#missing[@]} -eq ${#required[@]} ]]; then return fi if [[ ${#missing[@]} -gt 0 ]]; then printf 'Release signing secrets missing: %s\n' "${missing[*]}" >&2 exit 1 fi mkdir -p .ci-secrets printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > .ci-secrets/release-keystore.jks cat > keystore.properties <> .gitignore done } increment_versions() { local gradle_file="$MODULE/build.gradle.kts" python3 - "$gradle_file" "$CI_VERSION_CODE" <<'PY' import re import sys from pathlib import Path path = Path(sys.argv[1]) text = path.read_text() ci_version_code = sys.argv[2].strip() m_code = re.search(r'\bversionCode\s*=\s*(\d+)', text) m_name = re.search(r'\bversionName\s*=\s*"(\d+(?:\.\d+)*)"', text) if not m_code or not m_name: raise SystemExit('Could not find versionCode/versionName in app/build.gradle.kts') old_code = int(m_code.group(1)) new_code = int(ci_version_code) if ci_version_code else old_code + 1 parts = m_name.group(1).split('.') parts[-1] = str(int(parts[-1]) + 1) new_name = '.'.join(parts) text = re.sub(r'(\bversionCode\s*=\s*)\d+', rf'\g<1>{new_code}', text, count=1) if not ci_version_code: text = re.sub(r'(\bversionName\s*=\s*)"\d+(?:\.\d+)*"', rf'\g<1>"{new_name}"', text, count=1) path.write_text(text) print(new_code) PY } build_artifacts() { gradle_no_daemon clean assembleDebug if [[ "$WITH_AAB" == true ]]; then gradle_no_daemon bundleRelease fi } copy_artifacts() { local version_code="$1" mkdir -p "$DIST_DIR" local debug_apk debug_apk="$(find "$MODULE/build/outputs/apk/debug" -name "*.apk" | head -n 1 || true)" [[ -f "$debug_apk" ]] || { echo "Debug APK not found." >&2; exit 1; } cp "$debug_apk" "$DIST_DIR/${APP_NAME}-v${version_code}-debug.apk" if [[ "$WITH_AAB" == true ]]; then local release_aab release_aab="$(find "$MODULE/build/outputs/bundle/release" -name "*.aab" | head -n 1 || true)" [[ -f "$release_aab" ]] || { echo "Release AAB not found." >&2; exit 1; } local release_name if [[ -n "$CI_ARTIFACT_TIMESTAMP" ]]; then release_name="${APP_NAME}-${CI_ARTIFACT_TIMESTAMP}-release.aab" else release_name="${APP_NAME}-v${version_code}-release.aab" fi cp "$release_aab" "$DIST_DIR/$release_name" fi } commit_successful_build() { git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return local message if [[ -s "$COMMIT_MSG_FILE" ]]; then message="$(cat "$COMMIT_MSG_FILE")" else message="No Details" fi git add -A git reset -q HEAD -- keystore.properties '.ci-secrets' '*.jks' 2>/dev/null || true if ! git diff --cached --quiet; then git commit -m "$message" fi : > "$COMMIT_MSG_FILE" } main() { need_cmd python3 ensure_java ensure_android_sdk ensure_gradle_wrapper ensure_gitignore_entries printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties if [[ "$SETUP_ONLY" == true ]]; then echo "Build environment setup complete." exit 0 fi if [[ "$WITH_AAB" == true ]]; then prepare_optional_release_signing fi local version_code version_code="$(increment_versions)" build_artifacts copy_artifacts "$version_code" if [[ "$DO_COMMIT" == true ]]; then commit_successful_build fi echo "Build completed successfully for versionCode $version_code." } main "$@"