Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions tools/artifact_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Build a reproducible artifact manifest with file sizes and hashes."""

from __future__ import annotations

import argparse
import hashlib
import json
from pathlib import Path
from tempfile import TemporaryDirectory

PATTERNS = ["build/**/*.so", "dist/**/*", "benchmark/**/*.log", "logs/**/*", "*.txt"]


def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()


def collect(root: Path) -> dict[str, object]:
if not root.is_dir():
raise NotADirectoryError(f"artifact root is not a directory: {root}")

seen: set[str] = set()
artifacts: list[dict[str, object]] = []
for pattern in PATTERNS:
for path in sorted(root.glob(pattern)):
if not path.is_file():
continue
rel = path.relative_to(root).as_posix()
if rel in seen:
continue
seen.add(rel)
artifacts.append(
{"path": rel, "bytes": path.stat().st_size, "sha256": sha256(path)}
)
return {"root": str(root), "count": len(artifacts), "artifacts": artifacts}


def self_test() -> None:
with TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
sample = root / "artifact_manifest_sample.txt"
sample.write_text("maca artifact\n", encoding="utf-8")
data = collect(root)
if not any(item["path"] == sample.name for item in data["artifacts"]):
raise RuntimeError(f"self-test failed: {data}")
print(json.dumps({"ok": True, "count": data["count"]}, ensure_ascii=False))


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", default=".")
parser.add_argument("--self-test", action="store_true")
args = parser.parse_args()
if args.self_test:
self_test()
return 0
print(json.dumps(collect(Path(args.root)), ensure_ascii=False, indent=2))
return 0
Comment on lines +54 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the specified --root directory does not exist or is not a directory, Path.glob will silently yield no files, and the script will exit with status 0 while producing an empty manifest. This can lead to silent failures in CI/CD pipelines (e.g., due to a typo in the path or a build step that failed to produce the directory).

It is safer to validate that the --root path exists and is a directory, raising an error via parser.error if it is not.

Suggested change
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", default=".")
parser.add_argument("--self-test", action="store_true")
args = parser.parse_args()
if args.self_test:
self_test()
return 0
print(json.dumps(collect(Path(args.root)), ensure_ascii=False, indent=2))
return 0
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", default=".")
parser.add_argument("--self-test", action="store_true")
args = parser.parse_args()
if args.self_test:
self_test()
return 0
root = Path(args.root)
if not root.is_dir():
parser.error(f"Root path '{args.root}' is not a directory or does not exist.")
print(json.dumps(collect(root), ensure_ascii=False, indent=2))
return 0



if __name__ == "__main__":
raise SystemExit(main())