diff --git a/.gitea/scripts/desktop-stamp.sh b/.gitea/scripts/desktop-stamp.sh new file mode 100755 index 0000000..816ec7b --- /dev/null +++ b/.gitea/scripts/desktop-stamp.sh @@ -0,0 +1,162 @@ +#!/bin/sh +# desktop-stamp.sh -- Stempel aus Version + letztem Commit an den +# Desktop-Pfaden berechnen und pruefen, ob dazu bereits fertige Pakete im +# Zwischenspeicher des Runners liegen (quick-260917-jdh). +# +# Zweck: Der CI-Job `desktop` baut die Rust/Tauri-Pakete heute bei jedem +# Push auf main, auch wenn sich an der Desktop-App seit dem letzten Bau +# nichts geaendert hat. Dieses Skript berechnet einen Stempel aus der +# aktuellen Versionsnummer und dem letzten Commit an den Desktop-Pfaden; +# liegen zu diesem Stempel bereits geprueft-vollstaendige Pakete im +# Zwischenspeicher, kann der Job den kompletten Bau ueberspringen. +# +# Aufrufformen: +# desktop-stamp.sh stamp -- Stempel berechnen, Ausgaben: stamp, version, +# sha7, skip_allowed (Version aus +# desktop-version.sh --print; SHA aus dem +# letzten Commit an DESKTOP_PATHS) +# desktop-stamp.sh check -- prueft, ob ein per stamp-cache restaurierter +# desktop-dist/ vollstaendig und stempel-echt +# ist, Ausgaben: reuse, bei Treffer zusaetzlich +# files +# +# Umgebungsvariablen: +# DESKTOP_TAG nur fuer stamp, lokale Probe (siehe desktop-version.sh) +# GITHUB_REF nur fuer stamp: skip_allowed=true ausschliesslich bei +# refs/heads/main (Beta-Kanal) -- Tags bauen immer neu +# GITHUB_OUTPUT nur fuer stamp: wenn gesetzt, werden alle Ausgaben +# zusaetzlich per >> hineingeschrieben (wie im CI ueblich) +# CACHE_HIT nur fuer check: Ergebnis von actions/cache/restore +# (steps..outputs.cache-hit), muss exakt "true" sein +# STAMP_VERSION nur fuer check: Version aus dem stamp-Schritt, muss mit +# der Version im Manifest uebereinstimmen +# STAMP_SHA7 nur fuer check: 7-stelliger SHA aus dem stamp-Schritt, +# nur fuer die Log-Zeile bei Treffer +# DESKTOP_DIST nur fuer check: Zielordner der Pakete (Vorgabe: +# desktop-dist, wie desktop-collect.sh) +# +# DESKTOP_PATHS deckt alle Bau-Eingaben ab: apps/desktop (inkl. +# Cargo.lock/package.json), die drei Skripte und die Workflow-Datei selbst +# (eine Aenderung an der Stempelregel muss selbst einen Neubau ausloesen). +# `pnpm-lock.yaml` steht bewusst NICHT in der Liste: die Tauri-CLI-Version +# haengt an apps/desktop/package.json (darin enthalten), und der Desktop-Bau +# liest ausserhalb von apps/desktop keine Werkstatt-Datei -- frontendDist ist +# ../src innerhalb von apps/desktop, keine Abhaengigkeit auf packages/*. +# +# Dieses Skript kennt kein Secret. +set -eu + +DESKTOP_PATHS="apps/desktop .gitea/scripts/desktop-version.sh .gitea/scripts/desktop-collect.sh .gitea/scripts/desktop-stamp.sh .gitea/workflows/ci.yml" + +out() { + printf '%s=%s\n' "$1" "$2" + if [ -n "${GITHUB_OUTPUT:-}" ]; then + printf '%s=%s\n' "$1" "$2" >> "$GITHUB_OUTPUT" + fi +} + +cmd_stamp() { + VERSION="$("$(dirname "$0")/desktop-version.sh" --print)" + + # Bewusst ungequotet: Wortaufteilung der leerzeichengetrennten Pfadliste. + LAST="$(git log -1 --format=%H -- $DESKTOP_PATHS)" + if [ -z "$LAST" ]; then + echo "Keine Historie zu den Desktop-Pfaden -- im CI ist fetch-depth: 0 Pflicht." >&2 + exit 1 + fi + SHA7="$(git rev-parse --short=7 "$LAST")" + SUBJECT="$(git log -1 --format=%s "$LAST")" + + SKIP=false + REF="${GITHUB_REF:-}" + if [ "$REF" = "refs/heads/main" ]; then + SKIP=true + echo "Stempel $VERSION-$LAST ($SHA7: $SUBJECT) -- Ueberspringen erlaubt (main)." + else + echo "Stempel $VERSION-$LAST ($SHA7: $SUBJECT) -- Tag oder fremder Zweig, es wird immer gebaut." + fi + + out stamp "$VERSION-$LAST" + out version "$VERSION" + out sha7 "$SHA7" + out skip_allowed "$SKIP" +} + +cmd_check() { + CACHE_HIT="${CACHE_HIT:-}" + STAMP_VERSION="${STAMP_VERSION:-}" + STAMP_SHA7="${STAMP_SHA7:-}" + DESKTOP_DIST="${DESKTOP_DIST:-desktop-dist}" + MANIFEST="$DESKTOP_DIST/manifest.json" + + no_reuse() { + echo "Kein uebernehmbarer Stand ($1) -- Desktop wird gebaut." + rm -f "$DESKTOP_DIST"/*.AppImage "$DESKTOP_DIST"/*.exe "$MANIFEST" + out reuse false + exit 0 + } + + check_file() { + # $1 = Manifest-Schluessel (linux|windows), $2 = Dateiname + FPATH="$DESKTOP_DIST/$2" + if [ ! -f "$FPATH" ]; then + no_reuse "Datei $2 fehlt" + fi + SIZE="$(stat -c %s "$FPATH")" + EXP_SIZE="$(jq -r ".files.$1.size" "$MANIFEST")" + if [ "$SIZE" != "$EXP_SIZE" ]; then + no_reuse "Groesse von $2 weicht ab ($SIZE statt $EXP_SIZE)" + fi + SHA="$(sha256sum "$FPATH" | cut -d' ' -f1)" + EXP_SHA="$(jq -r ".files.$1.sha256" "$MANIFEST")" + if [ "$SHA" != "$EXP_SHA" ]; then + no_reuse "Pruefsumme von $2 weicht ab" + fi + } + + if [ "$CACHE_HIT" != "true" ]; then + no_reuse "kein Zwischenspeicher zum Stempel" + fi + + if [ ! -f "$MANIFEST" ] || ! jq -e . "$MANIFEST" >/dev/null 2>&1; then + no_reuse "Manifest fehlt oder ist kein gueltiges JSON" + fi + + CHANNEL="$(jq -r .channel "$MANIFEST")" + if [ "$CHANNEL" != "beta" ]; then + no_reuse "Kanal $CHANNEL statt beta" + fi + + MVERSION="$(jq -r .version "$MANIFEST")" + if [ "$MVERSION" != "$STAMP_VERSION" ]; then + no_reuse "Version $MVERSION statt $STAMP_VERSION" + fi + + LINUX_NAME="$(jq -r '.files.linux.name // empty' "$MANIFEST")" + WINDOWS_NAME="$(jq -r '.files.windows.name // empty' "$MANIFEST")" + if [ -z "$LINUX_NAME" ] || [ -z "$WINDOWS_NAME" ]; then + no_reuse "Dateiname fehlt im Manifest" + fi + + check_file linux "$LINUX_NAME" + check_file windows "$WINDOWS_NAME" + + COMMIT="$(jq -r .commit "$MANIFEST")" + BUILD_TIME="$(jq -r .buildTime "$MANIFEST")" + echo "Desktop unveraendert seit $STAMP_SHA7: Pakete $LINUX_NAME, $WINDOWS_NAME aus dem Zwischenspeicher (gebaut aus $COMMIT am $BUILD_TIME)" + out reuse true + out files "$LINUX_NAME,$WINDOWS_NAME" +} + +case "${1:-}" in + stamp) + cmd_stamp + ;; + check) + cmd_check + ;; + *) + echo "Aufruf: $0 stamp|check" >&2 + exit 1 + ;; +esac diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 87dd84f..2c90e76 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -9,6 +9,11 @@ # in der Pipeline. `tauri.conf.json`/`Cargo.toml` bleiben dabei immer rein # numerisch (X.Y.Z), weil NSIS' Windows-Ressourcenfelder das verlangen; die # Beta-Kennzeichnung lebt ausschliesslich im Dateinamen-Suffix. +# quick-260917-jdh: Job `desktop` ueberspringt den Bau auf `main`, wenn zum +# Stempel (Version aus dem letzten Freigabe-Tag + letzter Commit an den +# Desktop-Pfaden, siehe .gitea/scripts/desktop-stamp.sh) bereits fertige +# Pakete im Zwischenspeicher des Runners liegen; Tags v* bauen immer neu; +# `publish` bleibt unveraendert. name: Tessera CI/CD on: @@ -78,17 +83,46 @@ jobs: with: fetch-depth: 0 + # Die folgenden drei Schritte stehen bewusst vor setup-node/apt/rustup: + # Sie sollen beim Ueberspringen (Desktop-Stand unveraendert) gar nicht + # erst die teuren Werkzeuge installieren (quick-260917-jdh). + - name: Desktop-Stempel berechnen + id: stamp + run: sh .gitea/scripts/desktop-stamp.sh stamp + + - name: Fertige Pakete zum Stempel suchen + id: stamp-cache + if: steps.stamp.outputs.skip_allowed == 'true' + uses: actions/cache/restore@v4 + with: + path: desktop-dist + key: desktop-dist-stamp-${{ steps.stamp.outputs.stamp }} + # Bewusst OHNE restore-keys: act_runner sucht auch zum Hauptschluessel + # per Praefix -- ein aelterer Stand darf nie als Treffer gelten. + + - name: Gefundene Pakete pruefen + id: reuse + env: + CACHE_HIT: ${{ steps.stamp-cache.outputs.cache-hit }} + STAMP_VERSION: ${{ steps.stamp.outputs.version }} + STAMP_SHA7: ${{ steps.stamp.outputs.sha7 }} + run: sh .gitea/scripts/desktop-stamp.sh check + - uses: actions/setup-node@v4 + if: steps.reuse.outputs.reuse != 'true' with: node-version: 24 - name: Enable pnpm via corepack + if: steps.reuse.outputs.reuse != 'true' run: corepack enable && corepack prepare pnpm@9.15.0 --activate - name: Install dependencies + if: steps.reuse.outputs.reuse != 'true' run: pnpm install --frozen-lockfile - name: Systemabhaengigkeiten + if: steps.reuse.outputs.reuse != 'true' run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ @@ -98,12 +132,14 @@ jobs: lld llvm clang nsis - name: Rust-Toolchain + if: steps.reuse.outputs.reuse != 'true' run: | curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" "$HOME/.cargo/bin/rustup" component add clippy - name: Cargo-Zwischenspeicher + if: steps.reuse.outputs.reuse != 'true' uses: actions/cache@v4 with: path: | @@ -118,33 +154,53 @@ jobs: restore-keys: desktop-cargo- - name: Windows-Werkzeuge + if: steps.reuse.outputs.reuse != 'true' run: | rustup target add x86_64-pc-windows-msvc command -v cargo-xwin >/dev/null 2>&1 || cargo install --locked cargo-xwin - name: Version setzen + if: steps.reuse.outputs.reuse != 'true' run: sh .gitea/scripts/desktop-version.sh - name: Rust pruefen + if: steps.reuse.outputs.reuse != 'true' working-directory: apps/desktop/src-tauri run: | cargo check cargo clippy - name: Alte Bundles entfernen + if: steps.reuse.outputs.reuse != 'true' run: | rm -rf apps/desktop/src-tauri/target/release/bundle rm -rf apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle - name: Linux-AppImage bauen + if: steps.reuse.outputs.reuse != 'true' run: pnpm --filter @tessera/desktop exec tauri build --bundles appimage - name: Windows-Installer bauen (Cross-Bau) + if: steps.reuse.outputs.reuse != 'true' run: pnpm --filter @tessera/desktop exec tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis - name: Pakete einsammeln + if: steps.reuse.outputs.reuse != 'true' run: sh .gitea/scripts/desktop-collect.sh --require linux,windows + - name: Pakete unter dem Stempel ablegen + # Nur nach echtem Bau und nur auf main -- Tag-Pakete tragen keinen + # Beta-Suffix und duerfen nie unter einem Stempel liegen. Ein bereits + # vorhandener Schluessel loest bei actions/cache/save nur eine Info + # aus, keinen Fehler. + if: steps.reuse.outputs.reuse != 'true' && steps.stamp.outputs.skip_allowed == 'true' + uses: actions/cache/save@v4 + with: + path: desktop-dist + key: desktop-dist-stamp-${{ steps.stamp.outputs.stamp }} + + # Laeuft in beiden Faellen: im Skip-Fall sichert er den restaurierten + # Stand unter dem neuen SHA, deshalb muss `publish` nichts wissen. - name: Uebergabe an publish uses: actions/cache/save@v4 with: