CI: build pipeline testet nun immer automatisch vor dem build alle Test. Ausserdem gibts einen deploy-pfad

This commit is contained in:
Rob committed 2026-10-07 20:44:51 +02:00
1 parent 971459c9bf
commit 2aad2c9d99
12 files changed
+543 -27

No files matched your search

+68 -15
View File
@@ -2,7 +2,8 @@
Liest die aktuelle Version aus CHANGELOG.md, bump't sie gemäß
Benutzereingabe (major/minor/patch), aktualisiert das Changelog
und baut via PyInstaller eine standalone .exe nach 2_build/.
führt zuerst alle Tests aus, baut via PyInstaller eine standalone .exe
nach 2_build/ und stellt sie anschließend als ZIP auf dem SMB-Laufwerk bereit.
Verwendung:
python build.py # interaktiver Modus
@@ -20,6 +21,8 @@ from datetime import date
from pathlib import Path
from typing import Optional
from deploy import deploy, write_release_manifest
# ── Pfade ───────────────────────────────────────────────────────
BUILD_PIPELINE_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = BUILD_PIPELINE_DIR.parent
@@ -177,6 +180,21 @@ def _build_sections(bump_type: str, message: Optional[str]) -> str:
# ── Build ───────────────────────────────────────────────────────
def _run_tests() -> None:
"""Test with the same interpreter used to build the application."""
try:
import pytest # noqa: F401
except ImportError:
subprocess.check_call([sys.executable, "-m", "pip", "install", "pytest"])
print("\n[INFO] Führe alle Tests aus ...", flush=True)
result = subprocess.run(
[sys.executable, "-m", "pytest", str(APP_DIR / "tests"), "-v", "-p", "no:cacheprovider"],
cwd=str(PROJECT_ROOT),
)
if result.returncode != 0:
print("[FEHLER] Tests fehlgeschlagen. Kein Build und kein Deployment.")
raise SystemExit(1)
def _ensure_pyinstaller() -> None:
"""Stellt sicher, dass PyInstaller installiert ist."""
try:
@@ -196,14 +214,20 @@ def _prepare_build_dir() -> None:
target = BUILD_OUTPUT_DIR / folder
target.mkdir(exist_ok=True)
# Kopiere config-Dateien, falls noch nicht vorhanden
# Liefere die zum aktuellen Quellstand gehörende Konfiguration aus.
config_src = APP_DIR / "config"
config_dst = BUILD_OUTPUT_DIR / "config"
if config_src.exists():
for f in config_src.iterdir():
if f.is_file() and not (config_dst / f.name).exists():
if f.is_file():
shutil.copy2(f, config_dst / f.name)
shutil.copy2(CHANGELOG_PATH, BUILD_OUTPUT_DIR / "CHANGELOG.md")
shutil.copy2(
APP_DIR / "template" / "Tätigkeitsnachweise Stellenförderung.pdf",
BUILD_OUTPUT_DIR / "template" / "Tätigkeitsnachweise Stellenförderung.pdf",
)
# README für input-Ordner
input_readme = BUILD_OUTPUT_DIR / "input" / "_DATEIEN_HIER_ABLEGEN_.txt"
if not input_readme.exists():
@@ -308,11 +332,16 @@ def main() -> None:
parser = argparse.ArgumentParser(
description="Build mit Semantic Versioning",
)
parser.add_argument(
version_options = parser.add_mutually_exclusive_group()
version_options.add_argument(
"--bump",
choices=["major", "minor", "patch"],
help="Version bump-Typ (ohne Angabe: interaktiv)",
)
version_options.add_argument("--no-bump", action="store_true",
help="Aktuelle Version erneut bauen; Changelog unverändert übernehmen")
parser.add_argument("--no-deploy", action="store_true",
help="Nur lokal bauen, ohne anschließendes Deployment")
parser.add_argument(
"--message", "-m",
help="Changelog-Beschreibung für diese Version",
@@ -327,6 +356,8 @@ def main() -> None:
# ── Version ermitteln ────────────────────────────────────
current = get_current_version()
if not current:
if args.no_bump:
parser.error("Für --no-bump muss eine Version im CHANGELOG.md vorhanden sein.")
print("❌ Keine Version in CHANGELOG.md gefunden. Beginne mit 0.1.")
current = "0.1"
@@ -334,7 +365,7 @@ def main() -> None:
# Bump-Typ bestimmen
bump_type = args.bump
if not bump_type:
if not bump_type and not args.no_bump:
print("\nWelche Art von Änderung?")
print(" [1] major – inkompatible API-Änderungen")
print(" [2] minor – neue Funktionalität (abwärtskompatibel)")
@@ -346,32 +377,54 @@ def main() -> None:
print("\nAbgebrochen.")
sys.exit(0)
new_version = bump_version(current, bump_type)
new_version = current if args.no_bump else bump_version(current, bump_type)
print(f"🚀 Neue Version: {new_version}")
if args.dry_run:
print("\n[Trockenlauf – kein Build]")
print(f" Würde CHANGELOG.md aktualisieren: [{new_version}]")
print(" Würde zuerst alle Tests ausführen")
if not args.no_bump:
print(f" Würde CHANGELOG.md aktualisieren: [{new_version}]")
print(f" Würde PyInstaller-Build starten")
if not args.no_deploy:
print(" Würde anschließend ein versioniertes ZIP auf dem SMB-Laufwerk bereitstellen")
return
# ── CHANGELOG aktualisieren ──────────────────────────────
print("\n📝 Changelog …")
update_changelog(new_version, bump_type, args.message)
# ── Build ────────────────────────────────────────────────
_ensure_pyinstaller()
_prepare_build_dir()
_run_tests()
previous_changelog = _read_changelog()
try:
_ensure_pyinstaller()
(BUILD_OUTPUT_DIR / "release.json").unlink(missing_ok=True)
if not args.no_bump:
print("\n📝 Changelog …")
update_changelog(new_version, bump_type, args.message)
_prepare_build_dir()
exe_path = _run_pyinstaller()
final_exe = _copy_to_build(exe_path)
write_release_manifest(BUILD_OUTPUT_DIR, new_version)
except (Exception, SystemExit, KeyboardInterrupt):
_write_changelog(previous_changelog)
raise
finally:
_cleanup()
_save_version(new_version)
_print_summary(new_version, final_exe)
if args.no_deploy:
print("[INFO] Lokaler Build abgeschlossen; Deployment nicht angefordert.")
return
try:
deploy()
except (OSError, ValueError, KeyError) as exc:
print(f"[FEHLER] {exc}")
print("[INFO] Der Build ist fertig. Deployment später mit 1_build-pipeline\\deploy.bat wiederholen.")
raise SystemExit(1)
if __name__ == "__main__":
main()
try:
main()
except (OSError, ValueError, subprocess.CalledProcessError) as exc:
print(f"[FEHLER] Pipeline abgebrochen: {exc}")
raise SystemExit(1)