diff --git a/.github/workflows/binarky.yml b/.github/workflows/binarky.yml new file mode 100644 index 0000000..cfa479e --- /dev/null +++ b/.github/workflows/binarky.yml @@ -0,0 +1,124 @@ +# Binárky pro Windows a macOS. +# +# PyInstaller neumí křížovou kompilaci: binárku pro každý systém musí postavit +# ten systém. Kupovat kvůli tomu Mac nebo krmit Windows VM by bylo přehnané, +# tak si tyhle stroje půjčujeme od GitHub Actions. +# +# GitHub je tu jen půjčená dílna, ne druhý domov projektu: repozitář se sem +# z Gitey zrcadlí a hotové soubory se posílají zpátky na tamní release přes +# Gitea API. Jedna stránka s releasy, ta původní. +# +# Linux se tu schválně nestaví — o ten se stará vlastní runner +# (.gitea/workflows/binarka.yml) a dělá to líp, proti glibc 2.17. +name: binárky + +on: + push: + branches: [main] + tags: ["v*"] + workflow_dispatch: + +env: + GITEA: https://gitea.spacilovi.eu + GITEA_REPO: david-spacil/dice-counter + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + asset: kostky-windows-x86_64.exe + built: dist/kostky.exe + - os: macos-13 # poslední Intel běhoun + asset: kostky-macos-x86_64 + built: dist/kostky + - os: macos-latest # Apple Silicon + asset: kostky-macos-arm64 + built: dist/kostky + + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + + steps: + - uses: actions/checkout@v7 + + - uses: astral-sh/setup-uv@v10 + + - name: Testy + run: uv run --with-requirements requirements.txt --no-project pytest -q + + - name: Binárka + run: ./build.sh + + - name: Zkouška + run: | + # Binárka se musí umět rozbalit, najít šablony a začít poslouchat. + # Adresa se vnucuje, ať zkouška nezávisí na síti běhounu. + export DICE_HOST=127.0.0.1 DICE_PORT=8123 DICE_DB=zkouska/dice.db + "${{ matrix.built }}" & + + for _ in $(seq 30); do + curl -sf -o /dev/null "http://127.0.0.1:8123/" && break + sleep 1 + done + + curl -sf -o /dev/null "http://127.0.0.1:8123/" || { + echo "Server se nerozeběhl." >&2; exit 1; } + curl -sf -o /dev/null "http://127.0.0.1:8123/static/style.css" + curl -sf "http://127.0.0.1:8123/board" | grep -q "127.0.0.1:8123" + test -f zkouska/dice.db + + echo "Binárka jede." + + - name: Na release + if: startsWith(github.ref, 'refs/tags/') + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + ASSET: ${{ matrix.asset }} + run: | + api="$GITEA/api/v1/repos/$GITEA_REPO" + auth="Authorization: token $TOKEN" + json() { uv run --no-project python -c "$@"; } + + # Release u tagu být může, ale nemusí — dá se založit i ručně a + # sahají na něj čtyři buildy naráz, tak se po neúspěchu ptáme znovu. + for _ in 1 2 3; do + id=$(curl -s -H "$auth" "$api/releases/tags/${GITHUB_REF_NAME}" \ + | json 'import json,sys; print(json.load(sys.stdin).get("id",""))') + [ -n "$id" ] && break + + curl -s -o /dev/null -X POST -H "$auth" -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\"}" \ + "$api/releases" + done + [ -n "$id" ] || { echo "Release ${GITHUB_REF_NAME} se nepodařilo najít." >&2; exit 1; } + + # macOS zná jen shasum, Git Bash na Windows jen sha256sum. Formát + # výsledku je stejný, takže se dá ověřit kterýmkoli z nich. + if command -v sha256sum >/dev/null; then + soucet() { sha256sum "$@"; } + else + soucet() { shasum -a 256 "$@"; } + fi + + # Součet se počítá nad jménem, pod kterým se soubor stahuje, aby + # šel ověřit rovnou: shasum -a 256 -c kostky-macos-arm64.sha256 + cp "${{ matrix.built }}" "$ASSET" + soucet "$ASSET" | tee "$ASSET.sha256" + + for file in "$ASSET" "$ASSET.sha256"; do + # Aby se dal build zopakovat, stará příloha stejného jména jde pryč. + curl -s -H "$auth" "$api/releases/$id/assets" \ + | json 'import json,sys; [print(a["id"]) for a in json.load(sys.stdin) if a["name"] == sys.argv[1]]' "$file" \ + | while read -r old; do + curl -sf -X DELETE -H "$auth" "$api/releases/$id/assets/$old" + done + + curl -sf -o /dev/null -X POST -H "$auth" -F "attachment=@$file" \ + "$api/releases/$id/assets?name=$file" + echo "$file pověšen na ${GITHUB_REF_NAME}" + done diff --git a/README.md b/README.md index 9a0b596..7af5121 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,45 @@ ten samý Adam jako z minulého měsíce. Žádné účty, žádná hesla. ## Spuštění -Stáhni binárku z [releases](https://gitea.spacilovi.eu/david-spacil/dice-counter/releases) -a spusť ji: +Stáhni binárku pro svůj systém z +[releases](https://gitea.spacilovi.eu/david-spacil/dice-counter/releases): + +| Systém | Soubor | +|---|---| +| Linux (x86-64) | `kostky-linux-x86_64` | +| macOS (Apple Silicon) | `kostky-macos-arm64` | +| macOS (Intel) | `kostky-macos-x86_64` | +| Windows (x86-64) | `kostky-windows-x86_64.exe` | + +Nic se neinstaluje — Python, Flask i šablony jsou uvnitř. + +Na Linuxu a macOS: ```bash -chmod +x kostky -./kostky +chmod +x kostky-* +./kostky-linux-x86_64 ``` -Nic se neinstaluje — Python, Flask i šablony jsou uvnitř. Zatím jen pro Linux -na x86-64; postavené proti glibc 2.17, takže jede prakticky všude. +Na Windows stačí na `.exe` poklepat. + +Linuxová binárka je postavená proti glibc 2.17, takže jede prakticky všude. + +### Než to poprvé pustíš + +Binárky nejsou podepsané — podpisové certifikáty stojí tisíce ročně a na +počitadlo kostek by to byl nesmysl. Systémy si toho všimnou: + +- **macOS** stažený soubor označí za karanténní a odmítne ho spustit. Buď + značku sundej (`xattr -dr com.apple.quarantine kostky-macos-arm64`), nebo + soubor stáhni rovnou z terminálu přes `curl -LO` — tudy se karanténa + nenastavuje. +- **Windows** ukáže modré okno SmartScreenu. *Další informace* → + *Přesto spustit*. Občas si postěžuje i antivirus; u zabalených Python + programů je to běžný falešný poplach. Při prvním spuštění se ještě zeptá + brána firewall — bez povolení do místní sítě se telefon nepřipojí. + +Kdo tomu nechce věřit, ověří si stažený soubor podle přiloženého součtu +(níž) nebo si ho postaví sám — `./build.sh`, zdrojáky jsou tady celé. Ze zdrojáků to je stejně krátké: @@ -53,7 +82,9 @@ server vypíše při startu. | Spuštěno | Databáze | |---|---| -| binárkou | `~/.local/share/kostky/dice.db` | +| binárkou na Linuxu | `~/.local/share/kostky/dice.db` | +| binárkou na macOS | `~/Library/Application Support/kostky/dice.db` | +| binárkou na Windows | `%LOCALAPPDATA%\kostky\dice.db` | | ze zdrojáků | `dice.db` v pracovním adresáři | Binárka se při každém spuštění rozbaluje do dočasného adresáře a pouští se @@ -101,6 +132,8 @@ při remíze na prvním místě se hraje dál. | `web.py` | Flask aplikace | | `dice.py` | totéž v terminálu, bez ukládání | | `build.sh`, `kostky.spec` | stavba binárky | +| `.gitea/workflows/` | testy a linuxová binárka doma | +| `.github/workflows/` | binárky pro Windows a macOS | Terminálová verze zůstává funkční jako záloha: @@ -135,26 +168,44 @@ Staví se proti samostatnému CPythonu od `uv`, ne proti systémovému. Ten je slinkovaný s glibc 2.17; postavené proti Pythonu z Fedory 44 by to chtělo glibc 2.43 a nešlo by spustit skoro nikde — glibc drží zpětnou kompatibilitu, ne dopřednou. Nic z hostitele se do binárky nedostane, takže kontejner k tomu -potřeba není. Výsledek je v `dist/kostky`. +potřeba není. Výsledek je v `dist/`. + +Stejný `build.sh` staví i na macOS a na Windows (v Git Bash) — jen tam bez +té starosti o glibc. Křížem to nejde: PyInstaller balí do výsledku interpret +a knihovny toho systému, na kterém běží, takže **binárku pro každý systém +musí postavit ten systém.** Terminálová verze součástí binárky není; ta se pouští ze zdrojáků. -Releasy si tohle dělají samy. Otagovaný commit spustí -`.gitea/workflows/binarka.yml`, ta binárku postaví, projede testy, zkusí -ji nastartovat a pověsí ji na release jako `kostky-linux-x86_64`: +### Jak vznikají releasy + +Otagovaný commit spustí stavbu na obou stranách: + +| Kde | Co staví | Workflow | +|---|---|---| +| vlastní runner u Gitey | Linux | `.gitea/workflows/binarka.yml` | +| GitHub Actions | Windows, macOS ×2 | `.github/workflows/binarky.yml` | ```bash git tag v1.0.0 && git push origin v1.0.0 ``` -Vedle binárky se věší i `kostky-linux-x86_64.sha256`, takže se stažený -soubor dá ověřit: +GitHub je tu jen půjčená dílna. Repozitář se tam z Gitey zrcadlí +([mirror](https://github.com/david-spacil/dice-counter)), postavené soubory +se posílají zpátky na zdejší release přes Gitea API a projekt má pořád jednu +stránku s releasy — tuhle. Linux se na GitHubu schválně nestaví; doma to jde +proti staré glibc, a když GitHub vypadne, release má aspoň tu platformu, +na které to reálně poběží. + +Každá stavba nejdřív projede testy a zkusí hotovou binárku nastartovat, než +ji kamkoli pověsí. Vedle každé visí i `.sha256`, takže se stažený soubor dá +ověřit: ```bash -sha256sum -c kostky-linux-x86_64.sha256 +sha256sum -c kostky-linux-x86_64.sha256 # na macOS: shasum -a 256 -c ``` -Stejná stavba běží i nad každým pull requestem, jen se nikam nevěší. +Nad pull requesty se Linux staví taky, jen se nikam nevěší. ## Testy @@ -165,5 +216,5 @@ uv run --with-requirements requirements.txt --no-project pytest Nebo z připraveného prostředí prostě `pytest`. Nad každým pull requestem a nad `main` běží `.gitea/workflows/testy.yml` — -84 testů na Pythonu 3.11, 3.12, 3.13 i 3.14, a k tomu skriptovaná partie +99 testů na Pythonu 3.11, 3.12, 3.13 i 3.14, a k tomu skriptovaná partie v terminálové verzi, na kterou pytest nesahá. diff --git a/build.sh b/build.sh index ea0f553..ee0cf48 100755 --- a/build.sh +++ b/build.sh @@ -1,11 +1,15 @@ #!/usr/bin/env bash -# Postaví jednosouborovou binárku pro Linux do dist/kostky. +# Postaví jednosouborovou binárku do dist/ — pro systém, na kterém běží. # -# Staví se proti samostatnému CPythonu od uv, ne proti systémovému. Ten je -# slinkovaný s glibc 2.17, takže binárka jede i na letitých systémech. -# Postavená proti Pythonu z Fedory 44 by chtěla glibc 2.43 a nešla by -# spustit skoro nikde — glibc drží zpětnou kompatibilitu, ne dopřednou. -# Nic z hostitele se do ní nedostane, takže kontejner k tomu není potřeba. +# PyInstaller neumí křížovou kompilaci: binárku pro každý systém musí postavit +# ten systém. Linux se staví doma na vlastním runneru, Windows a macOS na +# půjčených strojích GitHub Actions (.github/workflows/binarky.yml). +# +# Staví se proti samostatnému CPythonu od uv, ne proti systémovému. Na Linuxu +# je slinkovaný s glibc 2.17, takže binárka jede i na letitých systémech. +# Postavená proti Pythonu z Fedory 44 by chtěla glibc 2.43 a nešla by spustit +# skoro nikde — glibc drží zpětnou kompatibilitu, ne dopřednou. Nic z hostitele +# se do ní nedostane, takže kontejner k tomu není potřeba. set -euo pipefail VERSION=${VERSION:-3.11} @@ -13,10 +17,19 @@ BUILD=${BUILD:-.build} uv python install "$VERSION" uv venv --quiet --managed-python --python "$VERSION" "$BUILD" -uv pip install --quiet --python "$BUILD/bin/python" -r requirements.txt pyinstaller -"$BUILD/bin/pyinstaller" --clean --noconfirm \ +# Windows dává spustitelné soubory venvu do Scripts/, zbytek světa do bin/. +python="$BUILD/bin/python" +pyinstaller="$BUILD/bin/pyinstaller" +if [ -d "$BUILD/Scripts" ]; then + python="$BUILD/Scripts/python.exe" + pyinstaller="$BUILD/Scripts/pyinstaller.exe" +fi + +uv pip install --quiet --python "$python" -r requirements.txt pyinstaller + +"$pyinstaller" --clean --noconfirm \ --distpath dist --workpath "$BUILD/work" kostky.spec echo -ls -lh dist/kostky +ls -lh dist/ diff --git a/kostky.spec b/kostky.spec index 857bd78..68164e8 100644 --- a/kostky.spec +++ b/kostky.spec @@ -5,6 +5,8 @@ Staví se přes `./build.sh`, ne ručně — ta se stará o to, aby výsledek je i na starších systémech, než je ten tvůj. """ +import sys + analysis = Analysis( ["web.py"], pathex=["."], @@ -21,6 +23,9 @@ exe = EXE( analysis.datas, name="kostky", console=True, - strip=True, + # Ořezání symbolů šetří pár megabajtů, ale jen na Linuxu. Na macOS by + # rozbilo podpis, který si PyInstaller sám přidává a bez kterého se + # binárka na Apple Silicon vůbec nespustí; na Windows nedělá nic. + strip=sys.platform == "linux", upx=False, )