Switch from hatch to setuptools/CFFI build to produce wheels correctly.
This commit is contained in:
@@ -1,100 +0,0 @@
|
||||
"""Hatch build hook for building dynamic libaegis library using Zig."""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
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
|
||||
|
||||
if not shutil.which("zig"):
|
||||
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}")
|
||||
|
||||
try:
|
||||
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",
|
||||
)
|
||||
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"
|
||||
|
||||
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}")
|
||||
|
||||
# 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"] = {}
|
||||
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}")
|
||||
+12
-2
@@ -34,8 +34,18 @@ def clean_declaration(text: str) -> str:
|
||||
if text == old:
|
||||
break
|
||||
|
||||
# Remove CRYPTO_ALIGN(...)
|
||||
text = re.sub(r"CRYPTO_ALIGN\s*\(\s*\d+\s*\)", "", text)
|
||||
# For structs with CRYPTO_ALIGN, replace the field with "...;" to make it flexible
|
||||
# This tells CFFI to use the C compiler's alignment instead of calculating it
|
||||
if "CRYPTO_ALIGN" in text and "typedef struct" in text:
|
||||
# Replace "CRYPTO_ALIGN(N) uint8_t opaque[SIZE];" with "...;"
|
||||
text = re.sub(
|
||||
r"CRYPTO_ALIGN\s*\(\s*\d+\s*\)\s+uint8_t\s+opaque\[\d+\];",
|
||||
"...;",
|
||||
text
|
||||
)
|
||||
else:
|
||||
# For non-struct declarations, just remove CRYPTO_ALIGN
|
||||
text = re.sub(r"CRYPTO_ALIGN\s*\(\s*\d+\s*\)", "", text)
|
||||
|
||||
# Normalize whitespace but preserve structure
|
||||
lines = []
|
||||
|
||||
Reference in New Issue
Block a user