Zamknout závislosti
Bez zamčených verzí si každá stavba stáhne to, co je zrovna nejnovější: binárka z dneška a binárka z příštího jara obsahují něco jiného a není jak zjistit co. Zároveň může vydání kterékoli knihovny rozbít build, aniž by se sáhlo na řádku kódu — a nejspíš zrovna ve chvíli, kdy se tagne release. Volné seznamy jsou nově v `.in`, zamčené v `.txt`. Rozděleno na tři: běh, testy a stavba. Pytest se tak přestane instalovat do prostředí, kde se staví binárka, a PyInstaller má konečně taky zamčenou verzi — ta na podobu výsledku sahá ze všech nejvíc. Zůstává, že se verze píšou i v hlavičce PEP 723, jinak by přestalo fungovat `uv run web.py`. Aby se ta dvě místa nerozešla, hlídá je test.
This commit is contained in:
1 parent
e0413b6adf
commit
3af8fe22ef
14 files changed
+202
-11
No files matched your search
@@ -53,7 +53,7 @@ jobs:
|
|||||||
uv --version
|
uv --version
|
||||||
|
|
||||||
- name: Testy
|
- name: Testy
|
||||||
run: uv run --with-requirements requirements.txt --no-project pytest -q
|
run: uv run --with-requirements requirements-dev.txt --no-project pytest -q
|
||||||
|
|
||||||
- name: Binárka
|
- name: Binárka
|
||||||
run: ./build.sh
|
run: ./build.sh
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
- name: Testy
|
- name: Testy
|
||||||
run: |
|
run: |
|
||||||
uv run --managed-python --python "${{ matrix.python }}" \
|
uv run --managed-python --python "${{ matrix.python }}" \
|
||||||
--with-requirements requirements.txt --no-project \
|
--with-requirements requirements-dev.txt --no-project \
|
||||||
pytest -q
|
pytest -q
|
||||||
|
|
||||||
- name: Terminálová verze
|
- name: Terminálová verze
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
- uses: astral-sh/setup-uv@v10.0.1
|
- uses: astral-sh/setup-uv@v10.0.1
|
||||||
|
|
||||||
- name: Testy
|
- name: Testy
|
||||||
run: uv run --with-requirements requirements.txt --no-project pytest -q
|
run: uv run --with-requirements requirements-dev.txt --no-project pytest -q
|
||||||
|
|
||||||
- name: Binárka
|
- name: Binárka
|
||||||
run: ./build.sh
|
run: ./build.sh
|
||||||
|
|||||||
@@ -155,10 +155,34 @@ python -m venv .venv
|
|||||||
Na Fedoře stačí i systémové balíčky, pak se nemusí řešit vůbec nic:
|
Na Fedoře stačí i systémové balíčky, pak se nemusí řešit vůbec nic:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dnf install python3-flask python3-qrcode python3-tabulate
|
sudo dnf install python3-flask python3-qrcode python3-tabulate python3-waitress
|
||||||
python3 web.py
|
python3 web.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Závislosti
|
||||||
|
|
||||||
|
Verze jsou zamčené, aby binárka postavená dnes a za půl roku obsahovala
|
||||||
|
totéž. Volné seznamy jsou v `.in`, zamčené v `.txt`:
|
||||||
|
|
||||||
|
| Soubor | K čemu |
|
||||||
|
|---|---|
|
||||||
|
| `requirements.in` → `.txt` | běh aplikace |
|
||||||
|
| `requirements-dev.in` → `.txt` | + pytest, pro testy |
|
||||||
|
| `requirements-build.in` → `.txt` | + PyInstaller, pro stavbu binárky |
|
||||||
|
|
||||||
|
Aktualizace je vědomý krok, ne vedlejší efekt toho, že něco vyšlo na PyPI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for f in requirements requirements-dev requirements-build; do
|
||||||
|
uv pip compile "$f.in" -o "$f.txt"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Verze se píšou ještě jednou v hlavičce PEP 723 na začátku `web.py` a
|
||||||
|
`dice.py`, aby fungovalo `uv run web.py`. Že se ty dva zápisy shodují, hlídá
|
||||||
|
`tests/test_zavislosti.py` — po každém přegenerování je potřeba hlavičky
|
||||||
|
srovnat.
|
||||||
|
|
||||||
## Vlastní binárka
|
## Vlastní binárka
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ if [ -d "$BUILD/Scripts" ]; then
|
|||||||
pyinstaller="$BUILD/Scripts/pyinstaller.exe"
|
pyinstaller="$BUILD/Scripts/pyinstaller.exe"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
uv pip install --quiet --python "$python" -r requirements.txt pyinstaller
|
uv pip install --quiet --python "$python" -r requirements-build.txt
|
||||||
|
|
||||||
"$pyinstaller" --clean --noconfirm \
|
"$pyinstaller" --clean --noconfirm \
|
||||||
--distpath dist --workpath "$BUILD/work" kostky.spec
|
--distpath dist --workpath "$BUILD/work" kostky.spec
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# /// script
|
# /// script
|
||||||
# requires-python = ">=3.11"
|
# requires-python = ">=3.11"
|
||||||
# dependencies = ["tabulate"]
|
# dependencies = ["tabulate==0.10.0"]
|
||||||
# ///
|
# ///
|
||||||
"""Počitadlo skóre v terminálu.
|
"""Počitadlo skóre v terminálu.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Co je potřeba ke stavbě binárky. Zamyká se do requirements-build.txt:
|
||||||
|
# uv pip compile requirements-build.in -o requirements-build.txt
|
||||||
|
-r requirements.in
|
||||||
|
pyinstaller
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile requirements-build.in -o requirements-build.txt --python-version 3.11
|
||||||
|
altgraph==0.17.5
|
||||||
|
# via pyinstaller
|
||||||
|
blinker==1.9.0
|
||||||
|
# via flask
|
||||||
|
click==8.4.2
|
||||||
|
# via flask
|
||||||
|
flask==3.1.3
|
||||||
|
# via -r requirements.in
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.6
|
||||||
|
# via flask
|
||||||
|
markupsafe==3.0.3
|
||||||
|
# via
|
||||||
|
# flask
|
||||||
|
# jinja2
|
||||||
|
# werkzeug
|
||||||
|
packaging==26.3
|
||||||
|
# via
|
||||||
|
# pyinstaller
|
||||||
|
# pyinstaller-hooks-contrib
|
||||||
|
pyinstaller==6.22.2
|
||||||
|
# via -r requirements-build.in
|
||||||
|
pyinstaller-hooks-contrib==2026.6
|
||||||
|
# via pyinstaller
|
||||||
|
qrcode==8.2
|
||||||
|
# via -r requirements.in
|
||||||
|
setuptools==84.0.0
|
||||||
|
# via pyinstaller
|
||||||
|
tabulate==0.10.0
|
||||||
|
# via -r requirements.in
|
||||||
|
waitress==3.0.2
|
||||||
|
# via -r requirements.in
|
||||||
|
werkzeug==3.1.8
|
||||||
|
# via flask
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Co je potřeba k vývoji a testům. Zamyká se do requirements-dev.txt:
|
||||||
|
# uv pip compile requirements-dev.in -o requirements-dev.txt
|
||||||
|
-r requirements.in
|
||||||
|
pytest
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile requirements-dev.in -o requirements-dev.txt --python-version 3.11
|
||||||
|
blinker==1.9.0
|
||||||
|
# via flask
|
||||||
|
click==8.4.2
|
||||||
|
# via flask
|
||||||
|
flask==3.1.3
|
||||||
|
# via -r requirements.in
|
||||||
|
iniconfig==2.3.0
|
||||||
|
# via pytest
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.6
|
||||||
|
# via flask
|
||||||
|
markupsafe==3.0.3
|
||||||
|
# via
|
||||||
|
# flask
|
||||||
|
# jinja2
|
||||||
|
# werkzeug
|
||||||
|
packaging==26.3
|
||||||
|
# via pytest
|
||||||
|
pluggy==1.6.0
|
||||||
|
# via pytest
|
||||||
|
pygments==2.21.0
|
||||||
|
# via pytest
|
||||||
|
pytest==9.1.1
|
||||||
|
# via -r requirements-dev.in
|
||||||
|
qrcode==8.2
|
||||||
|
# via -r requirements.in
|
||||||
|
tabulate==0.10.0
|
||||||
|
# via -r requirements.in
|
||||||
|
waitress==3.0.2
|
||||||
|
# via -r requirements.in
|
||||||
|
werkzeug==3.1.8
|
||||||
|
# via flask
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Běhové závislosti. Verze se zamykají do requirements.txt přes:
|
||||||
|
# uv pip compile requirements.in -o requirements.txt
|
||||||
|
flask
|
||||||
|
qrcode
|
||||||
|
tabulate
|
||||||
|
waitress
|
||||||
+25
-4
@@ -1,4 +1,25 @@
|
|||||||
flask
|
# This file was autogenerated by uv via the following command:
|
||||||
qrcode
|
# uv pip compile requirements.in -o requirements.txt --python-version 3.11
|
||||||
tabulate
|
blinker==1.9.0
|
||||||
pytest
|
# via flask
|
||||||
|
click==8.4.2
|
||||||
|
# via flask
|
||||||
|
flask==3.1.3
|
||||||
|
# via -r requirements.in
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.6
|
||||||
|
# via flask
|
||||||
|
markupsafe==3.0.3
|
||||||
|
# via
|
||||||
|
# flask
|
||||||
|
# jinja2
|
||||||
|
# werkzeug
|
||||||
|
qrcode==8.2
|
||||||
|
# via -r requirements.in
|
||||||
|
tabulate==0.10.0
|
||||||
|
# via -r requirements.in
|
||||||
|
waitress==3.0.2
|
||||||
|
# via -r requirements.in
|
||||||
|
werkzeug==3.1.8
|
||||||
|
# via flask
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"""Závislosti jsou zamčené a všechna místa se shodnou.
|
||||||
|
|
||||||
|
Verze se píšou na dvou místech: v `requirements.txt` pro stavbu binárky
|
||||||
|
a v hlavičce PEP 723 pro `uv run web.py`. Ta dvě místa se dřív nebo později
|
||||||
|
rozejdou — leda že by to někdo hlídal. Proto tenhle soubor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).parent.parent
|
||||||
|
|
||||||
|
# Skript a to, co si podle hlavičky nese s sebou.
|
||||||
|
SCRIPTS = {"web.py": {"flask", "qrcode", "waitress"},
|
||||||
|
"dice.py": {"tabulate"}}
|
||||||
|
|
||||||
|
|
||||||
|
def pins(name: str) -> dict[str, str]:
|
||||||
|
"""Zamčené verze z compilovaného requirements souboru."""
|
||||||
|
text = (ROOT / name).read_text(encoding="utf-8")
|
||||||
|
return {m.group(1).lower(): m.group(2)
|
||||||
|
for m in re.finditer(r"^([A-Za-z0-9_.\-]+)==(\S+)", text, re.M)}
|
||||||
|
|
||||||
|
|
||||||
|
def header(script: str) -> dict[str, str]:
|
||||||
|
"""Závislosti z hlavičky PEP 723 na začátku skriptu."""
|
||||||
|
text = (ROOT / script).read_text(encoding="utf-8")
|
||||||
|
line = re.search(r"^# dependencies = \[(.*)\]$", text, re.M)
|
||||||
|
assert line, f"{script} nemá hlavičku PEP 723"
|
||||||
|
|
||||||
|
return {m.group(1).lower(): m.group(2)
|
||||||
|
for m in re.finditer(r'"([A-Za-z0-9_.\-]+)==(\S+?)"', line.group(1))}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", ["requirements.txt", "requirements-dev.txt",
|
||||||
|
"requirements-build.txt"])
|
||||||
|
def test_zavislosti_jsou_zamcene(name):
|
||||||
|
"""Bez zamčených verzí se binárka z března liší od binárky z dubna."""
|
||||||
|
lines = [line.strip() for line in (ROOT / name).read_text().splitlines()]
|
||||||
|
volne = [line for line in lines
|
||||||
|
if line and not line.startswith("#") and "==" not in line]
|
||||||
|
|
||||||
|
assert not volne, f"{name} má nezamčené závislosti: {volne}"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("script", sorted(SCRIPTS))
|
||||||
|
def test_hlavicka_sedi_s_requirements(script):
|
||||||
|
"""`uv run web.py` musí dostat totéž, co se zabalí do binárky."""
|
||||||
|
zamcene = pins("requirements.txt")
|
||||||
|
v_hlavicce = header(script)
|
||||||
|
|
||||||
|
assert set(v_hlavicce) == SCRIPTS[script]
|
||||||
|
|
||||||
|
for balicek, verze in v_hlavicce.items():
|
||||||
|
assert balicek in zamcene, f"{balicek} chybí v requirements.txt"
|
||||||
|
assert verze == zamcene[balicek], (
|
||||||
|
f"{script}: {balicek}=={verze}, ale zamčeno je "
|
||||||
|
f"{zamcene[balicek]} — přepiš hlavičku")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# /// script
|
# /// script
|
||||||
# requires-python = ">=3.11"
|
# requires-python = ">=3.11"
|
||||||
# dependencies = ["flask", "qrcode"]
|
# dependencies = ["flask==3.1.3", "qrcode==8.2", "waitress==3.0.2"]
|
||||||
# ///
|
# ///
|
||||||
"""Webové počitadlo pro domácí síť.
|
"""Webové počitadlo pro domácí síť.
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user