name: Forgejo Release Builder # NOTE: keep Forgejo action refs node20-compatible for runner v3.5.1. on: push: tags: - v* permissions: contents: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: release: runs-on: forgejo-release steps: - name: Clone repository shell: bash env: FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail rm -rf .git git init . git remote add origin "http://copilot:${FORGEJO_TOKEN}@127.0.0.1:3030/${GITHUB_REPOSITORY}.git" git fetch --tags --prune --depth=1 origin "${GITHUB_SHA}" git checkout --force FETCH_HEAD - name: Set up JDK shell: bash run: | set -euo pipefail pkg_install() { if [ "$(id -u)" -eq 0 ]; then "$@" elif command -v sudo >/dev/null 2>&1; then sudo "$@" else echo "Need root or sudo to install packages." exit 1 fi } if command -v java >/dev/null 2>&1; then java -version else echo "PATH=$PATH" echo "pkg managers: dnf=$(command -v dnf || true) apt=$(command -v apt-get || true) apk=$(command -v apk || true) pacman=$(command -v pacman || true)" if [ -x /usr/bin/dnf ] || command -v dnf >/dev/null 2>&1; then pkg_install dnf install -y java-17-openjdk-devel elif [ -x /usr/bin/apt-get ] || command -v apt-get >/dev/null 2>&1; then pkg_install apt-get update pkg_install apt-get install -y openjdk-17-jdk elif [ -x /sbin/apk ] || [ -x /usr/sbin/apk ] || command -v apk >/dev/null 2>&1; then pkg_install apk add --no-cache openjdk17 elif [ -x /usr/bin/pacman ] || command -v pacman >/dev/null 2>&1; then pkg_install pacman -Sy --noconfirm jdk17-openjdk else echo "No supported package manager found for JDK installation." exit 1 fi java -version fi - name: Write google-services.json shell: bash run: | set -euo pipefail RAW_SECRET='${{ secrets.GOOGLE_SERVICES_JSON }}' python3 - <<'PY' import base64 import json import os from pathlib import Path raw = os.environ.get("RAW_SECRET", "") candidates = [raw, raw.strip()] try: decoded = base64.b64decode(raw, validate=True).decode("utf-8") candidates.extend([decoded, decoded.strip()]) except Exception: pass out = Path("app/google-services.json") for candidate in candidates: if not candidate: continue try: value = json.loads(candidate) if isinstance(value, str): value = json.loads(value) out.write_text(json.dumps(value, ensure_ascii=False)) break except Exception: continue else: raise SystemExit("GOOGLE_SERVICES_JSON is neither valid JSON nor base64 JSON") PY python3 -m json.tool app/google-services.json > /dev/null - name: Write client_secrets.json shell: bash run: | set -euo pipefail mkdir -p app/src/main/assets RAW_SECRET='${{ secrets.GOOGLE_CLIENT_SECRETS_JSON }}' python3 - <<'PY' import base64 import json import os from pathlib import Path raw = os.environ.get("RAW_SECRET", "") candidates = [raw, raw.strip()] try: decoded = base64.b64decode(raw, validate=True).decode("utf-8") candidates.extend([decoded, decoded.strip()]) except Exception: pass out = Path("app/src/main/assets/client_secrets.json") for candidate in candidates: if not candidate: continue try: value = json.loads(candidate) if isinstance(value, str): value = json.loads(value) out.write_text(json.dumps(value, ensure_ascii=False)) break except Exception: continue else: raise SystemExit("GOOGLE_CLIENT_SECRETS_JSON is neither valid JSON nor base64 JSON") PY python3 -m json.tool app/src/main/assets/client_secrets.json > /dev/null - name: Prepare release metadata id: prepare shell: bash run: | set -euo pipefail version_tag="${GITHUB_REF_NAME:-${GITHUB_REF#refs/tags/}}" previous_tag="$(git tag --list 'v*' --sort=-version:refname | grep -Fxv "$version_tag" | head -n 1 || true)" echo "VERSION_TAG=$version_tag" >> "$GITHUB_OUTPUT" echo "PREV_TAG_NAME=$previous_tag" >> "$GITHUB_OUTPUT" if [ -n "$previous_tag" ]; then commit_logs="$(git log --pretty=format:'- %s (@%an)' "${previous_tag}..HEAD")" else commit_logs='- Initial release' fi { echo 'COMMIT_LOGS<> "$GITHUB_OUTPUT" - name: Set up Gradle shell: bash run: | set -euo pipefail if [ -d /opt/android-sdk-linux ]; then export ANDROID_HOME=/opt/android-sdk-linux export ANDROID_SDK_ROOT=/opt/android-sdk-linux echo "ANDROID_HOME=$ANDROID_HOME" >> "$GITHUB_ENV" echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties fi chmod +x ./gradlew ./gradlew --no-daemon --version - name: Check code format run: ./gradlew --no-daemon --max-workers=2 spotlessCheck - name: Build app run: ./gradlew --no-daemon --max-workers=2 assembleRelease -Pinclude-telemetry -Penable-updater - name: Run unit tests run: ./gradlew --no-daemon --max-workers=2 testReleaseUnitTest - name: Rename release artifacts shell: bash run: | set -euo pipefail version_tag="${{ steps.prepare.outputs.VERSION_TAG }}" mv app/build/outputs/apk/release/app-universal-release-unsigned.apk "Komikku-${version_tag}-unsigned.apk" mv app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk "Komikku-arm64-v8a-${version_tag}-unsigned.apk" mv app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk "Komikku-armeabi-v7a-${version_tag}-unsigned.apk" mv app/build/outputs/apk/release/app-x86-release-unsigned.apk "Komikku-x86-${version_tag}-unsigned.apk" mv app/build/outputs/apk/release/app-x86_64-release-unsigned.apk "Komikku-x86_64-${version_tag}-unsigned.apk" zip -qr "Komikku-mapping-${version_tag}.zip" app/build/outputs/mapping/release - name: Create or update release id: release shell: bash env: FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION_TAG: ${{ steps.prepare.outputs.VERSION_TAG }} PREV_TAG_NAME: ${{ steps.prepare.outputs.PREV_TAG_NAME }} COMMIT_LOGS: ${{ steps.prepare.outputs.COMMIT_LOGS }} REPO_URL: ${{ github.server_url }}/${{ github.repository }} REPOSITORY_NAME: ${{ github.repository }} run: | set -euo pipefail api_base="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" payload_file="$(mktemp)" response_file="$(mktemp)" python3 - <<'PY' > "$payload_file" import json import os import textwrap version_tag = os.environ["VERSION_TAG"] previous_tag = os.environ.get("PREV_TAG_NAME", "").strip() commit_logs = os.environ.get("COMMIT_LOGS", "").strip() or "- Initial release" repo_url = os.environ["REPO_URL"] repository_name = os.environ["REPOSITORY_NAME"] if previous_tag: full_changelog = ( f"**Full Changelog**: " f"[{repository_name}@{previous_tag}...{version_tag}]" f"({repo_url}/compare/{previous_tag}...{version_tag})" ) else: full_changelog = "" body = textwrap.dedent( f"""\ #### What's Changed ##### New ##### Improve ##### Fix {commit_logs} ##### Based on {full_changelog} > [!TIP] > > ### If you are unsure which version to download then go with `Komikku-{version_tag}.apk` """, ) print( json.dumps( { "tag_name": version_tag, "name": f"Komikku {version_tag}", "body": body, "draft": True, "prerelease": False, }, ), ) PY status="$(curl -sS -o "$response_file" -w '%{http_code}' \ -H "Authorization: token ${FORGEJO_TOKEN}" \ "${api_base}/tags/${VERSION_TAG}")" if [ "$status" = "200" ]; then release_id="$(python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' < "$response_file")" curl -fsS \ -X PATCH \ -H "Authorization: token ${FORGEJO_TOKEN}" \ -H 'Content-Type: application/json' \ --data @"$payload_file" \ "${api_base}/${release_id}" \ > "$response_file" elif [ "$status" = "404" ]; then curl -fsS \ -X POST \ -H "Authorization: token ${FORGEJO_TOKEN}" \ -H 'Content-Type: application/json' \ --data @"$payload_file" \ "${api_base}" \ > "$response_file" release_id="$(python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' < "$response_file")" else cat "$response_file" exit 1 fi echo "RELEASE_ID=$release_id" >> "$GITHUB_OUTPUT" - name: Upload release assets shell: bash env: FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_ID: ${{ steps.release.outputs.RELEASE_ID }} run: | set -euo pipefail release_api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" assets_file="$(mktemp)" curl -fsS \ -H "Authorization: token ${FORGEJO_TOKEN}" \ "${release_api}/assets" \ > "$assets_file" for file in Komikku-*.apk Komikku-mapping-*.zip; do [ -e "$file" ] || continue asset_name="$(basename "$file")" asset_id="$( python3 -c 'import json, sys; assets = json.load(open(sys.argv[1], encoding="utf-8")); name = sys.argv[2]; print(next((str(asset["id"]) for asset in assets if asset.get("name") == name), ""))' \ "$assets_file" \ "$asset_name" )" if [ -n "$asset_id" ]; then curl -fsS \ -X DELETE \ -H "Authorization: token ${FORGEJO_TOKEN}" \ "${release_api}/assets/${asset_id}" \ > /dev/null fi encoded_name="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$asset_name")" curl -fsS \ -X POST \ -H "Authorization: token ${FORGEJO_TOKEN}" \ -H 'Content-Type: application/octet-stream' \ --data-binary @"$file" \ "${release_api}/assets?name=${encoded_name}" \ > /dev/null done