Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daba717ebb | ||
|
|
5b74bcb683 | ||
|
|
d9a18f43d5 | ||
|
|
98ee4ab7b3 |
No files matched your search
+2
-2
@@ -4,8 +4,8 @@
|
||||
/dist
|
||||
/build
|
||||
/pyaegis/build
|
||||
/pyaegis/_aegis.*.so
|
||||
/pyaegis/_aegis.*.pyd
|
||||
/pyaegis/_aegis*.so
|
||||
/pyaegis/_aegis*.pyd
|
||||
__pycache__
|
||||
!.gitignore
|
||||
!.gitmodules
|
||||
@@ -4,10 +4,8 @@ include tools/build_backend.py
|
||||
include BUILD.md
|
||||
include README.md
|
||||
recursive-include libaegis *.c *.h *.zig *.zon
|
||||
include libaegis/CMakeLists.txt
|
||||
include libaegis/LICENSE
|
||||
include libaegis/README.md
|
||||
recursive-include libaegis/cmake *.cmake *.cmake.in
|
||||
graft libaegis/src
|
||||
include libaegis/build.zig
|
||||
include libaegis/build.zig.zon
|
||||
@@ -295,9 +295,7 @@ def decrypt(
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be at least ct.nbytes - maclen"
|
||||
)
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128l_decrypt(
|
||||
@@ -582,7 +580,9 @@ class Mac:
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis128l_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||
rc = _lib.aegis128l_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
@@ -851,7 +851,9 @@ class Decryptor:
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
|
||||
@@ -295,9 +295,7 @@ def decrypt(
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be at least ct.nbytes - maclen"
|
||||
)
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x2_decrypt(
|
||||
@@ -582,7 +580,9 @@ class Mac:
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis128x2_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||
rc = _lib.aegis128x2_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
@@ -851,7 +851,9 @@ class Decryptor:
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
|
||||
@@ -295,9 +295,7 @@ def decrypt(
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be at least ct.nbytes - maclen"
|
||||
)
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis128x4_decrypt(
|
||||
@@ -582,7 +580,9 @@ class Mac:
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis128x4_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||
rc = _lib.aegis128x4_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
@@ -851,7 +851,9 @@ class Decryptor:
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
|
||||
+7
-5
@@ -295,9 +295,7 @@ def decrypt(
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be at least ct.nbytes - maclen"
|
||||
)
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256_decrypt(
|
||||
@@ -582,7 +580,9 @@ class Mac:
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis256_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||
rc = _lib.aegis256_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
@@ -851,7 +851,9 @@ class Decryptor:
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
|
||||
@@ -295,9 +295,7 @@ def decrypt(
|
||||
out = bytearray(expected_out)
|
||||
else:
|
||||
if into.nbytes < expected_out:
|
||||
raise TypeError(
|
||||
"into length must be at least ct.nbytes - maclen"
|
||||
)
|
||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
||||
out = into
|
||||
|
||||
rc = _lib.aegis256x2_decrypt(
|
||||
@@ -582,7 +580,9 @@ class Mac:
|
||||
out = into
|
||||
|
||||
clone = self.clone()
|
||||
rc = _lib.aegis256x2_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||
rc = _lib.aegis256x2_mac_final(
|
||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
||||
)
|
||||
if rc != 0:
|
||||
err_num = ffi.errno
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
@@ -851,7 +851,9 @@ class Decryptor:
|
||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||
w = int(written[0])
|
||||
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
assert w == expected_out, (
|
||||
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||
)
|
||||
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||
|
||||
def final(self, mac: Buffer) -> None:
|
||||
|
||||
+6
-2
@@ -1,11 +1,11 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "cffi>=2.0.0"]
|
||||
requires = ["setuptools>=61.0", "cffi>=2.0.0", "setuptools-scm>=8.0"]
|
||||
build-backend = "build_backend"
|
||||
backend-path = ["tools"]
|
||||
|
||||
[project]
|
||||
name = "pyaegis"
|
||||
version = "0.3.1"
|
||||
dynamic = ["version"]
|
||||
description = "Python bindings for libaegis"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
@@ -25,7 +25,9 @@ Homepage = "https://github.com/LeoVasanko/pyaegis"
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8.4.2",
|
||||
"ruff>=0.14.4",
|
||||
"setuptools>=80.9.0",
|
||||
"setuptools-scm>=9.2.2",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
@@ -33,3 +35,5 @@ packages = ["pyaegis"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
pyaegis = ["*.h", "*.so", "*.pyd"]
|
||||
|
||||
[tool.setuptools_scm]
|
||||
@@ -1,28 +1,23 @@
|
||||
"""Setup script for pyaegis - builds CFFI extension linking to libaegis.a"""
|
||||
"""Setup script for pyaegis - builds CFFI extension with libaegis C library."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from cffi import FFI
|
||||
from setuptools import setup
|
||||
|
||||
# Locate the static library (built by build_backend.py before this runs)
|
||||
lib_name = "aegis.lib" if sys.platform == "win32" else "libaegis.a"
|
||||
libaegis_static = Path("libaegis/zig-out/lib") / lib_name
|
||||
if not libaegis_static.exists():
|
||||
raise RuntimeError(f"libaegis static library not found at {libaegis_static}")
|
||||
libaegis_static = str(libaegis_static.resolve())
|
||||
|
||||
def find_libaegis():
|
||||
"""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("/usr/local/lib/libaegis.a"), # System install
|
||||
Path("/usr/lib/libaegis.a"), # System install
|
||||
]
|
||||
|
||||
for path in libaegis_paths:
|
||||
if path.exists():
|
||||
print(f"Found libaegis.a at: {path.resolve()}")
|
||||
return str(path.resolve())
|
||||
|
||||
# Return None instead of raising - will be caught during build
|
||||
return None
|
||||
|
||||
# Include directory for headers
|
||||
libaegis_include = Path("libaegis/src/include")
|
||||
if not libaegis_include.exists():
|
||||
raise RuntimeError(f"libaegis include directory not found at {libaegis_include}")
|
||||
include_dirs = [str(libaegis_include)]
|
||||
|
||||
# Read the CDEF header
|
||||
cdef_path = Path(__file__).parent / "pyaegis" / "aegis_cdef.h"
|
||||
@@ -32,15 +27,6 @@ cdef_content = cdef_path.read_text(encoding="utf-8")
|
||||
ffibuilder = FFI()
|
||||
ffibuilder.cdef(cdef_content)
|
||||
|
||||
# Include directory for headers
|
||||
include_dirs = []
|
||||
libaegis_include = Path("libaegis/src/include")
|
||||
if libaegis_include.exists():
|
||||
include_dirs.append(str(libaegis_include.resolve()))
|
||||
|
||||
# Try to find libaegis.a, but don't fail if not found (build backend will build it)
|
||||
libaegis_static = find_libaegis()
|
||||
|
||||
# Set the source
|
||||
ffibuilder.set_source(
|
||||
"pyaegis._aegis", # module name
|
||||
@@ -54,7 +40,7 @@ ffibuilder.set_source(
|
||||
#include "aegis256x4.h"
|
||||
""",
|
||||
include_dirs=include_dirs,
|
||||
extra_objects=[libaegis_static] if libaegis_static else [],
|
||||
extra_objects=[libaegis_static],
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
|
||||
def tvdump(topic, tvs):
|
||||
with open(filename(topic), "w") as f:
|
||||
f.write(json.dumps(tvs, indent=2))
|
||||
|
||||
print(json.dumps(tvs, indent=2))
|
||||
|
||||
|
||||
def filename(topic):
|
||||
return re.sub(r"[^a-z0-9]+", "-", topic.lower()) + ".json"
|
||||
|
||||
|
||||
header = True
|
||||
in_tv = False
|
||||
tv = {}
|
||||
tvs = []
|
||||
must_fail = False
|
||||
with open("../draft-irtf-cfrg-aegis-aead.md") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line == "":
|
||||
continue
|
||||
if line.startswith("# Test Vectors"):
|
||||
header = False
|
||||
continue
|
||||
if header:
|
||||
continue
|
||||
|
||||
if line.startswith("## "):
|
||||
if len(tvs) > 0:
|
||||
tvdump(topic, tvs)
|
||||
topic = line[3:]
|
||||
tv_name = topic
|
||||
tvs = []
|
||||
continue
|
||||
|
||||
if line.startswith("### "):
|
||||
tv_name = line[4:]
|
||||
tv = {"test": tv_name}
|
||||
in_tv = False
|
||||
continue
|
||||
|
||||
if line == "~~~ test-vectors":
|
||||
in_tv = True
|
||||
tv = {"name": tv_name}
|
||||
if must_fail:
|
||||
tv["error"] = "verification failed"
|
||||
must_fail = False
|
||||
continue
|
||||
|
||||
if line == "~~~":
|
||||
tvs.append(tv)
|
||||
in_tv = False
|
||||
current_key = None
|
||||
continue
|
||||
|
||||
if line.find("verification failed") != -1:
|
||||
must_fail = True
|
||||
continue
|
||||
|
||||
if line == "After initialization:":
|
||||
tv_name = tv_name + " (after initialization)"
|
||||
|
||||
if not in_tv:
|
||||
continue
|
||||
|
||||
parts = line.split(":")
|
||||
if len(parts) == 2:
|
||||
key = parts[0].strip()
|
||||
value = parts[1].strip()
|
||||
if key == "After Update":
|
||||
continue
|
||||
if key in tv:
|
||||
key = key + "_2"
|
||||
tv[key] = value
|
||||
current_key = key
|
||||
continue
|
||||
|
||||
if not current_key:
|
||||
continue
|
||||
|
||||
tv[key] += line.strip()
|
||||
|
||||
tvdump(topic, tvs)
|
||||
+2
-2
@@ -267,7 +267,7 @@ def generate_python_modules(
|
||||
if dst.exists() and dst.read_text(encoding="utf-8") == new_content:
|
||||
unchanged.append(dst)
|
||||
else:
|
||||
dst.write_text(new_content, encoding="utf-8")
|
||||
dst.write_bytes(new_content.encode())
|
||||
updated.append(dst)
|
||||
|
||||
return updated, unchanged
|
||||
@@ -301,7 +301,7 @@ def main() -> int:
|
||||
if cdef_path.exists() and cdef_path.read_text(encoding="utf-8") == cdef_content:
|
||||
print(f" - No changes to {cdef_path}", file=sys.stderr)
|
||||
else:
|
||||
cdef_path.write_text(cdef_content, encoding="utf-8")
|
||||
cdef_path.write_bytes(cdef_content.encode())
|
||||
print(f" - Updated {cdef_path}", file=sys.stderr)
|
||||
|
||||
print("Step 3: Generating Python modules...", file=sys.stderr)
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build wheels for all supported Python versions using uv."""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from packaging.specifiers import SpecifierSet
|
||||
from packaging.version import Version
|
||||
|
||||
# Import generate module from same directory
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
import generate
|
||||
|
||||
|
||||
def get_python_versions():
|
||||
"""Get supported Python versions."""
|
||||
pyproject_toml = Path(__file__).parent.parent / "pyproject.toml"
|
||||
data = tomllib.loads(pyproject_toml.read_text(encoding="utf-8"))
|
||||
spec = SpecifierSet(data["project"]["requires-python"])
|
||||
# Generate versions that match the specifier (up to Python 3.14)
|
||||
return [f"3.{minor}" for minor in range(10, 15) if f"3.{minor}" in spec]
|
||||
|
||||
|
||||
def get_version_from_scm():
|
||||
"""Get version from setuptools-scm (git tags)."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["uv", "run", "-m", "setuptools_scm"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
cwd=Path(__file__).parent.parent,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Error getting version from setuptools-scm: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def is_release_version(version):
|
||||
"""Check if version is a clean release (no dev/post/local identifiers)."""
|
||||
# A release version is just x.y.z with optional alpha/beta/rc suffixes
|
||||
# No +local or .devN or .postN
|
||||
if not version:
|
||||
return False
|
||||
return not any(marker in version for marker in ["+", ".dev", ".post"])
|
||||
|
||||
|
||||
def get_next_version(current_version):
|
||||
"""Get the next release version from a dev version."""
|
||||
# Parse base version (strips dev/local parts)
|
||||
try:
|
||||
v = Version(current_version)
|
||||
return f"{v.major}.{v.minor}.{v.micro}"
|
||||
except Exception:
|
||||
return current_version
|
||||
|
||||
|
||||
def is_working_copy_clean():
|
||||
"""Check if git working copy is clean."""
|
||||
result = subprocess.run(
|
||||
["git", "status", "--porcelain"], capture_output=True, text=True
|
||||
)
|
||||
return result.returncode == 0 and not result.stdout.strip()
|
||||
|
||||
|
||||
def make_release_message(version):
|
||||
"""Generate message for making a release."""
|
||||
next_version = get_next_version(version)
|
||||
is_clean = is_working_copy_clean()
|
||||
|
||||
msg = "\n⚠️ This is not a clean release version; upload to PyPI skipped.\n\n"
|
||||
msg += f"To create a release (e.g. {next_version}) and upload to PyPI:\n"
|
||||
|
||||
if not is_clean:
|
||||
msg += " 1. Add and commit changes on the working copy\n"
|
||||
msg += f" 2. Tag the commit: git tag v{next_version}\n"
|
||||
msg += " 3. Run this script again\n"
|
||||
msg += f" 4. Push the tag: git push origin v{next_version}\n"
|
||||
else:
|
||||
msg += f" 1. Tag the current commit: git tag v{next_version}\n"
|
||||
msg += " 2. Run this script again\n"
|
||||
msg += f" 3. Push the tag: git push origin v{next_version}\n"
|
||||
|
||||
msg += (
|
||||
f"\nIf the build didn't work, delete the tag with git tag -d v{next_version}\n"
|
||||
)
|
||||
return msg
|
||||
|
||||
|
||||
PYTHON_VERSIONS = get_python_versions()
|
||||
|
||||
|
||||
def run_command(cmd, description):
|
||||
"""Run a command and handle errors."""
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f"{description}")
|
||||
print(f"{'=' * 70}")
|
||||
print(f">>> {' '.join(cmd)}")
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
print(f"✓ {description} completed successfully")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ {description} failed with exit code {e.returncode}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def normalize_line_endings(repo_root: Path):
|
||||
"""Normalize all text files to LF line endings."""
|
||||
# Patterns for files to normalize
|
||||
patterns = [
|
||||
"pyaegis/**/*.py",
|
||||
"pyaegis/**/*.h",
|
||||
"tests/**/*.py",
|
||||
"tools/**/*.py",
|
||||
"*.py",
|
||||
"*.md",
|
||||
"*.txt",
|
||||
"*.toml",
|
||||
"*.in",
|
||||
]
|
||||
for pattern in patterns:
|
||||
for file_path in repo_root.glob(pattern):
|
||||
if file_path.is_file():
|
||||
content = file_path.read_bytes()
|
||||
if b"\r\n" in content:
|
||||
content = content.replace(b"\r\n", b"\n")
|
||||
file_path.write_bytes(content)
|
||||
|
||||
|
||||
def main():
|
||||
"""Build wheels for all supported Python versions."""
|
||||
repo_root = Path(__file__).parent.parent
|
||||
dist_dir = repo_root / "dist"
|
||||
|
||||
# Generate CFFI definitions and Python modules
|
||||
print(f"\n{'=' * 70}")
|
||||
print("Code generation from C headers (tools/generate.py)")
|
||||
print(f"{'=' * 70}")
|
||||
if generate.main() != 0:
|
||||
print("✗ Code generation failed", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Run ruff to check and fix any issues
|
||||
if not run_command(
|
||||
["uv", "run", "ruff", "check", "--fix", "."], "Running ruff check --fix"
|
||||
):
|
||||
print("✗ Ruff check failed", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Run ruff format
|
||||
if not run_command(["uv", "run", "ruff", "format", "."], "Running ruff format"):
|
||||
print("✗ Ruff format failed", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Normalize all line endings to LF (important for consistent builds)
|
||||
normalize_line_endings(repo_root)
|
||||
|
||||
# Get version from git repo
|
||||
version = get_version_from_scm()
|
||||
if not version:
|
||||
return 1
|
||||
is_release = is_release_version(version)
|
||||
|
||||
# Main header for the packaging process
|
||||
print(f"\n{'=' * 70}")
|
||||
print(
|
||||
f"Packaging pyaegis-{version}"
|
||||
+ (" for release" if is_release else " (not release)")
|
||||
)
|
||||
print(f"Building wheels for Python versions: {', '.join(PYTHON_VERSIONS)}")
|
||||
print(f"Output directory: {dist_dir}", end=" ")
|
||||
|
||||
# Clean dist directory
|
||||
if dist_dir.exists():
|
||||
print("(wiped)")
|
||||
shutil.rmtree(dist_dir)
|
||||
else:
|
||||
print("(created)")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Build source distribution first
|
||||
if not run_command(
|
||||
["uv", "build", "--sdist", "--quiet"], "Building source distribution"
|
||||
):
|
||||
print("✗ Source distribution build failed", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
failed_builds = []
|
||||
successful_wheels = []
|
||||
|
||||
for py_version in PYTHON_VERSIONS:
|
||||
# Build wheel
|
||||
description = f"Building wheel for Python {py_version}"
|
||||
cmd = ["uv", "build", "--python", py_version, "--wheel", "--quiet"]
|
||||
|
||||
if not run_command(cmd, description):
|
||||
failed_builds.append(py_version)
|
||||
continue
|
||||
|
||||
# Find the wheel for this version
|
||||
wheel_pattern = f"pyaegis-*-cp{py_version.replace('.', '')}-*.whl"
|
||||
wheels = list(dist_dir.glob(wheel_pattern))
|
||||
if not wheels:
|
||||
print(f"✗ Could not find wheel for Python {py_version}", file=sys.stderr)
|
||||
failed_builds.append(py_version)
|
||||
continue
|
||||
|
||||
wheel = wheels[0]
|
||||
|
||||
# Test the wheel with pytest (use --isolated to avoid .venv conflicts)
|
||||
test_cmd = [
|
||||
"uv",
|
||||
"run",
|
||||
"--isolated",
|
||||
"--python",
|
||||
py_version,
|
||||
"--with",
|
||||
str(wheel),
|
||||
"--with",
|
||||
"pytest",
|
||||
"pytest",
|
||||
]
|
||||
if not run_command(
|
||||
test_cmd, f"Testing wheel for Python {py_version} with pytest"
|
||||
):
|
||||
print(f"✗ Tests failed for Python {py_version}", file=sys.stderr)
|
||||
failed_builds.append(py_version)
|
||||
continue
|
||||
|
||||
# Run benchmark (use --isolated to avoid .venv conflicts)
|
||||
bench_cmd = [
|
||||
"uv",
|
||||
"run",
|
||||
"--isolated",
|
||||
"--python",
|
||||
py_version,
|
||||
"--with",
|
||||
str(wheel),
|
||||
"-m",
|
||||
"pyaegis.benchmark",
|
||||
]
|
||||
if not run_command(bench_cmd, f"Running benchmark for Python {py_version}"):
|
||||
print(f"✗ Benchmark failed for Python {py_version}", file=sys.stderr)
|
||||
failed_builds.append(py_version)
|
||||
continue
|
||||
|
||||
successful_wheels.append(wheel)
|
||||
|
||||
# Summary
|
||||
print(f"\n{'=' * 70}")
|
||||
print("BUILD SUMMARY")
|
||||
print(f"{'=' * 70}")
|
||||
print(
|
||||
f"Successful builds: sdist and {len(successful_wheels)}/{len(PYTHON_VERSIONS)} wheels"
|
||||
)
|
||||
|
||||
if failed_builds:
|
||||
print(f"\nFailed builds: {len(failed_builds)}")
|
||||
for version in failed_builds:
|
||||
print(f" ✗ Python {version}")
|
||||
|
||||
if not successful_wheels:
|
||||
print("\n✗ No successful wheels to upload")
|
||||
return 1
|
||||
|
||||
# List files to upload
|
||||
sdist = list(dist_dir.glob("*.tar.gz"))
|
||||
upload_files = sdist + successful_wheels
|
||||
|
||||
for file in upload_files:
|
||||
print(f" - {file.name}")
|
||||
|
||||
# Only upload if this is a clean release version
|
||||
if not is_release:
|
||||
print(make_release_message(version))
|
||||
return 0
|
||||
|
||||
# Upload with twine
|
||||
upload_cmd = ["uvx", "twine", "upload"] + [str(f) for f in upload_files]
|
||||
if not run_command(upload_cmd, "Uploading to PyPI with twine"):
|
||||
print("\n✗ Upload failed")
|
||||
return 1
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
print("All builds and upload completed successfully!")
|
||||
print(f"{'=' * 70}")
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user