"""Build hook that ships an integrity manifest inside the wheel. The manifest hashes every packaged file. At runtime the package recomputes those hashes and refuses to run if any differ. The manifest is written to a temporary path and force-included, so the source tree stays clean and editable installs remain unenforced. """ # Credit: @paparichens from __future__ import annotations import hashlib import json import tempfile from pathlib import Path from typing import Any from hatchling.builders.hooks.plugin.interface import BuildHookInterface PACKAGE = "affix_huggingface" MANIFEST_NAME = "_integrity_manifest.json" ALGORITHM = "sha256" _SKIP_SUFFIXES = (".pyc", ".pyo") _SKIP_DIRS = {"__pycache__"} def _hash_file(path: Path) -> str: digest = hashlib.new(ALGORITHM) with path.open("rb") as handle: for chunk in iter(lambda: handle.read(65536), b""): digest.update(chunk) return digest.hexdigest() class CustomBuildHook(BuildHookInterface): PLUGIN_NAME = "custom" def initialize(self, version: str, build_data: dict[str, Any]) -> None: if self.target_name != "wheel": return root = Path(self.root) / "src" / PACKAGE files: dict[str, str] = {} for path in sorted(root.rglob("*")): if not path.is_file(): continue rel = path.relative_to(root) if any(part in _SKIP_DIRS for part in rel.parts): continue if path.suffix in _SKIP_SUFFIXES: continue if path.name == MANIFEST_NAME: continue files[rel.as_posix()] = _hash_file(path) manifest = {"version": 1, "algorithm": ALGORITHM, "files": files} tmp = Path(tempfile.mkdtemp(prefix="affix-manifest-")) / MANIFEST_NAME tmp.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") force_include = build_data.setdefault("force_include", {}) force_include[str(tmp)] = f"{PACKAGE}/{MANIFEST_NAME}"