Files
jellytau/.gitea/workflows/build-test-apk.yml
T
dtourolle 10d77f1380
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 29m49s
🏗️ Build and Test JellyTau / Supply Chain (push) Successful in 57s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 9m25s
Traceability Validation / Check Requirement Traces (push) Successful in 17s
🏗️ Build and Test JellyTau / Android Compile Check (push) Failing after 51s
ci: publish a test APK as a pre-release for outside testers
Gitea artifacts need an account with read access to download, which makes
them useless for handing a build to someone outside the project -- the
actual reason a test APK gets built in the first place.

An optional publish input attaches the APK to a pre-release instead,
whose assets are a plain public URL on a public repo. No merge to master,
no MR, no version tag, and the tester needs no account.

Safe from a feature branch on two counts. The tag is test-<branch> rather
than v*, and only v* triggers build-release.yml, so nothing else reacts
to it. And it cannot reach existing users: the desktop updater reads a
static latest.json from the updater branch, not the release list.

Re-dispatching the same branch replaces the APK on the existing
pre-release rather than accumulating one release per attempt.
2026-08-30 20:06:40 +02:00

267 lines
11 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: '📱 Test APK'
# An installable APK from any branch, on demand, without cutting a release.
#
# Why this exists separately from build-release.yml: that workflow is tag-driven,
# builds Linux + Windows + Android and then *creates a release*, which is not
# what you want from a feature branch. This builds one Android APK from whatever
# ref you dispatch it on and hands it back as an artifact.
#
# Deliberately `workflow_dispatch` only — no push trigger. The runner has a
# single slot shared with two other projects, so a build on every feature-branch
# commit would starve everything else. Dispatch it when you actually want to
# install something.
#
# Both variants install as com.dtourolle.jellytau.debug ("JellyTau Debug"),
# side by side with a real install and with their own data directory. Neither
# needs the release signing key.
#
# Getting the APK to somebody else: Gitea artifacts need an account with read
# access to download, so `publish: true` also attaches the APK to a pre-release
# whose assets are a plain public URL. That is the only way an outside tester
# gets the file without being given an account.
on:
workflow_dispatch:
inputs:
variant:
description: 'Which build to produce'
required: true
default: 'side-by-side-release'
type: choice
options:
# R8-minified, exactly what ships, in the debug slot. Use this unless
# you need stack traces: R8 stripping JNI-loaded classes has broken
# release APKs here before, and a plain debug build cannot catch it.
- side-by-side-release
# Unminified. Faster, readable stack traces, but does not exercise
# minification at all.
- debug
abi:
description: 'Target ABI'
required: true
default: 'aarch64'
type: choice
options:
- aarch64
- armv7
- x86_64
publish:
description: 'Also publish as a pre-release, for testers with no Gitea account'
required: false
default: false
type: boolean
concurrency:
# One test build at a time; a newer dispatch supersedes an in-flight one.
group: build-test-apk
cancel-in-progress: true
env:
# Incremental state is never reused between CI runs -- pure disk cost.
CARGO_INCREMENTAL: 0
jobs:
build:
name: Build test APK (${{ inputs.variant }}, ${{ inputs.abi }})
runs-on: linux/amd64
container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
env:
ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk
ANDROID_NDK_HOME: /opt/android-sdk/ndk/27.0.11902837
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# set-version.sh derives a dev version from `git describe --tags`, so
# the tags have to be here. A shallow checkout yields 0.0.0.
fetch-depth: 0
- name: Cache Rust dependencies
uses: actions/cache@v3
with:
# Registry only -- never src-tauri/target. Same reasoning (and the
# same key) as every other job: that directory is ~16 GB and caching
# it filled the runner's 74 GB disk. Sharing the key means this
# workflow restores what the others saved rather than adding a
# fourth copy of the registry.
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-
- name: Cache Node dependencies
uses: actions/cache@v3
with:
path: |
~/.bun/install/cache
node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
# Before `android init`: it derives the generated project (including the
# initial versionCode) from tauri.conf.json.
- name: Stamp a dev version
run: ./scripts/set-version.sh
- name: Initialize Android project
run: bun run tauri android init
# Again after init: tauri.properties only exists now, and its
# autogenerated versionCode is neither large enough nor monotonic against
# the 1000 floor already shipped. On a branch this derives from
# `git describe`, so a test APK always sorts above the last release.
- name: Pin a monotonic Android versionCode
run: ./scripts/set-version.sh
# Built through the same script used locally, rather than a hand-rolled
# gradle/tauri invocation. That is what keeps CI and a developer's machine
# producing the same thing -- and the script asserts the applicationId the
# APK actually carries, which has silently regressed before.
- name: Build APK
run: |
if [ "${{ inputs.variant }}" = "side-by-side-release" ]; then
./scripts/build-android.sh release --debug --abi "${{ inputs.abi }}"
else
./scripts/build-android.sh debug --abi "${{ inputs.abi }}"
fi
- name: Collect APK
id: collect
run: |
mkdir -p dist/test-apk
if [ "${{ inputs.variant }}" = "side-by-side-release" ]; then
PATTERN='*-release.apk'
else
PATTERN='*-debug.apk'
fi
APK=$(find src-tauri/gen/android/app/build/outputs/apk -name "$PATTERN" | head -1)
if [ -z "$APK" ]; then
echo "❌ No APK produced for variant ${{ inputs.variant }}"
find src-tauri/gen/android/app/build/outputs/apk -name '*.apk' || true
exit 1
fi
REF_NAME=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '-')
OUT="dist/test-apk/jellytau-${REF_NAME}-${GITHUB_SHA::8}-${{ inputs.variant }}.apk"
cp "$APK" "$OUT"
# Report what the thing actually is, not what it was meant to be.
APKSIGNER=$(find "$ANDROID_SDK_ROOT/build-tools" -name apksigner | sort -V | tail -1)
"$APKSIGNER" verify --print-certs "$OUT" || echo "⚠️ Could not verify signature"
{
echo "### 📱 Test APK"
echo ""
echo "| | |"
echo "|---|---|"
echo "| Branch | \`${GITHUB_REF#refs/heads/}\` |"
echo "| Commit | \`${GITHUB_SHA::8}\` |"
echo "| Variant | \`${{ inputs.variant }}\` |"
echo "| ABI | \`${{ inputs.abi }}\` |"
echo "| Size | $(du -h "$OUT" | cut -f1) |"
echo "| SHA256 | \`$(sha256sum "$OUT" | cut -d' ' -f1)\` |"
echo ""
echo "Installs as \`com.dtourolle.jellytau.debug\` — side by side with a real"
echo "install, with its own data directory. Download the artifact, then:"
echo ""
echo '```'
echo "adb install -r $(basename "$OUT")"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
ls -lah dist/test-apk/
# Deliberately NOT tagged `v*`: that pattern triggers build-release.yml,
# which would run the whole three-platform release matrix and publish a
# real release off a feature branch. The tag here is derived from the
# branch name and carries no version, so nothing else reacts to it.
#
# This also cannot reach existing users. The desktop updater reads a
# static latest.json from the `updater` branch, not the release list, so a
# pre-release published here is invisible to anyone without the link.
- name: Publish as a pre-release
if: ${{ inputs.publish }}
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
AUTO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
command -v jq >/dev/null || { echo "❌ jq is required on the runner"; exit 1; }
API="${GITHUB_SERVER_URL}/api/v1"
REPO="${GITHUB_REPOSITORY}"
TOKEN="${GITEA_TOKEN:-$AUTO_TOKEN}"
BRANCH="${GITHUB_REF#refs/heads/}"
TAG="test-$(echo "$BRANCH" | tr '/' '-')"
# printf, not a heredoc: inside a YAML block scalar every line is
# indented, and a heredoc terminator has to sit at column 0.
BODY=$(printf '%s\n' \
"Test build of \`$BRANCH\` at \`${GITHUB_SHA::8}\` — **not a release**." \
"" \
"Installs as **JellyTau Debug** (\`com.dtourolle.jellytau.debug\`), alongside a" \
"normal install and with its own separate data. Uninstalling it does not touch" \
"the real app." \
"" \
"Variant: \`${{ inputs.variant }}\` · ABI: \`${{ inputs.abi }}\`" \
"" \
"Android will warn about installing from an unknown source; that is expected" \
"for a build signed with a debug key rather than the store key.")
PAYLOAD=$(jq -n \
--arg tag "$TAG" \
--arg name "Test build: $BRANCH" \
--arg body "$BODY" \
--arg target "$GITHUB_SHA" \
'{tag_name:$tag, target_commitish:$target, name:$name, body:$body, draft:false, prerelease:true}')
HTTP=$(curl -sS -o resp.json -w '%{http_code}' -X POST "$API/repos/$REPO/releases" \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD")
if [ "$HTTP" = "201" ]; then
RELEASE_ID=$(jq -r '.id' resp.json)
elif [ "$HTTP" = "409" ]; then
# Re-dispatching for the same branch replaces the previous APK rather
# than accumulating one release per attempt.
echo "️ Pre-release $TAG exists; reusing it"
RELEASE_ID=$(curl -fsS "$API/repos/$REPO/releases/tags/$TAG" \
-H "Authorization: token $TOKEN" | jq -r '.id')
for id in $(curl -fsS "$API/repos/$REPO/releases/$RELEASE_ID/assets" \
-H "Authorization: token $TOKEN" | jq -r '.[].id'); do
curl -fsS -X DELETE "$API/repos/$REPO/releases/$RELEASE_ID/assets/$id" \
-H "Authorization: token $TOKEN" >/dev/null
done
else
echo "❌ Failed to create pre-release (HTTP $HTTP):"; cat resp.json; exit 1
fi
for f in dist/test-apk/*.apk; do
echo "⬆️ $(basename "$f")"
curl -fsS -X POST \
"$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$(basename "$f")" \
-H "Authorization: token $TOKEN" -F "attachment=@$f" >/dev/null
done
{
echo ""
echo "**Published:** ${GITHUB_SERVER_URL}/${REPO}/releases/tag/${TAG}"
echo ""
echo "Public link — no Gitea account needed. Delete the release when testing is done."
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: jellytau-test-apk
path: dist/test-apk/
retention-days: 7