bin/create-repository-set.py
#!/usr/bin/env python3
"""Erzeugt eine lokale Repository-ZIP-Menge ueber engineering-tools."""
from __future__ import annotations
import argparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path
def parse_repositories(values: list[str]) -> list[tuple[str, Path]]:
repositories: list[tuple[str, Path]] = []
names: set[str] = set()
for value in values:
if "=" not in value:
raise ValueError(f"Repository muss NAME=PFAD verwenden: {value}")
name, raw_path = value.split("=", 1)
if not re.fullmatch(r"[A-Za-z0-9._-]+", name) or name in names:
raise ValueError(f"Ungueltiger oder doppelter Repositoryname: {name}")
source = Path(raw_path).expanduser().resolve()
if not source.is_dir():
raise ValueError(f"Repositorypfad ist kein Verzeichnis: {source}")
names.add(name)
repositories.append((name, source))
if not repositories:
raise ValueError("Mindestens ein Repository ist erforderlich")
return repositories
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repository", action="append", default=[], metavar="NAME=PFAD")
parser.add_argument("--output-dir", required=True, type=Path)
parser.add_argument(
"--tool",
type=Path,
default=Path(
os.environ.get(
"NS_DETERMINISTIC_ZIP_TOOL",
str(Path.home() / "engineering/engineering-tools/60-packaging/deterministic_zip.py"),
)
),
)
args = parser.parse_args()
try:
repositories = parse_repositories(args.repository)
tool = args.tool.expanduser().resolve()
if not tool.is_file():
raise ValueError(f"Deterministisches ZIP-Werkzeug nicht gefunden: {tool}")
output = args.output_dir.expanduser().resolve()
if output.exists():
raise ValueError(f"Ausgabeziel existiert bereits: {output}")
output.parent.mkdir(parents=True, exist_ok=True)
staging = Path(tempfile.mkdtemp(prefix=f".{output.name}-", dir=output.parent))
try:
for name, source in repositories:
archive = staging / f"{name}.zip"
# Lokale Download-Historie ist weder Quellcode noch eine
# fachliche Quelle des Workspace-Roundtrips. Sie bleibt auf
# dem Rechner und darf Uploadpakete nicht aufblÀhen.
excludes = ["--exclude", "downloads/**"] if name == "workspace" else []
result = subprocess.run(
[sys.executable, str(tool), str(source), str(archive), *excludes],
check=False,
)
if result.returncode != 0:
raise ValueError(f"Repository-ZIP-Erzeugung fehlgeschlagen: {name}")
if not zipfile.is_zipfile(archive):
raise ValueError(f"Repository-ZIP ist ungueltig: {archive}")
with zipfile.ZipFile(archive) as handle:
if handle.testzip() is not None:
raise ValueError(f"Repository-ZIP ist beschaedigt: {archive}")
os.replace(staging, output)
except Exception:
shutil.rmtree(staging, ignore_errors=True)
raise
print(f"Repository-Menge erzeugt: {output} ({len(repositories)} Repositorys)")
return 0
except (OSError, ValueError) as exc:
parser.exit(2, f"FEHLER: {exc}\n")
if __name__ == "__main__":
raise SystemExit(main())