Merge pull request 'Binárky pro Windows a macOS' (#9) from agents/claude/binarky-win-mac into main
Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
commit
68a9f1d19f
8 files changed
+337
-31
No files matched your search
@@ -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
|
||||||
@@ -8,16 +8,45 @@ ten samý Adam jako z minulého měsíce. Žádné účty, žádná hesla.
|
|||||||
|
|
||||||
## Spuštění
|
## Spuštění
|
||||||
|
|
||||||
Stáhni binárku z [releases](https://gitea.spacilovi.eu/david-spacil/dice-counter/releases)
|
Stáhni binárku pro svůj systém z
|
||||||
a spusť ji:
|
[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
|
```bash
|
||||||
chmod +x kostky
|
chmod +x kostky-*
|
||||||
./kostky
|
./kostky-linux-x86_64
|
||||||
```
|
```
|
||||||
|
|
||||||
Nic se neinstaluje — Python, Flask i šablony jsou uvnitř. Zatím jen pro Linux
|
Na Windows stačí na `.exe` poklepat.
|
||||||
na x86-64; postavené proti glibc 2.17, takže jede prakticky všude.
|
|
||||||
|
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é:
|
Ze zdrojáků to je stejně krátké:
|
||||||
|
|
||||||
@@ -53,7 +82,9 @@ server vypíše při startu.
|
|||||||
|
|
||||||
| Spuštěno | Databáze |
|
| 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 |
|
| 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
|
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 |
|
| `web.py` | Flask aplikace |
|
||||||
| `dice.py` | totéž v terminálu, bez ukládání |
|
| `dice.py` | totéž v terminálu, bez ukládání |
|
||||||
| `build.sh`, `kostky.spec` | stavba binárky |
|
| `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:
|
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
|
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,
|
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
|
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ů.
|
Terminálová verze součástí binárky není; ta se pouští ze zdrojáků.
|
||||||
|
|
||||||
Releasy si tohle dělají samy. Otagovaný commit spustí
|
### Jak vznikají releasy
|
||||||
`.gitea/workflows/binarka.yml`, ta binárku postaví, projede testy, zkusí
|
|
||||||
ji nastartovat a pověsí ji na release jako `kostky-linux-x86_64`:
|
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
|
```bash
|
||||||
git tag v1.0.0 && git push origin v1.0.0
|
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ý
|
GitHub je tu jen půjčená dílna. Repozitář se tam z Gitey zrcadlí
|
||||||
soubor dá ověřit:
|
([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
|
```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
|
## Testy
|
||||||
|
|
||||||
@@ -165,5 +216,5 @@ uv run --with-requirements requirements.txt --no-project pytest
|
|||||||
Nebo z připraveného prostředí prostě `pytest`.
|
Nebo z připraveného prostředí prostě `pytest`.
|
||||||
|
|
||||||
Nad každým pull requestem a nad `main` běží `.gitea/workflows/testy.yml` —
|
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á.
|
v terminálové verzi, na kterou pytest nesahá.
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
# PyInstaller neumí křížovou kompilaci: binárku pro každý systém musí postavit
|
||||||
# slinkovaný s glibc 2.17, takže binárka jede i na letitých systémech.
|
# ten systém. Linux se staví doma na vlastním runneru, Windows a macOS na
|
||||||
# Postavená proti Pythonu z Fedory 44 by chtěla glibc 2.43 a nešla by
|
# půjčených strojích GitHub Actions (.github/workflows/binarky.yml).
|
||||||
# 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.
|
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION=${VERSION:-3.11}
|
VERSION=${VERSION:-3.11}
|
||||||
@@ -13,10 +17,19 @@ BUILD=${BUILD:-.build}
|
|||||||
|
|
||||||
uv python install "$VERSION"
|
uv python install "$VERSION"
|
||||||
uv venv --quiet --managed-python --python "$VERSION" "$BUILD"
|
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
|
--distpath dist --workpath "$BUILD/work" kostky.spec
|
||||||
|
|
||||||
echo
|
echo
|
||||||
ls -lh dist/kostky
|
ls -lh dist/
|
||||||
+6
-1
@@ -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.
|
i na starších systémech, než je ten tvůj.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
analysis = Analysis(
|
analysis = Analysis(
|
||||||
["web.py"],
|
["web.py"],
|
||||||
pathex=["."],
|
pathex=["."],
|
||||||
@@ -21,6 +23,9 @@ exe = EXE(
|
|||||||
analysis.datas,
|
analysis.datas,
|
||||||
name="kostky",
|
name="kostky",
|
||||||
console=True,
|
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,
|
upx=False,
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,9 @@ adresy a obecný typ rozhraní, ne to, co má kdo zrovna nainstalované.
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import socket
|
import socket
|
||||||
|
import subprocess
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
# Rozsah, ze kterého adresy přiděluje Tailscale (RFC 6598, CGNAT).
|
# Rozsah, ze kterého adresy přiděluje Tailscale (RFC 6598, CGNAT).
|
||||||
@@ -123,8 +125,45 @@ def _from_interfaces() -> list[tuple[str, str]]:
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ifconfig(text: str) -> list[tuple[str, str]]:
|
||||||
|
"""Rozhraní a jejich IPv4 adresy z výstupu `ifconfig`.
|
||||||
|
|
||||||
|
Oddělená čistá funkce, ať se dá otestovat i bez macOS pod rukama.
|
||||||
|
"""
|
||||||
|
found = []
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
for line in text.splitlines():
|
||||||
|
head = re.match(r"(\S+):", line)
|
||||||
|
if head:
|
||||||
|
name = head.group(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
addr = re.search(r"\binet (\d+\.\d+\.\d+\.\d+)", line)
|
||||||
|
if addr:
|
||||||
|
found.append((name, addr.group(1)))
|
||||||
|
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def _from_ifconfig() -> list[tuple[str, str]]:
|
||||||
|
"""Adresy přes `ifconfig`. Záloha pro macOS, kde ioctl výše nesedí.
|
||||||
|
|
||||||
|
Spouští se jen tam, kde předchozí cesta nic nenašla, takže na Linuxu
|
||||||
|
se neplatí nic. Trvá jednotky milisekund, cachovat to nemá cenu.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
done = subprocess.run(["ifconfig", "-a"], capture_output=True,
|
||||||
|
text=True, timeout=5, check=True)
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return []
|
||||||
|
|
||||||
|
return parse_ifconfig(done.stdout)
|
||||||
|
|
||||||
|
|
||||||
def _from_hostname() -> list[tuple[str, str]]:
|
def _from_hostname() -> list[tuple[str, str]]:
|
||||||
"""Záloha pro systémy, kde předchozí cesta nefunguje."""
|
"""Poslední záloha. Na Windows vrátí adresy všech rozhraní, jinde
|
||||||
|
obvykle jen jednu — jména rozhraní se takhle nedozvíme."""
|
||||||
try:
|
try:
|
||||||
infos = socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET)
|
infos = socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET)
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -144,7 +183,7 @@ def addresses() -> list[Address]:
|
|||||||
|
|
||||||
primary = default_route_address()
|
primary = default_route_address()
|
||||||
|
|
||||||
found = _from_interfaces() or _from_hostname()
|
found = _from_interfaces() or _from_ifconfig() or _from_hostname()
|
||||||
if primary and primary not in [ip for _name, ip in found]:
|
if primary and primary not in [ip for _name, ip in found]:
|
||||||
found.append(("", primary))
|
found.append(("", primary))
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -16,20 +16,30 @@ from pathlib import Path
|
|||||||
|
|
||||||
from core import FINAL_SCORE, Game, Turn
|
from core import FINAL_SCORE, Game, Turn
|
||||||
|
|
||||||
|
def data_home() -> Path:
|
||||||
|
"""Adresář, kam podle zvyklostí systému patří data aplikací."""
|
||||||
|
if sys.platform == "win32":
|
||||||
|
local = os.environ.get("LOCALAPPDATA")
|
||||||
|
return Path(local) if local else Path.home() / "AppData" / "Local"
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
return Path.home() / "Library" / "Application Support"
|
||||||
|
return Path(os.environ.get("XDG_DATA_HOME")
|
||||||
|
or Path.home() / ".local" / "share")
|
||||||
|
|
||||||
|
|
||||||
def default_db() -> Path:
|
def default_db() -> Path:
|
||||||
"""Kam se ukládá databáze.
|
"""Kam se ukládá databáze.
|
||||||
|
|
||||||
Ze zdrojáků do pracovního adresáře — dá se tak mít víc rozehraných
|
Ze zdrojáků do pracovního adresáře — dá se tak mít víc rozehraných
|
||||||
sad vedle sebe a je vidět, s čím se pracuje. Z binárky do datového
|
sad vedle sebe a je vidět, s čím se pracuje. Z binárky do datového
|
||||||
adresáře podle XDG: binárka se spouští odkudkoli a rozbaluje se do
|
adresáře podle systému: binárka se spouští odkudkoli a rozbaluje se do
|
||||||
dočasného adresáře, takže relativní cesta by databázi rozsypala po disku.
|
dočasného adresáře, takže relativní cesta by databázi rozsypala po disku.
|
||||||
"""
|
"""
|
||||||
chosen = os.environ.get("DICE_DB")
|
chosen = os.environ.get("DICE_DB")
|
||||||
if chosen:
|
if chosen:
|
||||||
return Path(chosen)
|
return Path(chosen)
|
||||||
if getattr(sys, "frozen", False):
|
if getattr(sys, "frozen", False):
|
||||||
share = os.environ.get("XDG_DATA_HOME") or Path.home() / ".local" / "share"
|
return data_home() / "kostky" / "dice.db"
|
||||||
return Path(share) / "kostky" / "dice.db"
|
|
||||||
return Path("dice.db")
|
return Path("dice.db")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,3 +90,50 @@ def test_v_systemu_se_neco_najde():
|
|||||||
|
|
||||||
assert all(net.classify(a.ip, a.interface) for a in found)
|
assert all(net.classify(a.ip, a.interface) for a in found)
|
||||||
assert len({a.ip for a in found}) == len(found)
|
assert len({a.ip for a in found}) == len(found)
|
||||||
|
|
||||||
|
|
||||||
|
IFCONFIG_MACOS = """\
|
||||||
|
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
|
||||||
|
\toptions=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
|
||||||
|
\tinet 127.0.0.1 netmask 0xff000000
|
||||||
|
\tinet6 ::1 prefixlen 128
|
||||||
|
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
|
||||||
|
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
|
||||||
|
\tether 3c:22:fb:aa:bb:cc
|
||||||
|
\tinet6 fe80::14b0:6b1a:9e2f:1a4c%en0 prefixlen 64 secured scopeid 0xb
|
||||||
|
\tinet 192.168.0.42 netmask 0xffffff00 broadcast 192.168.0.255
|
||||||
|
\tmedia: autoselect
|
||||||
|
\tstatus: active
|
||||||
|
utun4: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1280
|
||||||
|
\tinet 100.99.98.97 --> 100.99.98.97 netmask 0xff000000
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_ifconfig_vytahne_rozhrani_a_adresy():
|
||||||
|
"""macOS na ioctl z Linuxu neslyší, tak se čte výstup ifconfigu."""
|
||||||
|
assert net.parse_ifconfig(IFCONFIG_MACOS) == [
|
||||||
|
("lo0", "127.0.0.1"),
|
||||||
|
("en0", "192.168.0.42"),
|
||||||
|
("utun4", "100.99.98.97"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_ifconfig_si_neplete_inet6_s_inet():
|
||||||
|
"""`inet6` začíná stejně jako `inet`; IPv6 adresy tudy projít nesmí."""
|
||||||
|
assert net.parse_ifconfig("en0: flags=1\n\tinet6 fe80::1 prefixlen 64\n") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_adresy_z_ifconfigu_projdou_celym_retezem(monkeypatch):
|
||||||
|
"""Bez linuxového ioctl se sáhne po ifconfigu, teprve pak po hostname."""
|
||||||
|
monkeypatch.delenv("DICE_HOST", raising=False)
|
||||||
|
monkeypatch.setattr(net, "_from_interfaces", lambda: [])
|
||||||
|
monkeypatch.setattr(net, "_from_ifconfig",
|
||||||
|
lambda: net.parse_ifconfig(IFCONFIG_MACOS))
|
||||||
|
monkeypatch.setattr(net, "_from_hostname", lambda: pytest.fail("moc brzy"))
|
||||||
|
monkeypatch.setattr(net, "default_route_address", lambda: "192.168.0.42")
|
||||||
|
|
||||||
|
found = net.addresses()
|
||||||
|
|
||||||
|
assert [a.ip for a in found] == ["192.168.0.42", "100.99.98.97"]
|
||||||
|
assert found[0].primary and found[0].interface == "en0"
|
||||||
|
assert found[1].kind == "tailnet"
|
||||||
@@ -148,11 +148,28 @@ def test_umisteni_databaze_z_binarky(monkeypatch, tmp_path):
|
|||||||
"""Binárka se rozbaluje do dočasného adresáře, databáze tam nesmí."""
|
"""Binárka se rozbaluje do dočasného adresáře, databáze tam nesmí."""
|
||||||
monkeypatch.delenv("DICE_DB", raising=False)
|
monkeypatch.delenv("DICE_DB", raising=False)
|
||||||
monkeypatch.setattr(storage.sys, "frozen", True, raising=False)
|
monkeypatch.setattr(storage.sys, "frozen", True, raising=False)
|
||||||
|
monkeypatch.setattr(storage.sys, "platform", "linux")
|
||||||
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
||||||
|
|
||||||
assert storage.default_db() == tmp_path / "kostky" / "dice.db"
|
assert storage.default_db() == tmp_path / "kostky" / "dice.db"
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_home_podle_systemu(monkeypatch, tmp_path):
|
||||||
|
"""Každý systém má svůj adresář na data; XDG je jen ten linuxový."""
|
||||||
|
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path / "AppData" / "Local"))
|
||||||
|
monkeypatch.setattr(Path, "home", classmethod(lambda cls: tmp_path))
|
||||||
|
|
||||||
|
monkeypatch.setattr(storage.sys, "platform", "win32")
|
||||||
|
assert storage.data_home() == tmp_path / "AppData" / "Local"
|
||||||
|
|
||||||
|
monkeypatch.setattr(storage.sys, "platform", "darwin")
|
||||||
|
assert storage.data_home() == tmp_path / "Library" / "Application Support"
|
||||||
|
|
||||||
|
monkeypatch.setattr(storage.sys, "platform", "linux")
|
||||||
|
monkeypatch.delenv("XDG_DATA_HOME", raising=False)
|
||||||
|
assert storage.data_home() == tmp_path / ".local" / "share"
|
||||||
|
|
||||||
|
|
||||||
def test_dice_db_prebije_oboje(monkeypatch, tmp_path):
|
def test_dice_db_prebije_oboje(monkeypatch, tmp_path):
|
||||||
monkeypatch.setattr(storage.sys, "frozen", True, raising=False)
|
monkeypatch.setattr(storage.sys, "frozen", True, raising=False)
|
||||||
monkeypatch.setenv("DICE_DB", str(tmp_path / "jinde.db"))
|
monkeypatch.setenv("DICE_DB", str(tmp_path / "jinde.db"))
|
||||||
|
|||||||
Reference in new issue
Block a user