BallOrcs Battle: the phone build stops being a button somebody has to remember
No new battlefield this week — this was a week under the floorboards.
In plain words
No new battlefield this week — this was a week under the floorboards. Battle builds for four platforms, and the Android one was the one nobody trusted, so it sat behind a button that a person had to press by hand. It kept dying during packaging with a message that explained nothing: the packaging tool was helping itself to as much of the machine as it liked and getting shut down for it. Once it was told how much it may use, the build finished — and immediately revealed that every Android build had been compiling a second version for emulators and then throwing it away. Both are fixed, and Android now builds automatically on every change.
Under the hood
Four days of build plumbing, all of it on things that were failing quietly or lying about their own state.
--jobsnever reached Gradle (41304da). Pipeline 402'sbuild:androidcompiled cleanly atjobs=2, peaking at 2.4 GiB — then the export stage shelled out to Gradle, which started a daemon that grew to 2.7 GB RSS and was cgroup-OOM-killed againstBUILD_MEM_LIMIT. Gradle reported only "Gradle build daemon disappeared unexpectedly"; the actual cause appears indmesg(constraint=CONSTRAINT_MEMCG, task=java) and nowhere in the job log. 43 minutes, no APK.--jobsgoverns scons and nothing else — Gradle sizes its daemon, worker pool and heap fromnprocand available RAM, so on an 8-CPU box it ran about seven ways wide regardless.GRADLE_OPTSis the knob that bounds it, and it joins the-eforwarding list because it is read on the far side of the container boundary.- An ABI built four times a week and shipped never (
5bd376f).verify:android-packageran for the first time on pipeline 403 and immediately caught what it was written to catch:TARGET_ARCHESbuilds arm64 and x86_64, but neithergame.gdextensionnorexport_presets.cfgshipped x86_64. Every Android build compiled an x86_64 engine template and an x86_64 GDExtension and threw both away, and the APK went out arm64-only — so the emulator those artifacts existed for could not run the package they were built for. Shipping it (rather than dropping the arch) is the deliberate call: the build already pays the compile cost. Note this escalates the checker — a shipped ABI missing from the APK is an error where built-but-unshipped was a warning. - Android goes automatic on main (
f4653b6). Both jobs move off the shared.optional_platform_rulesonto their own.android_rules— same conditions, minuswhen: manual. Manual is how the ABI drift survived: a gate nobody triggers is not a gate.allow_failurestays true for now (two green runs is not a track record, and it is the heaviest job on a box with 7.8 GB of guest RAM), and web stays manual because the web artifact is still unproven. Costs ~25 min warm, and the runner isconcurrent = 1, so it roughly doubles a main pipeline. - Which is why auto-cancel had to start working (
a8b4e07).auto_cancel_pending_pipelinesonly ever cancelled pipelines with no job started — so on 2026-08-11 #407 was 22 minutes intobuild:linuxwhen #408 superseded it, and #408's ten-second shellcheck was queued to wait about 45 minutes behind a build nobody wanted the answer to.workflow.auto_cancel.on_new_commit: interruptibleextends this to running jobs, but GitLab only cancels if every started job opts in, so a per-job sprinkling achieves nothing:default: interruptible: truemakes jobs opt out instead. Safe here because the builds are pure functions of the commit. Scheduled pipelines are exempt — the Monday release train tags and writesSHA256SUMS, and killing that halfway is precisely the state that once shipped an empty checksum file. - A guard that had stopped covering its own subject (
f9b36ea).GRADLE_OPTSwas added to the container's-elist and never joinedMUST_FORWARDin the test that exists to catch unforwarded knobs — so for a day the check did not cover the fourth instance of the thing it checks. Watched failing, not passing: removing the-eline now reddens the test with the variable named. The general form is worth stating, because two guards here have it — when a check carries a hand-maintained list, adding the thing without adding the list entry is the same defect one level up. - The docs that still described the old arrangement (
ff582f3,b02caad,468b2a2). Two files still assumed Android was manual on main, one of them arguing that an emulator job would inherit those rules and therefore stay cheap — now wrong in the expensive direction. The dated CI-facts log was left in place with a SUPERSEDED marker rather than rewritten, because the pipeline timings around it were measured under the old arrangement. The open-items note also had an item asserting two faults and evidencing one: a 401 from the registry establishes dead credentials and says nothing at all about whether the image digest resolves, and those have different fixes.
RTS phase work stands where it did — no commit this week claims progress past what has already landed. The payoff is that the pipeline that will have to carry it now runs the Android build on every push instead of when someone remembers.