Build cleanup, pathname reorganisation, cdef to repository (although generated with gen_cdef.py).

This commit is contained in:
Leo Vasanko
2025-11-06 15:23:30 -06:00
parent c8fe16d21f
commit d43a22bc6c
14 changed files with 805 additions and 292 deletions
+67 -37
View File
@@ -10,8 +10,11 @@ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class BuildHook(BuildHookInterface):
"""Build dynamic library with Zig and include in wheel."""
PLUGIN_NAME = "pyaegis_build_hook"
def initialize(self, version: str, build_data: dict) -> None:
"""Build library with Zig and add it to the wheel."""
super().initialize(version, build_data)
if self.target_name != "wheel":
return
@@ -19,52 +22,79 @@ class BuildHook(BuildHookInterface):
raise RuntimeError("Zig compiler not found in PATH")
libaegis_dir = Path(self.root) / "libaegis"
self.app.display_info(f"[aegis] Using libaegis source at: {libaegis_dir}")
original_build_zig = libaegis_dir / "build.zig"
if not original_build_zig.exists():
raise RuntimeError(f"libaegis source not found at {libaegis_dir}")
# Prepare a temporary build directory (avoid touching original files)
build_dir = Path.cwd() / "libaegis-build"
build_dir.mkdir(exist_ok=True)
build_zig = build_dir / "build.zig"
build_zig.write_text(
original_build_zig.read_text(encoding="utf-8").replace(
".linkage = .static,", ".linkage = .dynamic,"
),
encoding="utf-8",
)
for res in "build.zig.zon", "src":
(build_dir / res).symlink_to(libaegis_dir / res)
self.app.display_info("[aegis] Building libaegis dynamic library with Zig...")
try:
subprocess.run(
["zig", "build", "-Drelease"],
check=True,
cwd=str(build_dir),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
build_dir = Path("libaegis-build")
build_dir.mkdir(exist_ok=True)
build_zig = build_dir / "build.zig"
build_zig.write_text(
original_build_zig.read_text(encoding="utf-8").replace(
".linkage = .static,", ".linkage = .dynamic,"
),
encoding="utf-8",
)
except subprocess.CalledProcessError as e:
output = e.stdout.decode(errors="replace") if e.stdout else ""
raise RuntimeError(f"Zig build failed:\n{output}") from e
for res in ("build.zig.zon", "src"):
(build_dir / res).symlink_to(libaegis_dir / res)
self.app.display_info(
"[aegis] Building libaegis dynamic library with Zig..."
)
try:
subprocess.run(
["zig", "build", "-Drelease"],
check=True,
cwd=str(build_dir),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
except subprocess.CalledProcessError as e:
output = e.stdout.decode(errors="replace") if e.stdout else ""
raise RuntimeError(f"Zig build failed:\n{output}") from e
lib_dir = build_dir / "zig-out" / "lib"
lib_dir = build_dir / "zig-out" / "lib"
dynamic_lib = None
for lib_file in lib_dir.iterdir():
if lib_file.name.startswith("libaegis") and lib_file.suffix in (
".so",
".dylib",
".dll",
):
dynamic_lib = lib_file
break
dynamic_lib = None
for lib_file in lib_dir.iterdir():
if lib_file.name.startswith("libaegis") and lib_file.suffix in (
".so",
".dylib",
".dll",
):
dynamic_lib = lib_file
break
if not dynamic_lib or not dynamic_lib.exists():
raise RuntimeError(f"Built dynamic library not found in {lib_dir}")
if not dynamic_lib or not dynamic_lib.exists():
raise RuntimeError(f"Built dynamic library not found in {lib_dir}")
# Copy the built dynamic library into the Python package tree so that it
# is naturally included as package data. Hatch will pick up anything
# under the listed packages ("pyaegis"), so a direct copy is simpler
# than relying on force_include. We still leave the original artifact
# in place in case other hooks/tools want to inspect it.
package_build_dir = Path(self.root) / "pyaegis" / "build"
package_build_dir.mkdir(parents=True, exist_ok=True)
self.app.display_info(
f"[aegis] Staging dynamic library to package... {package_build_dir} {Path.cwd()}"
)
dest_path = package_build_dir / dynamic_lib.name
try:
shutil.copy2(dynamic_lib, dest_path)
except Exception as e: # pragma: no cover - defensive
raise RuntimeError(
f"Failed to copy dynamic library to package: {e}"
) from e
finally:
shutil.rmtree(build_dir, ignore_errors=True)
# Retain force_include as a fallback for environments where an older
# Hatch might not automatically include non-.py files, or if wheels are
# built with custom exclusion rules.
if "force_include" not in build_data:
build_data["force_include"] = {}
dest_rel = str(Path("build") / dynamic_lib.name)
build_data["force_include"][str(dynamic_lib)] = dest_rel
self.app.display_info(f"[aegis] Added dynamic library to wheel: {dest_rel}")
build_data["force_include"][str(dest_path)] = str(
Path("pyaegis") / "build" / dynamic_lib.name
)
self.app.display_info(f"[aegis] Dynamic library staged at: {dest_path}")