ci: extract the repeated ccache and apt scaffolding from native_release - #85
Merged
Merged
Conversation
The four ccache build lanes each carried the same restore/prime/stats/summary steps. They now call .github/actions/setup-ccache and .github/actions/ccache-stats with the cache-key prefix and summary label each lane already used; the cache key, restore keys, prime commands and summary heading are unchanged. The apt retry helpers move to tools/apt_retry.sh, sourced by the two install steps that run after checkout. The helper picks sudo by uid instead of hardcoding it, so the container lane and the hosted runner share one copy. The arm64 sources list is rendered from tools/docker/ubuntu.sources.template. The Docker builder image renders the same template from its own os-release instead of shipping a noble-pinned copy.
The extracted build-linux-hip step set DEBIAN_FRONTEND at step level, so it was also exported to python3 -m pip install --upgrade and cmake --version. Put it back on the apt_get_install call, where only apt-get sees it, as on main. tools/apt_retry.sh stays frontend-agnostic. The ccache lane tests compared the set of key prefixes and the set of labels across all four call sites, so swapping two lanes' values passed. They now resolve steps per job and assert each lane's own prefix, restore keys and label.
The extracted steps declare `shell: bash`; build-linux-hip's inline
originals had no `shell:` and, being in a container, ran under `sh -e {0}`.
Record each lane's container and pre-extraction default shell so that delta
cannot widen unnoticed, and assert no extracted script grows a pipeline,
which is what would make the added `-o pipefail` load-bearing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Remaining work from #49, after #83 took the CUDA version and Android CPU variant lists. The issue's opening line is stale:
native_release.ymlis 1470 lines onmain, not 1242; this brings it to 1365.native_release.ymlisworkflow_dispatch-only, so PR CI never runs it. Nothing here was verified by reading the diff — each extraction was executed and compared againstorigin/main, as recorded below.1. ccache scaffolding, 4 lanes
.github/actions/setup-ccache(restore + prime) and.github/actions/ccache-stats(stats + summary). Two actions, not one: the trio brackets the build step, and a composite action cannot contribute apost:step.Before (android):
After:
Proof. A script loaded
origin/main's workflow and this branch's, expanded the composite action'skey/restore-keys/pathagainst each call site's inputs, and compared: cache action version, path, key, restore-key list, prime script, stats script, summary script, bothif: always()conditions, the position of setup before / stats afterBuild, and the remaining step list. All identical for all four lanes, plus all 15 concrete matrix expansions:The empty
extra-restore-keysline on the apple and hip lanes is dropped, not passed as a wildcard. Inactions/cache@v6source,restoreImpl.tsreads the input asutils.getInputAsArray(Inputs.RestoreKeys), andgetInputAsArrayis.split("\n").map(s => …trim()).filter(x => x !== "")— the blank line is filtered out.The
shell:the composite steps getComposite steps must name a shell, so the extracted scripts carry
shell: bashwhere the inline originals carried nothing. Grounded in runner v2.337.0 — the version that ran this workflow, per line 1 of the log for run 34969208714:ScriptHandlerHelpers._defaultArgumentsmaps"bash"to--noprofile --norc -e -o pipefail {0}and"sh"to-e {0}.ScriptHandlerwith noshell:setsshellCommand = "sh"and resolves the path aswhich("bash") ?? which("sh"). For a step the runner executes directly that lands on the bash binary invoked with-e {0}; a step inside acontainer:gets the unresolvedsh, which the log prints verbatim asshell: sh -e {0}.That same run's log shows exactly this split. 79 steps report
shell: /usr/bin/bash -e {0}and 34 reportshell: /bin/bash -e {0}— all plainrun:steps. 23 report a--noprofile --norc -e -o pipefailshell (11/usr/bin/bash, 6/bin/bashon macOS, 5Git\bin\bash.EXEon Windows, 1 barebashin the ROCm container), and every one of those 23 belongs to a step whose script opensset -euo pipefail— the singleshell: bashstep of./.github/actions/checkout-llama-ref, whose 8 call sites expand to 23 job instances. That isorigin/main's only local composite action. The run declares 177 shells in all; the balance is 33 PowerShell steps on the Windows lanes and 8shell: sh -e {0}, every one of those 8 inbuild-linux-hip.Named delta:
build-linux-hipmoves from dash to bashThis is the one behaviour change in the PR, and it lands on a
workflow_dispatchpath PR CI never runs.For
build-android,build-appleandbuild-linuxthe binary is unchanged — their inline steps already resolved to bash — and the only additions are--noprofile/--norc, inert for a non-interactive script read from a file, and-o pipefail.build-linux-hipis the exception: it runs incontainer: rocm/dev-ubuntu-22.04:6.1.2, where/bin/shisdash. The run log reportsshell: sh -e {0}for all 8 of its no-shell:steps; three of those are the ones extracted here —mkdir -p "${CCACHE_DIR}",ccache --show-stats || true, and the{ … } >> "$GITHUB_STEP_SUMMARY"block. Under the composite actions those three becomebash --noprofile --norc -e -o pipefail {0}: a different interpreter, pluspipefail.Keeping dash for that lane alone would mean an expression-valued
shell:in the composite (shell: ${{ inputs.shell }}). That is not adopted here: an unevaluated expression would break the composite in all four ccache lanes rather than changing the shell in one, and that risk asymmetry did not justify the change. Instead the move is disclosed, and both halves of it were checked by running them:docker run --rm --platform linux/amd64 rocm/dev-ubuntu-22.04:6.1.2 sh -c 'which bash; bash --version | head -1; readlink -f /bin/sh'prints/usr/bin/bash,GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu),/usr/bin/dash. The step cannot fail for want of a shell.pipefailcannot bite. It changes only the exit status of a pipeline, and none of the three scripts contains one.grep -n '|'over both new action files returns five lines, not two: three block-scalar indicators (setup-ccache:25restore-keys: |,setup-ccache:33run: |,ccache-stats:17run: |) and two logical-OR operators inside extracted script text (ccache-stats:12andccache-stats:21, bothccache --show-stats || true).||is not a pipe.sh -eand underbash --noprofile --norc -e -o pipefail. Exit status, combined stdout/stderr, and the bytes written to$GITHUB_STEP_SUMMARYwere identical for all three.tests/test_native_release_dedup.py::CcacheStepShellTestspins the result: every lane'scontainer:and the default shell its steps had before extraction, the single shell the extracted steps declare, and an assertion that no extracted script grows a pipeline — the thing that would make-o pipefailload-bearing. Each of those four assertions was confirmed to fail under a deliberate mutation.The nested
actions/cachesave still runsTraced through the same runner v2.337.0 source, not release notes:
ActionRunner.RunAsync— when the nested action'shandlerData.HasPostand the stage isPreorMain, it builds anActionRunnerwithStage = ActionRunStage.Post, names it$"Post {this.DisplayName}", and callsExecutionContext.RegisterPostJobStep.ExecutionContext.RegisterPostJobStep— when the contextIsEmbedded(the step sits inside a composite), it recordsRoot.EmbeddedStepsWithPostRegistered[action.Id] = conditionand returns.CompositeActionHandler.RunAsyncatActionRunStage.Post— walksData.PostStepsand runs exactly those whose id is inEmbeddedStepsWithPostRegistered, restoring the recorded condition.CompositeActionExecutionData.HasPostisPostSteps.Count > 0, soSet up ccacheitself registers a post step.The one local-action restriction on that path is
pre, notpost:ActionRunnerwarns`pre` execution is not supported for local actionand has nopostequivalent.Windows sccache stays separate. It shares no scaffolding with the ccache lanes: setup is a third-party action (
mozilla-actions/sccache-action), storage is the GHA cache service viaSCCACHE_GHA_ENABLEDrather thanactions/cacheover a directory, there is no--max-size/--zero-statspriming and no stats summary, and it is gated onmatrix.arch == 'x64'with an arm64 step that clears the launcher instead. Folding it in would mean one action with two disjoint bodies.2. apt retry helpers
tools/apt_retry.sh, sourced by the twoInstall build depssteps, which run after checkout. It pickssudoby uid, which is what let the hosted-runner copy and the ROCm container copy merge:How many declarations, exactly
grep -con.github/workflows/native_release.yml:apt_get_update() {apt_get_install() {origin/mainThe five on
mainsit in three steps:build-linux→Install build deps(2),build-linux-hip→Install build deps(1),build-linux-hip→Install Git(2). Three declarations go; the two inInstall Gitstay, because that step runs beforeactions/checkout, sotools/does not exist yet and neither a sourced script nor a local composite action is reachable. That step is byte-identical toorigin/main— 26 lines each side, md5ca597d1ce748c92901d10c030740c619, againstorigin/mainate56add1:The android lane's plain
sudo apt-get update && sudo apt-get install -y ninja-build ccacheis left alone — wiring it to the retry helper would add behaviour, not preserve it.build-linux, all 5 matrix rowsBoth versions of the step were extracted from YAML, matrix-expanded, and executed in
ubuntu:24.04as uid 1001 underbash -e— the shell and user the run log shows for this lane — withapt-get/sudo/dpkg/tee/sleep/python3/nvccstubbed onPATHto log argv andDEBIAN_FRONTEND. All five logs are byte-identical before and after. Example,linux/arm64/vulkan:The two arm64 rows also had the file they pipe into
sudo teecaptured: byte-identical before and after, 664 bytes, md53a2ff1e0fe891d31dc1a913b9f0c5849.Textually, that step is byte-identical to
origin/mainfrom theapt_get_updatecall to the end of the step — 42 lines, md547955c66265077baec9988dde4fbe73bon both sides. Only the head of the step changed.build-linux-hipruns undersh, not bashThe run log reports
shell: sh -e {0}for this job's plainrun:steps. That follows from the source above: inside a containervalidateShellOnHostis false, the runner skips thewhich("bash")lookup, and the default stays the literalsh— the image's/bin/sh, which on therocm/dev-ubuntu-22.04:6.1.2base is dash.So
tools/apt_retry.shis held to POSIX, not bash.shellcheckis clean under-s sh,-s bashand-s dash, and this lane's equivalence run below was executed undersh -einubuntu:22.04.Named delta:
DEBIAN_FRONTENDon the hip laneAn earlier revision of this branch moved
DEBIAN_FRONTEND=noninteractivefrom theapt-getcommand prefix to the step'senv:. That exported it for the whole step, includingpython3 -m pip install --upgrade cmakeandcmake --version. Reverted — it is now a prefix on the call, so only the helper'sapt-getsees it andtools/apt_retry.shstays frontend-agnostic (the hostedbuild-linuxlane must not get it):. tools/apt_retry.sh DEBIAN_FRONTEND=noninteractive apt_get_install build-essential binutils ccache ... python3 -m pip install --upgrade cmakeReverting rather than documenting, because the prefix form is exactly equivalent where it counts and costs nothing. Executed in
ubuntu:24.04undersh -eas root, before vs after, with stubs logging argv andDEBIAN_FRONTEND— identical:A prefixed assignment on a function call could have leaked into the rest of the step under POSIX rules; it does not, in either shell that can run this step — checked explicitly under both dash and bash.
One residual, stated plainly. With every
apt-getforced to fail, both versions produce the same ladder — 3 attempts,sleep 15,sleep 30, exit 1 — and everyapt-getseesDEBIAN_FRONTEND=noninteractivein both. The only difference in the whole comparison is that the ladder's twosleepcalls now also inheritDEBIAN_FRONTEND=noninteractive, where onmainthey saw it unset.sleep(1)reads no such variable. This is the entire behaviour delta of item 2.3.
ubuntu.sourcesdivergencetools/docker/ubuntu.sourceshardcodednoble; the workflow emitted the same three stanzas from aprintfheredoc with${VERSION_CODENAME}. Both now rendertools/docker/ubuntu.sources.templatefrom their own/etc/os-release, so the builder image follows its base image instead of a pinned codename.Proof. Three renderings, all 664 bytes, all md5
3a2ff1e0fe891d31dc1a913b9f0c5849, all byte-identical toorigin/main's committedtools/docker/ubuntu.sources:sudo teestub in thebuild-linuxarm64 runs above;sed … ubuntu.sources.template, captured the same way in the same runs;docker buildonubuntu:24.04with the Dockerfile'sCOPY+RUN . /etc/os-release && sed …, thencatout of the built image.4. Inline bash validators — not done
Left out deliberately. The android validator reaches into NDK tool discovery,
mktemp/trapcleanup and matrix conditionals; porting it totools/validate_*.pycould not be proven equivalent by execution the way items 1-3 were, and the only way to exercise it is a release dispatch. Items 1-3 are the issue's explicit suggested fixes; this one is the trailing aside.Checks
python3 -m unittest discover -s tests -p 'test_*.py'— 157 pass (137 onorigin/main; 20 new).python3 -m unittest discover -s tests -p '*_test.py'— 18 pass.actionlint— 26 findings onorigin/main, 26 here, all innative_release.yml(pre-existing shellcheck noise on the PowerShell steps); the diff of findings with line/column stripped is empty. It does read both new composite actions and validate every input name at all 8 call sites — misspellingkey-prefixat one call site produces bothinput "key-prefixx" is not defined in action "Setup ccache"andmissing input "key-prefix" which is required.shellcheck tools/apt_retry.sh— clean under-s sh,-s bashand-s dash.key-prefixbetweenbuild-linuxandbuild-linux-hip— the old set-comparing test passed this; the per-lane test fails it.labelbetweenbuild-androidandbuild-apple— likewise.DEBIAN_FRONTENDback in the hip step'senv:— failstest_container_step_installs_noninteractively_without_exporting_it.