74 lines
2.0 KiB
Python
Executable File
74 lines
2.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Create deterministic zip archives.
|
|
|
|
Usage:
|
|
zip_deterministic.py <zip_path> <root_dir>
|
|
|
|
The archive will include the root directory itself and all files under it.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
|
|
def _timestamp() -> tuple[int, int, int, int, int, int]:
|
|
epoch = os.environ.get("SOURCE_DATE_EPOCH")
|
|
if epoch:
|
|
try:
|
|
value = int(epoch)
|
|
return time.gmtime(value)[:6]
|
|
except Exception:
|
|
pass
|
|
return (2000, 1, 1, 0, 0, 0)
|
|
|
|
|
|
def _iter_files(root: Path):
|
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
dirnames[:] = sorted([d for d in dirnames if d != "__pycache__"])
|
|
for filename in sorted(filenames):
|
|
if filename.endswith(".pyc"):
|
|
continue
|
|
yield Path(dirpath) / filename
|
|
|
|
|
|
def _add_file(zf: zipfile.ZipFile, file_path: Path, arcname: str) -> None:
|
|
info = zipfile.ZipInfo(arcname, date_time=_timestamp())
|
|
info.compress_type = zipfile.ZIP_DEFLATED
|
|
info.external_attr = (0o644 & 0xFFFF) << 16
|
|
with file_path.open("rb") as handle:
|
|
data = handle.read()
|
|
zf.writestr(info, data, compress_type=zipfile.ZIP_DEFLATED)
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) != 3:
|
|
print("Usage: zip_deterministic.py <zip_path> <root_dir>")
|
|
return 2
|
|
|
|
zip_path = Path(sys.argv[1]).resolve()
|
|
root = Path(sys.argv[2]).resolve()
|
|
if not root.exists() or not root.is_dir():
|
|
print(f"Missing root dir: {root}")
|
|
return 2
|
|
|
|
base = root.parent
|
|
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
|
if zip_path.exists():
|
|
zip_path.unlink()
|
|
|
|
with zipfile.ZipFile(zip_path, "w") as zf:
|
|
for file_path in sorted(_iter_files(root)):
|
|
arcname = str(file_path.relative_to(base)).replace(os.sep, "/")
|
|
_add_file(zf, file_path, arcname)
|
|
|
|
print(str(zip_path))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|