"""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}