Some checks failed
Forgejo Release Builder / release (push) Failing after 3s
Accept raw JSON, quoted JSON, or base64 JSON for secret payloads and emit valid canonical JSON files for Gradle processing.
346 lines
12 KiB
YAML
Executable file
346 lines
12 KiB
YAML
Executable file
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<<EOF'
|
|
printf '%s\n' "$commit_logs"
|
|
echo 'EOF'
|
|
} >> "$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
|