Compare commits
3
Commits
503ce8e491
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9ae37a03d | ||
|
|
e29035f768 | ||
|
|
3ff145a0f0 |
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
@@ -8,5 +8,6 @@
|
|||||||
/src/aeg/_aegis*.pyd
|
/src/aeg/_aegis*.pyd
|
||||||
__pycache__
|
__pycache__
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
!.gitattributes
|
||||||
!.gitea/
|
!.gitea/
|
||||||
!.gitmodules
|
!.gitmodules
|
||||||
@@ -14,8 +14,10 @@ if sys.maxsize <= 2**32:
|
|||||||
"aeg does not support 32-bit platforms; build with a 64-bit Python"
|
"aeg does not support 32-bit platforms; build with a 64-bit Python"
|
||||||
)
|
)
|
||||||
|
|
||||||
libaegis_static = ROOT / "libaegis/zig-out/lib" / (
|
libaegis_static = (
|
||||||
"aegis.lib" if sys.platform == "win32" else "libaegis.a"
|
ROOT
|
||||||
|
/ "libaegis/zig-out/lib"
|
||||||
|
/ ("aegis.lib" if sys.platform == "win32" else "libaegis.a")
|
||||||
)
|
)
|
||||||
|
|
||||||
ffibuilder = FFI()
|
ffibuilder = FFI()
|
||||||
|
|||||||
+69
-26
@@ -73,6 +73,42 @@ def format_declaration(decl: str, max_width: int = 100) -> str:
|
|||||||
return decl
|
return decl
|
||||||
|
|
||||||
|
|
||||||
|
def _ruff_format_source(content: str, filename: str) -> str:
|
||||||
|
"""Format Python source in memory (ruff format via stdin, no file writes)."""
|
||||||
|
result = subprocess.run(
|
||||||
|
["uv", "run", "ruff", "format", "--stdin-filename", filename, "-"],
|
||||||
|
input=content,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def _is_up_to_date(path: pathlib.Path, content: str, check: bool) -> bool:
|
||||||
|
"""True if writing content to path would leave it unchanged. In check mode,
|
||||||
|
Python content is compared in its ruff-formatted form, matching what a
|
||||||
|
write + format pass would produce."""
|
||||||
|
if not path.exists():
|
||||||
|
return False
|
||||||
|
if check and path.suffix == ".py":
|
||||||
|
content = _ruff_format_source(content, path.name)
|
||||||
|
return path.read_text(encoding="utf-8") == content
|
||||||
|
|
||||||
|
|
||||||
|
def _sync_file(
|
||||||
|
path: pathlib.Path, content: str, check: bool, stale: list[pathlib.Path]
|
||||||
|
) -> None:
|
||||||
|
"""Write content to path, or in check mode record it as stale instead."""
|
||||||
|
if _is_up_to_date(path, content, check):
|
||||||
|
print(f" - No changes to {path.name}", file=sys.stderr)
|
||||||
|
elif check:
|
||||||
|
stale.append(path)
|
||||||
|
else:
|
||||||
|
path.write_bytes(content.encode())
|
||||||
|
print(f" - Updated {path}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def generate_cdef(include_dir: pathlib.Path) -> str:
|
def generate_cdef(include_dir: pathlib.Path) -> str:
|
||||||
# cffi pre-defines uint8_t, size_t, etc. with the correct platform sizes.
|
# cffi pre-defines uint8_t, size_t, etc. with the correct platform sizes.
|
||||||
# Do not typedef them here: "unsigned long" matches size_t on LP64 but is
|
# Do not typedef them here: "unsigned long" matches size_t on LP64 but is
|
||||||
@@ -240,6 +276,7 @@ def generate_python_modules(
|
|||||||
template_path: pathlib.Path,
|
template_path: pathlib.Path,
|
||||||
output_dir: pathlib.Path,
|
output_dir: pathlib.Path,
|
||||||
constants: dict[str, dict[str, int]],
|
constants: dict[str, dict[str, int]],
|
||||||
|
check: bool = False,
|
||||||
) -> tuple[list[pathlib.Path], list[pathlib.Path]]:
|
) -> tuple[list[pathlib.Path], list[pathlib.Path]]:
|
||||||
if not template_path.exists():
|
if not template_path.exists():
|
||||||
raise FileNotFoundError(f"Template not found: {template_path}")
|
raise FileNotFoundError(f"Template not found: {template_path}")
|
||||||
@@ -287,10 +324,11 @@ def generate_python_modules(
|
|||||||
else:
|
else:
|
||||||
new_content = generate_variant(template_src, variant, const_dict)
|
new_content = generate_variant(template_src, variant, const_dict)
|
||||||
|
|
||||||
if dst.exists() and dst.read_text(encoding="utf-8") == new_content:
|
if _is_up_to_date(dst, new_content, check):
|
||||||
unchanged.append(dst)
|
unchanged.append(dst)
|
||||||
else:
|
else:
|
||||||
dst.write_bytes(new_content.encode())
|
if not check:
|
||||||
|
dst.write_bytes(new_content.encode())
|
||||||
updated.append(dst)
|
updated.append(dst)
|
||||||
|
|
||||||
return updated, unchanged
|
return updated, unchanged
|
||||||
@@ -313,7 +351,9 @@ def generate_ciphers_module(constants: dict[str, dict[str, int]]) -> str:
|
|||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main(check: bool = False) -> int:
|
||||||
|
"""Regenerate files from the C sources. With check=True, only verify the
|
||||||
|
committed files are up to date; nothing is written."""
|
||||||
root = pathlib.Path(__file__).parent.parent
|
root = pathlib.Path(__file__).parent.parent
|
||||||
libaegis_src_dir = root / "libaegis" / "src"
|
libaegis_src_dir = root / "libaegis" / "src"
|
||||||
include_dir = libaegis_src_dir / "include"
|
include_dir = libaegis_src_dir / "include"
|
||||||
@@ -333,36 +373,26 @@ def main() -> int:
|
|||||||
print("Error: No constants extracted", file=sys.stderr)
|
print("Error: No constants extracted", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print("Step 2: Generating CFFI cdef header...", file=sys.stderr)
|
stale: list[pathlib.Path] = []
|
||||||
pyaegis_dir.mkdir(exist_ok=True)
|
|
||||||
cdef_path = pyaegis_dir / "aegis_cdef.h"
|
|
||||||
cdef_content = generate_cdef(include_dir)
|
|
||||||
|
|
||||||
if cdef_path.exists() and cdef_path.read_text(encoding="utf-8") == cdef_content:
|
print("Step 2: Generating CFFI cdef header...", file=sys.stderr)
|
||||||
print(f" - No changes to {cdef_path}", file=sys.stderr)
|
if not check:
|
||||||
else:
|
pyaegis_dir.mkdir(exist_ok=True)
|
||||||
cdef_path.write_bytes(cdef_content.encode())
|
_sync_file(pyaegis_dir / "aegis_cdef.h", generate_cdef(include_dir), check, stale)
|
||||||
print(f" - Updated {cdef_path}", file=sys.stderr)
|
|
||||||
|
|
||||||
print("Step 3: Generating _ciphers.py...", file=sys.stderr)
|
print("Step 3: Generating _ciphers.py...", file=sys.stderr)
|
||||||
ciphers_path = pyaegis_dir / "_ciphers.py"
|
_sync_file(
|
||||||
ciphers_content = generate_ciphers_module(constants)
|
pyaegis_dir / "_ciphers.py", generate_ciphers_module(constants), check, stale
|
||||||
|
)
|
||||||
if (
|
|
||||||
ciphers_path.exists()
|
|
||||||
and ciphers_path.read_text(encoding="utf-8") == ciphers_content
|
|
||||||
):
|
|
||||||
print(f" - No changes to {ciphers_path.name}", file=sys.stderr)
|
|
||||||
else:
|
|
||||||
ciphers_path.write_bytes(ciphers_content.encode())
|
|
||||||
print(f" - Updated {ciphers_path.name}", file=sys.stderr)
|
|
||||||
|
|
||||||
print("Step 4: Generating Python modules...", file=sys.stderr)
|
print("Step 4: Generating Python modules...", file=sys.stderr)
|
||||||
try:
|
try:
|
||||||
updated, unchanged = generate_python_modules(
|
updated, unchanged = generate_python_modules(
|
||||||
pyaegis_dir / "aegis256x4.py", pyaegis_dir, constants
|
pyaegis_dir / "aegis256x4.py", pyaegis_dir, constants, check=check
|
||||||
)
|
)
|
||||||
if updated:
|
if check:
|
||||||
|
stale.extend(updated)
|
||||||
|
else:
|
||||||
for p in updated:
|
for p in updated:
|
||||||
print(f" - {p.relative_to(root)}", file=sys.stderr)
|
print(f" - {p.relative_to(root)}", file=sys.stderr)
|
||||||
if unchanged:
|
if unchanged:
|
||||||
@@ -377,6 +407,19 @@ def main() -> int:
|
|||||||
print(f"Error generating Python modules: {e}", file=sys.stderr)
|
print(f"Error generating Python modules: {e}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
if check:
|
||||||
|
for p in stale:
|
||||||
|
print(f" - Out of date: {p.relative_to(root)}", file=sys.stderr)
|
||||||
|
if stale:
|
||||||
|
print(
|
||||||
|
"✗ Generated files are out of date; "
|
||||||
|
"run tools/generate.py and commit the result",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
print("All generated files are up to date.", file=sys.stderr)
|
||||||
|
return 0
|
||||||
|
|
||||||
print("Step 5: Formatting generated files with ruff...", file=sys.stderr)
|
print("Step 5: Formatting generated files with ruff...", file=sys.stderr)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["uv", "run", "ruff", "format", str(pyaegis_dir)],
|
["uv", "run", "ruff", "format", str(pyaegis_dir)],
|
||||||
@@ -392,4 +435,4 @@ def main() -> int:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
raise SystemExit(main())
|
raise SystemExit(main(check="--check" in sys.argv))
|
||||||
+71
-33
@@ -129,27 +129,29 @@ def get_build_env():
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def normalize_line_endings(repo_root: Path):
|
# Text files that must have LF line endings (.gitattributes enforces this on
|
||||||
"""Normalize all text files to LF line endings."""
|
# checkout; release validation fails instead of rewriting)
|
||||||
# Patterns for files to normalize
|
LINE_ENDING_PATTERNS = [
|
||||||
patterns = [
|
"src/aeg/**/*.py",
|
||||||
"src/aeg/**/*.py",
|
"src/aeg/**/*.h",
|
||||||
"src/aeg/**/*.h",
|
"tests/**/*.py",
|
||||||
"tests/**/*.py",
|
"tools/**/*.py",
|
||||||
"tools/**/*.py",
|
"*.py",
|
||||||
"*.py",
|
"*.md",
|
||||||
"*.md",
|
"*.txt",
|
||||||
"*.txt",
|
"*.toml",
|
||||||
"*.toml",
|
"*.in",
|
||||||
"*.in",
|
]
|
||||||
]
|
|
||||||
for pattern in patterns:
|
|
||||||
|
def find_crlf_files(repo_root: Path) -> list[Path]:
|
||||||
|
"""Return text files containing CRLF line endings."""
|
||||||
|
bad = []
|
||||||
|
for pattern in LINE_ENDING_PATTERNS:
|
||||||
for file_path in repo_root.glob(pattern):
|
for file_path in repo_root.glob(pattern):
|
||||||
if file_path.is_file():
|
if file_path.is_file() and b"\r\n" in file_path.read_bytes():
|
||||||
content = file_path.read_bytes()
|
bad.append(file_path)
|
||||||
if b"\r\n" in content:
|
return bad
|
||||||
content = content.replace(b"\r\n", b"\n")
|
|
||||||
file_path.write_bytes(content)
|
|
||||||
|
|
||||||
|
|
||||||
def get_wheel_pattern(py_version: str, abi3: bool = False) -> str:
|
def get_wheel_pattern(py_version: str, abi3: bool = False) -> str:
|
||||||
@@ -317,29 +319,38 @@ def main():
|
|||||||
repo_root = Path(__file__).parent.parent
|
repo_root = Path(__file__).parent.parent
|
||||||
dist_dir = repo_root / "dist"
|
dist_dir = repo_root / "dist"
|
||||||
|
|
||||||
# Generate CFFI definitions and Python modules
|
# The repository must already contain up-to-date generated code and
|
||||||
|
# lint-clean sources: a release build never writes to tracked files.
|
||||||
|
# Line endings must be LF (.gitattributes enforces this on checkout).
|
||||||
print(f"\n{'=' * 70}")
|
print(f"\n{'=' * 70}")
|
||||||
print("Code generation from C headers (tools/generate.py)")
|
print("Validating generated files (tools/generate.py --check)")
|
||||||
print(f"{'=' * 70}")
|
print(f"{'=' * 70}")
|
||||||
if generate.main() != 0:
|
if generate.main(check=True) != 0:
|
||||||
print("✗ Code generation failed", file=sys.stderr)
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Run ruff to check and fix any issues
|
|
||||||
print(f"\n{'=' * 70}")
|
print(f"\n{'=' * 70}")
|
||||||
print("Linting and formatting")
|
print("Lint and format validation")
|
||||||
print(f"{'=' * 70}")
|
print(f"{'=' * 70}")
|
||||||
if not run_command(["uv", "run", "ruff", "check", "--fix", "."]):
|
if not run_command(["uv", "run", "ruff", "check", "."]):
|
||||||
print("✗ Ruff check failed", file=sys.stderr)
|
print("✗ Ruff check failed; fix and commit before releasing", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Run ruff format
|
if not run_command(["uv", "run", "ruff", "format", "--check", "."]):
|
||||||
if not run_command(["uv", "run", "ruff", "format", "."]):
|
print(
|
||||||
print("✗ Ruff format failed", file=sys.stderr)
|
"✗ Ruff format check failed; run 'uv run ruff format .' and commit",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Normalize all line endings to LF (important for consistent builds)
|
if crlf_files := find_crlf_files(repo_root):
|
||||||
normalize_line_endings(repo_root)
|
print("✗ CRLF line endings found in:", file=sys.stderr)
|
||||||
|
for p in crlf_files:
|
||||||
|
print(f" - {p.relative_to(repo_root)}", file=sys.stderr)
|
||||||
|
print(
|
||||||
|
"Convert to LF and commit (.gitattributes enforces eol=lf).",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
# Get version from git repo
|
# Get version from git repo
|
||||||
version = get_version_from_scm()
|
version = get_version_from_scm()
|
||||||
@@ -347,6 +358,33 @@ def main():
|
|||||||
return 1
|
return 1
|
||||||
is_release = is_release_version(version)
|
is_release = is_release_version(version)
|
||||||
|
|
||||||
|
# On a tagged commit the version must be a clean release (e.g. 0.10.6).
|
||||||
|
# A dev/dirty version means the checkout itself is dirty (PyPI rejects
|
||||||
|
# such versions), so fail before building.
|
||||||
|
head_tags = subprocess.run(
|
||||||
|
["git", "tag", "--points-at", "HEAD", "--list", "v*"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
).stdout.split()
|
||||||
|
if head_tags and not is_release:
|
||||||
|
print(
|
||||||
|
f"✗ HEAD is tagged ({', '.join(head_tags)}) but the version resolved "
|
||||||
|
f"to {version}, not a clean release.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
dirty = subprocess.run(
|
||||||
|
["git", "status", "--porcelain"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
).stdout
|
||||||
|
if dirty.strip():
|
||||||
|
print("Working copy is dirty:", file=sys.stderr)
|
||||||
|
print(dirty, file=sys.stderr, end="")
|
||||||
|
print("Commit the changes, re-tag, and run again.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
# Main header for the packaging process
|
# Main header for the packaging process
|
||||||
print(f"\n{'=' * 70}")
|
print(f"\n{'=' * 70}")
|
||||||
print(
|
print(
|
||||||
|
|||||||
Reference in new issue
Block a user