Switch from hatch to setuptools/CFFI build to produce wheels correctly.
This commit is contained in:
@@ -3,6 +3,9 @@
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
/dist
|
/dist
|
||||||
/pyaegis/build
|
/pyaegis/build
|
||||||
|
/pyaegis/_aegis.*.so
|
||||||
|
/pyaegis/_aegis.*.pyd
|
||||||
|
/pyaegis/_aegis.c
|
||||||
__pycache__
|
__pycache__
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!.gitmodules
|
!.gitmodules
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
include pyaegis/aegis_cdef.h
|
||||||
|
include setup.py
|
||||||
|
include BUILD.md
|
||||||
|
include README.md
|
||||||
|
recursive-include libaegis/src/include *.h
|
||||||
|
include libaegis/zig-out/lib/libaegis.a
|
||||||
+3
-32
@@ -1,36 +1,7 @@
|
|||||||
"""Dynamic loader for libaegis using CFFI (ABI mode)."""
|
"""Loader for libaegis CFFI extension module."""
|
||||||
|
|
||||||
import os
|
from pyaegis._aegis import ffi, lib
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from cffi import FFI
|
__all__ = ["ffi", "lib"]
|
||||||
|
|
||||||
__ALL__ = ["ffi", "lib"]
|
|
||||||
|
|
||||||
|
|
||||||
def _platform_lib_name() -> str:
|
|
||||||
if sys.platform == "darwin":
|
|
||||||
return "libaegis.dylib"
|
|
||||||
if os.name == "nt":
|
|
||||||
return "aegis.dll"
|
|
||||||
return "libaegis.so"
|
|
||||||
|
|
||||||
|
|
||||||
def _load_libaegis():
|
|
||||||
pkg_dir = Path(__file__).parent
|
|
||||||
candidate = pkg_dir / "build" / _platform_lib_name()
|
|
||||||
if candidate.exists():
|
|
||||||
try:
|
|
||||||
return ffi.dlopen(str(candidate))
|
|
||||||
except Exception as e:
|
|
||||||
raise OSError(f"Failed to load libaegis from {candidate}: {e}")
|
|
||||||
else:
|
|
||||||
raise OSError(f"Could not find libaegis at {candidate}")
|
|
||||||
|
|
||||||
|
|
||||||
ffi = FFI()
|
|
||||||
ffi.cdef(Path(__file__).with_name("aegis_cdef.h").read_text(encoding="utf-8"))
|
|
||||||
lib: Any = _load_libaegis()
|
|
||||||
lib.aegis_init()
|
lib.aegis_init()
|
||||||
|
|||||||
+12
-12
@@ -9,8 +9,8 @@ int aegis_verify_16(const uint8_t *x, const uint8_t *y) ;
|
|||||||
int aegis_verify_32(const uint8_t *x, const uint8_t *y) ;
|
int aegis_verify_32(const uint8_t *x, const uint8_t *y) ;
|
||||||
|
|
||||||
/* aegis128l.h */
|
/* aegis128l.h */
|
||||||
typedef struct aegis128l_state { uint8_t opaque[256]; } aegis128l_state;
|
typedef struct aegis128l_state { ...; } aegis128l_state;
|
||||||
typedef struct aegis128l_mac_state { uint8_t opaque[384]; } aegis128l_mac_state;
|
typedef struct aegis128l_mac_state { ...; } aegis128l_mac_state;
|
||||||
size_t aegis128l_keybytes(void);
|
size_t aegis128l_keybytes(void);
|
||||||
size_t aegis128l_npubbytes(void);
|
size_t aegis128l_npubbytes(void);
|
||||||
size_t aegis128l_abytes_min(void);
|
size_t aegis128l_abytes_min(void);
|
||||||
@@ -103,8 +103,8 @@ void aegis128l_mac_reset(aegis128l_mac_state *st_);
|
|||||||
void aegis128l_mac_state_clone(aegis128l_mac_state *dst, const aegis128l_mac_state *src);
|
void aegis128l_mac_state_clone(aegis128l_mac_state *dst, const aegis128l_mac_state *src);
|
||||||
|
|
||||||
/* aegis128x2.h */
|
/* aegis128x2.h */
|
||||||
typedef struct aegis128x2_state { uint8_t opaque[448]; } aegis128x2_state;
|
typedef struct aegis128x2_state { ...; } aegis128x2_state;
|
||||||
typedef struct aegis128x2_mac_state { uint8_t opaque[704]; } aegis128x2_mac_state;
|
typedef struct aegis128x2_mac_state { ...; } aegis128x2_mac_state;
|
||||||
size_t aegis128x2_keybytes(void);
|
size_t aegis128x2_keybytes(void);
|
||||||
size_t aegis128x2_npubbytes(void);
|
size_t aegis128x2_npubbytes(void);
|
||||||
size_t aegis128x2_abytes_min(void);
|
size_t aegis128x2_abytes_min(void);
|
||||||
@@ -197,8 +197,8 @@ void aegis128x2_mac_reset(aegis128x2_mac_state *st_);
|
|||||||
void aegis128x2_mac_state_clone(aegis128x2_mac_state *dst, const aegis128x2_mac_state *src);
|
void aegis128x2_mac_state_clone(aegis128x2_mac_state *dst, const aegis128x2_mac_state *src);
|
||||||
|
|
||||||
/* aegis128x4.h */
|
/* aegis128x4.h */
|
||||||
typedef struct aegis128x4_state { uint8_t opaque[832]; } aegis128x4_state;
|
typedef struct aegis128x4_state { ...; } aegis128x4_state;
|
||||||
typedef struct aegis128x4_mac_state { uint8_t opaque[1344]; } aegis128x4_mac_state;
|
typedef struct aegis128x4_mac_state { ...; } aegis128x4_mac_state;
|
||||||
size_t aegis128x4_keybytes(void);
|
size_t aegis128x4_keybytes(void);
|
||||||
size_t aegis128x4_npubbytes(void);
|
size_t aegis128x4_npubbytes(void);
|
||||||
size_t aegis128x4_abytes_min(void);
|
size_t aegis128x4_abytes_min(void);
|
||||||
@@ -291,8 +291,8 @@ void aegis128x4_mac_reset(aegis128x4_mac_state *st_);
|
|||||||
void aegis128x4_mac_state_clone(aegis128x4_mac_state *dst, const aegis128x4_mac_state *src);
|
void aegis128x4_mac_state_clone(aegis128x4_mac_state *dst, const aegis128x4_mac_state *src);
|
||||||
|
|
||||||
/* aegis256.h */
|
/* aegis256.h */
|
||||||
typedef struct aegis256_state { uint8_t opaque[192]; } aegis256_state;
|
typedef struct aegis256_state { ...; } aegis256_state;
|
||||||
typedef struct aegis256_mac_state { uint8_t opaque[288]; } aegis256_mac_state;
|
typedef struct aegis256_mac_state { ...; } aegis256_mac_state;
|
||||||
size_t aegis256_keybytes(void);
|
size_t aegis256_keybytes(void);
|
||||||
size_t aegis256_npubbytes(void);
|
size_t aegis256_npubbytes(void);
|
||||||
size_t aegis256_abytes_min(void);
|
size_t aegis256_abytes_min(void);
|
||||||
@@ -385,8 +385,8 @@ void aegis256_mac_reset(aegis256_mac_state *st_);
|
|||||||
void aegis256_mac_state_clone(aegis256_mac_state *dst, const aegis256_mac_state *src);
|
void aegis256_mac_state_clone(aegis256_mac_state *dst, const aegis256_mac_state *src);
|
||||||
|
|
||||||
/* aegis256x2.h */
|
/* aegis256x2.h */
|
||||||
typedef struct aegis256x2_state { uint8_t opaque[320]; } aegis256x2_state;
|
typedef struct aegis256x2_state { ...; } aegis256x2_state;
|
||||||
typedef struct aegis256x2_mac_state { uint8_t opaque[512]; } aegis256x2_mac_state;
|
typedef struct aegis256x2_mac_state { ...; } aegis256x2_mac_state;
|
||||||
size_t aegis256x2_keybytes(void);
|
size_t aegis256x2_keybytes(void);
|
||||||
size_t aegis256x2_npubbytes(void);
|
size_t aegis256x2_npubbytes(void);
|
||||||
size_t aegis256x2_abytes_min(void);
|
size_t aegis256x2_abytes_min(void);
|
||||||
@@ -479,8 +479,8 @@ void aegis256x2_mac_reset(aegis256x2_mac_state *st_);
|
|||||||
void aegis256x2_mac_state_clone(aegis256x2_mac_state *dst, const aegis256x2_mac_state *src);
|
void aegis256x2_mac_state_clone(aegis256x2_mac_state *dst, const aegis256x2_mac_state *src);
|
||||||
|
|
||||||
/* aegis256x4.h */
|
/* aegis256x4.h */
|
||||||
typedef struct aegis256x4_state { uint8_t opaque[576]; } aegis256x4_state;
|
typedef struct aegis256x4_state { ...; } aegis256x4_state;
|
||||||
typedef struct aegis256x4_mac_state { uint8_t opaque[960]; } aegis256x4_mac_state;
|
typedef struct aegis256x4_mac_state { ...; } aegis256x4_mac_state;
|
||||||
size_t aegis256x4_keybytes(void);
|
size_t aegis256x4_keybytes(void);
|
||||||
size_t aegis256x4_npubbytes(void);
|
size_t aegis256x4_npubbytes(void);
|
||||||
size_t aegis256x4_abytes_min(void);
|
size_t aegis256x4_abytes_min(void);
|
||||||
|
|||||||
+7
-9
@@ -1,11 +1,11 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling", "cffi>=2.0.0"]
|
requires = ["setuptools>=61.0", "cffi>=2.0.0"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pyaegis"
|
name = "pyaegis"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Python bindings for libaegis (links to system library)"
|
description = "Python bindings for libaegis (links to static library)"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
@@ -27,10 +27,8 @@ dev = [
|
|||||||
"pytest>=8.4.2",
|
"pytest>=8.4.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.hatch.build.hooks.custom]
|
[tool.setuptools]
|
||||||
# Placeholder build hook for compiling libaegis with Zig during wheel builds.
|
|
||||||
# The actual Zig build is intentionally not executed yet.
|
|
||||||
path = "tools/build_hook.py"
|
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
|
||||||
packages = ["pyaegis"]
|
packages = ["pyaegis"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
pyaegis = ["*.h", "*.so", "*.pyd"]
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
"""Setup script for pyaegis - builds CFFI extension linking to libaegis.a"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from cffi import FFI
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
# Read the CDEF header
|
||||||
|
cdef_path = Path(__file__).parent / "pyaegis" / "aegis_cdef.h"
|
||||||
|
cdef_content = cdef_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# Create CFFI builder
|
||||||
|
ffibuilder = FFI()
|
||||||
|
ffibuilder.cdef(cdef_content)
|
||||||
|
|
||||||
|
# Locate libaegis.a - check common locations
|
||||||
|
libaegis_paths = [
|
||||||
|
Path("libaegis/zig-out/lib/libaegis.a"), # Zig build output (repo build)
|
||||||
|
Path("libaegis/build/libaegis.a"), # CMake build output (repo build)
|
||||||
|
Path("../libaegis/zig-out/lib/libaegis.a"), # When building from extracted sdist
|
||||||
|
Path("../libaegis/build/libaegis.a"), # When building from extracted sdist
|
||||||
|
Path("/usr/local/lib/libaegis.a"), # System install
|
||||||
|
Path("/usr/lib/libaegis.a"), # System install
|
||||||
|
]
|
||||||
|
|
||||||
|
libaegis_static = None
|
||||||
|
for path in libaegis_paths:
|
||||||
|
if path.exists():
|
||||||
|
libaegis_static = path.resolve()
|
||||||
|
print(f"Found libaegis.a at: {libaegis_static}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not libaegis_static:
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"libaegis.a not found. Please build libaegis first. "
|
||||||
|
f"Searched: {[str(p) for p in libaegis_paths]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include directory for headers
|
||||||
|
include_dirs = []
|
||||||
|
libaegis_include = Path("libaegis/src/include")
|
||||||
|
if libaegis_include.exists():
|
||||||
|
include_dirs.append(str(libaegis_include.resolve()))
|
||||||
|
|
||||||
|
# Set the source - we don't need any C source files since we're linking to the static library
|
||||||
|
# CFFI will generate the wrapper code automatically
|
||||||
|
ffibuilder.set_source(
|
||||||
|
"pyaegis._aegis", # module name
|
||||||
|
"""
|
||||||
|
#include "aegis.h"
|
||||||
|
#include "aegis128l.h"
|
||||||
|
#include "aegis128x2.h"
|
||||||
|
#include "aegis128x4.h"
|
||||||
|
#include "aegis256.h"
|
||||||
|
#include "aegis256x2.h"
|
||||||
|
#include "aegis256x4.h"
|
||||||
|
""",
|
||||||
|
include_dirs=include_dirs,
|
||||||
|
extra_objects=[str(libaegis_static)], # Link against the static library
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
setup(
|
||||||
|
cffi_modules=["setup.py:ffibuilder"],
|
||||||
|
)
|
||||||
@@ -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}")
|
|
||||||
+11
-1
@@ -34,7 +34,17 @@ def clean_declaration(text: str) -> str:
|
|||||||
if text == old:
|
if text == old:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Remove CRYPTO_ALIGN(...)
|
# 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)
|
text = re.sub(r"CRYPTO_ALIGN\s*\(\s*\d+\s*\)", "", text)
|
||||||
|
|
||||||
# Normalize whitespace but preserve structure
|
# Normalize whitespace but preserve structure
|
||||||
|
|||||||
Reference in New Issue
Block a user