Skip to content

Extract the preview-emulator setup into a composite action - #22

Merged
jpcottin merged 2 commits into
mainfrom
ci/preview-emulator-composite-action
Jul 26, 2026
Merged

Extract the preview-emulator setup into a composite action#22
jpcottin merged 2 commits into
mainfrom
ci/preview-emulator-composite-action

Conversation

@jpcottin

Copy link
Copy Markdown
Owner

Why

Both emulator-preview-* jobs carried their own copy of the same setup — enable KVM, refresh cmdline-tools, install the ps16k image, create the AVD (including the ~30-line hand-written-ini fallback), install emulators;latest, install libpulse0.

The copies had already drifted: the multi-run one had lost three explanatory comments and both the AVD root ini and preview emulator version diagnostics. #21 widened the gap further by rewriting shutdown logic in only one of the two.

What changed

New .github/actions/preview-emulator, parameterised by api-level / target / abi / avd-name / device / ram-size / cores / disk-size, so another image can be added without a third copy. ci.yml loses 153 lines and both jobs reduce to one uses:.

The action also owns the console auth token workaround from #21. Two consequences:

  • The other preview job's Stop emulator step (adb emu kill) stops being a silent no-op — it had the same unauthenticated-console problem, just without anything depending on it.
  • That step now defers to an existing token rather than overwriting it. A fix has been proposed upstream so the emulator creates the file itself; once a preview build ships it, this step becomes inert and can be deleted outright.

Scope

Only the two non-blocking preview jobs are touched. build, test, smoke and android-cli-experiment parse byte-identical to main — verified by diffing the parsed YAML job objects, not just eyeballing.

Verification

Composite actions can't run locally, so the parts that could be checked were:

  • Rendered the action's AVD step with its default inputs and executed it against a sandbox HOME. The generated config.ini is byte-identical to what the inline version produced.
  • That check also caught dead code inherited from the inline copies: a sed intended to strip heredoc indentation that never matched anything, because the YAML block scalar had already stripped it. Dropped rather than carried forward.
  • All 29 run steps in ci.yml and all 6 in the action pass bash -n.

The real test is this CI run; both jobs remain continue-on-error, so a mistake here can't gate anything.

jpcottin added 2 commits July 25, 2026 18:52
The two emulator-preview-* jobs each carried their own copy of the same
setup: enable KVM, refresh cmdline-tools, install the ps16k image, create
the AVD (with the 30-line hand-written-ini fallback), install
emulators;latest, and install libpulse0. The copies had already started to
drift -- the multi-run one had lost three explanatory comments and the
"AVD root ini" / "preview emulator version" diagnostics -- and #21 widened
the gap by rewriting shutdown logic in only one of them.

Move all of it to .github/actions/preview-emulator, parameterised by
api-level / target / abi / avd-name / device / ram-size / cores / disk-size
so a second image can be added without another copy. ci.yml loses 153 lines.

The action also owns the console auth token workaround from #21, which
means the other preview job's "Stop emulator" step (adb emu kill) stops
being a silent no-op too. That step now defers to an existing token instead
of overwriting it, so once a preview build ships the proposed upstream fix
that creates the file itself, the workaround becomes inert and can simply be
deleted.

Only the two non-blocking preview jobs are touched; build, test, smoke and
android-cli-experiment are byte-identical. Verified by rendering the
action's AVD step with its default inputs and running it against a sandbox
HOME: the generated config.ini is byte-identical to what the inline version
produced. (That check also caught dead code carried over from the inline
copies -- a sed meant to strip heredoc indentation that never matched
anything, since the YAML block scalar had already stripped it.)
Composite action steps must declare `shell: bash`, which GitHub runs as
`bash --noprofile --norc -e -o pipefail`. The inline workflow steps this
action replaced had no `shell:` key, so they got the workflow default
`bash -e` -- without pipefail.

Under pipefail, `yes | sdkmanager ... > /dev/null` fails: yes takes SIGPIPE
when sdkmanager exits and the pipeline reports 141. Both preview jobs died
on the first install.

Turn pipefail off for the three steps that pipe into sdkmanager/avdmanager.
That restores the previous semantics exactly -- the pipeline reports the
last command's status, so a genuine sdkmanager failure still fails the
step; only the SIGPIPE from yes is ignored.

The remaining pipeline in the action (echo | sudo tee) is safe: tee
consumes its input fully, and a tee failure propagating is the behaviour
we want.
@jpcottin
jpcottin merged commit 158506a into main Jul 26, 2026
13 checks passed
@jpcottin
jpcottin deleted the ci/preview-emulator-composite-action branch July 26, 2026 02:20
jpcottin added a commit that referenced this pull request Jul 26, 2026
setsid(2) runs asynchronously in the child, so for a brief window after
backgrounding the launcher its pid still reports the *shell's* process
group. Reading the pgid exactly once loses that race intermittently: the
guard then sees EMU_PGID == SELF_PGID, correctly refuses to group-kill our
own step, and silently drops to the single-pid fallback -- so the process
group scoping added in #21 was not reliably active.

Observed on the runner in 1 of 4 boot cycles (#22's run); 0 of 4 in #21's,
which is why it was missed. Consequence was mild -- 'adb emu kill' is the
primary shutdown path and the fallback still terminates the launcher -- but
the scoping is the whole point of that change.

Poll until the pgid settles instead, giving up only if the emulator dies or
after 10s. The first iteration is identical to the previous single read, so
this can only ever do better. Also log the resolved pgid, so a future run
shows whether isolation actually happened rather than staying silent.

The failure cannot be reproduced on an unloaded dev machine (0 losses in 40
trials); forcing the timing -- a child that lingers in our process group for
300ms before setsid -- the old single read loses 10/10 and the polling
version loses 0/10.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant