Release / release (push) Failing after 11s
Release runs were fully cold every time: ~13s npm install, ~39s full cgo/WebKit compile, and ~34s rebuilding the Wails CLI from source on each tag. Now that the runner cache backend is reachable again, persist all three across runs. Expected total drops from ~3.5 min toward ~2.5 min with apt as the remaining floor. - Set up Node: cache npm keyed on frontend/package-lock.json, so the npm install inside wails build stops fetching cold - New Cache Go build cache step: setup-go only persists modules (GOMODCACHE); this persists compiled packages (GOCACHE) with a go.sum-hashed key plus a version-prefix restore-key fallback, turning the WebKit compile incremental from the second run on - Wails CLI: split install into cache (key wails-v2.15.0-Linux, bump with the version pin), conditional go install on miss, and an unconditional PATH step so cache hits still land on PATH First run after this still compiles cold (it saves); the payoff shows from the second run on. Verify with the throwaway-tag loop and compare Build Linux binary times.
281 lines
12 KiB
YAML
281 lines
12 KiB
YAML
name: Release
|
|
|
|
# Overrides the default run name (the tagged commit's message) so the Actions
|
|
# runs list shows "Release 1.6.7" instead.
|
|
run-name: "Release ${{ gitea.event.inputs.tag || gitea.ref_name }}"
|
|
|
|
# Builds the Wails Linux binary via `make build`, packages
|
|
# AniTrack-<version>.tar.gz from build/, and creates/updates a Gitea Release
|
|
# named AniTrack-<version> with the archive attached. Triggered by a plain
|
|
# version tag push (1.6.7), or manually via workflow_dispatch with a tag for
|
|
# re-runs and secrets tests. Pushing to main does nothing, so
|
|
# work-in-progress commits never ship.
|
|
#
|
|
# Local flow: `./release 1.6.7` (or `make release VERSION=1.6.7`) bumps
|
|
# wails.json, commits the bump, creates an annotated tag carrying the
|
|
# git-cliff notes, and pushes commit + tag. This workflow verifies
|
|
# wails.json matches the tag before building.
|
|
#
|
|
# Secrets test (verifies environment.go wiring without shipping): tag the
|
|
# current bumped commit with a suffix and push, e.g.
|
|
# git tag 1.6.7-rc1 && git push origin 1.6.7-rc1
|
|
# Tags containing '-' are published as pre-releases; delete the test
|
|
# Release/tag afterwards.
|
|
on:
|
|
push:
|
|
tags:
|
|
- '[0-9]*.[0-9]*.[0-9]*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Tag to release (e.g. 1.6.7)'
|
|
required: true
|
|
type: string
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
env:
|
|
# Resolve the target tag for both triggers: explicit input on manual
|
|
# dispatch, otherwise the pushed tag ref.
|
|
TAG: ${{ gitea.event.inputs.tag || gitea.ref_name }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
# Full history ensures the tag annotation (the release notes) is present.
|
|
fetch-depth: 0
|
|
ref: ${{ gitea.event.inputs.tag || gitea.ref }}
|
|
|
|
- name: Guard wails.json matches tag
|
|
run: |
|
|
set -euo pipefail
|
|
: "${TAG:?TAG is required}"
|
|
# Normalize "v1.6.7" / "AniTrack-1.6.7" to plain "1.6.7" (make release
|
|
# only ever creates plain tags; this tolerates manual typos).
|
|
NORM="${TAG#v}"
|
|
NORM="${NORM#AniTrack-}"
|
|
BASE="${NORM%%-*}"
|
|
WAILS_VER="$(python3 -c "import json; print(json.load(open('wails.json'))['info']['productVersion'])")"
|
|
if [ "${NORM}" = "${BASE}" ]; then
|
|
if [ "${WAILS_VER}" != "${NORM}" ]; then
|
|
echo "::error::wails.json productVersion (${WAILS_VER}) != tag (${NORM}). Bump via ./release ${NORM} first." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
# Pre-release (e.g. 1.6.7-rc1): wails.json must match the base version.
|
|
if [ "${WAILS_VER}" != "${BASE}" ]; then
|
|
echo "::error::wails.json productVersion (${WAILS_VER}) != tag base (${BASE}). Bump via ./release ${BASE} first." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
echo "VERSION=${NORM}" >> "${GITHUB_ENV}"
|
|
echo "Version guard passed: wails.json=${WAILS_VER} tag=${NORM}"
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.25'
|
|
cache-dependency-path: go.sum
|
|
|
|
- name: Cache Go build cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
# setup-go only caches modules (GOMODCACHE). This caches compiled
|
|
# packages (GOCACHE) so the cgo/WebKit compile goes incremental.
|
|
# restore-keys gives a close cache instead of a cold one when
|
|
# go.sum changes. First run after adding this still compiles cold
|
|
# (it saves); the payoff shows from the second run on.
|
|
path: ~/.cache/go-build
|
|
key: go-build-1.25-${{ runner.os }}-${{ hashFiles('go.sum') }}
|
|
restore-keys: |
|
|
go-build-1.25-${{ runner.os }}-
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
# Caches npm's download cache keyed on the frontend lockfile, so
|
|
# the `npm install` inside `wails build` stops fetching cold.
|
|
cache: 'npm'
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Install Wails Linux build dependencies
|
|
run: |
|
|
set -euo pipefail
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
build-essential pkg-config \
|
|
libgtk-3-dev libwebkit2gtk-4.1-dev \
|
|
jq
|
|
|
|
- name: Cache Wails CLI
|
|
uses: actions/cache@v4
|
|
id: wails-cli
|
|
with:
|
|
# The compiled CLI binary. Bump the key whenever the @version pin
|
|
# below changes, or the old CLI will be silently reused.
|
|
path: ~/go/bin/wails
|
|
key: wails-v2.15.0-${{ runner.os }}
|
|
|
|
- name: Install Wails CLI
|
|
if: steps.wails-cli.outputs.cache-hit != 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
go install github.com/wailsapp/wails/v2/cmd/wails@v2.15.0
|
|
|
|
- name: Add Go bin to PATH
|
|
run: echo "${HOME}/go/bin" >> "${GITHUB_PATH}"
|
|
|
|
- name: Write environment.go from secrets
|
|
env:
|
|
ANILIST_SECRET_TOKEN: ${{ secrets.ANILIST_SECRET_TOKEN }}
|
|
ANILIST_APP_ID: ${{ secrets.ANILIST_APP_ID }}
|
|
ANILIST_APP_NAME: ${{ secrets.ANILIST_APP_NAME }}
|
|
ANILIST_CALLBACK_URI: ${{ secrets.ANILIST_CALLBACK_URI }}
|
|
SIMKL_CLIENT_ID: ${{ secrets.SIMKL_CLIENT_ID }}
|
|
SIMKL_CLIENT_SECRET: ${{ secrets.SIMKL_CLIENT_SECRET }}
|
|
SIMKL_CALLBACK_URI: ${{ secrets.SIMKL_CALLBACK_URI }}
|
|
MAL_CLIENT_ID: ${{ secrets.MAL_CLIENT_ID }}
|
|
MAL_CLIENT_SECRET: ${{ secrets.MAL_CLIENT_SECRET }}
|
|
MAL_CALLBACK_URI: ${{ secrets.MAL_CALLBACK_URI }}
|
|
run: |
|
|
set -euo pipefail
|
|
# All 10 fields come from Gitea Actions secrets (never committed).
|
|
# Values are masked in logs; do not echo them or run with set -x.
|
|
for v in ANILIST_SECRET_TOKEN ANILIST_APP_ID ANILIST_APP_NAME ANILIST_CALLBACK_URI SIMKL_CLIENT_ID SIMKL_CLIENT_SECRET SIMKL_CALLBACK_URI MAL_CLIENT_ID MAL_CLIENT_SECRET MAL_CALLBACK_URI; do
|
|
if [ -z "${!v:-}" ]; then
|
|
echo "::error::Missing secret ${v}. Add it under Settings → Secrets → Actions." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
cat > environment.go <<EOF
|
|
package main
|
|
|
|
var Environment = EnvironmentStruct{
|
|
ANILIST_SECRET_TOKEN: "${ANILIST_SECRET_TOKEN}",
|
|
ANILIST_APP_ID: "${ANILIST_APP_ID}",
|
|
ANILIST_APP_NAME: "${ANILIST_APP_NAME}",
|
|
ANILIST_CALLBACK_URI: "${ANILIST_CALLBACK_URI}",
|
|
SIMKL_CLIENT_ID: "${SIMKL_CLIENT_ID}",
|
|
SIMKL_CLIENT_SECRET: "${SIMKL_CLIENT_SECRET}",
|
|
SIMKL_CALLBACK_URI: "${SIMKL_CALLBACK_URI}",
|
|
MAL_CLIENT_ID: "${MAL_CLIENT_ID}",
|
|
MAL_CLIENT_SECRET: "${MAL_CLIENT_SECRET}",
|
|
MAL_CALLBACK_URI: "${MAL_CALLBACK_URI}",
|
|
}
|
|
EOF
|
|
echo "Wrote environment.go from secrets."
|
|
|
|
- name: Build Linux binary
|
|
run: |
|
|
set -euo pipefail
|
|
export PATH="${HOME}/go/bin:${PATH}"
|
|
make build
|
|
test -x build/bin/AniTrack || { echo "::error::build/bin/AniTrack missing after make build" >&2; exit 1; }
|
|
|
|
- name: Package release archive
|
|
run: |
|
|
set -euo pipefail
|
|
: "${VERSION:?VERSION missing from version-guard step}"
|
|
STAGE="dist/AniTrack-${VERSION}"
|
|
rm -rf dist "AniTrack-${VERSION}.tar.gz"
|
|
mkdir -p "${STAGE}/bin"
|
|
cp build/bin/AniTrack "${STAGE}/bin/"
|
|
cp -r build/icon "${STAGE}/"
|
|
cp build/AniTrack.desktop build/install_linux.sh build/README.md "${STAGE}/"
|
|
chmod +x "${STAGE}/bin/AniTrack" "${STAGE}/install_linux.sh"
|
|
tar -czf "AniTrack-${VERSION}.tar.gz" -C dist "AniTrack-${VERSION}"
|
|
tar tzf "AniTrack-${VERSION}.tar.gz"
|
|
echo "ARCHIVE=AniTrack-${VERSION}.tar.gz" >> "${GITHUB_ENV}"
|
|
|
|
- name: Create Gitea Release
|
|
env:
|
|
# REGISTRY_TOKEN is reused for release creation because Gitea's auto
|
|
# token cannot create releases on this instance. The PAT must carry
|
|
# write:repository scope. Idempotent: re-runs update an existing
|
|
# release for this tag instead of failing with 409. On any HTTP error
|
|
# the API response body is printed so a 403 names the missing scope.
|
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
REPO: ${{ gitea.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
: "${TAG:?TAG is required}"
|
|
: "${VERSION:?VERSION missing from version-guard step}"
|
|
API="https://git.linuxhg.com/api/v1/repos/${REPO}/releases"
|
|
AUTH="Authorization: token ${TOKEN}"
|
|
# Release body = the annotated tag's message (the git-cliff notes).
|
|
# Manual test tags without an annotation fall back to the tag name.
|
|
BODY="$(git tag -l --format='%(contents)' "${TAG}")"
|
|
if [ -z "$(printf '%s' "${BODY}" | tr -d '[:space:]')" ]; then
|
|
BODY="${TAG}"
|
|
fi
|
|
|
|
# Tags containing a '-' (e.g. 1.6.7-rc1) are published as pre-releases.
|
|
PRE="false"; case "${TAG}" in *-*) PRE="true";; esac
|
|
|
|
PAYLOAD=$(jq -n \
|
|
--arg t "${TAG}" --arg n "AniTrack-${VERSION}" --arg b "${BODY}" --argjson p "${PRE}" \
|
|
'{tag_name:$t, name:$n, body:$b, draft:false, prerelease:$p}')
|
|
|
|
# POST/PATCH the release, surfacing Gitea's error message on failure
|
|
# (e.g. "token does not have write scope") instead of failing silently.
|
|
api_call() {
|
|
local method="$1" url="$2" resp code rbody
|
|
resp="$(curl -sS -w '\n%{http_code}' -X "${method}" \
|
|
-H "${AUTH}" -H "Content-Type: application/json" \
|
|
-d "${PAYLOAD}" "${url}")"
|
|
code="$(printf '%s' "${resp}" | tail -n1)"
|
|
rbody="$(printf '%s' "${resp}" | sed '$d')"
|
|
if [ "${code}" -ge 400 ]; then
|
|
echo "::error::Release API ${code} (${method} ${url}): ${rbody}" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
EXISTING_ID="$(curl -sS -H "${AUTH}" "${API}/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null || true)"
|
|
if [ -n "${EXISTING_ID}" ]; then
|
|
api_call PATCH "${API}/${EXISTING_ID}"
|
|
echo "Updated existing release id=${EXISTING_ID} for ${TAG}"
|
|
else
|
|
api_call POST "${API}"
|
|
echo "Created new release for ${TAG}"
|
|
fi
|
|
|
|
- name: Upload release archive
|
|
env:
|
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
REPO: ${{ gitea.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
: "${TAG:?TAG is required}"
|
|
: "${ARCHIVE:?ARCHIVE missing from packaging step}"
|
|
API="https://git.linuxhg.com/api/v1/repos/${REPO}/releases"
|
|
AUTH="Authorization: token ${TOKEN}"
|
|
test -f "${ARCHIVE}" || { echo "::error::${ARCHIVE} not found" >&2; exit 1; }
|
|
|
|
RID="$(curl -sS -H "${AUTH}" "${API}/tags/${TAG}" | jq -r '.id // empty')"
|
|
if [ -z "${RID}" ]; then
|
|
echo "::error::No release found for tag ${TAG} after create step" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Replace a same-named asset so re-runs stay idempotent.
|
|
AID="$(curl -sS -H "${AUTH}" "${API}/${RID}/assets" | jq -r --arg n "${ARCHIVE}" '.[] | select(.name==$n) | .id // empty')"
|
|
if [ -n "${AID}" ]; then
|
|
curl -sS -X DELETE -H "${AUTH}" "${API}/${RID}/assets/${AID}" >/dev/null
|
|
echo "Deleted existing asset id=${AID} (${ARCHIVE})"
|
|
fi
|
|
|
|
resp="$(curl -sS -w '\n%{http_code}' -X POST -H "${AUTH}" \
|
|
-F "attachment=@${ARCHIVE}" "${API}/${RID}/assets?name=${ARCHIVE}")"
|
|
code="$(printf '%s' "${resp}" | tail -n1)"
|
|
rbody="$(printf '%s' "${resp}" | sed '$d')"
|
|
if [ "${code}" -ge 400 ]; then
|
|
echo "::error::Asset upload ${code}: ${rbody}" >&2
|
|
exit 1
|
|
fi
|
|
echo "Uploaded ${ARCHIVE} to release id=${RID}"
|