Files
fds-os/tools/release_artifacts.py

68 lines
3.3 KiB
Python

"""The complete versioned image/package set used by comparison and release assembly."""
import hashlib
import json
from pathlib import Path
from fds_version import validate
def digest(path):
with Path(path).open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def artifacts(project):
project = Path(project).resolve(strict=True)
out = project / 'out'
result = {}
for name in ('fds-system-cli.img', 'fds-system-development.img', 'fds-recovery.img',
'fds-boot.img', 'fds-internal.img'):
result[name] = (out / name).resolve(strict=True)
for profile in ('cli', 'development', 'recovery'):
result[f'rootfs-{profile}.tar'] = (out / f'rootfs-{profile}.tar').resolve(strict=True)
for suffix in ('', '.gz', '.lz4', '.zst'):
name = 'initramfs.cpio' + suffix
result[name] = (out / 'initramfs' / name).resolve(strict=True)
for name in ('kernel_2712.img', 'bcm2712-rpi-5-b.dtb'):
result[name] = (out / 'kernel/boot' / name).resolve(strict=True)
for name in ('configured.bin', 'rollback.bin', 'configured.conf', 'original.conf'):
result['eeprom-production-' + name] = (out / 'eeprom-production-latest' / name).resolve(strict=True)
packages = sorted((out / 'packages').glob('fds-*.aarch64.xbps'))
required = {'fds-base', 'fds-base-files', 'fds-init', 'fds-cli', 'fds-cartridged',
'fds-dasungd', 'fds-kernel', 'fds-dhcpcd', 'fds-eink'}
names = {p.name.rsplit('-', 1)[0] for p in packages}
if names != required or len(packages) != len(required):
raise ValueError('The release requires exactly one version of every FDS base package')
for path in packages:
result[path.name] = path.resolve(strict=True)
for name, path in result.items():
if not path.is_file() or path.stat().st_size == 0:
raise ValueError(f'Missing or empty release artifact: {name}')
return result
def read_lock(project):
path = Path(project) / '.host/frozen/lock.json'
lock = json.loads(path.read_text())
if lock.get('format') != 1 or not validate(lock.get('version')):
raise ValueError('Build does not identify a supported frozen input snapshot')
return lock, digest(path)
def compare(first, second):
left_lock, left_hash = read_lock(first)
right_lock, right_hash = read_lock(second)
if left_hash != right_hash:
raise ValueError('Builds did not use the same frozen input lock')
left, right = artifacts(first), artifacts(second)
if left.keys() != right.keys():
raise ValueError('Builds produced different artifact names')
records = []
for name in sorted(left):
one, two = digest(left[name]), digest(right[name])
records.append({'name': name, 'first_sha256': one, 'second_sha256': two,
'first_bytes': left[name].stat().st_size, 'second_bytes': right[name].stat().st_size,
'identical': one == two and left[name].stat().st_size == right[name].stat().st_size})
return {'format': 1, 'status': 'passed' if all(x['identical'] for x in records) else 'failed',
'version': left_lock['version'], 'source_sha256': left_lock['source_sha256'],
'input_lock_sha256': left_hash, 'hardware_validation': 'deferred', 'artifacts': records}