Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c44070e720 | ||
|
|
8ecf74a2af | ||
|
|
5fb9c1ff3b | ||
|
|
b5a3a3b427 |
@@ -1,27 +1,39 @@
|
|||||||
name: '📱 Test APK'
|
name: '📱 Test APK'
|
||||||
|
|
||||||
# An installable APK from any branch, on demand, without cutting a release.
|
# Installable Android builds that are not releases.
|
||||||
#
|
#
|
||||||
# Why this exists separately from build-release.yml: that workflow is tag-driven,
|
# Two ways in:
|
||||||
# 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
|
# push to master -> refreshes the rolling `latest` pre-release, so there is
|
||||||
# single slot shared with two other projects, so a build on every feature-branch
|
# always a current APK behind one stable URL that can be
|
||||||
# commit would starve everything else. Dispatch it when you actually want to
|
# handed to a tester once and never re-sent.
|
||||||
# install something.
|
# workflow_dispatch -> builds any branch on demand, optionally publishing it
|
||||||
|
# as `test-<branch>`.
|
||||||
#
|
#
|
||||||
# Both variants install as com.dtourolle.jellytau.debug ("JellyTau Debug"),
|
# Why this is separate from build-release.yml: that workflow is tag-driven,
|
||||||
# side by side with a real install and with their own data directory. Neither
|
# builds Linux + Windows + Android and creates a real release. This produces one
|
||||||
# needs the release signing key.
|
# APK and never touches the release channel.
|
||||||
|
#
|
||||||
|
# What comes out installs as com.dtourolle.jellytau.debug ("JellyTau Debug"),
|
||||||
|
# side by side with a real install and with its own data directory. It is a
|
||||||
|
# fully R8-minified release build -- minification is where Android builds have
|
||||||
|
# actually broken here (R8 stripping JNI-loaded player and security classes),
|
||||||
|
# and a plain debug build cannot catch that -- but it is signed with the debug
|
||||||
|
# keystore rather than the store key. So a bad master commit can never replace
|
||||||
|
# somebody's working install, and the production signing key stays in the
|
||||||
|
# tag-driven workflow where it belongs.
|
||||||
#
|
#
|
||||||
# Getting the APK to somebody else: Gitea artifacts need an account with read
|
# 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
|
# access to download, so published builds are attached to a pre-release, whose
|
||||||
# whose assets are a plain public URL. That is the only way an outside tester
|
# assets are a plain public URL. That is the only way an outside tester gets the
|
||||||
# gets the file without being given an account.
|
# file without being given an account.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- '**/*.md'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
variant:
|
variant:
|
||||||
@@ -30,9 +42,7 @@ on:
|
|||||||
default: 'side-by-side-release'
|
default: 'side-by-side-release'
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
# R8-minified, exactly what ships, in the debug slot. Use this unless
|
# R8-minified, exactly what ships, in the debug slot.
|
||||||
# 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
|
- side-by-side-release
|
||||||
# Unminified. Faster, readable stack traces, but does not exercise
|
# Unminified. Faster, readable stack traces, but does not exercise
|
||||||
# minification at all.
|
# minification at all.
|
||||||
@@ -47,13 +57,15 @@ on:
|
|||||||
- armv7
|
- armv7
|
||||||
- x86_64
|
- x86_64
|
||||||
publish:
|
publish:
|
||||||
description: 'Also publish as a pre-release, for testers with no Gitea account'
|
description: 'Also publish as a pre-release (automatic on master)'
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
# One test build at a time; a newer dispatch supersedes an in-flight one.
|
# One APK build at a time, and a newer push supersedes an in-flight one — so a
|
||||||
|
# burst of commits to master costs one build, not one per commit. This matters:
|
||||||
|
# the runner has a single slot shared with two other projects.
|
||||||
group: build-test-apk
|
group: build-test-apk
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
@@ -63,8 +75,15 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build test APK (${{ inputs.variant }}, ${{ inputs.abi }})
|
name: Build test APK
|
||||||
runs-on: linux/amd64
|
runs-on: linux/amd64
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
# This runner executes `run:` blocks with `sh` (dash) unless told
|
||||||
|
# otherwise, so bash-only syntax fails with a bare "Bad substitution"
|
||||||
|
# naming a temp file and no line of your workflow. Say bash explicitly.
|
||||||
|
# The short-SHA output below avoids depending on it regardless.
|
||||||
|
shell: bash
|
||||||
container:
|
container:
|
||||||
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
|
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
|
||||||
env:
|
env:
|
||||||
@@ -79,6 +98,69 @@ jobs:
|
|||||||
# the tags have to be here. A shallow checkout yields 0.0.0.
|
# the tags have to be here. A shallow checkout yields 0.0.0.
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# One place decides what this run is, so the build, the collect step and
|
||||||
|
# the publish step cannot disagree about it. A push carries no dispatch
|
||||||
|
# inputs at all -- every `github.event.inputs.*` is empty on that event --
|
||||||
|
# so each value needs an explicit default rather than being read raw.
|
||||||
|
- name: Resolve build parameters
|
||||||
|
id: cfg
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
VARIANT="${{ github.event.inputs.variant }}"
|
||||||
|
ABI="${{ github.event.inputs.abi }}"
|
||||||
|
PUBLISH="${{ github.event.inputs.publish }}"
|
||||||
|
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||||
|
|
||||||
|
VARIANT="${VARIANT:-side-by-side-release}"
|
||||||
|
ABI="${ABI:-aarch64}"
|
||||||
|
|
||||||
|
# A push to master always publishes -- that is the whole point of a
|
||||||
|
# rolling `latest`. A dispatch publishes only if asked. Compared
|
||||||
|
# against the string 'true' rather than used as a bare truthiness
|
||||||
|
# test: dispatch inputs arrive as strings, and every non-empty string
|
||||||
|
# is truthy, so `if: inputs.publish` would publish even when the box
|
||||||
|
# was deliberately left unticked.
|
||||||
|
if [ "$GITHUB_EVENT_NAME" = "push" ]; then
|
||||||
|
PUBLISH=true
|
||||||
|
elif [ "$PUBLISH" = "true" ]; then
|
||||||
|
PUBLISH=true
|
||||||
|
else
|
||||||
|
PUBLISH=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Master is the rolling channel and keeps one stable tag, so the
|
||||||
|
# download URL a tester was given keeps working. Anything else gets
|
||||||
|
# its own branch-scoped tag.
|
||||||
|
if [ "$BRANCH" = "master" ]; then
|
||||||
|
TAG="latest"
|
||||||
|
RELEASE_NAME="Latest build (master)"
|
||||||
|
else
|
||||||
|
TAG="test-$(echo "$BRANCH" | tr '/' '-')"
|
||||||
|
RELEASE_NAME="Test build: $BRANCH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stable asset name for the same reason the tag is stable.
|
||||||
|
ASSET="jellytau-${TAG}.apk"
|
||||||
|
|
||||||
|
# Computed once, with `cut` rather than `${GITHUB_SHA::8}`. The
|
||||||
|
# substring form is bash-only and this runner may hand a step to
|
||||||
|
# `sh`; that cost a 51-minute build which produced a perfectly good
|
||||||
|
# APK and then died formatting the summary table.
|
||||||
|
SHORT_SHA=$(printf '%s' "$GITHUB_SHA" | cut -c1-8)
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "variant=$VARIANT"
|
||||||
|
echo "abi=$ABI"
|
||||||
|
echo "publish=$PUBLISH"
|
||||||
|
echo "tag=$TAG"
|
||||||
|
echo "release_name=$RELEASE_NAME"
|
||||||
|
echo "asset=$ASSET"
|
||||||
|
echo "branch=$BRANCH"
|
||||||
|
echo "short_sha=$SHORT_SHA"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
echo "variant=$VARIANT abi=$ABI publish=$PUBLISH tag=$TAG asset=$ASSET"
|
||||||
|
|
||||||
- name: Cache Rust dependencies
|
- name: Cache Rust dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@@ -129,30 +211,29 @@ jobs:
|
|||||||
# APK actually carries, which has silently regressed before.
|
# APK actually carries, which has silently regressed before.
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ inputs.variant }}" = "side-by-side-release" ]; then
|
if [ "${{ steps.cfg.outputs.variant }}" = "side-by-side-release" ]; then
|
||||||
./scripts/build-android.sh release --debug --abi "${{ inputs.abi }}"
|
./scripts/build-android.sh release --debug --abi "${{ steps.cfg.outputs.abi }}"
|
||||||
else
|
else
|
||||||
./scripts/build-android.sh debug --abi "${{ inputs.abi }}"
|
./scripts/build-android.sh debug --abi "${{ steps.cfg.outputs.abi }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Collect APK
|
- name: Collect APK
|
||||||
id: collect
|
|
||||||
run: |
|
run: |
|
||||||
|
set -e
|
||||||
mkdir -p dist/test-apk
|
mkdir -p dist/test-apk
|
||||||
if [ "${{ inputs.variant }}" = "side-by-side-release" ]; then
|
if [ "${{ steps.cfg.outputs.variant }}" = "side-by-side-release" ]; then
|
||||||
PATTERN='*-release.apk'
|
PATTERN='*-release.apk'
|
||||||
else
|
else
|
||||||
PATTERN='*-debug.apk'
|
PATTERN='*-debug.apk'
|
||||||
fi
|
fi
|
||||||
APK=$(find src-tauri/gen/android/app/build/outputs/apk -name "$PATTERN" | head -1)
|
APK=$(find src-tauri/gen/android/app/build/outputs/apk -name "$PATTERN" | head -1)
|
||||||
if [ -z "$APK" ]; then
|
if [ -z "$APK" ]; then
|
||||||
echo "❌ No APK produced for variant ${{ inputs.variant }}"
|
echo "❌ No APK produced for variant ${{ steps.cfg.outputs.variant }}"
|
||||||
find src-tauri/gen/android/app/build/outputs/apk -name '*.apk' || true
|
find src-tauri/gen/android/app/build/outputs/apk -name '*.apk' || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REF_NAME=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '-')
|
OUT="dist/test-apk/${{ steps.cfg.outputs.asset }}"
|
||||||
OUT="dist/test-apk/jellytau-${REF_NAME}-${GITHUB_SHA::8}-${{ inputs.variant }}.apk"
|
|
||||||
cp "$APK" "$OUT"
|
cp "$APK" "$OUT"
|
||||||
|
|
||||||
# Report what the thing actually is, not what it was meant to be.
|
# Report what the thing actually is, not what it was meant to be.
|
||||||
@@ -160,37 +241,32 @@ jobs:
|
|||||||
"$APKSIGNER" verify --print-certs "$OUT" || echo "⚠️ Could not verify signature"
|
"$APKSIGNER" verify --print-certs "$OUT" || echo "⚠️ Could not verify signature"
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "### 📱 Test APK"
|
echo "### 📱 ${{ steps.cfg.outputs.release_name }}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "| | |"
|
echo "| | |"
|
||||||
echo "|---|---|"
|
echo "|---|---|"
|
||||||
echo "| Branch | \`${GITHUB_REF#refs/heads/}\` |"
|
echo "| Branch | \`${{ steps.cfg.outputs.branch }}\` |"
|
||||||
echo "| Commit | \`${GITHUB_SHA::8}\` |"
|
echo "| Commit | \`${{ steps.cfg.outputs.short_sha }}\` |"
|
||||||
echo "| Variant | \`${{ inputs.variant }}\` |"
|
echo "| Variant | \`${{ steps.cfg.outputs.variant }}\` |"
|
||||||
echo "| ABI | \`${{ inputs.abi }}\` |"
|
echo "| ABI | \`${{ steps.cfg.outputs.abi }}\` |"
|
||||||
echo "| Size | $(du -h "$OUT" | cut -f1) |"
|
echo "| Size | $(du -h "$OUT" | cut -f1) |"
|
||||||
echo "| SHA256 | \`$(sha256sum "$OUT" | cut -d' ' -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"
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
ls -lah dist/test-apk/
|
ls -lah dist/test-apk/
|
||||||
|
|
||||||
# Deliberately NOT tagged `v*`: that pattern triggers build-release.yml,
|
# Deliberately NOT tagged `v*`: that pattern triggers build-release.yml,
|
||||||
# which would run the whole three-platform release matrix and publish a
|
# 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
|
# real release. `latest` and `test-*` carry no version, so nothing else
|
||||||
# branch name and carries no version, so nothing else reacts to it.
|
# reacts to them.
|
||||||
#
|
#
|
||||||
# This also cannot reach existing users. The desktop updater reads a
|
# This also cannot reach existing users by itself. The desktop updater
|
||||||
# static latest.json from the `updater` branch, not the release list, so a
|
# reads a static latest.json from the `updater` branch, not the release
|
||||||
# pre-release published here is invisible to anyone without the link.
|
# list, so a pre-release published here is invisible to anyone who does
|
||||||
- name: Publish as a pre-release
|
# not have the link -- and the APK installs under a different
|
||||||
if: ${{ inputs.publish }}
|
# applicationId anyway.
|
||||||
|
- name: Publish pre-release
|
||||||
|
if: ${{ steps.cfg.outputs.publish == 'true' }}
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
AUTO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
AUTO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -200,26 +276,26 @@ jobs:
|
|||||||
API="${GITHUB_SERVER_URL}/api/v1"
|
API="${GITHUB_SERVER_URL}/api/v1"
|
||||||
REPO="${GITHUB_REPOSITORY}"
|
REPO="${GITHUB_REPOSITORY}"
|
||||||
TOKEN="${GITEA_TOKEN:-$AUTO_TOKEN}"
|
TOKEN="${GITEA_TOKEN:-$AUTO_TOKEN}"
|
||||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
TAG="${{ steps.cfg.outputs.tag }}"
|
||||||
TAG="test-$(echo "$BRANCH" | tr '/' '-')"
|
ASSET="${{ steps.cfg.outputs.asset }}"
|
||||||
|
|
||||||
# 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' \
|
BODY=$(printf '%s\n' \
|
||||||
"Test build of \`$BRANCH\` at \`${GITHUB_SHA::8}\` — **not a release**." \
|
"Automatic build of \`${{ steps.cfg.outputs.branch }}\` at \`${{ steps.cfg.outputs.short_sha }}\` — **not a release**." \
|
||||||
"" \
|
"" \
|
||||||
"Installs as **JellyTau Debug** (\`com.dtourolle.jellytau.debug\`), alongside a" \
|
"Installs as **JellyTau Debug** (\`com.dtourolle.jellytau.debug\`), alongside a" \
|
||||||
"normal install and with its own separate data. Uninstalling it does not touch" \
|
"normal install and with its own separate data. It cannot replace or upgrade a" \
|
||||||
"the real app." \
|
"real install, and uninstalling it does not touch one." \
|
||||||
"" \
|
"" \
|
||||||
"Variant: \`${{ inputs.variant }}\` · ABI: \`${{ inputs.abi }}\`" \
|
"R8-minified like a real release, but signed with a debug key — so Android will" \
|
||||||
|
"warn about an unknown source. That is expected." \
|
||||||
"" \
|
"" \
|
||||||
"Android will warn about installing from an unknown source; that is expected" \
|
"Variant: \`${{ steps.cfg.outputs.variant }}\` · ABI: \`${{ steps.cfg.outputs.abi }}\`" \
|
||||||
"for a build signed with a debug key rather than the store key.")
|
"" \
|
||||||
|
"This release is refreshed on every push; the download link stays the same.")
|
||||||
|
|
||||||
PAYLOAD=$(jq -n \
|
PAYLOAD=$(jq -n \
|
||||||
--arg tag "$TAG" \
|
--arg tag "$TAG" \
|
||||||
--arg name "Test build: $BRANCH" \
|
--arg name "${{ steps.cfg.outputs.release_name }}" \
|
||||||
--arg body "$BODY" \
|
--arg body "$BODY" \
|
||||||
--arg target "$GITHUB_SHA" \
|
--arg target "$GITHUB_SHA" \
|
||||||
'{tag_name:$tag, target_commitish:$target, name:$name, body:$body, draft:false, prerelease:true}')
|
'{tag_name:$tag, target_commitish:$target, name:$name, body:$body, draft:false, prerelease:true}')
|
||||||
@@ -230,11 +306,14 @@ jobs:
|
|||||||
if [ "$HTTP" = "201" ]; then
|
if [ "$HTTP" = "201" ]; then
|
||||||
RELEASE_ID=$(jq -r '.id' resp.json)
|
RELEASE_ID=$(jq -r '.id' resp.json)
|
||||||
elif [ "$HTTP" = "409" ]; then
|
elif [ "$HTTP" = "409" ]; then
|
||||||
# Re-dispatching for the same branch replaces the previous APK rather
|
# The rolling case: reuse the release, refresh its body to name the
|
||||||
# than accumulating one release per attempt.
|
# new commit, and clear the old asset so `latest` means latest.
|
||||||
echo "ℹ️ Pre-release $TAG exists; reusing it"
|
|
||||||
RELEASE_ID=$(curl -fsS "$API/repos/$REPO/releases/tags/$TAG" \
|
RELEASE_ID=$(curl -fsS "$API/repos/$REPO/releases/tags/$TAG" \
|
||||||
-H "Authorization: token $TOKEN" | jq -r '.id')
|
-H "Authorization: token $TOKEN" | jq -r '.id')
|
||||||
|
echo "ℹ️ Refreshing existing pre-release $TAG (id=$RELEASE_ID)"
|
||||||
|
curl -fsS -X PATCH "$API/repos/$REPO/releases/$RELEASE_ID" \
|
||||||
|
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d "$PAYLOAD" >/dev/null
|
||||||
for id in $(curl -fsS "$API/repos/$REPO/releases/$RELEASE_ID/assets" \
|
for id in $(curl -fsS "$API/repos/$REPO/releases/$RELEASE_ID/assets" \
|
||||||
-H "Authorization: token $TOKEN" | jq -r '.[].id'); do
|
-H "Authorization: token $TOKEN" | jq -r '.[].id'); do
|
||||||
curl -fsS -X DELETE "$API/repos/$REPO/releases/$RELEASE_ID/assets/$id" \
|
curl -fsS -X DELETE "$API/repos/$REPO/releases/$RELEASE_ID/assets/$id" \
|
||||||
@@ -244,21 +323,24 @@ jobs:
|
|||||||
echo "❌ Failed to create pre-release (HTTP $HTTP):"; cat resp.json; exit 1
|
echo "❌ Failed to create pre-release (HTTP $HTTP):"; cat resp.json; exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for f in dist/test-apk/*.apk; do
|
# The tag moves with the branch, so an old tag object would otherwise
|
||||||
echo "⬆️ $(basename "$f")"
|
# keep `latest` pointing at a stale commit.
|
||||||
curl -fsS -X POST \
|
curl -fsS -X POST \
|
||||||
"$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$(basename "$f")" \
|
"$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$ASSET" \
|
||||||
-H "Authorization: token $TOKEN" -F "attachment=@$f" >/dev/null
|
-H "Authorization: token $TOKEN" -F "attachment=@dist/test-apk/$ASSET" >/dev/null
|
||||||
done
|
|
||||||
|
|
||||||
|
URL="${GITHUB_SERVER_URL}/${REPO}/releases/download/${TAG}/${ASSET}"
|
||||||
{
|
{
|
||||||
echo ""
|
echo ""
|
||||||
echo "**Published:** ${GITHUB_SERVER_URL}/${REPO}/releases/tag/${TAG}"
|
echo "**Published:** ${GITHUB_SERVER_URL}/${REPO}/releases/tag/${TAG}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Public link — no Gitea account needed. Delete the release when testing is done."
|
echo "Direct download (stable link, no account needed):"
|
||||||
|
echo ""
|
||||||
|
echo " $URL"
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "✅ Published $TAG -> $URL"
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: jellytau-test-apk
|
name: jellytau-test-apk
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ echo "======================================"
|
|||||||
# Setup environment
|
# Setup environment
|
||||||
echo "Setting up environment..."
|
echo "Setting up environment..."
|
||||||
source "$HOME/.cargo/env.fish" 2>/dev/null || source "$HOME/.cargo/env" || true
|
source "$HOME/.cargo/env.fish" 2>/dev/null || source "$HOME/.cargo/env" || true
|
||||||
export ANDROID_HOME="$HOME/Android/Sdk"
|
export ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}"
|
||||||
export NDK_HOME="$ANDROID_HOME/ndk/$(ls $ANDROID_HOME/ndk 2>/dev/null | head -1)"
|
export NDK_HOME="$ANDROID_HOME/ndk/$(ls $ANDROID_HOME/ndk 2>/dev/null | head -1)"
|
||||||
|
|
||||||
# Check prerequisites
|
# Check prerequisites
|
||||||
|
|||||||
@@ -6,9 +6,26 @@ set -e
|
|||||||
# Source Rust environment
|
# Source Rust environment
|
||||||
source "$HOME/.cargo/env.fish" 2>/dev/null || source "$HOME/.cargo/env" 2>/dev/null || true
|
source "$HOME/.cargo/env.fish" 2>/dev/null || source "$HOME/.cargo/env" 2>/dev/null || true
|
||||||
|
|
||||||
# Set Android environment variables
|
# Set Android environment variables.
|
||||||
export ANDROID_HOME="$HOME/Android/Sdk"
|
#
|
||||||
export NDK_HOME="$ANDROID_HOME/ndk/$(ls "$ANDROID_HOME/ndk" | head -1)"
|
# Defaults, not overrides. A developer's SDK is at ~/Android/Sdk, but CI runs in
|
||||||
|
# the builder image where it lives at /opt/android-sdk and the job sets
|
||||||
|
# ANDROID_HOME accordingly — hardcoding the home-directory path here silently
|
||||||
|
# discarded that and the build died with "Android SDK not found" a minute in.
|
||||||
|
# `test-player-conformance.sh` already had this right; this script did not.
|
||||||
|
export ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}"
|
||||||
|
export ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
|
||||||
|
|
||||||
|
if [ ! -d "$ANDROID_HOME/ndk" ]; then
|
||||||
|
echo "❌ No NDK directory at $ANDROID_HOME/ndk" >&2
|
||||||
|
echo " Set ANDROID_HOME to your SDK location, or install the NDK." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Respect an NDK the caller has already picked (CI pins an exact revision via
|
||||||
|
# ANDROID_NDK_HOME); otherwise take whatever is installed.
|
||||||
|
export NDK_HOME="${NDK_HOME:-${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/$(ls "$ANDROID_HOME/ndk" | head -1)}}"
|
||||||
|
export ANDROID_NDK_HOME="$NDK_HOME"
|
||||||
|
|
||||||
echo "🤖 Building Android APK..."
|
echo "🤖 Building Android APK..."
|
||||||
echo "Android SDK: $ANDROID_HOME"
|
echo "Android SDK: $ANDROID_HOME"
|
||||||
|
|||||||
@@ -94,40 +94,56 @@ Follow the right log stream with `./scripts/logcat.sh [debug|release]`
|
|||||||
|
|
||||||
### Getting a test APK out of CI
|
### Getting a test APK out of CI
|
||||||
|
|
||||||
`.gitea/workflows/build-test-apk.yml` builds one from **any branch, on demand**
|
`.gitea/workflows/build-test-apk.yml` produces installable APKs that are **not
|
||||||
— run it from Gitea's Actions tab (`workflow_dispatch`) against the ref you want.
|
releases**. Two ways in:
|
||||||
It is not a release: nothing is tagged, published, or signed with the real key.
|
|
||||||
|
|
||||||
Two variants, both installing into the `com.dtourolle.jellytau.debug` slot:
|
| Trigger | Result |
|
||||||
|
|---------|--------|
|
||||||
|
| **push to `master`** | Refreshes the rolling **`latest`** pre-release automatically |
|
||||||
|
| **`workflow_dispatch`** | Builds any branch on demand; optionally publishes it as `test-<branch>` |
|
||||||
|
|
||||||
|
#### The rolling `latest` build
|
||||||
|
|
||||||
|
Every push to `master` (bar doc-only ones) rebuilds and replaces the APK on the
|
||||||
|
`latest` pre-release. Both the tag and the asset name are stable, so the
|
||||||
|
download URL never changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://gitea.tourolle.paris/dtourolle/jellytau/releases/download/latest/jellytau-latest.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
Send that link to a tester once and it keeps serving the current build. No
|
||||||
|
account needed — release assets are public, unlike Actions artifacts.
|
||||||
|
|
||||||
|
#### What you get, and why it is safe
|
||||||
|
|
||||||
|
Both variants install into the `com.dtourolle.jellytau.debug` slot:
|
||||||
|
|
||||||
| Variant | What it is | When |
|
| Variant | What it is | When |
|
||||||
|---------|-----------|------|
|
|---------|-----------|------|
|
||||||
| `side-by-side-release` (default) | R8-minified, exactly what ships, signed with the debug keystore | Almost always — a plain debug build cannot catch R8 stripping JNI-loaded classes, which has broken release APKs here before |
|
| `side-by-side-release` (default, and what `latest` always is) | R8-minified, exactly what ships, signed with the **debug** keystore | Almost always — a plain debug build cannot catch R8 stripping JNI-loaded classes, which has broken release APKs here before |
|
||||||
| `debug` | Unminified | When you need readable stack traces |
|
| `debug` | Unminified | When you need readable stack traces |
|
||||||
|
|
||||||
There is deliberately **no push trigger**: the runner has one slot shared with
|
Three properties make an automatic build on every master push safe:
|
||||||
two other projects, so building on every feature-branch commit would starve
|
|
||||||
them. The APK lands as the `jellytau-test-apk` artifact (7-day retention), named
|
|
||||||
for the branch and short SHA, with its size and SHA256 in the run summary.
|
|
||||||
|
|
||||||
#### Sending a build to an outside tester
|
- **It cannot replace a real install.** The applicationId is suffixed `.debug`,
|
||||||
|
so it sits beside the store build with its own data. A broken master commit
|
||||||
|
can never take out somebody's working app.
|
||||||
|
- **The production signing key is not involved.** That stays in the tag-driven
|
||||||
|
`build-release.yml`. This workflow needs no secrets beyond the API token.
|
||||||
|
- **The tag is `latest`/`test-*`, never `v*`.** Only `v*` triggers
|
||||||
|
`build-release.yml`. And the desktop updater reads a static `latest.json` from
|
||||||
|
the `updater` branch rather than the release list, so nothing here is offered
|
||||||
|
to existing users.
|
||||||
|
|
||||||
Gitea **artifacts require an account** with read access to download, so an
|
**Known gap:** the APK builds in parallel with `build-and-test.yml`, not after
|
||||||
artifact is no use to someone outside the project. Tick **`publish`** on the
|
it, so `latest` can carry a commit whose tests later fail. Cross-workflow
|
||||||
dispatch and the APK is also attached to a **pre-release**, whose assets are a
|
dependencies are not reliably available here, and duplicating the test job would
|
||||||
plain public URL on a public repo — no account, no MR, no merge to `master`.
|
double an already hour-long queue on a single-slot runner. Check the commit's
|
||||||
|
CI status before handing the link to somebody.
|
||||||
|
|
||||||
Two things make that safe to do from a feature branch:
|
Runs are serialised and `cancel-in-progress` is on, so a burst of pushes to
|
||||||
|
master collapses into one build rather than one per commit.
|
||||||
- The tag is `test-<branch>`, **not** `v*`. Only `v*` triggers
|
|
||||||
`build-release.yml`, so nothing else reacts to it.
|
|
||||||
- It 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 this way is invisible to anyone without the link.
|
|
||||||
|
|
||||||
Re-dispatching for the same branch replaces the APK on the existing
|
|
||||||
pre-release rather than piling up one release per attempt. Delete the release
|
|
||||||
when testing is over.
|
|
||||||
|
|
||||||
### Key Files
|
### Key Files
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user