From b370afe16792db7003f79de1678205ef6e699946 Mon Sep 17 00:00:00 2001 From: "itdrui.de" Date: Sat, 7 Feb 2026 17:28:49 +0100 Subject: [PATCH] Nightly: reproducible zips and plugin manifest --- README.md | 1 + .../einschalten_plugin.cpython-312.pyc | Bin 48789 -> 0 bytes addon/plugins/einschalten_plugin.py | 4 + addon/plugins/filmpalast_plugin.py | 4 + docs/PLUGIN_DEVELOPMENT.md | 1 + docs/PLUGIN_MANIFEST.json | 104 +++++++++++++++++ docs/PLUGIN_SYSTEM.md | 5 + scripts/build_kodi_zip.sh | 2 +- scripts/build_local_kodi_repo.sh | 2 +- scripts/generate_plugin_manifest.py | 106 ++++++++++++++++++ scripts/zip_deterministic.py | 73 ++++++++++++ 11 files changed, 300 insertions(+), 2 deletions(-) delete mode 100644 addon/plugins/__pycache__/einschalten_plugin.cpython-312.pyc create mode 100644 docs/PLUGIN_MANIFEST.json create mode 100755 scripts/generate_plugin_manifest.py create mode 100755 scripts/zip_deterministic.py diff --git a/README.md b/README.md index 79e8abd..e46090c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ ViewIT ist ein Kodi‑Addon zum Durchsuchen und Abspielen von Inhalten der unter - Addon‑Ordner bauen: `./scripts/build_install_addon.sh` → `dist//` - Kodi‑ZIP bauen: `./scripts/build_kodi_zip.sh` → `dist/-.zip` - Addon‑Version in `addon/addon.xml` +- Reproduzierbare ZIPs: optional `SOURCE_DATE_EPOCH` setzen ## Lokales Kodi-Repository - Repository bauen (inkl. ZIPs + `addons.xml` + `addons.xml.md5`): `./scripts/build_local_kodi_repo.sh` diff --git a/addon/plugins/__pycache__/einschalten_plugin.cpython-312.pyc b/addon/plugins/__pycache__/einschalten_plugin.cpython-312.pyc deleted file mode 100644 index b5da04f3334215cebf7837ef852bbe3ddd9a08a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48789 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/dev/null; echo "${INSTALL_DIR}/${ADDON_ID}")" rm -f "${ZIP_PATH}" -(cd "${INSTALL_DIR}" && zip -r "${ZIP_NAME}" "$(basename "${ADDON_DIR}")" >/dev/null) +python3 "${ROOT_DIR}/scripts/zip_deterministic.py" "${ZIP_PATH}" "${ADDON_DIR}" >/dev/null echo "${ZIP_PATH}" diff --git a/scripts/build_local_kodi_repo.sh b/scripts/build_local_kodi_repo.sh index ddedb92..752fe32 100755 --- a/scripts/build_local_kodi_repo.sh +++ b/scripts/build_local_kodi_repo.sh @@ -73,7 +73,7 @@ PY REPO_ZIP_NAME="${REPO_ADDON_ID}-${REPO_ADDON_VERSION}.zip" REPO_ZIP_PATH="${REPO_DIR}/${REPO_ZIP_NAME}" rm -f "${REPO_ZIP_PATH}" -(cd "${TMP_DIR}" && zip -r "${REPO_ZIP_PATH}" "${REPO_ADDON_ID}" >/dev/null) +python3 "${ROOT_DIR}/scripts/zip_deterministic.py" "${REPO_ZIP_PATH}" "${TMP_REPO_ADDON_DIR}" >/dev/null python3 - "${PLUGIN_ADDON_XML}" "${TMP_REPO_ADDON_DIR}/addon.xml" "${REPO_DIR}/addons.xml" <<'PY' import sys diff --git a/scripts/generate_plugin_manifest.py b/scripts/generate_plugin_manifest.py new file mode 100755 index 0000000..fe8d3b9 --- /dev/null +++ b/scripts/generate_plugin_manifest.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Generate a JSON manifest for addon plugins.""" +from __future__ import annotations + +import importlib.util +import inspect +import json +import sys +from pathlib import Path + +ROOT_DIR = Path(__file__).resolve().parents[1] +PLUGIN_DIR = ROOT_DIR / "addon" / "plugins" +OUTPUT_PATH = ROOT_DIR / "docs" / "PLUGIN_MANIFEST.json" + +sys.path.insert(0, str(ROOT_DIR / "addon")) + +try: + from plugin_interface import BasisPlugin # type: ignore +except Exception as exc: # pragma: no cover + raise SystemExit(f"Failed to import BasisPlugin: {exc}") + + +def _import_module(path: Path): + spec = importlib.util.spec_from_file_location(path.stem, path) + if spec is None or spec.loader is None: + raise ImportError(f"Missing spec for {path}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def _collect_plugins(): + plugins = [] + for file_path in sorted(PLUGIN_DIR.glob("*.py")): + if file_path.name.startswith("_"): + continue + entry = { + "file": str(file_path.relative_to(ROOT_DIR)), + "module": file_path.stem, + "name": None, + "class": None, + "version": None, + "capabilities": [], + "prefer_source_metadata": False, + "base_url_setting": None, + "available": None, + "unavailable_reason": None, + "error": None, + } + try: + module = _import_module(file_path) + preferred = getattr(module, "Plugin", None) + if inspect.isclass(preferred) and issubclass(preferred, BasisPlugin) and preferred is not BasisPlugin: + classes = [preferred] + else: + classes = [ + obj + for obj in module.__dict__.values() + if inspect.isclass(obj) and issubclass(obj, BasisPlugin) and obj is not BasisPlugin + ] + classes.sort(key=lambda cls: cls.__name__.casefold()) + + if not classes: + entry["error"] = "No plugin classes found" + plugins.append(entry) + continue + + cls = classes[0] + instance = cls() + entry["class"] = cls.__name__ + entry["name"] = str(getattr(instance, "name", "") or "") or None + entry["version"] = str(getattr(instance, "version", "0.0.0") or "0.0.0") + entry["prefer_source_metadata"] = bool(getattr(instance, "prefer_source_metadata", False)) + entry["available"] = bool(getattr(instance, "is_available", True)) + entry["unavailable_reason"] = getattr(instance, "unavailable_reason", None) + try: + caps = instance.capabilities() # type: ignore[call-arg] + entry["capabilities"] = sorted([str(c) for c in caps]) if caps else [] + except Exception: + entry["capabilities"] = [] + + entry["base_url_setting"] = getattr(module, "SETTING_BASE_URL", None) + except Exception as exc: # pragma: no cover + entry["error"] = str(exc) + plugins.append(entry) + + plugins.sort(key=lambda item: (item.get("name") or item["module"]).casefold()) + return plugins + + +def main() -> int: + if not PLUGIN_DIR.exists(): + raise SystemExit("Plugin directory missing") + manifest = { + "schema_version": 1, + "plugins": _collect_plugins(), + } + OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) + OUTPUT_PATH.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + print(str(OUTPUT_PATH)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/zip_deterministic.py b/scripts/zip_deterministic.py new file mode 100755 index 0000000..f3cea34 --- /dev/null +++ b/scripts/zip_deterministic.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Create deterministic zip archives. + +Usage: + zip_deterministic.py + +The archive will include the root directory itself and all files under it. +""" +from __future__ import annotations + +import os +import sys +import time +import zipfile +from pathlib import Path + + +def _timestamp() -> tuple[int, int, int, int, int, int]: + epoch = os.environ.get("SOURCE_DATE_EPOCH") + if epoch: + try: + value = int(epoch) + return time.gmtime(value)[:6] + except Exception: + pass + return (2000, 1, 1, 0, 0, 0) + + +def _iter_files(root: Path): + for dirpath, dirnames, filenames in os.walk(root): + dirnames[:] = sorted([d for d in dirnames if d != "__pycache__"]) + for filename in sorted(filenames): + if filename.endswith(".pyc"): + continue + yield Path(dirpath) / filename + + +def _add_file(zf: zipfile.ZipFile, file_path: Path, arcname: str) -> None: + info = zipfile.ZipInfo(arcname, date_time=_timestamp()) + info.compress_type = zipfile.ZIP_DEFLATED + info.external_attr = (0o644 & 0xFFFF) << 16 + with file_path.open("rb") as handle: + data = handle.read() + zf.writestr(info, data, compress_type=zipfile.ZIP_DEFLATED) + + +def main() -> int: + if len(sys.argv) != 3: + print("Usage: zip_deterministic.py ") + return 2 + + zip_path = Path(sys.argv[1]).resolve() + root = Path(sys.argv[2]).resolve() + if not root.exists() or not root.is_dir(): + print(f"Missing root dir: {root}") + return 2 + + base = root.parent + zip_path.parent.mkdir(parents=True, exist_ok=True) + if zip_path.exists(): + zip_path.unlink() + + with zipfile.ZipFile(zip_path, "w") as zf: + for file_path in sorted(_iter_files(root)): + arcname = str(file_path.relative_to(base)).replace(os.sep, "/") + _add_file(zf, file_path, arcname) + + print(str(zip_path)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())