Bauen und Releases ohne lokale Abhaengigkeiten ueber ./build.sh
- build.sh (release, build, test) startet einen temporaeren Runner-Container (Python, Git, Docker-CLI), der die Build-Container ueber den Docker-Socket startet - release.py: Quellexport unter releases/ statt /tmp (Host-Pfade fuer Bind-Mounts), Test-Befehl in Docker, Git-Identitaet vorab pruefen, bei Commit-Fehler Versionen zuruecknehmen - Android-Build-Image enthaelt Platform-Tools fest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ecefd347b2
commit
298c142c14
@@ -4,6 +4,11 @@ Alle nennenswerten Änderungen an NetClip. Format nach [Keep a Changelog](https:
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Geändert
|
||||
- Bauen und Releases über `./build.sh` (release, build, test): läuft komplett in temporären
|
||||
Docker-Containern, auf dem Rechner wird außer Docker nichts mehr benötigt.
|
||||
- Android-Build-Image enthält die Platform-Tools fest (schnellere Builds).
|
||||
|
||||
## [1.0.1] - 2026-10-17
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
@@ -79,7 +79,8 @@ python -m netclip
|
||||
|
||||
### Pakete bauen
|
||||
|
||||
PyInstaller baut immer für das System, auf dem es läuft – also einmal je Betriebssystem:
|
||||
Windows, Linux und Android: `./build.sh build` (siehe [Bauen und Release erstellen](#bauen-und-release-erstellen)).
|
||||
Ohne Docker baut PyInstaller immer für das System, auf dem es läuft:
|
||||
|
||||
```bash
|
||||
cd client-desktop
|
||||
@@ -152,21 +153,27 @@ cd client-android
|
||||
Das Release-APK ist zum Testen mit dem Debug-Schlüssel signiert; für die Verteilung in `app/build.gradle`
|
||||
einen eigenen Keystore eintragen.
|
||||
|
||||
## Release erstellen
|
||||
## Bauen und Release erstellen
|
||||
|
||||
Die Version steht zentral in der Datei `VERSION`. Neue Änderungen kommen laufend in
|
||||
`CHANGELOG.md` unter `## [Unreleased]`. Ein Release:
|
||||
Auf dem Rechner wird **nur Docker** benötigt – Python, Git, JDK, Android-SDK, Wine usw. laufen in
|
||||
temporären Containern, die nach jedem Lauf wieder verschwinden (`docker run --rm`).
|
||||
|
||||
```bash
|
||||
python3 scripts/release.py # 1.0.0 -> 1.0.1 (patch), committen + taggen
|
||||
python3 scripts/release.py minor --build # 1.0.1 -> 1.1.0 und alle Pakete bauen
|
||||
python3 scripts/release.py --build-only # aktuellen Stand nur bauen
|
||||
./build.sh release # Version 1.0.1 -> 1.0.2 (patch), committen + taggen
|
||||
./build.sh release minor --build # -> 1.1.0 und alle Pakete bauen
|
||||
./build.sh build # aktuellen Stand (HEAD) bauen, ohne Versionswechsel
|
||||
./build.sh build --targets windows,android
|
||||
./build.sh test # Krypto-Kompatibilität Python <-> Kotlin prüfen
|
||||
git push --follow-tags
|
||||
```
|
||||
|
||||
Das Skript schreibt die neue Version in Server, Desktop-Client, Windows-Installer und Android-App
|
||||
Die Version steht zentral in der Datei `VERSION`. Neue Änderungen kommen laufend in
|
||||
`CHANGELOG.md` unter `## [Unreleased]`; ohne Einträge dort verweigert `release` den Release.
|
||||
Das Release schreibt die neue Version in Server, Desktop-Client, Windows-Installer und Android-App
|
||||
(`versionName`, `versionCode` +1) und macht aus `[Unreleased]` den neuen Versionsabschnitt.
|
||||
Mit `--build` wird ausschließlich in Docker gebaut (`docker-build/`), Ergebnis in `releases/v<version>/`:
|
||||
|
||||
Gebaut wird aus einem sauberen `git archive` (Release-Tag bzw. HEAD) – uncommittete Änderungen
|
||||
fließen nicht ein. Ergebnis in `releases/v<version>/`:
|
||||
|
||||
| Datei | Inhalt |
|
||||
|---|---|
|
||||
@@ -177,8 +184,11 @@ Mit `--build` wird ausschließlich in Docker gebaut (`docker-build/`), Ergebnis
|
||||
| `netclip-server-<v>.tar.gz` | Server-Image für `docker load -i …` (offline verteilbar) |
|
||||
| `RELEASE-NOTES.md`, `SHA256SUMS` | Änderungen und Prüfsummen |
|
||||
|
||||
Wie es funktioniert: `build.sh` startet einen Runner-Container (Python, Git, Docker-CLI), der über
|
||||
den Docker-Socket des Hosts die Build-Container aus `docker-build/` startet. Der erste Lauf lädt
|
||||
die Images (einige GB, vor allem Wine und Android-SDK), danach geht es aus dem Cache.
|
||||
|
||||
macOS kann nicht in Docker gebaut werden – dafür auf einem Mac in `client-desktop/` `python build.py`.
|
||||
Mit `--targets windows,android` lassen sich einzelne Pakete bauen.
|
||||
|
||||
**Android-Signatur:** Beim ersten Android-Build wird `secrets/netclip-release.jks` erzeugt (nicht im
|
||||
Git). Diesen Ordner sichern – ohne den Schlüssel lassen sich installierte Apps nicht mehr aktualisieren.
|
||||
@@ -186,10 +196,7 @@ Git). Diesen Ordner sichern – ohne den Schlüssel lassen sich installierte App
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
# Kompatibilität der Verschlüsselung Python <-> Kotlin
|
||||
python tools/interop.py gen /tmp/interop
|
||||
(cd client-android && ./gradlew testDebugUnitTest -Pinterop=/tmp/interop)
|
||||
python tools/interop.py check /tmp/interop
|
||||
./build.sh test # Kompatibilität der Verschlüsselung Python <-> Kotlin (in Docker)
|
||||
```
|
||||
|
||||
## Protokoll (für eigene Clients)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
# Bauen und Releases ohne lokale Abhaengigkeiten - auf dem Rechner wird nur Docker benoetigt.
|
||||
#
|
||||
# ./build.sh release [patch|minor|major] [--build] [--targets ...] Version hochzaehlen, committen, taggen
|
||||
# ./build.sh build [--targets windows,android] aktuellen Stand (HEAD) bauen
|
||||
# ./build.sh test Krypto-Kompatibilitaet Python <-> Kotlin
|
||||
#
|
||||
# Alles laeuft in temporaeren Containern (--rm). Ergebnis: releases/v<version>/
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
ROOT=$(pwd)
|
||||
|
||||
case "${1:-}" in
|
||||
release) shift ;;
|
||||
build) shift; set -- --build-only "$@" ;;
|
||||
test) shift; set -- --test "$@" ;;
|
||||
*) sed -n '2,8p' "$0"; exit 1 ;;
|
||||
esac
|
||||
|
||||
docker build -q -f docker-build/runner.Dockerfile -t netclip-build-runner docker-build > /dev/null
|
||||
|
||||
# Der Runner startet weitere Container ueber den Docker-Socket. Damit deren Bind-Mounts stimmen,
|
||||
# liegt das Repo im Runner unter demselben absoluten Pfad wie auf dem Host.
|
||||
SOCK=/var/run/docker.sock
|
||||
TTY=""; [ -t 0 ] && [ -t 1 ] && TTY="-it"
|
||||
GITCFG=""; [ -f "$HOME/.gitconfig" ] && GITCFG="-v $HOME/.gitconfig:/home/build/.gitconfig:ro"
|
||||
# Ersatz-Identitaet fuer Commits, falls user.name/user.email nicht gesetzt sind (wie git auf dem Host)
|
||||
GITNAME=""
|
||||
grep -qs "^[[:space:]]*name[[:space:]]*=" "$HOME/.gitconfig" || GITNAME="-e GIT_AUTHOR_NAME=$(id -un) -e GIT_COMMITTER_NAME=$(id -un)"
|
||||
|
||||
exec docker run --rm $TTY \
|
||||
--user "$(id -u):$(id -g)" --group-add "$(stat -c %g "$SOCK")" \
|
||||
-v "$SOCK:$SOCK" -v "$ROOT:$ROOT" -w "$ROOT" \
|
||||
-e HOME=/home/build $GITCFG $GITNAME -e EMAIL="$(id -un)@$(hostname -f 2>/dev/null || hostname)" \
|
||||
netclip-build-runner python3 scripts/release.py "$@"
|
||||
@@ -7,6 +7,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends unzip wget && r
|
||||
&& unzip -q tools.zip -d $ANDROID_HOME/cmdline-tools && mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest \
|
||||
&& rm tools.zip \
|
||||
&& yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null \
|
||||
&& $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platforms;android-35" "build-tools;35.0.0" > /dev/null
|
||||
&& $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platforms;android-35" "build-tools;35.0.0" "platform-tools" > /dev/null
|
||||
COPY docker-build/build-android.sh /usr/local/bin/build-android.sh
|
||||
CMD ["sh", "/usr/local/bin/build-android.sh"]
|
||||
|
||||
Regular → Executable
Regular → Executable
Regular → Executable
@@ -0,0 +1,6 @@
|
||||
# Temporaerer Steuer-Container fuer ./build.sh: Python + Git + Docker-CLI.
|
||||
# Startet die eigentlichen Build-Container ueber den Docker-Socket des Hosts.
|
||||
FROM python:3.12-slim-bookworm
|
||||
COPY --from=docker:27-cli /usr/local/bin/docker /usr/local/bin/docker
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* \
|
||||
&& git config --system --add safe.directory '*'
|
||||
+49
-4
@@ -1,6 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Release erstellen: Version hochzaehlen, CHANGELOG abschliessen, committen, taggen, optional bauen.
|
||||
|
||||
Normalerweise ueber ./build.sh aufrufen (laeuft komplett in Docker, nichts lokal zu installieren):
|
||||
./build.sh release [patch|minor|major] [--build] ./build.sh build ./build.sh test
|
||||
|
||||
Direkt (benoetigt python3, git, docker auf dem Rechner):
|
||||
python3 scripts/release.py # patch: 1.0.0 -> 1.0.1
|
||||
python3 scripts/release.py minor # 1.0.1 -> 1.1.0
|
||||
python3 scripts/release.py major # 1.1.0 -> 2.0.0
|
||||
@@ -131,7 +135,10 @@ def ensure_android_keystore():
|
||||
def build(version: str, ref: str, targets: list[str]):
|
||||
out = ROOT / "releases" / f"v{version}"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix="netclip-release-") as tmp:
|
||||
# Unterhalb des Repos statt /tmp: laeuft das Skript selbst im Runner-Container (build.sh),
|
||||
# muessen die Pfade fuer die Bind-Mounts der Build-Container auch auf dem Host gelten.
|
||||
(ROOT / "releases").mkdir(exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix=".src-", dir=ROOT / "releases") as tmp:
|
||||
src = Path(tmp) / "src"
|
||||
src.mkdir()
|
||||
archive = subprocess.run(["git", "archive", "--format=tar", ref], cwd=ROOT, check=True, capture_output=True).stdout
|
||||
@@ -169,6 +176,30 @@ def build(version: str, ref: str, targets: list[str]):
|
||||
print(f" {f.name:45} {f.stat().st_size / 1024 / 1024:8.1f} MB")
|
||||
|
||||
|
||||
def test():
|
||||
"""Krypto-Kompatibilitaet Python <-> Kotlin, komplett in den Build-Containern."""
|
||||
(ROOT / "releases").mkdir(exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix=".test-", dir=ROOT / "releases") as tmp:
|
||||
src = Path(tmp) / "src"
|
||||
data = Path(tmp) / "interop"
|
||||
src.mkdir()
|
||||
data.mkdir()
|
||||
archive = subprocess.run(["git", "archive", "--format=tar", "HEAD"], cwd=ROOT, check=True, capture_output=True).stdout
|
||||
subprocess.run(["tar", "-x", "-C", str(src)], input=archive, check=True)
|
||||
for name in ("linux", "android"):
|
||||
docker_image(name, src)
|
||||
user = ["--user", f"{os.getuid()}:{os.getgid()}", "-e", "HOME=/tmp"]
|
||||
py = ["docker", "run", "--rm", *user, "-v", f"{src}:/src:ro", "-v", f"{data}:/data", "netclip-build-linux",
|
||||
"python", "/src/tools/interop.py"]
|
||||
run(*py, "gen", "/data")
|
||||
run("docker", "run", "--rm", "-v", f"{src}:/src:ro", "-v", f"{data}:/data",
|
||||
"-v", "netclip-gradle-cache:/root/.gradle", "netclip-build-android", "sh", "-c",
|
||||
"cp -r /src/client-android /work && cd /work && rm -f local.properties && "
|
||||
"./gradlew --no-daemon -q testDebugUnitTest -Pinterop=/data; r=$?; "
|
||||
f"chown -R {os.getuid()}:{os.getgid()} /data; exit $r")
|
||||
run(*py, "check", "/data")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- Ablauf
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="NetClip-Release erstellen")
|
||||
@@ -177,12 +208,17 @@ def main():
|
||||
p.add_argument("--build-only", action="store_true", help="nur die aktuelle Version (HEAD) bauen")
|
||||
p.add_argument("--targets", default=",".join(TARGETS), help=f"Auswahl aus {','.join(TARGETS)}")
|
||||
p.add_argument("--allow-empty", action="store_true", help="auch ohne Eintraege unter [Unreleased]")
|
||||
p.add_argument("--test", action="store_true", help="nur Kompatibilitaetstest Python <-> Kotlin")
|
||||
args = p.parse_args()
|
||||
|
||||
if args.test:
|
||||
test()
|
||||
return
|
||||
|
||||
targets = [t.strip() for t in args.targets.split(",") if t.strip()]
|
||||
if unknown := set(targets) - set(TARGETS):
|
||||
die(f"Unbekannte Ziele: {', '.join(unknown)}")
|
||||
if (args.build or args.build_only) and not shutil.which("docker"):
|
||||
if not shutil.which("docker"):
|
||||
die("docker nicht gefunden")
|
||||
|
||||
current = (ROOT / "VERSION").read_text().strip()
|
||||
@@ -198,16 +234,25 @@ def main():
|
||||
if not notes and not args.allow_empty:
|
||||
die("Keine Eintraege unter '## [Unreleased]' im CHANGELOG (oder --allow-empty).")
|
||||
|
||||
if subprocess.run(["git", "var", "GIT_COMMITTER_IDENT"], cwd=ROOT, capture_output=True).returncode:
|
||||
die("Git kennt keinen Namen/E-Mail fuer Commits: git config --global user.name/user.email setzen")
|
||||
|
||||
new = bump(current, args.part)
|
||||
tag = f"v{new}"
|
||||
if run("git", "tag", "--list", tag, capture=True).strip():
|
||||
die(f"Tag {tag} existiert bereits")
|
||||
print(f"Release {current} -> {new}")
|
||||
|
||||
files = ["CHANGELOG.md", *VERSION_SPOTS.keys()]
|
||||
write_versions(new)
|
||||
close_changelog(new)
|
||||
run("git", "add", "VERSION", "CHANGELOG.md", "client-android/app/build.gradle", *VERSION_SPOTS.keys())
|
||||
run("git", "commit", "-q", "-m", f"Release {tag}\n\n{notes}".strip())
|
||||
try:
|
||||
run("git", "add", *files)
|
||||
run("git", "commit", "-q", "-m", f"Release {tag}\n\n{notes}".strip())
|
||||
except subprocess.CalledProcessError:
|
||||
run("git", "reset", "-q", "--", *files)
|
||||
run("git", "checkout", "--", *files)
|
||||
die("Commit fehlgeschlagen – Versionsaenderungen wurden zurueckgenommen.")
|
||||
run("git", "tag", "-a", tag, "-m", f"NetClip {new}\n\n{notes}".strip())
|
||||
|
||||
if args.build:
|
||||
|
||||
Reference in New Issue
Block a user