#!/usr/bin/env python3 """Verify a built artifact's flavour without installing or launching it. check-flavor.py Exits non-zero unless the artifact contains exactly one `AMBER_FLAVOR::` marker and it matches. Handles an `.apk`, a Linux bundle `.zip` and a Windows bundle `.zip` — it finds the Dart AOT snapshot inside each. **Why this exists.** The clean flavour is what anonymous downloads and every child profile receive, so publishing an adult build into a clean slot is the worst mistake this release process can make — and until now the only way to tell the two apart offline was to install the APK on a television and look at the tab row. That made a content-safety gate depend on a TV being awake and on adb still being authorised, which is exactly how it gets skipped "just this once". Nothing cheaper worked. Class names such as `AdultBrowse` survive in **both** snapshots whatever the dart-define, and the `'adult'`/`'clean'` strings the updater compares sit inside a function body, so both literals ship in both builds. The two APKs are also routinely **byte-identical in size** (zip alignment absorbs the difference), so size proves nothing either. `config.dart` therefore compiles in a deliberate const-folded marker; see [kFlavorMarker]. """ import re import sys import zipfile MARKER = re.compile(rb'AMBER_FLAVOR::(adult|clean)') # Where the Dart AOT snapshot lives, per artifact kind. SNAPSHOTS = ( 'lib/arm64-v8a/libapp.so', # apk 'lib/armeabi-v7a/libapp.so', # apk, 32-bit 'lib/libapp.so', # linux bundle zip 'data/app.so', # windows bundle zip ) def markers_in(blob: bytes) -> set: return {m.group(1).decode() for m in MARKER.finditer(blob)} def main(path: str, expected: str) -> int: if expected not in ('adult', 'clean'): print(f'error: expected must be adult|clean, got {expected!r}') return 2 found = set() checked = [] with zipfile.ZipFile(path) as z: names = set(z.namelist()) for candidate in SNAPSHOTS: if candidate in names: checked.append(candidate) found |= markers_in(z.read(candidate)) if not checked: # Better to fail loudly than to pass an artifact nothing was read from. print(f'FAIL {path}: no Dart snapshot found (looked for {SNAPSHOTS})') return 1 if not found: print(f'FAIL {path}: no AMBER_FLAVOR marker in {checked} — ' 'built before the marker existed?') return 1 if len(found) > 1: print(f'FAIL {path}: ambiguous, contains {sorted(found)}') return 1 actual = found.pop() if actual != expected: print(f'FAIL {path}: is {actual!r}, expected {expected!r}') return 1 print(f'ok {path}: {actual} (from {", ".join(checked)})') return 0 if __name__ == '__main__': if len(sys.argv) != 3: raise SystemExit(__doc__) raise SystemExit(main(sys.argv[1], sys.argv[2]))