| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455 |
- #!/usr/bin/env python3
- """Create a byte-reproducible tar.gz from one directory tree."""
- from __future__ import annotations
- import argparse
- import gzip
- import os
- import tarfile
- from pathlib import Path
- def create_archive(source: Path, output: Path, source_date_epoch: int) -> None:
- source = source.resolve()
- output.parent.mkdir(parents=True, exist_ok=True)
- paths = [source, *sorted(source.rglob("*"), key=lambda path: path.relative_to(source).as_posix())]
- with output.open("wb") as raw:
- with gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=source_date_epoch) as compressed:
- with tarfile.open(fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT) as archive:
- for path in paths:
- arcname = source.name if path == source else f"{source.name}/{path.relative_to(source).as_posix()}"
- info = archive.gettarinfo(str(path), arcname=arcname)
- info.uid = 0
- info.gid = 0
- info.uname = "root"
- info.gname = "root"
- info.mtime = source_date_epoch
- info.pax_headers = {}
- if info.isdir():
- info.mode = 0o755
- archive.addfile(info)
- elif info.isfile():
- executable = bool(os.stat(path).st_mode & 0o111)
- info.mode = 0o755 if executable else 0o644
- with path.open("rb") as handle:
- archive.addfile(info, handle)
- elif info.issym():
- archive.addfile(info)
- else:
- raise ValueError(f"unsupported archive entry: {path}")
- def main() -> int:
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument("--source", type=Path, required=True)
- parser.add_argument("--output", type=Path, required=True)
- parser.add_argument("--source-date-epoch", type=int, required=True)
- args = parser.parse_args()
- create_archive(args.source, args.output, args.source_date_epoch)
- return 0
- if __name__ == "__main__":
- raise SystemExit(main())
|