From 4faac4c28083ce8a8f07984b7a324089713d749c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 1 Aug 2026 15:57:37 +0200 Subject: [PATCH] check-flavor: accept a build directory, refuse a .exe explicitly --- scripts/check-flavor.py | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/scripts/check-flavor.py b/scripts/check-flavor.py index 31489c7..d49c0f9 100755 --- a/scripts/check-flavor.py +++ b/scripts/check-flavor.py @@ -4,8 +4,13 @@ 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. +and it matches. Handles an `.apk`, a Linux bundle `.zip`, a Windows bundle `.zip` +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 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` therefore compiles in a deliberate const-folded marker; see [kFlavorMarker]. """ +import os import re import sys import zipfile @@ -45,14 +51,35 @@ def main(path: str, expected: str) -> int: print(f'error: expected must be adult|clean, got {expected!r}') 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() checked = [] - with zipfile.ZipFile(path) as z: - names = set(z.namelist()) + if os.path.isdir(path): + # The staged build folder, which is where the truth actually lives. 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) - 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: # Better to fail loudly than to pass an artifact nothing was read from.