#!/usr/bin/env python3 """Build and verify the final-source manifest used by Linux executables.""" from __future__ import annotations import argparse import hashlib from pathlib import Path import sys def sha256(data: bytes) -> str: return hashlib.sha256(data).hexdigest() def manifest_entries(root: Path, include_unreal: bool = False) -> list[tuple[str, bytes]]: standalone = root / "Standalone" files: set[Path] = set() core = standalone / "Core" for suffix in ("*.h", "*.hpp", "*.cpp", "*.txt"): files.update(core.rglob(suffix)) source = standalone / "Source" for suffix in ("*.h", "*.hpp", "*.cpp"): files.update(source.glob(suffix)) for path in standalone.glob("Build*"): if path.is_file(): files.add(path) for suffix in ("*.cmake", "*.mk", "Makefile", "CMakeLists.txt"): files.update(standalone.glob(suffix)) core_build = standalone / "Core" for suffix in ("*.cmake", "*.mk", "Makefile", "CMakeLists.txt"): files.update(core_build.glob(suffix)) helper = standalone / "Tools/verify/source_stamp.py" if helper.is_file(): files.add(helper) if include_unreal: unreal = source / "VoxelForge" for suffix in ("*.h", "*.hpp", "*.cpp", "*.cs"): files.update(unreal.rglob(suffix)) entries = [(path.relative_to(root).as_posix(), path.read_bytes()) for path in files if path.is_file()] # BuildLinux creates one namespace wrapper per listed Core implementation. # Include each exact generated wrapper as a virtual source input. source_list = core / "core_sources.txt" for raw in source_list.read_text(encoding="utf-8").splitlines(): name = raw.split("#", 1)[0].strip() if not name or name == "Core/src/VoxelForgeCore.cpp": continue source_path = standalone / name if not source_path.is_file(): raise SystemExit(f"missing source listed in Core/core_sources.txt: {name}") wrapper_name = "vfcore_" + "".join(c if c.isalnum() else "_" for c in name) wrapper = ('#include "CoreNamespacePreamble.h"\n' "namespace vfcore::detail\n{\n" f'#include "{source_path}"\n' "}\n").encode("utf-8") entries.append((f"generated/wrappers/{wrapper_name}.cpp", wrapper)) return sorted(entries, key=lambda item: item[0]) def rendered_manifest(root: Path, include_unreal: bool = False) -> bytes: lines = [f"{sha256(data)} {name}" for name, data in manifest_entries(root, include_unreal)] return ("\n".join(lines) + "\n").encode("utf-8") def source_stamp(root: Path, include_unreal: bool = False) -> str: return sha256(rendered_manifest(root, include_unreal)) def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("command", choices=("stamp", "write-manifest")) parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[3]) parser.add_argument("--output", type=Path) parser.add_argument("--include-unreal-adapter", action="store_true") args = parser.parse_args() root = args.root.resolve() manifest = rendered_manifest(root, args.include_unreal_adapter) stamp = sha256(manifest) if args.command == "write-manifest": if args.output is None: parser.error("write-manifest requires --output") args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_bytes(manifest) print(stamp) return 0 if __name__ == "__main__": raise SystemExit(main())