From 553c512082c0e65608a997288390ef506fb2dec5 Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 1 Sep 2026 17:16:48 -0500 Subject: [PATCH] ci: route Android builds through project tooling --- .gitea/workflows/android-build.yml | 138 ++++------------------------- AndroidProjectTooling.sh | 53 +++++++++-- README.md | 14 +-- 3 files changed, 73 insertions(+), 132 deletions(-) diff --git a/.gitea/workflows/android-build.yml b/.gitea/workflows/android-build.yml index 34ece15..6b527d7 100644 --- a/.gitea/workflows/android-build.yml +++ b/.gitea/workflows/android-build.yml @@ -29,73 +29,6 @@ jobs: distribution: temurin java-version: '17' - - name: Install Android SDK command-line tools and packages - shell: bash - run: | - set -euxo pipefail - export ANDROID_SDK_ROOT="$HOME/.android/sdk" - export ANDROID_HOME="$ANDROID_SDK_ROOT" - mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" - if [ ! -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then - tools_url="$(curl -fsSL https://developer.android.com/studio | grep -o 'https://dl.google.com/android/repository/commandlinetools-linux-[0-9][0-9]*_latest.zip' | head -n 1)" - test -n "$tools_url" - curl -fsSL -o /tmp/commandlinetools.zip "$tools_url" - rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest" "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" - unzip -q /tmp/commandlinetools.zip -d "$ANDROID_SDK_ROOT/cmdline-tools" - mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest" - fi - export PATH="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$PATH" - set +o pipefail - yes | sdkmanager --licenses >/dev/null - set -o pipefail - sdkmanager \ - "platform-tools" \ - "platforms;android-36" \ - "build-tools;36.0.0" \ - "ndk;27.2.12479018" \ - "cmake;3.22.1" - - - name: Prepare optional release signing - if: github.event_name == 'release' - continue-on-error: true - env: - ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} - ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} - shell: bash - run: | - set -euo pipefail - missing=() - for key in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do - if [ -z "${!key:-}" ]; then - missing+=("$key") - fi - done - if [ ${#missing[@]} -gt 0 ]; then - printf -v missing_csv '%s, ' "${missing[@]}" - missing_csv="${missing_csv%, }" - echo "::error::Release signing secrets missing: ${missing_csv}. Continuing with unsigned/default-signed release AAB." - exit 1 - fi - mkdir -p .ci-secrets - printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > .ci-secrets/release-keystore.jks - cat > keystore.properties < local.properties - - name: Install Rust toolchain shell: bash run: | @@ -109,47 +42,17 @@ jobs: rustc --version rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android - - name: Compile bundled native Git binaries + - name: Run project build tooling for debug APK + if: github.event_name != 'release' shell: bash + env: + CI_VERSION_CODE: ${{ github.run_number }} + CI_ARTIFACT_TIMESTAMP: ${{ github.run_id }} run: | set -euxo pipefail export PATH="$HOME/.cargo/bin:$PATH" - export ANDROID_SDK_ROOT="$HOME/.android/sdk" - export ANDROID_HOME="$ANDROID_SDK_ROOT" chmod +x ./AndroidProjectTooling.sh - bash ./AndroidProjectTooling.sh --compile-android-git - - - name: Prepare CI build metadata - id: build_meta - shell: bash - run: | - set -euxo pipefail - timestamp_utc="$(date -u +%Y%m%d-%H%M%S)" - version_code="$(date -u +%s)" - project_name="$(basename "$GITHUB_REPOSITORY")" - python3 - "$version_code" <<'PY' - from pathlib import Path - import re, sys - path = Path('app/build.gradle.kts') - text = path.read_text(encoding='utf-8') - new_text, count = re.subn(r'(\bversionCode\s*=\s*)\d+', rf'\g<1>{sys.argv[1]}', text, count=1) - if count != 1: - raise SystemExit('versionCode not found in app/build.gradle.kts') - path.write_text(new_text, encoding='utf-8') - PY - echo "Using CI versionCode: $version_code" - echo "project_name=$project_name" >> "$GITHUB_OUTPUT" - echo "timestamp_utc=$timestamp_utc" >> "$GITHUB_OUTPUT" - echo "version_code=$version_code" >> "$GITHUB_OUTPUT" - - - name: Build debug APK - if: github.event_name != 'release' - shell: bash - run: | - set -euxo pipefail - export ANDROID_SDK_ROOT="$HOME/.android/sdk" - export ANDROID_HOME="$ANDROID_SDK_ROOT" - ./gradlew --no-daemon clean assembleDebug + bash ./AndroidProjectTooling.sh --build --no-commit - name: Verify debug APK bundles native Git if: github.event_name != 'release' @@ -175,14 +78,21 @@ jobs: raise SystemExit(f'Missing native Git libraries in {apk}: {missing}') PY - - name: Build release AAB + - name: Run project build tooling for release AAB if: github.event_name == 'release' shell: bash + env: + CI_VERSION_CODE: ${{ github.run_number }} + CI_ARTIFACT_TIMESTAMP: ${{ github.run_id }} + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | set -euxo pipefail - export ANDROID_SDK_ROOT="$HOME/.android/sdk" - export ANDROID_HOME="$ANDROID_SDK_ROOT" - ./gradlew --no-daemon bundleRelease + export PATH="$HOME/.cargo/bin:$PATH" + chmod +x ./AndroidProjectTooling.sh + bash ./AndroidProjectTooling.sh --build-release-aab --no-commit - name: Verify release AAB bundles native Git if: github.event_name == 'release' @@ -208,18 +118,6 @@ jobs: raise SystemExit(f'Missing native Git libraries in {aab}: {missing}') PY - - name: Rename release AAB for upload - if: github.event_name == 'release' - id: release_aab - shell: bash - run: | - set -euxo pipefail - aab="$(find app/build/outputs/bundle/release -name '*.aab' | head -n 1)" - renamed="app/build/outputs/bundle/release/${{ steps.build_meta.outputs.project_name }}-${{ steps.build_meta.outputs.timestamp_utc }}-release.aab" - mv -f "$aab" "$renamed" - echo "Renamed release AAB to $(basename "$renamed")" - echo "path=$renamed" >> "$GITHUB_OUTPUT" - - name: Upload debug APK if: github.event_name != 'release' uses: actions/upload-artifact@v3 @@ -242,7 +140,7 @@ jobs: exit 1 fi release_id="$(python3 -c 'import json, os; print(json.load(open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8"))["release"]["id"])')" - aab="${{ steps.release_aab.outputs.path }}" + aab="$(find app/build/outputs/bundle/release -name '*.aab' | head -n 1)" curl --fail-with-body \ --request POST \ --header "Authorization: token ${token}" \ diff --git a/AndroidProjectTooling.sh b/AndroidProjectTooling.sh index efe5f8f..7fe3b98 100755 --- a/AndroidProjectTooling.sh +++ b/AndroidProjectTooling.sh @@ -38,6 +38,9 @@ ANDROID_TOOLBIN="$ANDROID_NDK_DIR/toolchains/llvm/prebuilt/linux-x86_64/bin" STATE_TTL_SECONDS=86400 HIDE_EMULATOR_WINDOW="false" QUIET_LOG_DIR="$PROJECT_DIR/build/reports/android-project-tooling" +NO_COMMIT="false" +CI_VERSION_CODE="${CI_VERSION_CODE:-}" +CI_ARTIFACT_TIMESTAMP="${CI_ARTIFACT_TIMESTAMP:-}" log() { printf '\n[%s] %s\n' "tooling" "$1" @@ -299,6 +302,7 @@ import re path = Path(r'''$gradle_file''') text = path.read_text() +ci_version_code = r'''$CI_VERSION_CODE'''.strip() version_code_match = re.search(r'versionCode\s*=\s*(\d+)', text) version_name_match = re.search(r'versionName\s*=\s*"(\d+)\.(\d+)\.(\d+)"', text) @@ -309,14 +313,15 @@ if not version_code_match or not version_name_match: current_code = int(version_code_match.group(1)) major, minor, patch = map(int, version_name_match.groups()) -new_code = current_code + 1 +new_code = int(ci_version_code) if ci_version_code else current_code + 1 new_name = f'{major}.{minor}.{patch + 1}' text = re.sub(r'versionCode\s*=\s*\d+', f'versionCode = {new_code}', text, count=1) -text = re.sub(r'versionName\s*=\s*"\d+\.\d+\.\d+"', f'versionName = "{new_name}"', text, count=1) +if not ci_version_code: + text = re.sub(r'versionName\s*=\s*"\d+\.\d+\.\d+"', f'versionName = "{new_name}"', text, count=1) path.write_text(text) -print(f'Updated versionCode to {new_code} and versionName to {new_name}') +print(f'Updated versionCode to {new_code}' + (f' and versionName to {new_name}' if not ci_version_code else '')) PY } @@ -352,6 +357,34 @@ upload_build_artifact_if_possible() { bash "$UPLOAD_SCRIPT" "$artifact_path" } +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 "$PROJECT_DIR/.ci-secrets" + printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$PROJECT_DIR/.ci-secrets/release-keystore.jks" + cat > "$PROJECT_DIR/keystore.properties" <&2 diff --git a/README.md b/README.md index 34f059e..466e065 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The UI supports a terminal-centered workflow with optional inspection panes: ## Development Workflow -Development tasks should be run through the project tooling script. It provisions the local toolchain, keeps paths project-relative, and runs Gradle with the project configuration expected by this repository. +Development tasks should be run through the project tooling script. Local developers and Gitea Actions both use this same script so the compile path stays aligned. While implementing changes, keep `commit-summary.txt` updated as you go. This file is intentionally ignored by git and excluded from automatic commits so it can be edited manually too; the build tooling uses it as the commit message when it exists and is non-empty. @@ -50,8 +50,8 @@ Available commands: | `bash ./AndroidProjectTooling.sh --clean-test` | Compile host Git, clean Gradle outputs, set `GITHUG_TEST_GIT_BINARY`, and run JVM unit tests. | Fresh test reports under `app/build/reports/` | | `bash ./AndroidProjectTooling.sh --test-emulator` | Install emulator packages if needed, create/start the visible project test AVD, compile Android Git, and run debug instrumentation tests. | Instrumentation reports under `app/build/reports/androidTests/` | | `bash ./AndroidProjectTooling.sh --test-emulator --hide-emulator-window` | Run the same emulator instrumentation tests without showing the emulator window. | Instrumentation reports under `app/build/reports/androidTests/` | -| `bash ./AndroidProjectTooling.sh --build` | Build the debug APK. | `app/build/outputs/apk/debug/githug-android-debug-v.apk` | -| `bash ./AndroidProjectTooling.sh --build-release-aab` | Build the release Android App Bundle. | `app/build/outputs/bundle/release/githug-android-release-v.aab` | +| `bash ./AndroidProjectTooling.sh --build` | Build the debug APK. Add `--no-commit` to skip the post-build commit. | `app/build/outputs/apk/debug/githug-android-debug-v.apk` | +| `bash ./AndroidProjectTooling.sh --build-release-aab` | Build the release Android App Bundle. Add `--no-commit` to skip the post-build commit. | `app/build/outputs/bundle/release/githug-android-release-v.aab` or `Githug-Android--release.aab` when `CI_ARTIFACT_TIMESTAMP` is set | | `bash ./AndroidProjectTooling.sh --compile-git` | Compile Git for the development host and all Android target ABIs. | Host and Android `libgit.so` binaries | Add `--quiet` before any tooling command when running through an AI or other log-sensitive automation: @@ -120,13 +120,13 @@ If all required components were verified successfully, the script will skip SDK When `--build` or `--build-release-aab` is used, the script also: -- increments `versionCode` by 1 -- increments the patch component of `versionName`, for example `0.1.0` to `0.1.1` +- increments `versionCode` by 1 for local builds, or honors `CI_VERSION_CODE` when provided +- increments the patch component of `versionName` for local builds - compiles the generated Android `libgit.so` binaries when they are missing or stale - bundles full Git manpage source files from Git's `Documentation/` directory into app assets -- renames the generated artifact to a `githug-android-*` filename that includes the post-bump `versionCode` +- renames the generated artifact to a `githug-android-*` filename that includes the post-bump `versionCode`, or to `Githug-Android--release.aab` when `CI_ARTIFACT_TIMESTAMP` is set for CI release builds - uploads the renamed APK/AAB with local `./upload2DL.sh` when that script exists -- attempts to create a git commit after a successful build if there are source changes +- attempts to create a git commit after a successful build unless `--no-commit` is passed The build commands currently run these Gradle tasks: