Skip to content

chore(release): v1.2.3 #44

chore(release): v1.2.3

chore(release): v1.2.3 #44

Workflow file for this run

---
name: Release APK
"on":
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag_name:
description: "Existing v* tag to release"
required: true
type: string
prerelease:
description: "Mark the GitHub Release as a prerelease"
required: false
default: false
type: boolean
permissions:
contents: write
jobs:
resolve-release:
name: Resolve release metadata
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release_tag.outputs.tag }}
version_name: ${{ steps.release_tag.outputs.version_name }}
version_code: ${{ steps.release_tag.outputs.version_code }}
steps:
- name: Resolve release tag
id: release_tag
shell: bash
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
input_tag="${{ inputs.tag_name }}"
else
input_tag="${GITHUB_REF_NAME}"
fi
version_name="${input_tag#v}"
if [[ ! "${version_name}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "Release tag must be semantic, for example v1.2.3. Got: ${input_tag}" >&2
exit 1
fi
tag="v${version_name}"
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"
patch="${BASH_REMATCH[3]}"
version_code=$((major * 10000 + minor * 100 + patch))
{
echo "tag=${tag}"
echo "version_name=${version_name}"
echo "version_code=${version_code}"
} >> "${GITHUB_OUTPUT}"
release-apk:
name: Build signed release APK
runs-on: ubuntu-latest
needs: resolve-release
timeout-minutes: 90
env:
CMAKE_VERSION: "3.22.1"
NDK_VERSION: "29.0.14206865"
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.resolve-release.outputs.tag }}
fetch-depth: 1
- name: Initialize required submodules
shell: bash
run: |
set -euo pipefail
git submodule update --init --recursive third_party/hoshidicts-kotlin-bridge
git submodule status --recursive
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Set up Android SDK
uses: android-actions/setup-android@v3
- name: Install Android build tools
shell: bash
run: |
set -euo pipefail
sdkmanager \
"platforms;android-36.1" \
"build-tools;36.0.0" \
"cmake;${CMAKE_VERSION}" \
"ndk;${NDK_VERSION}"
ndk_home="${ANDROID_HOME}/ndk/${NDK_VERSION}"
echo "ANDROID_NDK_HOME=${ndk_home}" >> "${GITHUB_ENV}"
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android
- name: Restore Cargo cache
uses: Swatinem/rust-cache@v2
with:
workspaces: app/src/main/rust/hoshiepub
- name: Install cargo-ndk
shell: bash
run: |
set -euo pipefail
if ! command -v cargo-ndk >/dev/null 2>&1; then
cargo install cargo-ndk --locked
fi
- name: Restore Gradle cache
uses: gradle/actions/setup-gradle@v4
- name: Decode release keystore
shell: bash
run: |
set -euo pipefail
if [[ -z "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" ]]; then
echo "ANDROID_KEYSTORE_BASE64 secret is required." >&2
exit 1
fi
keystore="${RUNNER_TEMP}/hoshi-release-keystore.jks"
printf '%s' "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" \
| base64 --decode > "${keystore}"
echo "ANDROID_KEYSTORE_FILE=${keystore}" >> "${GITHUB_ENV}"
- name: Warn if Gradle defaults differ from tag
shell: bash
run: |
set -euo pipefail
version_name="${{ needs.resolve-release.outputs.version_name }}"
version_code="${{ needs.resolve-release.outputs.version_code }}"
actual_name="$(sed -n 's/^[[:space:]]*versionName[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' app/build.gradle.kts | head -n 1)"
actual_code="$(sed -n 's/^[[:space:]]*versionCode[[:space:]]*=[[:space:]]*\([0-9][0-9]*\).*/\1/p' app/build.gradle.kts | head -n 1)"
if [[ "${actual_name}" != "${version_name}" ]]; then
echo "::warning::app/build.gradle.kts versionName=${actual_name}, expected ${version_name}"
fi
if [[ "${actual_code}" != "${version_code}" ]]; then
echo "::warning::app/build.gradle.kts versionCode=${actual_code}, expected ${version_code}"
fi
- name: Prepare split ABI release build
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
path = Path("app/build.gradle.kts")
text = path.read_text()
text = text.replace(
'val releaseVersionCode = providers.gradleProperty("releaseVersionCode").orNull?.toIntOrNull()\n',
'val releaseVersionCode = providers.gradleProperty("releaseVersionCode").orNull?.toIntOrNull()\n'
'val releaseAbi = providers.gradleProperty("releaseAbi").orNull\n',
)
text = text.replace(
' ndk {\n'
' abiFilters += listOf("arm64-v8a")\n'
' }\n',
' ndk {\n'
' abiFilters += listOf(releaseAbi ?: "arm64-v8a")\n'
' }\n',
)
text = text.replace(
' outputs.file(rustReleaseJniLibsDir.resolve("arm64-v8a/libhoshiepub.so"))\n',
' outputs.file(rustReleaseJniLibsDir.resolve("${releaseAbi ?: "arm64-v8a"}/libhoshiepub.so"))\n',
)
text = text.replace(
' "-t",\n'
' "arm64-v8a",\n'
' "-o",\n'
' rustReleaseJniLibsDir.absolutePath,\n'
' "build",\n'
' "--lib",\n'
' "--release",\n'
' )\n'
'}\n'
'\n'
'tasks.named("preBuild")',
' "-t",\n'
' releaseAbi ?: "arm64-v8a",\n'
' "-o",\n'
' rustReleaseJniLibsDir.absolutePath,\n'
' "build",\n'
' "--lib",\n'
' "--release",\n'
' )\n'
'}\n'
'\n'
'tasks.named("preBuild")',
)
path.write_text(text)
PY
- name: Build and check split release APKs
id: assets
shell: bash
run: |
set -euo pipefail
tag="${{ needs.resolve-release.outputs.tag }}"
version_name="${{ needs.resolve-release.outputs.version_name }}"
version_code="${{ needs.resolve-release.outputs.version_code }}"
build_tools="${ANDROID_HOME}/build-tools/36.0.0"
apk_assets=()
for abi in arm64-v8a armeabi-v7a; do
rm -rf app/build/jniLibs/release app/build/outputs/apk/release
./gradlew --no-daemon clean lintVitalRelease assembleRelease \
-PreleaseAbi="${abi}" \
-PreleaseVersionName="${version_name}" \
-PreleaseVersionCode="${version_code}"
python3 - <<PY
import json
from pathlib import Path
expected_name = "${version_name}"
expected_code = int("${version_code}")
metadata_path = Path("app/build/outputs/apk/release/output-metadata.json")
metadata = json.loads(metadata_path.read_text())
element = metadata["elements"][0]
actual_name = element["versionName"]
actual_code = element["versionCode"]
if actual_name != expected_name or actual_code != expected_code:
raise SystemExit(
f"Release APK metadata mismatch for ${abi}: "
f"versionName={actual_name}, versionCode={actual_code}; "
f"expected versionName={expected_name}, versionCode={expected_code}"
)
PY
apk_source="app/build/outputs/apk/release/app-release.apk"
apk_asset="Hoshi-Reader-${tag}-${abi}.apk"
if [[ ! -f "${apk_source}" ]]; then
echo "Expected signed APK was not found at ${apk_source}" >&2
find app/build/outputs/apk -type f -name '*.apk' -print >&2 || true
exit 1
fi
mapfile -t packaged_abis < <(zipinfo -1 "${apk_source}" \
| sed -n 's#^lib/\([^/]*\)/.*\.so$#\1#p' \
| sort -u)
if [[ "${#packaged_abis[@]}" -ne 1 || "${packaged_abis[0]}" != "${abi}" ]]; then
echo "Expected only ${abi} native libraries, found: ${packaged_abis[*]:-none}" >&2
zipinfo -1 "${apk_source}" | grep '^lib/' >&2 || true
exit 1
fi
zipinfo -1 "${apk_source}" | grep -qx "lib/${abi}/libhoshiepub.so"
"${build_tools}/apksigner" verify --print-certs "${apk_source}"
cp "${apk_source}" "${apk_asset}"
apk_assets+=("${apk_asset}")
done
echo "apk_assets=${apk_assets[*]}" >> "${GITHUB_OUTPUT}"
- name: Generate release notes from CHANGELOG
shell: bash
run: |
set -euo pipefail
version_name="${{ needs.resolve-release.outputs.version_name }}"
changelog="docs/CHANGELOG.md"
changelog_section="$(awk -v v="${version_name}" '
$0 ~ "^## \\[(v?" v ")\\]" { found = 1; next }
found && $0 ~ "^## \\[" { exit }
found { print }
' "${changelog}")"
if [[ -z "${changelog_section//[[:space:]]/}" ]]; then
changelog_section="$(awk '
$0 ~ "^## \\[Unreleased\\]" { found = 1; next }
found && $0 ~ "^## \\[" { exit }
found { print }
' "${changelog}")"
fi
if [[ -n "${changelog_section//[[:space:]]/}" ]]; then
printf '%s\n' "${changelog_section}" > release_notes.md
else
{
echo "Release ${{ needs.resolve-release.outputs.tag }}"
echo
echo "No matching section found in docs/CHANGELOG.md for ${version_name}."
} > release_notes.md
fi
- name: Publish GitHub Release
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
tag="${{ needs.resolve-release.outputs.tag }}"
apk_assets="${{ steps.assets.outputs.apk_assets }}"
prerelease_flag=()
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" \
&& "${{ inputs.prerelease }}" == "true" ]]; then
prerelease_flag=(--prerelease)
fi
if gh release view "${tag}" >/dev/null 2>&1; then
gh release edit "${tag}" --notes-file release_notes.md
if [[ ${#prerelease_flag[@]} -gt 0 ]]; then
gh release edit "${tag}" "${prerelease_flag[@]}"
fi
else
gh release create "${tag}" \
--title "Hoshi Reader ${tag}" \
--notes-file release_notes.md \
"${prerelease_flag[@]}"
fi
gh release upload "${tag}" ${apk_assets} LICENSE --clobber