sysupgrade: put the mounts back when the pivot does not happen #2945
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
| name: build | |
| on: | |
| pull_request: | |
| branches: | |
| - master | |
| schedule: | |
| - cron: '30 22 * * *' | |
| workflow_dispatch: | |
| jobs: | |
| preflight: | |
| name: Preflight | |
| # Guard rail for clones of this repo. A clone pushed to a new repository | |
| # (a private mirror, an internal re-host) inherits this file along with | |
| # its cron, and the full platform matrix — ~2000 runner-minutes a pass — | |
| # then runs on that owner's Actions bill: nightly via the schedule, and | |
| # again on every internal PR they open. Forks are partially covered | |
| # (GitHub disables scheduled workflows there), a fresh repository is not. | |
| # | |
| # The line is drawn where the minutes stop being free: | |
| # - canonical repo: everything runs, unchanged | |
| # - workflow_dispatch: an operator explicitly asked; runs anywhere | |
| # - pull_request on a PUBLIC repo: free minutes (contributor PRs here | |
| # run in this repo's context; within-fork PRs are public by | |
| # construction) — runs | |
| # - anything unattended or billed on a non-canonical repo — skips | |
| if: >- | |
| github.repository == 'OpenIPC/firmware' || | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && !github.event.repository.private) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.gate.outputs.should_build }} | |
| head_sha: ${{ steps.gate.outputs.head_sha }} | |
| short_sha: ${{ steps.gate.outputs.short_sha }} | |
| build_id: ${{ steps.gate.outputs.build_id }} | |
| built_at: ${{ steps.gate.outputs.built_at }} | |
| webui_digest: ${{ steps.gate.outputs.webui_digest }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - id: gate | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| HEAD=$(git rev-parse HEAD) | |
| SHORT=$(git rev-parse --short HEAD) | |
| BUILD_ID="nightly-$(date -u +%Y%m%d)-${SHORT}" | |
| BUILT_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| NOTES_PREV=$(gh release view nightly --json body -q .body 2>/dev/null || true) | |
| PREV=$(printf '%s\n' "$NOTES_PREV" | sed -n 's/^sha=//p' | head -1) | |
| PREV_WEBUI=$(printf '%s\n' "$NOTES_PREV" | sed -n 's/^webui=//p' | head -1) | |
| # majestic-webui ships as a rolling `dist` release asset, so a WebUI fix | |
| # changes what the nightly would contain without ever moving this repo's | |
| # HEAD — on a quiet firmware tree the schedule would skip forever and the | |
| # fix would never reach an image. Compare the asset's content digest as | |
| # well. It is content-addressed, so a re-upload of identical bytes does | |
| # not force a pointless rebuild. An empty value (API hiccup, or an asset | |
| # with no digest) falls through to building: stale is worse than spare. | |
| WEBUI=$(gh api repos/OpenIPC/majestic-webui/releases/tags/dist \ | |
| --jq '.assets[]|select(.name=="majestic-webui-dist.tar.gz")|.digest // empty' \ | |
| 2>/dev/null || true) | |
| if [ "${{ github.event_name }}" = "schedule" ] && [ "$PREV" = "$HEAD" ] \ | |
| && [ -n "$WEBUI" ] && [ "$PREV_WEBUI" = "$WEBUI" ]; then | |
| echo "Skip: HEAD ($HEAD) already published as the latest nightly, majestic-webui dist unchanged ($WEBUI)." | |
| echo "should_build=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Build: $BUILD_ID (event=${{ github.event_name }}, prev=$PREV, webui=$WEBUI, prev_webui=$PREV_WEBUI)" | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "head_sha=$HEAD" >> "$GITHUB_OUTPUT" | |
| echo "short_sha=$SHORT" >> "$GITHUB_OUTPUT" | |
| echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT" | |
| echo "built_at=$BUILT_AT" >> "$GITHUB_OUTPUT" | |
| echo "webui_digest=$WEBUI" >> "$GITHUB_OUTPUT" | |
| buildroot: | |
| name: Firmware | |
| needs: preflight | |
| if: needs.preflight.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| env: | |
| BUILD_ID: ${{ needs.preflight.outputs.build_id }} | |
| BUILD_SHA: ${{ needs.preflight.outputs.head_sha }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| # Sigmastar [I6] | |
| - ssc325_lite | |
| - ssc325de_lite | |
| # Sigmastar [I6B] | |
| - ssc333_lite | |
| - ssc335_lite | |
| - ssc335de_lite | |
| - ssc337_lite | |
| - ssc337de_lite | |
| # Sigmastar [I6C] | |
| - ssc377_lite | |
| - ssc377d_lite | |
| - ssc377de_lite | |
| - ssc377qe_lite | |
| - ssc378de_lite | |
| - ssc378qe_lite | |
| # Sigmastar [I6E] | |
| - ssc30kd_lite | |
| - ssc30kq_lite | |
| - ssc338q_lite | |
| # Ingenic [T21] | |
| - t10_lite | |
| - t20_lite | |
| - t21_lite | |
| - t30_lite | |
| # Ingenic [T23] | |
| - t23_lite | |
| # Ingenic [T31] | |
| - t31_lite | |
| # Ingenic [T40] | |
| - t40_lite | |
| # Hisilicon [HI3516AV100] | |
| - hi3516av100_lite | |
| - hi3516av100_neo | |
| - hi3516dv100_lite | |
| # Hisilicon [HI3516CV100] | |
| - hi3516cv100_lite | |
| - hi3516cv100_neo | |
| - hi3518cv100_lite | |
| - hi3518ev100_lite | |
| # Hisilicon [HI3516CV200] | |
| - hi3516cv200_lite | |
| - hi3516cv200_neo | |
| - hi3518ev200_lite | |
| # Hisilicon [HI3516CV300] | |
| - hi3516cv300_lite | |
| - hi3516cv300_neo | |
| - hi3516ev100_lite | |
| # Hisilicon [HI3516CV500] | |
| - hi3516av300_lite | |
| - hi3516av300_neo | |
| - hi3516cv500_lite | |
| - hi3516dv300_lite | |
| # Hisilicon [HI3516CV6XX] | |
| - hi3516cv6xx_ultimate | |
| # Hisilicon [HI3519DV500] | |
| - hi3519dv500_ultimate | |
| # Hisilicon [HI3516EV200] | |
| - hi3516dv200_lite | |
| - hi3516ev200_lite | |
| - hi3516ev300_lite | |
| - hi3518ev300_lite | |
| - hi3516ev300_neo | |
| # Hisilicon [HI3519V101] | |
| - hi3516av200_lite | |
| - hi3516av200_neo | |
| - hi3519v101_lite | |
| # Hisilicon [HI3520DV200] | |
| - hi3520dv200_lite | |
| # Hisilicon [HI3536CV100] | |
| - hi3536cv100_lite | |
| # Hisilicon [HI3536DV100] | |
| - hi3536dv100_lite | |
| # Goke [GK710X] | |
| - gk7102_lite | |
| - gk7102s_lite | |
| # Goke [GK7205V200] | |
| - gk7202v300_lite | |
| - gk7205v200_lite | |
| # gk7205v210 is firmware-identical to gk7205v200 — built once and | |
| # served via gk7205v200's BR2_OPENIPC_SOC_ALIASES (manifest @alias). | |
| - gk7205v300_lite | |
| - gk7605v100_lite | |
| # Goke [GK7205V500] | |
| - gk7205v500_lite | |
| # Allwinner | |
| - v851s_lite | |
| # Fullhan | |
| - fh8852v100_lite | |
| - fh8852v200_lite | |
| # Grainmedia | |
| - gm8135_lite | |
| - gm8136_lite | |
| # Novatek | |
| - nt98562_lite | |
| - nt98566_lite | |
| # Rockchip | |
| - rv1103_lite | |
| - rv1106_lite | |
| - rv1109_lite | |
| - rv1126_lite | |
| # Xiongmai | |
| - xm510_lite | |
| - xm530_lite | |
| # xm550 is firmware-identical to xm530 — built once and served via | |
| # xm530's BR2_OPENIPC_SOC_ALIASES (manifest @alias). | |
| # Ultimate | |
| - ssc333_ultimate | |
| - ssc335_ultimate | |
| - ssc335de_ultimate | |
| - ssc337_ultimate | |
| - ssc337de_ultimate | |
| - ssc30kd_ultimate | |
| - ssc30kq_ultimate | |
| - ssc338q_ultimate | |
| - t20_ultimate | |
| - t21_ultimate | |
| - t31_ultimate | |
| - t40_ultimate | |
| - hi3516av100_ultimate | |
| - hi3516dv100_ultimate | |
| - hi3518ev200_ultimate | |
| - hi3516cv300_ultimate | |
| - hi3516ev200_ultimate | |
| - hi3516ev300_ultimate | |
| - hi3518ev300_ultimate | |
| - hi3516av200_ultimate | |
| - gk7202v300_ultimate | |
| - gk7205v200_ultimate | |
| - gk7205v300_ultimate | |
| steps: | |
| - name: Checkout source | |
| uses: actions/checkout@v4 | |
| - name: Prepare firmware | |
| run: | | |
| echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf | |
| echo CACHE_DATE=$(date +%m) >> ${GITHUB_ENV} | |
| - name: Setup ccache | |
| if: github.event_name != 'pull_request' | |
| uses: actions/cache@v4 | |
| with: | |
| path: /tmp/ccache | |
| key: ${{matrix.platform}}-${{env.CACHE_DATE}} | |
| - name: Restore ccache | |
| if: github.event_name == 'pull_request' | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: /tmp/ccache | |
| key: ${{matrix.platform}}-${{env.CACHE_DATE}} | |
| - name: Setup dl cache | |
| if: github.event_name != 'pull_request' | |
| uses: actions/cache@v4 | |
| with: | |
| path: output/dl | |
| key: dl-${{env.CACHE_DATE}} | |
| restore-keys: dl- | |
| - name: Restore dl cache | |
| if: github.event_name == 'pull_request' | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: output/dl | |
| key: dl-${{env.CACHE_DATE}} | |
| restore-keys: dl- | |
| - name: Refresh moving-ref package downloads | |
| run: | | |
| # Packages pinned to a moving ref keep a constant download filename. The dl | |
| # cache key is the month (date +%m) and actions/cache only saves on a key | |
| # miss, so that snapshot is frozen for weeks; buildroot then keeps reusing the | |
| # stale tarball and the nightly silently ships an old version. This is what | |
| # desynced majestic-webui from majestic (blanking settings labels; later a t31 | |
| # lite image shipped a pre-x-groups majestic vs the current webui — "No | |
| # settings groups in schema"). Drop both naming forms so buildroot re-fetches: | |
| # - dash: <pkg>-<ref>.tar.gz e.g. majestic-webui-dist.tar.gz | |
| # - dot: <pkg>.<ref>.tar.bz2 e.g. majestic.t31.lite.master.tar.bz2 | |
| # The dot form is majestic's S3 tarball — it LOOKS S3-pinned but is a moving | |
| # `master` build re-uploaded every majestic CI run, with no .hash to force a | |
| # refetch. Only genuinely content-addressed tarballs (semver / SHA) keep cache. | |
| find output/dl -type f -regextype posix-extended \ | |
| -regex '.*[-.](HEAD|master|main|dist)\.tar\.(gz|bz2|xz)' \ | |
| -print -delete 2>/dev/null || true | |
| - name: Build firmware | |
| run: | | |
| export GIT_HASH=$(git rev-parse --short ${GITHUB_SHA}) | |
| export GIT_BRANCH=${GITHUB_REF_NAME} | |
| echo GIT_HASH=${GIT_HASH} >> ${GITHUB_ENV} | |
| echo GIT_BRANCH=${GIT_BRANCH} >> ${GITHUB_ENV} | |
| mkdir -p /tmp/ccache | |
| ln -s /tmp/ccache ${HOME}/.ccache | |
| # Backoffs give 7 attempts total. The longer tail (600, 1200) covers | |
| # the GitHub releases CDN / toolchain mirror flakes that took down | |
| # hi3516av100_ultimate on 2026-05-20 (run 26196546684) — 502s | |
| # storming for >10 min outlasted the previous 30/60/120/300 budget. | |
| backoffs="30 60 120 300 600 1200" | |
| attempt=1 | |
| for sleep_for in $backoffs ""; do | |
| make BOARD=${{matrix.platform}} && break | |
| if [ -z "$sleep_for" ]; then | |
| echo "::error::build failed after ${attempt} attempts" | |
| exit 1 | |
| fi | |
| echo "::warning::attempt ${attempt} failed, retrying after ${sleep_for}s" | |
| sleep "$sleep_for" | |
| attempt=$((attempt + 1)) | |
| done | |
| TIME=$(date -d @${SECONDS} +%M:%S) | |
| echo TIME=${TIME} >> ${GITHUB_ENV} | |
| NORFW=$(find output/images -name openipc*nor*) | |
| if [ -e ${NORFW} ]; then | |
| echo NORFW=${NORFW} >> ${GITHUB_ENV} | |
| fi | |
| NANDFW=$(find output/images -name openipc*nand*) | |
| if [ -e ${NANDFW} ]; then | |
| echo NANDFW=${NANDFW} >> ${GITHUB_ENV} | |
| fi | |
| - name: Verify kernel modules | |
| run: sh .github/scripts/check_target_modules.sh | |
| - name: Build size report | |
| run: | | |
| make BOARD=${{matrix.platform}} size-report || \ | |
| echo "::warning::size-report failed for ${{matrix.platform}}" | |
| SIZES=$(find output/images -name 'sizes.*.json' | head -1) | |
| if [ -e "$SIZES" ]; then | |
| echo "SIZES=$SIZES" >> ${GITHUB_ENV} | |
| fi | |
| - name: Build kconfig graph | |
| run: | | |
| python3 -m pip install --user --break-system-packages kconfiglib || \ | |
| { echo "::warning::kconfiglib install failed for ${{matrix.platform}}"; exit 0; } | |
| make BOARD=${{matrix.platform}} kconfig-graph || \ | |
| { echo "::warning::kconfig-graph failed for ${{matrix.platform}}"; exit 0; } | |
| KCONFIG=$(find output/images -name 'kconfig-graph.*.json' | head -1) | |
| KHELP=$(find output/images -name 'kconfig-help.*.json' | head -1) | |
| if [ -e "$KCONFIG" ]; then echo "KCONFIG=$KCONFIG" >> ${GITHUB_ENV}; fi | |
| if [ -e "$KHELP" ]; then echo "KHELP=$KHELP" >> ${GITHUB_ENV}; fi | |
| # Hand the firmware + sidecars to the single `publish` job instead of | |
| # uploading to releases from here. ~90 matrix jobs all deleting and | |
| # re-uploading assets on the SAME shared `nightly`/`latest` releases at | |
| # once made the GitHub releases API return HTTP 500 ("Server Error") — | |
| # e.g. run 27108181857, where every build succeeded but 14 jobs went red | |
| # in their upload step. Consolidating every release write into one job | |
| # removes the concurrent-writer contention. | |
| - name: Upload build artifacts | |
| if: github.event_name != 'pull_request' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: fw-${{ matrix.platform }} | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| path: | | |
| output/images/openipc*.tgz | |
| output/images/sizes.*.json | |
| output/images/kconfig-graph.*.json | |
| output/images/kconfig-help.*.json | |
| - name: Send binary | |
| if: github.event_name != 'pull_request' && env.NORFW | |
| run: | | |
| TG_MSG="Build: ${BUILD_ID}\nCommit: ${GIT_HASH}\nBranch: ${GIT_BRANCH}\nTime: ${TIME}\n\n" | |
| TG_ICON="\xE2\x9C\x85 GitHub Actions" | |
| TG_HEADER=$(echo -e ${TG_MSG}${TG_ICON}) | |
| TG_TOKEN=${{secrets.TELEGRAM_TOKEN_BOT_OPENIPC}} | |
| TG_CHANNEL=${{secrets.TELEGRAM_CHANNEL_OPENIPC_DEV}} | |
| HTTP=$(curl -s -o /dev/null -w %{http_code} https://api.telegram.org/bot${TG_TOKEN}/sendDocument -F chat_id=${TG_CHANNEL} -F caption="${TG_HEADER}" -F document=@${NORFW}) | |
| echo Telegram response: ${HTTP} | |
| # All release writes happen here, once, so only a SINGLE job ever touches the | |
| # shared `nightly`/`latest` releases (and the per-run dated release). The old | |
| # design had every matrix job upload to these releases, so ~90 jobs raced to | |
| # delete/re-upload assets on the same release and the GitHub releases API | |
| # returned HTTP 500 ("Server Error") under that concurrency — run | |
| # 27108181857 built fine on every board yet went red on 14 upload steps. | |
| # | |
| # Best-effort by design: publish whatever boards produced, even if some | |
| # failed (matches enrich_manifest.py, which indexes whatever assets exist). | |
| # Never runs on PRs (no artifacts) or on a scheduled no-op (buildroot skipped). | |
| publish: | |
| name: Publish releases | |
| needs: [preflight, buildroot] | |
| if: >- | |
| github.event_name != 'pull_request' && | |
| needs.preflight.outputs.should_build == 'true' && | |
| contains(fromJSON('["success", "failure"]'), needs.buildroot.result) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| pattern: fw-* | |
| path: dist | |
| merge-multiple: true | |
| - name: Collect assets | |
| id: collect | |
| run: | | |
| mkdir -p dist | |
| count=$(find dist -type f | wc -l) | |
| echo "Collected ${count} asset(s):" | |
| ls -la dist || true | |
| echo "count=${count}" >> "$GITHUB_OUTPUT" | |
| # Drive the release writes ourselves instead of softprops/action-gh-release. | |
| # That action uploads assets CONCURRENTLY with no throttle/retry knob, so | |
| # firing ~390 asset writes at three releases tripped GitHub's per-actor | |
| # secondary rate limit — run 27314054079's "dated" step died after | |
| # uploading 350/393 assets with "You have exceeded a secondary rate limit". | |
| # Here every asset is uploaded one at a time, paced under the per-minute | |
| # mutation ceiling, with exponential backoff so a transient 403/secondary | |
| # limit is retried rather than failing the nightly. This is the third | |
| # iteration of the publish design: per-job uploads raced to HTTP 500 → | |
| # single-job concurrent uploads tripped the secondary limit → single-job | |
| # paced uploads (here). The single-writer property that fixed the 500s is | |
| # preserved; only the within-job upload rate changed. | |
| - name: Publish releases (paced, retry-aware) | |
| if: steps.collect.outputs.count != '0' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| BUILD_ID: ${{ needs.preflight.outputs.build_id }} | |
| HEAD_SHA: ${{ needs.preflight.outputs.head_sha }} | |
| SHORT_SHA: ${{ needs.preflight.outputs.short_sha }} | |
| BUILT_AT: ${{ needs.preflight.outputs.built_at }} | |
| WEBUI_DIGEST: ${{ needs.preflight.outputs.webui_digest }} | |
| run: | | |
| set -euo pipefail | |
| # Retry any gh invocation with exponential backoff. Covers the 403 / | |
| # 429 secondary-rate-limit response per GitHub's own guidance (pause, | |
| # back off, retry) — the asset uploads are the calls that hit it. | |
| gh_retry() { | |
| local n=0 max=6 delay=15 | |
| until "$@"; do | |
| n=$((n + 1)) | |
| if [ "$n" -ge "$max" ]; then | |
| echo "::error::gh failed after ${max} attempts: $*" | |
| return 1 | |
| fi | |
| echo "::warning::gh attempt ${n} failed, sleeping ${delay}s before retry" | |
| sleep "$delay" | |
| delay=$((delay * 2)) | |
| done | |
| } | |
| # Create the release at the built commit if it does not exist yet. | |
| ensure_release() { # <tag> [extra gh release create args...] | |
| local tag="$1"; shift | |
| if ! gh release view "$tag" >/dev/null 2>&1; then | |
| gh_retry gh release create "$tag" --target "$HEAD_SHA" --notes "$NOTES" "$@" | |
| fi | |
| } | |
| # Upload one asset at a time, pacing ~1/s to stay under the per-minute | |
| # content-mutation ceiling that triggers the secondary rate limit. | |
| upload_paced() { # <tag> <file>... | |
| local tag="$1"; shift | |
| local total=$# i=0 | |
| for f in "$@"; do | |
| i=$((i + 1)) | |
| echo "[${tag}] (${i}/${total}) ${f##*/}" | |
| gh_retry gh release upload "$tag" "$f" --clobber | |
| sleep 1 | |
| done | |
| } | |
| # `webui=` is what the next preflight diffs against to notice a WebUI-only | |
| # change; drop the line rather than record an empty value, so a failed | |
| # lookup reads as "unknown" and builds instead of matching a future empty. | |
| NOTES=$(printf 'sha=%s\nshort=%s\nbuilt_at=%s\n' "$HEAD_SHA" "$SHORT_SHA" "$BUILT_AT") | |
| if [ -n "$WEBUI_DIGEST" ]; then | |
| NOTES=$(printf '%s\nwebui=%s\n' "$NOTES" "$WEBUI_DIGEST") | |
| fi | |
| # Full asset set (firmware images + sizes/kconfig sidecars) → dated. | |
| mapfile -t DATED < <(find dist -maxdepth 1 -type f | sort) | |
| # nightly/latest are firmware-delivery aliases for flashers, so ship | |
| # only the firmware images there — re-uploading the small JSON sidecars | |
| # to two more releases is pure rate-limit cost for no consumer. | |
| mapfile -t IMAGES < <(find dist -maxdepth 1 -type f -name 'openipc*.tgz' | sort) | |
| # --- dated: immutable per-build history --- | |
| ensure_release "$BUILD_ID" --prerelease --title "$BUILD_ID" | |
| upload_paced "$BUILD_ID" "${DATED[@]}" | |
| # Completion marker, uploaded LAST: its presence is the single downstream | |
| # signal that the dated release finished publishing. firmware-explorer's | |
| # prebuild gates ingestion on it so a rate-limit-truncated release is | |
| # never indexed as a complete build. | |
| printf '{"build_id":"%s","expected_assets":%s,"head_sha":"%s","built_at":"%s"}\n' \ | |
| "$BUILD_ID" "${#DATED[@]}" "$HEAD_SHA" "$BUILT_AT" > dist/_manifest.json | |
| gh_retry gh release upload "$BUILD_ID" dist/_manifest.json --clobber | |
| # --- rolling nightly: move tag + body to this build, refresh images --- | |
| ensure_release nightly | |
| gh_retry gh release edit nightly --notes "$NOTES" | |
| gh_retry gh api -X PATCH "repos/${GH_REPO}/git/refs/tags/nightly" -f sha="$HEAD_SHA" -F force=true | |
| upload_paced nightly "${IMAGES[@]}" | |
| # --- latest: legacy alias --- | |
| ensure_release latest | |
| gh_retry gh api -X PATCH "repos/${GH_REPO}/git/refs/tags/latest" -f sha="$HEAD_SHA" -F force=true | |
| upload_paced latest "${IMAGES[@]}" | |
| # Single umbrella status check that reflects the whole build matrix, so the | |
| # branch-protection required-checks list does not need one hardcoded | |
| # "Firmware (<board>)" context per board (adding/removing a board no longer | |
| # requires a settings change). Make "CI Gate" the required check on master | |
| # instead of the per-board contexts. | |
| ci-gate: | |
| name: CI Gate | |
| needs: [preflight, buildroot, publish] | |
| # always() so the gate still reports when a needed job fails -- but not on a | |
| # run whose preflight the guard above deliberately skipped, where it would | |
| # read preflight=skipped and turn a saved build into a red run. Upstream the | |
| # added disjunct is true, so this reduces to always(). | |
| if: >- | |
| always() && (github.repository == 'OpenIPC/firmware' || | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && !github.event.repository.private)) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Require preflight + firmware matrix to succeed | |
| run: | | |
| echo "preflight=${{ needs.preflight.result }} buildroot=${{ needs.buildroot.result }} publish=${{ needs.publish.result }}" | |
| if [ "${{ needs.preflight.result }}" != "success" ]; then | |
| echo "::error::preflight did not succeed"; exit 1 | |
| fi | |
| # On pull_request, preflight always sets should_build=true so the | |
| # matrix runs; 'skipped' only happens on a scheduled no-op build. | |
| case "${{ needs.buildroot.result }}" in | |
| success|skipped) echo "firmware matrix OK (${{ needs.buildroot.result }})" ;; | |
| *) echo "::error::firmware matrix result=${{ needs.buildroot.result }}"; exit 1 ;; | |
| esac | |
| # publish is skipped on PRs and on scheduled no-ops; only a real | |
| # failure of the single release-writer should fail the gate. | |
| case "${{ needs.publish.result }}" in | |
| success|skipped) echo "publish OK (${{ needs.publish.result }})" ;; | |
| *) echo "::error::publish result=${{ needs.publish.result }}"; exit 1 ;; | |
| esac |