Files
jellytau/.gitea/workflows/build-release.yml
T
dtourolle 51d914777a
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 28m26s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m36s
Traceability Validation / Check Requirement Traces (push) Successful in 26s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 2m41s
ci: fix cache-key collisions and skip duplicate release-commit test run
The test job and build-linux shared one cargo cache key; the test job's
debug artifacts claimed it first and actions/cache skips saving on an
exact-key hit, so Linux release builds compiled cold every time (~31min
vs ~9min for the correctly-keyed Windows job). Same collision between
android-check and build-android. Give the release jobs their own keys.

Also skip build-and-test.yml for chore(release) commits: the tag push
triggers build-release.yml on the same commit, which runs the identical
test suite, and the two ~1h workflows contended for the single runner
slot.
2026-08-19 22:00:19 +02:00

440 lines
16 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: Build & Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., v1.0.0)'
required: false
env:
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
jobs:
test:
name: Run Tests
runs-on: linux/amd64
container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Cache Rust dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
src-tauri/target
key: ${{ runner.os }}-cargo-host-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-host-
- 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
- name: Run frontend tests
run: |
bunx svelte-kit sync
bun run test --run
continue-on-error: false
# Same gate as build-and-test.yml. A release must not ship from a tree
# that would fail the per-commit checks. rustfmt/clippy come from the
# builder image; nothing is installed here.
- name: Check Rust formatting
run: |
cd src-tauri
cargo fmt --all -- --check
continue-on-error: false
# Advisory until the ~51 pre-existing warnings are cleared; see the longer
# note in build-and-test.yml. Tighten both to `-- -D warnings` together.
- name: Run clippy (advisory)
run: |
cd src-tauri
cargo clippy --all-targets
- name: Run Rust tests
run: bun run test:rust
continue-on-error: false
- name: Check TypeScript
run: bun run check
continue-on-error: false
build-linux:
name: Build Linux
runs-on: linux/amd64
needs: test
container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
# ⚠️ Key must NOT collide with the test job's `cargo-host` key: the test
# job runs first and saves debug/clippy artifacts under its key, and
# actions/cache skips saving on an exact-key hit — so a shared key meant
# this job's *release* artifacts were never cached and every Linux release
# build compiled cold (~31min vs ~9min for the correctly-keyed Windows job).
- name: Cache Rust dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
src-tauri/target
key: ${{ runner.os }}-cargo-linux-release-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-linux-release-
- 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
# The Linux job previously had no version step at all, so a tagged release
# built Linux packages from whatever version happened to be committed.
- name: Set app version from tag
run: ./scripts/set-version.sh "${GITHUB_REF#refs/tags/}"
if: startsWith(github.ref, 'refs/tags/v')
- name: Build for Linux
run: bun run tauri build
env:
TAURI_SKIP_UPDATER: true
- name: Prepare Linux artifacts
run: |
mkdir -p dist/linux
# Copy AppImage
if [ -f "src-tauri/target/release/bundle/appimage/jellytau_"*.AppImage ]; then
cp src-tauri/target/release/bundle/appimage/jellytau_*.AppImage dist/linux/
fi
# Copy .deb if built
if [ -f "src-tauri/target/release/bundle/deb/jellytau_"*.deb ]; then
cp src-tauri/target/release/bundle/deb/jellytau_*.deb dist/linux/
fi
ls -lah dist/linux/
- name: Upload Linux build artifact
uses: actions/upload-artifact@v3
with:
name: jellytau-linux
path: dist/linux/
retention-days: 30
build-windows:
name: Build Windows
runs-on: linux/amd64
needs: test
# Cross-compiled from Linux via the official Tauri path (MSVC + cargo-xwin),
# baked into the builder image. No toolchain installs here — the image has
# cargo-xwin, clang/clang-cl, lld, llvm, nsis and the msvc target.
container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Cache Rust dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cache/cargo-xwin
src-tauri/target
key: ${{ runner.os }}-cargo-windows-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-windows-
- 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-
# The tag is the single source of truth for a release version; the script
# stamps every file that carries it (package.json, tauri.conf.json,
# Cargo.toml, Cargo.lock). This step used to sed only tauri.conf.json, so
# the other three shipped whatever was committed.
- name: Set app version from tag
run: ./scripts/set-version.sh "${GITHUB_REF#refs/tags/}"
if: startsWith(github.ref, 'refs/tags/v')
- name: Build Windows (NSIS installer + exe)
run: OUTPUT_DIR="$PWD/dist/windows" WIN_BUNDLES=nsis ./scripts/build-windows-cross.sh
- name: List Windows artifacts
run: ls -lah dist/windows/
- name: Upload Windows build artifact
uses: actions/upload-artifact@v3
with:
name: jellytau-windows
path: dist/windows/
retention-days: 30
build-android:
name: Build Android
runs-on: linux/amd64
needs: test
container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest
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
- name: Cache Rust dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
src-tauri/target
# `-release` suffix keeps this distinct from build-and-test.yml's
# android-check key, whose `cargo check` artifacts would otherwise
# claim the key first and block this job's release cache from ever
# being saved (same collision as the Linux job above).
key: ${{ runner.os }}-cargo-android-release-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-android-release-
- 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
# Stamp before `android init`: it derives its generated project (including
# the initial versionCode) from tauri.conf.json.
- name: Set app version from tag
run: ./scripts/set-version.sh "${GITHUB_REF#refs/tags/}"
if: startsWith(github.ref, 'refs/tags/v')
- name: Initialize Android project
run: bun run tauri android init
# Re-run after init: tauri.properties only exists now, and its
# autogenerated versionCode (0.0.15 -> 15) is both tiny and NOT monotonic
# against the 1000 floor already shipped in the field. The script rewrites
# it as 1000 + major*10000 + minor*100 + patch. Runs unconditionally so
# untagged builds get a sane code too, derived from git describe.
- name: Pin a monotonic Android versionCode
run: ./scripts/set-version.sh "${GITHUB_REF#refs/tags/}"
- name: Sync custom Android sources & gradle config
run: ./scripts/sync-android-sources.sh
- name: Write signing keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > "$RUNNER_TEMP/jellytau-release.jks"
cat > src-tauri/gen/android/keystore.properties <<EOF
storeFile=$RUNNER_TEMP/jellytau-release.jks
storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
EOF
- name: Build signed Android APK
run: bun run tauri android build --apk true --target aarch64
- name: Collect & verify signed APK
run: |
mkdir -p dist/android
APK=$(find src-tauri/gen/android/app/build/outputs/apk -name '*-release.apk' | head -1)
if [ -z "$APK" ]; then echo "❌ No release APK produced"; exit 1; fi
cp "$APK" dist/android/jellytau-release.apk
APKSIGNER=$(find "$ANDROID_SDK_ROOT/build-tools" -name apksigner | sort -V | tail -1)
echo "🔏 Verifying signature with $APKSIGNER"
"$APKSIGNER" verify --print-certs dist/android/jellytau-release.apk
ls -lah dist/android/
- name: Upload Android build artifact
uses: actions/upload-artifact@v3
with:
name: jellytau-android
path: dist/android/
retention-days: 30
create-release:
name: Create Release
runs-on: linux/amd64
needs: [build-linux, build-windows, build-android]
if: startsWith(github.ref, 'refs/tags/v')
container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Get version from tag
id: tag_name
run: |
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "RELEASE_NAME=JellyTau ${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Download Linux artifacts
uses: actions/download-artifact@v3
with:
name: jellytau-linux
path: artifacts/linux/
- name: Download Windows artifacts
uses: actions/download-artifact@v3
with:
name: jellytau-windows
path: artifacts/windows/
- name: Download Android artifacts
uses: actions/download-artifact@v3
with:
name: jellytau-android
path: artifacts/android/
- name: Prepare release notes
id: release_notes
run: |
VERSION="${{ steps.tag_name.outputs.VERSION }}"
echo "## JellyTau $VERSION Release" > release_notes.md
echo "" >> release_notes.md
echo "### Downloads" >> release_notes.md
echo "" >> release_notes.md
echo "#### Linux" >> release_notes.md
echo "- **AppImage** - Run directly on most Linux distributions" >> release_notes.md
echo "- **DEB** - Install via \`sudo dpkg -i jellytau_*.deb\` (Ubuntu/Debian)" >> release_notes.md
echo "" >> release_notes.md
echo "#### Windows" >> release_notes.md
echo "- **Installer (.exe)** - Run \`jellytau_*-setup.exe\` (NSIS). Unsigned — SmartScreen may warn on first run." >> release_notes.md
echo "" >> release_notes.md
echo "#### Android" >> release_notes.md
echo "- **APK** - Install via \`adb install jellytau-release.apk\` or sideload via file manager" >> release_notes.md
echo "- **AAB** - Upload to Google Play Console or testing platforms" >> release_notes.md
echo "" >> release_notes.md
echo "### What's New" >> release_notes.md
echo "" >> release_notes.md
echo "See [CHANGELOG.md](CHANGELOG.md) for detailed changes." >> release_notes.md
echo "" >> release_notes.md
echo "### Installation" >> release_notes.md
echo "" >> release_notes.md
echo "#### Linux (AppImage)" >> release_notes.md
echo "\`\`\`bash" >> release_notes.md
echo "chmod +x jellytau_*.AppImage" >> release_notes.md
echo "./jellytau_*.AppImage" >> release_notes.md
echo "\`\`\`" >> release_notes.md
echo "" >> release_notes.md
echo "#### Linux (DEB)" >> release_notes.md
echo "\`\`\`bash" >> release_notes.md
echo "sudo dpkg -i jellytau_*.deb" >> release_notes.md
echo "jellytau" >> release_notes.md
echo "\`\`\`" >> release_notes.md
echo "" >> release_notes.md
echo "#### Android" >> release_notes.md
echo "- Sideload: Download APK and install via file manager or ADB" >> release_notes.md
echo "- Play Store: Coming soon" >> release_notes.md
echo "" >> release_notes.md
echo "### Known Issues" >> release_notes.md
echo "" >> release_notes.md
echo "See [GitHub Issues](../../issues) for reported bugs." >> release_notes.md
echo "" >> release_notes.md
echo "### Requirements" >> release_notes.md
echo "" >> release_notes.md
echo "**Linux:**" >> release_notes.md
echo "- 64-bit Linux system" >> release_notes.md
echo "- GLIBC 2.29+" >> release_notes.md
echo "" >> release_notes.md
echo "**Android:**" >> release_notes.md
echo "- Android 8.0 or higher" >> release_notes.md
echo "- 50MB free storage" >> release_notes.md
echo "" >> release_notes.md
echo "---" >> release_notes.md
echo "Built with Tauri, SvelteKit, and Rust" >> release_notes.md
- name: Publish Gitea release & upload assets
env:
# GITEA_TOKEN (a PAT) is preferred; falls back to the auto-provided token.
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; }
VERSION="${{ steps.tag_name.outputs.VERSION }}"
API="${GITHUB_SERVER_URL}/api/v1"
REPO="${GITHUB_REPOSITORY}"
TOKEN="${GITEA_TOKEN:-$AUTO_TOKEN}"
case "$VERSION" in *rc*|*beta*|*alpha*) PRE=true;; *) PRE=false;; esac
PAYLOAD=$(jq -n \
--arg tag "$VERSION" \
--arg name "JellyTau $VERSION" \
--rawfile body release_notes.md \
--argjson pre "$PRE" \
'{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:$pre}')
echo "📦 Creating release $VERSION on $REPO"
# -f drops on HTTP error; capture status so an existing release (409) is handled gracefully.
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
echo "️ Release $VERSION already exists; fetching its id to upload assets"
RELEASE_ID=$(curl -fsS "$API/repos/$REPO/releases/tags/$VERSION" \
-H "Authorization: token $TOKEN" | jq -r '.id')
else
echo "❌ Failed to create release (HTTP $HTTP):"; cat resp.json; exit 1
fi
echo "Release id=$RELEASE_ID"
for f in artifacts/android/* artifacts/linux/* artifacts/windows/*; do
[ -f "$f" ] || continue
echo "⬆️ Uploading $(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 "✅ Release $VERSION published with assets"