""" build_engine.py ~~~~~~~~~~~~~~~ Cross-platform build script for the ``hft_auditor`` C++ extension module. Usage ----- python build_engine.py The script: 1. Detects the host OS (Linux/WSL or Windows). 2. Cleans any previous build artefacts. 3. Runs CMake configure + build inside ``hf auditor/build/``. 4. Locates the compiled shared library (.so / .pyd) and copies it to the project root so that ``import hft_auditor`` works from any script in this directory. """ import os import platform import shutil import subprocess import sys import argparse from pathlib import Path # --------------------------------------------------------------------------- # Paths # --------------------------------------------------------------------------- ROOT = Path(__file__).resolve().parent # project root (fin_auditor/) SOURCE_DIR = ROOT / "hf auditor" # note: directory has a space BUILD_DIR = SOURCE_DIR / "build" # Glob patterns for the compiled extension in the build tree SO_PATTERNS = ["hft_auditor*.so", "hft_auditor*.pyd"] # Destination files that should be removed from the project root on clean ROOT_ARTEFACTS = list(ROOT.glob("hft_auditor*.so")) + list( ROOT.glob("hft_auditor*.pyd") ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def run(cmd: list[str], *, description: str = "") -> None: """Run *cmd* with real-time stdout/stderr passthrough.""" if description: print(f"\n{'=' * 60}") print(f" {description}") print(f"{'=' * 60}") print(f"$ {' '.join(cmd)}\n") result = subprocess.run(cmd, check=True) # noqa: S603 return result def clean() -> None: """Remove existing build artefacts.""" print("\n-- Clean -----------------------------------------------------") if BUILD_DIR.exists(): print(f" Removing build directory: {BUILD_DIR}") def remove_readonly(func, path, excinfo): os.chmod(path, 0o777) func(path) shutil.rmtree(BUILD_DIR, onerror=remove_readonly) else: print(f" Build directory not found (nothing to remove): {BUILD_DIR}") for path in list(ROOT.glob("hft_auditor*.so")) + list( ROOT.glob("hft_auditor*.pyd") ): print(f" Removing root artefact: {path}") path.unlink() print(" Clean complete.\n") def cmake_configure(os_name: str, docker_safe: bool = False) -> None: """Run the CMake configure step.""" cmd = [ "cmake", "-B", str(BUILD_DIR), "-S", str(SOURCE_DIR), f"-DCMAKE_BUILD_TYPE=Release", ] if docker_safe: # -O1 peaks at ~1.2 GB vs -O3's ~5 GB for this translation unit. # DOCKER_SAFE_BUILD=ON triggers the matching CMakeLists.txt option. cmd += ["-DDOCKER_SAFE_BUILD=ON", "-DCMAKE_CXX_FLAGS=-O1"] print(" Memory-safe mode: -j1, -O1, DOCKER_SAFE_BUILD=ON") if os_name == "Windows": cmd += ["-G", "Visual Studio 17 2022"] run(cmd, description="CMake – Configure") def cmake_build(docker_safe: bool = False) -> None: """Run the CMake build step.""" cmd = ["cmake", "--build", str(BUILD_DIR), "--config", "Release"] if docker_safe: # --parallel 1 → make -j1 → caps peak RAM to ~1.5 GB cmd += ["--parallel", "1"] run(cmd, description="CMake – Build") def copy_to_root() -> Path: """ Walk the build tree looking for the compiled extension and copy it to the project root. Returns the destination path. """ print("\n-- Post-Build: Locate & Copy Extension -----------------------") found: list[Path] = [] for pattern in SO_PATTERNS: found.extend(BUILD_DIR.rglob(pattern)) if not found: sys.exit( "ERROR: Could not find a compiled hft_auditor extension under " f"{BUILD_DIR}. Did the build succeed?" ) # If somehow multiple matches exist (e.g. debug + release), prefer the # one in a 'Release' sub-directory; otherwise take the first hit. chosen = next( (p for p in found if "Release" in p.parts or "release" in p.parts), found[0], ) dest = ROOT / chosen.name print(f" Source : {chosen}") print(f" Dest : {dest}") shutil.copy2(chosen, dest) print(f" Copied '{chosen.name}' → project root.") return dest # --------------------------------------------------------------------------- # Entry point # --------------------------------------------------------------------------- def main() -> None: parser = argparse.ArgumentParser(description="Build the hft_auditor C++ extension.") parser.add_argument( "--docker-safe", action="store_true", help="Memory-safe build for Docker: -j1, -O1 instead of -O3 -march=native.", ) args = parser.parse_args() os_name = platform.system() # 'Linux', 'Windows', or 'Darwin' print(f"Detected OS : {os_name}") if args.docker_safe: print("Mode : DOCKER_SAFE (--parallel 1, -O1)") if os_name not in ("Linux", "Windows"): print( f"WARNING: OS '{os_name}' is not explicitly supported. Proceeding " "with generic Unix-style CMake commands." ) # 1. Clean clean() # 2. Configure cmake_configure(os_name, docker_safe=args.docker_safe) # 3. Build cmake_build(docker_safe=args.docker_safe) # 4. Copy extension to project root dest = copy_to_root() print(f"\n✓ Build complete. Extension available at: {dest}\n") if __name__ == "__main__": main()