Files
shacraft-core/scripts/make_release.py
T

67 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Package a clean, committed source tree and optional Linux executables.
No user worlds, tokens, Cargo caches, Minecraft JAR, or untracked files enter
an archive. Every extracted file is checked against the in-archive manifest.
"""
import argparse
import gzip
import hashlib
import io
import json
import subprocess
import tarfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def git(*args):
return subprocess.check_output(['git', *args], cwd=ROOT)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--binaries', action='store_true')
args = parser.parse_args()
if git('status', '--porcelain').strip():
raise SystemExit('Commit the reviewed tree before packaging.')
revision = git('rev-parse', 'HEAD').decode().strip()
epoch = int(git('show', '-s', '--format=%ct', 'HEAD'))
paths = [Path(p.decode()) for p in git('ls-files', '-z').split(b'\0') if p]
if args.binaries:
paths += [Path('target/release') / name for name in
['shacraft-server', 'shacraft-mcp', 'shacraft-compat', 'shacraft-tools']]
files = []
for relative in sorted(paths):
path = ROOT / relative
if path.is_symlink() or not path.is_file():
raise SystemExit(f'Only regular files are distributable: {relative}')
if relative.name.endswith('.token') or relative.name == '.env':
raise SystemExit(f'Secret filename cannot be distributed: {relative}')
data = path.read_bytes()
files.append((relative.as_posix(), data, 0o755 if path.stat().st_mode & 0o111 else 0o644))
manifest = {'project':'Shacraft Core','git_revision':revision,'source_epoch':epoch,
'kind':'linux-x86_64' if args.binaries else 'source',
'license':'MIT OR Apache-2.0',
'files':{name:{'sha256':hashlib.sha256(data).hexdigest(),'size':len(data)} for name,data,_ in files}}
files.append(('RELEASE.json', (json.dumps(manifest,indent=2)+'\n').encode(), 0o644))
output = ROOT / 'artifacts' / f"shacraft-core-mvp-{revision[:8]}-{manifest['kind']}.tar.gz"
output.parent.mkdir(exist_ok=True)
with output.open('wb') as raw, gzip.GzipFile(fileobj=raw,mode='wb',mtime=0) as zipped:
with tarfile.open(fileobj=zipped,mode='w') as archive:
for name,data,mode in files:
info=tarfile.TarInfo('shacraft-core/'+name)
info.size=len(data);info.mode=mode;info.mtime=epoch
archive.addfile(info,io.BytesIO(data))
digest=hashlib.sha256(output.read_bytes()).hexdigest()
output.with_name(output.name+'.sha256').write_text(f'{digest} {output.name}\n')
with tarfile.open(output,'r:gz') as archive:
for name,data,_ in files:
actual=archive.extractfile('shacraft-core/'+name).read()
if actual!=data:
raise SystemExit(f'Archive verification failed for {name}')
print(json.dumps({'archive':str(output),'sha256':digest,'bytes':output.stat().st_size,
'git_revision':revision,'verified_files':len(files)},indent=2))
if __name__=='__main__':
main()