check-flavor: accept a build directory, refuse a .exe explicitly

This commit is contained in:
Claude 2026-08-01 15:57:37 +02:00
parent 4bf6523ab3
commit 4faac4c280

View file

@ -4,8 +4,13 @@
check-flavor.py <artifact> <expected: adult|clean> check-flavor.py <artifact> <expected: adult|clean>
Exits non-zero unless the artifact contains exactly one `AMBER_FLAVOR::` marker 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 and it matches. Handles an `.apk`, a Linux bundle `.zip`, a Windows bundle `.zip`
`.zip` it finds the Dart AOT snapshot inside each. and an unpacked build **directory** it finds the Dart AOT snapshot inside each.
A Windows `.exe` installer cannot be checked here and is refused rather than
passed: Inno LZMA-compresses the payload, so the marker only exists in a form
nothing can grep. That is why the release script gates the staged folder before
packaging instead of gating the artifact afterwards.
**Why this exists.** The clean flavour is what anonymous downloads and every **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 child profile receive, so publishing an adult build into a clean slot is the
@ -21,6 +26,7 @@ builds. The two APKs are also routinely **byte-identical in size** (zip
alignment absorbs the difference), so size proves nothing either. `config.dart` alignment absorbs the difference), so size proves nothing either. `config.dart`
therefore compiles in a deliberate const-folded marker; see [kFlavorMarker]. therefore compiles in a deliberate const-folded marker; see [kFlavorMarker].
""" """
import os
import re import re
import sys import sys
import zipfile import zipfile
@ -45,14 +51,35 @@ def main(path: str, expected: str) -> int:
print(f'error: expected must be adult|clean, got {expected!r}') print(f'error: expected must be adult|clean, got {expected!r}')
return 2 return 2
if path.lower().endswith('.exe'):
# Say why rather than throwing a BadZipFile, and fail rather than pass:
# a checker that prints something reassuring about an artifact it never
# read is worse than no checker.
print(f'FAIL {path}: an Inno installer LZMA-compresses its payload, so '
'the marker is not readable here.\n'
' The gate for a .exe is Assert-Flavor in '
'amber-app/scripts/release_windows.ps1, which reads the staged\n'
' folder BEFORE packaging. Point this script at that folder, '
'or at the .zip built beside the installer.')
return 1
found = set() found = set()
checked = [] checked = []
with zipfile.ZipFile(path) as z: if os.path.isdir(path):
names = set(z.namelist()) # The staged build folder, which is where the truth actually lives.
for candidate in SNAPSHOTS: for candidate in SNAPSHOTS:
if candidate in names: f = os.path.join(path, candidate.replace('/', os.sep))
if os.path.isfile(f):
checked.append(candidate) checked.append(candidate)
found |= markers_in(z.read(candidate)) with open(f, 'rb') as fh:
found |= markers_in(fh.read())
else:
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: if not checked:
# Better to fail loudly than to pass an artifact nothing was read from. # Better to fail loudly than to pass an artifact nothing was read from.