"""Build twice, smoke-test an isolated wheel, and emit release evidence and SBOM.""" import argparse import hashlib import json import os from pathlib import Path import platform import shutil import subprocess import sys import tempfile import tomllib import zipfile ROOT = Path(__file__).resolve().parents[1] EPOCH = '315532800' # Fixed ZIP-compatible build timestamp, not trusted time. def run(*args, cwd=ROOT, env=None): try: return subprocess.run([str(a) for a in args], cwd=cwd, env=env, check=True, capture_output=True, text=True).stdout except subprocess.CalledProcessError as exc: print(exc.stderr, file=sys.stderr) raise def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest() def write(path, value): path.write_text(json.dumps(value, indent=2, sort_keys=True) + '\n') def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--output', type=Path, required=True, help='New evidence directory') args = parser.parse_args() output = args.output.resolve() output.mkdir(parents=True, exist_ok=False) env = dict(os.environ, SOURCE_DATE_EPOCH=EPOCH, PYTHONHASHSEED='0') env.pop('PYTHONPATH', None) inputs = [ROOT / 'pyproject.toml', ROOT / 'README.md', ROOT / '.gitignore', *sorted((ROOT / 'src').rglob('*.py')), *sorted((ROOT / 'src').rglob('*.json'))] manifest = {str(p.relative_to(ROOT)): sha(p) for p in inputs} # Verify the separately reviewed spec and packaged schema copies agree. for p in (ROOT / 'src/railiance_clock/schemas').glob('*.json'): assert p.read_bytes() == (ROOT / 'specs' / p.name).read_bytes(), p.name with tempfile.TemporaryDirectory(prefix='rclk-release-') as tmp: temp = Path(tmp) build_env = temp / 'build-env' run('uv', 'venv', '--python', sys.executable, build_env) build_python = build_env / 'bin/python' run('uv', 'pip', 'install', '--python', build_python, '--require-hashes', '-r', ROOT / 'tools/build-requirements.txt') artifacts = [] for number in (1, 2): source = temp / f'source-{number}' source.mkdir() for original in inputs: target = source / original.relative_to(ROOT) target.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(original, target) # Deliberately vary source mtimes between the independent roots. os.utime(target, (int(EPOCH) + number * 100, int(EPOCH) + number * 100)) dest = temp / f'dist-{number}' run('uv', 'build', source, '--python', build_python, '--no-build-isolation', '--out-dir', dest, env=env) artifacts.append({p.name: sha(p) for p in dest.iterdir() if p.name.endswith(('.whl', '.tar.gz'))}) assert artifacts[0] == artifacts[1], 'build outputs differ' checkout_dist = temp / 'checkout-dist' run('uv', 'build', ROOT, '--python', build_python, '--no-build-isolation', '--out-dir', checkout_dist, env=env) checkout_artifacts = {p.name: sha(p) for p in checkout_dist.iterdir() if p.name.endswith(('.whl', '.tar.gz'))} assert artifacts[0] == checkout_artifacts, 'checkout build differs from copied inputs' wheel = next((temp / 'dist-1').glob('*.whl')) with zipfile.ZipFile(wheel) as archive: members = archive.namelist() assert len([m for m in members if m.endswith('.schema.json')]) == 3 requirements = temp / 'runtime.txt' run('uv', 'export', '--locked', '--no-dev', '--no-emit-project', '-o', requirements) install_env = temp / 'installed-env' run('uv', 'venv', '--python', sys.executable, install_env) python = install_env / 'bin/python' run('uv', 'pip', 'install', '--python', python, '--require-hashes', '-r', requirements) run('uv', 'pip', 'install', '--python', python, '--no-deps', wheel) run('uv', 'pip', 'check', '--python', python) smoke = json.loads(run(python, '-I', ROOT / 'tools/wheel_smoke.py', cwd=temp, env=env)) locked = {p['name']: p['version'] for p in tomllib.loads((ROOT / 'uv.lock').read_text())['package']} for name, version in smoke['distributions']: assert locked[name.lower().replace('_', '-')] == version, (name, version) components = [{'type': 'library', 'name': name, 'version': version, 'purl': f'pkg:pypi/{name.lower().replace("_", "-")}@{version}'} for name, version in smoke['distributions']] write(output / 'runtime.cdx.json', {'bomFormat': 'CycloneDX', 'specVersion': '1.5', 'version': 1, 'components': components}) build_tools = json.loads(run(build_python, '-I', '-c', 'import importlib.metadata as m,json; print(json.dumps(sorted((d.metadata["Name"],d.version) for d in m.distributions())))')) write(output / 'verification.json', { 'source_base_commit': run('git', 'rev-parse', 'HEAD').strip(), 'source_inputs_sha256': manifest, 'runtime_lock_sha256': sha(ROOT / 'uv.lock'), 'build_requirements_sha256': sha(ROOT / 'tools/build-requirements.txt'), 'verifier_sha256': sha(Path(__file__)), 'smoke_script_sha256': sha(ROOT / 'tools/wheel_smoke.py'), 'python': platform.python_version(), 'platform': platform.system() + '/' + platform.machine(), 'uv': run('uv', '--version').strip(), 'build_tools': build_tools, 'source_date_epoch': EPOCH, 'independent_builds': artifacts, 'checkout_build': checkout_artifacts, 'byte_identical': True, 'smoke': smoke, 'sbom_sha256': sha(output / 'runtime.cdx.json'), 'limits': 'Local Linux build/install evidence; no native UTC, security admission, vulnerability audit or cross-platform reproducibility claim.'}) print(f'Verified two identical builds and isolated wheel smoke test: {output}') if __name__ == '__main__': main()