68 lines
3.3 KiB
Python
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}
|