diff options
Diffstat (limited to 'prototypes/connections/package_source.py')
| -rw-r--r-- | prototypes/connections/package_source.py | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/prototypes/connections/package_source.py b/prototypes/connections/package_source.py new file mode 100644 index 0000000..4a2487c --- /dev/null +++ b/prototypes/connections/package_source.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Make a deterministic, buildable source snapshot without result/build trees.""" +import argparse +import gzip +import hashlib +import io +import json +from pathlib import Path +import tarfile + +ROOT = Path(__file__).resolve().parents[2] +PREFIX = 'pure-source-preview' +SOURCE_ROOTS = ('prototypes/connections', 'prototypes/workbench', 'prototypes/common') +SKIP_DIRS = {'build', 'results', '__pycache__', 'node_modules', '.git', '.playwright-cli'} +SUFFIXES = {'.c', '.h', '.py', '.mjs', '.js', '.zig', '.json', '.pure', + '.html', '.css', '.svg', '.wasm', '.md', '.txt'} + + +def files(): + selected = {} + for relative_root in SOURCE_ROOTS: + for path in (ROOT / relative_root).rglob('*'): + relative = path.relative_to(ROOT) + if any(part in SKIP_DIRS for part in relative.parts) or path.is_symlink() or not path.is_file(): + continue + if path.name != 'Makefile' and path.suffix not in SUFFIXES: + continue + selected[relative.as_posix()] = path.read_bytes() + return selected + + +def add(archive, name, content): + info = tarfile.TarInfo(PREFIX + '/' + name) + info.size = len(content) + info.mode = 0o644 + info.mtime = info.uid = info.gid = 0 + info.uname = info.gname = '' + archive.addfile(info, io.BytesIO(content)) + + +def make_archive(output): + selected = files() + selected['RUN.md'] = selected.pop('prototypes/connections/SOURCE_PREVIEW.md') + manifest = {'format': 'pure-source-preview-1', 'files': { + name: {'bytes': len(content), 'sha256': hashlib.sha256(content).hexdigest()} + for name, content in sorted(selected.items())}} + selected['manifest.json'] = (json.dumps(manifest, indent=2, sort_keys=True) + '\n').encode() + output.parent.mkdir(parents=True, exist_ok=True) + with output.open('xb') as stream: + with gzip.GzipFile(fileobj=stream, mode='wb', filename='', mtime=0, compresslevel=9) as compressed: + with tarfile.open(fileobj=compressed, mode='w', format=tarfile.USTAR_FORMAT) as archive: + for name, content in sorted(selected.items()): + add(archive, name, content) + return {'archive': str(output.resolve()), 'bytes': output.stat().st_size, + 'sha256': hashlib.sha256(output.read_bytes()).hexdigest(), + 'source_files': len(manifest['files'])} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', type=Path, required=True) + args = parser.parse_args() + print(json.dumps(make_archive(args.output), sort_keys=True)) + + +if __name__ == '__main__': + main() |
