Build Images #363
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 Images | |
| # Builds the Swift Linux A/B UEFI images in the plain buildroot-swift:latest | |
| # container. Each job seeds its Buildroot output tree from the rolling | |
| # toolchain-latest release that build-toolchain.yml publishes (br-seed), so | |
| # the cross-toolchain, Swift and the mesa host tools are reused and only the | |
| # image profile's extra packages - sway, GRUB, RAUC, steam, ... - compile. | |
| # The trees persist on the runner at /mnt/br/profiles/<profile>/<arch>, | |
| # bind-mounted to /mnt/br/output/<arch> in the container because that is the | |
| # prefix Buildroot baked into the packaged toolchain - see docs/build.md. | |
| # The Swift SDKs are built by a separate workflow (build-swift-sdk.yml): they | |
| # share nothing with the images but the container, and splitting them means a | |
| # broken SDK and a broken image are separate red marks, each re-runnable | |
| # without paying for the other. | |
| # | |
| # Runs on the self-hosted x86 runner (see docs/build.md), which is a single | |
| # machine: the jobs below execute one at a time rather than side by side on | |
| # GitHub's pool. The ccache lives on that machine's own disk at | |
| # /mnt/br/ccache/<arch> - the same directory the toolchain and SDK workflows | |
| # use - so it survives between runs and there is nothing to upload or restore. | |
| # | |
| # A full run builds four Buildroot trees (two images, two 32-bit companions) | |
| # and takes hours, so it is not worth spending on every push: it runs once a | |
| # night, and on demand from the Actions tab (or "gh workflow run | |
| # build-images.yml") when a change needs checking before then. | |
| on: | |
| schedule: | |
| # 07:00 UTC - after the working day in the Americas, so the nightly | |
| # result is waiting in the morning. | |
| - cron: '0 7 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| frontend: | |
| description: > | |
| Frontend the image boots into (sdk/defconfig/frontend/), or "none" | |
| for the frontend-independent baseline. | |
| required: false | |
| default: minimal | |
| type: choice | |
| options: [minimal, emulationstation, gmenu2x, gnome, phosh, xfce, none] | |
| arch: | |
| description: > | |
| Architecture to build, for the generic UEFI boards. "both" is the | |
| nightly's pair. Ignored when a device is given, since a board is | |
| one architecture already. | |
| required: false | |
| default: both | |
| type: choice | |
| options: [both, x86_64, arm64] | |
| device: | |
| description: > | |
| Board to build for (a directory under sdk/board/, e.g. | |
| chromebook-scarlet), instead of the generic UEFI image for the | |
| architecture. Only that board's architecture is built. Leave empty | |
| for the usual x86_64 + arm64 pair. | |
| required: false | |
| default: '' | |
| type: string | |
| rebuild: | |
| description: > | |
| Space-separated packages to dirclean before building, for when a | |
| persistent tree holds a copy built the wrong way. Needed after | |
| adding a patch to a package the tree already extracted: Buildroot | |
| applies patches once, at extract time, so a new one is otherwise | |
| ignored. build-images.sh watches the patch directories itself and | |
| re-extracts on a change it saw happen, which leaves only the first | |
| sighting - a package it had never recorded before - to name here. | |
| required: false | |
| default: '' | |
| type: string | |
| # One runner, so a nightly still running when the next fires would otherwise | |
| # queue behind itself. Supersede instead. | |
| concurrency: | |
| # Keyed the same way the tree is (see the --volume below): a frontend other | |
| # than minimal, and a device, each get their own /mnt/br/profiles/image | |
| # directory. Grouping on workflow and ref alone ignored that and made every | |
| # image run supersede every other one regardless of what it was building - | |
| # the nightly cancelled a gnome build four and a half hours in, having | |
| # gained nothing, because the two share no tree and the runner serialises | |
| # them anyway. Two nightlies still collide, which is what this is for. | |
| group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.device || inputs.arch || 'both' }}-${{ inputs.frontend || 'minimal' }} | |
| # Back to cancel-in-progress now this is self-hosted again. It was turned | |
| # off while these jobs ran on GitHub's pool, where the argument held: the | |
| # runners are ephemeral, so two runs of the same ref never touch the same | |
| # tree, and cancelling one threw away hours that had to be recompiled from | |
| # nothing. | |
| # | |
| # Neither half of that is true here. The trees live on this machine at | |
| # /mnt/br/profiles/image/<arch>[-<frontend>] and every run that shares a key | |
| # uses the same one, so overlapping runs interleave on shared state. And | |
| # superseding costs far less: the winner inherits the tree the loser was | |
| # part way through, so the work is picked up rather than discarded. | |
| cancel-in-progress: true | |
| env: | |
| # Which frontend the image boots into. build-images.sh passes this to | |
| # generate-config.sh as --frontend, which is emitted after the board so it | |
| # beats the emulationstation the image profile would otherwise include. | |
| # | |
| # A scheduled run has no inputs, so it takes the fallback rather than an | |
| # empty string - without it the schedule would silently build a different | |
| # image from the one a dispatch builds. | |
| FRONTEND: ${{ inputs.frontend || 'minimal' }} | |
| # The caches are on the runner's disk, not in the 10 GB repo-wide | |
| # actions/cache budget, so they are sized for hit rate. | |
| CCACHE_MAX_SIZE: 20G | |
| jobs: | |
| # Which architectures this run builds. Normally both; with a device, only the | |
| # one that board is, read from its own boardinfo rather than from a list kept | |
| # here - sdk/board has 116 boards and a second copy of that mapping would be | |
| # wrong the first time one is added. Runs on the hosted runners: it reads two | |
| # files and the self-hosted box is the scarce resource. | |
| resolve: | |
| name: Resolve build targets | |
| runs-on: ubuntu-latest | |
| outputs: | |
| arches: ${{ steps.pick.outputs.arches }} | |
| lib32: ${{ steps.pick.outputs.lib32 }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - id: pick | |
| env: | |
| DEVICE: ${{ inputs.device }} | |
| ARCH: ${{ inputs.arch }} | |
| run: | | |
| set -eu | |
| if [ -z "${DEVICE:-}" ]; then | |
| case "${ARCH:-both}" in | |
| x86_64) echo 'arches=["x86_64"]' >> "$GITHUB_OUTPUT" | |
| echo 'lib32=["i386"]' >> "$GITHUB_OUTPUT" | |
| echo "building x86_64 only" ;; | |
| arm64) echo 'arches=["arm64"]' >> "$GITHUB_OUTPUT" | |
| echo 'lib32=["armv7"]' >> "$GITHUB_OUTPUT" | |
| echo "building arm64 only" ;; | |
| *) echo 'arches=["x86_64","arm64"]' >> "$GITHUB_OUTPUT" | |
| echo 'lib32=["i386","armv7"]' >> "$GITHUB_OUTPUT" | |
| echo "building the usual pair" ;; | |
| esac | |
| exit 0 | |
| fi | |
| # The generic UEFI boards are not devices - see build-images.sh for | |
| # what passing one as --device actually produces. | |
| case "$DEVICE" in | |
| x86_64|arm64|common) | |
| echo "'$DEVICE' is an architecture, not a device: use the arch input" >&2 | |
| exit 1 ;; | |
| esac | |
| board="sdk/board/$DEVICE" | |
| if [ ! -f "$board/board.config" ]; then | |
| echo "unknown device '$DEVICE': no $board/board.config" >&2 | |
| echo "available: $(cd sdk/board && ls -d */board.config 2>/dev/null \ | |
| | sed 's|/board.config||' | grep -vE '^(x86_64|arm64|common)$' \ | |
| | tr '\n' ' ')" >&2 | |
| exit 1 | |
| fi | |
| arch=$(sed -n 's/^ARCH=//p' "$board/boardinfo" | head -1) | |
| [ -n "$arch" ] || { echo "no ARCH= in $board/boardinfo" >&2; exit 1; } | |
| case "$arch" in | |
| x86_64) companion=i386 ;; | |
| arm64) companion=armv7 ;; | |
| *) echo "device '$DEVICE' is $arch, which has no image profile" >&2; exit 1 ;; | |
| esac | |
| printf 'arches=["%s"]\n' "$arch" >> "$GITHUB_OUTPUT" | |
| printf 'lib32=["%s"]\n' "$companion" >> "$GITHUB_OUTPUT" | |
| echo "building $DEVICE ($arch, companion $companion)" | |
| image: | |
| name: Image (${{ inputs.device || matrix.arch }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| arch: ${{ fromJSON(needs.resolve.outputs.arches) }} | |
| runs-on: [self-hosted, Linux, X64] | |
| needs: [resolve, lib32] | |
| # "needs" is kept for ordering - the companion must finish building before | |
| # an image reads it - but the image does not require the lib32 job to have | |
| # *succeeded*. What it consumes is the tree on the shared disk, not that | |
| # job's conclusion, and the two have come apart three times: the companion | |
| # built and packed cleanly, then the runner process itself died during the | |
| # artifact upload (SIGBUS in node, with an I/O error out of runsvc.sh), so | |
| # continue-on-error on that step could not save the job. Each time a | |
| # perfectly good companion sat on disk while both images were skipped. | |
| # | |
| # "Locate the 32-bit companion" below is the real gate: it checks the tree | |
| # exists and carries a 32-bit loader, and fails the image if it does not. | |
| # That check is what correctness rests on, so a dead upload step no longer | |
| # costs the run. | |
| if: ${{ !cancelled() && needs.resolve.result == 'success' }} | |
| # Generous, not calibrated - the six-hour hosted-runner ceiling does not | |
| # apply here. It exists so a wedged build eventually releases the runner | |
| # rather than blocking every other workflow forever, which matters more | |
| # here than on the pool: this is one machine and the jobs queue behind | |
| # each other. | |
| # | |
| # For scale: on hosted runners arm64 took 4h32m and x86_64 5h18m from a | |
| # cold tree. This runner keeps its trees, so a repeat run compiles only | |
| # what changed. | |
| timeout-minutes: 1440 | |
| # Plain base image; the toolchain comes from br-seed. /mnt is | |
| # bind-mounted so the tree and ccache outlive the container, and the | |
| # per-profile host dir is mounted at the path the packaged toolchain was | |
| # built at. | |
| container: | |
| image: docker.io/colemancda/buildroot-swift:latest | |
| options: >- | |
| --volume /mnt:/mnt | |
| --volume /mnt/br/profiles/image/${{ inputs.device || matrix.arch }}${{ (inputs.frontend != '' && inputs.frontend != 'minimal') && format('-{0}', inputs.frontend) || '' }}:/mnt/br/output/${{ matrix.arch }} | |
| --security-opt label=disable | |
| env: | |
| CCACHE_DIR: /mnt/br/ccache/${{ matrix.arch }} | |
| steps: | |
| - name: Checkout swift-linux | |
| uses: actions/checkout@v4 | |
| # Seed before br-setup: any defconfig has to land on the seeded tree, | |
| # not be buried under the unpack. | |
| - name: Seed from the toolchain release | |
| uses: ./.github/actions/br-seed | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| arch: ${{ matrix.arch }} | |
| dest-dir: /mnt/br/output/${{ matrix.arch }} | |
| # configure: 'false' - build-images.sh owns this tree's defconfig (it | |
| # merges the lib32 and FRONTEND fragments), so br-setup only does the | |
| # prerequisites, disk guard and submodule checkout here. | |
| - name: Prepare the tree | |
| uses: ./.github/actions/br-setup | |
| with: | |
| output-dir: /mnt/br/output/${{ matrix.arch }} | |
| arch: ${{ matrix.arch }} | |
| configure: 'false' | |
| ccache-dir: /mnt/br/ccache/${{ matrix.arch }} | |
| ccache-max-size: ${{ env.CCACHE_MAX_SIZE }} | |
| # Back to a real guard now this runs on the self-hosted box: the | |
| # 20G was sized for a hosted runner's ~70G /mnt, and on a machine | |
| # that keeps four output trees, the shared host-tool tree, dl/ and | |
| # the ccaches it is low enough to let a build start and then hit | |
| # ENOSPC hours in. Same figure the toolchain jobs use. | |
| min-free-gb: '60' | |
| # The seed ships host packages as stamps without sources, and the Swift | |
| # packages read host-swift's build directory rather than only what it | |
| # installed into host/. See the action for why that combination fails. | |
| - name: Fetch the shared host-swift | |
| uses: ./.github/actions/host-swift | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| build-dir: /mnt/br/output/${{ matrix.arch }}/build | |
| # The companion is already on this machine. The lib32 job builds it into | |
| # /mnt/br/profiles/lib32/<arch>, /mnt is bind-mounted into this | |
| # container, so the image reads that tree where it lies. | |
| # | |
| # It used to travel as an Actions artifact - tar it up, upload, download, | |
| # verify, untar - which is a 300 MB round trip through GitHub between two | |
| # jobs on the same disk. The transfer is the only part of that which has | |
| # ever gone wrong: once a truncated download (a good 225224255-byte | |
| # artifact arriving short, so tar stopped three seconds into the build), | |
| # once "BlobNotFound" on the upload. Both times a companion that had | |
| # built perfectly was thrown away and the image never started. The | |
| # artifact is still published for anyone who wants to fetch one; it is | |
| # just no longer on the path between these two jobs. | |
| # | |
| # Reading it live is safe: this job "needs: lib32", so every companion | |
| # has finished before any image starts, and post-build-lib32.sh only | |
| # copies out of the tree. | |
| - name: Locate the 32-bit companion | |
| env: | |
| # i386 goes with the x86_64 image, armv7 with arm64. | |
| LIB32_ARCH: ${{ matrix.arch == 'x86_64' && 'i386' || 'armv7' }} | |
| run: | | |
| set -eu | |
| root="/mnt/br/profiles/lib32/$LIB32_ARCH/target" | |
| [ -d "$root" ] || { | |
| echo "no $LIB32_ARCH companion tree at $root" >&2 | |
| echo "the lib32 job builds it - see that job's log" >&2 | |
| exit 1 | |
| } | |
| # Without a 32-bit loader the merge produces libraries nothing can | |
| # run, and post-build-lib32.sh only warns. An empty or half-built | |
| # tree is worth stopping for here rather than shipping an image whose | |
| # /usr/lib32 is decorative. | |
| ls "$root"/lib/ld-*.so.* >/dev/null 2>&1 || { | |
| echo "$root has no 32-bit loader in lib/ - tree is incomplete" >&2 | |
| exit 1 | |
| } | |
| echo "companion: $root ($(du -sh "$root" | cut -f1))" | |
| - name: Build image | |
| run: | | |
| set -eu | |
| ccache -d "$CCACHE_DIR" --max-size="$CCACHE_MAX_SIZE" \ | |
| --set-config=compression=true --set-config=compiler_check=content || true | |
| # Everything comes from the repo's submodules; the seeded tree at | |
| # /mnt/br/output/<arch> supplies the toolchain and swift so only the | |
| # image profile's delta compiles. | |
| export BUILDROOT="$GITHUB_WORKSPACE/buildroot" | |
| export BR2_EXTERNAL_SWIFT="$GITHUB_WORKSPACE/swift" | |
| export BR2_EXTERNAL_PORTS="$GITHUB_WORKSPACE/ports" | |
| export OUTPUT_BASE=/mnt/br/output | |
| export IMAGE_OUTPUT_DIR="/mnt/br/output/${{ matrix.arch }}" | |
| # Must match the seeded tree's BR2_DL_DIR, or its download stamps | |
| # point at files that are not there. | |
| export DL_DIR=/mnt/br/dl | |
| # Merge the companion built by the lib32 job into /usr/lib32 rather | |
| # than building one here (its toolchain lives in another tree). | |
| export LIB32_ROOT=/mnt/br/profiles/lib32/${{ matrix.arch == 'x86_64' && 'i386' || 'armv7' }}/target | |
| export CCACHE=1 | |
| export DEVICE="${{ inputs.device }}" | |
| export REBUILD_PKGS="${{ inputs.rebuild }}" | |
| "$GITHUB_WORKSPACE/build-images.sh" ${{ matrix.arch }} | |
| # Also on cancellation, not just failure. build-images.sh sends the build | |
| # output to a file and this step is the only thing that surfaces it, so | |
| # when the job was cancelled - by GitHub's six-hour ceiling, or by hand - | |
| # "if: failure()" skipped it and the whole build left no trace at all. | |
| - name: Dump build log on failure | |
| if: ${{ failure() || cancelled() }} | |
| run: | | |
| log="/mnt/br/output/${{ matrix.arch }}-image.log" | |
| echo "===== tail of $log =====" | |
| tail -n 200 "$log" || echo "(no log found)" | |
| - name: Show image | |
| run: ls -la /mnt/br/output/${{ matrix.arch }}/images/ || true | |
| - name: Upload disk image | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: swift-linux-${{ inputs.device || matrix.arch }}-disk | |
| # swift-linux-<arch>.img rather than the board's own name, because | |
| # every board's post-image leaves this symlink pointing at whatever | |
| # it built - disk.img on the UEFI boards, chromebook.img on the | |
| # Chromebooks - and the upload resolves it. One glob, one file, no | |
| # list of names to keep current. Globbing *.img instead would match | |
| # the symlink and its target and upload the image twice. | |
| path: /mnt/br/output/${{ matrix.arch }}/images/swift-linux-*.img | |
| if-no-files-found: error | |
| - name: ccache stats | |
| if: always() | |
| run: | | |
| ccache -d "$CCACHE_DIR" -s 2>/dev/null || true | |
| df -h /mnt | |
| # The container runs as root; the workspace belongs to the runner user on | |
| # the host and, unlike a hosted VM, survives the job. Without this the | |
| # next run's actions/checkout git-cleans as the runner user and hits EPERM | |
| # on the root-owned ports tree this job created. | |
| - name: Return the workspace to the runner user | |
| if: always() | |
| run: chown -R "$(stat -c '%u:%g' "$GITHUB_WORKSPACE")" "$GITHUB_WORKSPACE" || true | |
| # The 32-bit companion userland that gets merged into each image as | |
| # /usr/lib32. It is built from its own architecture's seeded toolchain in | |
| # its own tree; the image job then merges the target tree. i386 pairs with | |
| # the x86_64 image, armv7 with arm64. | |
| lib32: | |
| name: lib32 (${{ matrix.arch }}) | |
| needs: resolve | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| arch: ${{ fromJSON(needs.resolve.outputs.lib32) }} | |
| runs-on: [self-hosted, Linux, X64] | |
| timeout-minutes: 1440 | |
| # Same mount arrangement as the image job, one profile over. | |
| container: | |
| image: docker.io/colemancda/buildroot-swift:latest | |
| options: >- | |
| --volume /mnt:/mnt | |
| --volume /mnt/br/profiles/lib32/${{ matrix.arch }}:/mnt/br/output/${{ matrix.arch }} | |
| --security-opt label=disable | |
| env: | |
| CCACHE_DIR: /mnt/br/ccache/${{ matrix.arch }} | |
| steps: | |
| - name: Checkout swift-linux | |
| uses: actions/checkout@v4 | |
| - name: Seed from the toolchain release | |
| uses: ./.github/actions/br-seed | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| arch: ${{ matrix.arch }} | |
| dest-dir: /mnt/br/output/${{ matrix.arch }} | |
| - name: Prepare and configure the tree | |
| uses: ./.github/actions/br-setup | |
| with: | |
| output-dir: /mnt/br/output/${{ matrix.arch }} | |
| arch: ${{ matrix.arch }} | |
| profile: lib32 | |
| ccache-dir: /mnt/br/ccache/${{ matrix.arch }} | |
| ccache-max-size: ${{ env.CCACHE_MAX_SIZE }} | |
| # Back to a real guard now this runs on the self-hosted box: the | |
| # 20G was sized for a hosted runner's ~70G /mnt, and on a machine | |
| # that keeps four output trees, the shared host-tool tree, dl/ and | |
| # the ccaches it is low enough to let a build start and then hit | |
| # ENOSPC hours in. Same figure the toolchain jobs use. | |
| min-free-gb: '60' | |
| # Same reason as the image job: the companion builds the Swift runtime | |
| # for its architecture, so it needs the real host-swift tree. | |
| - name: Fetch the shared host-swift | |
| uses: ./.github/actions/host-swift | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| build-dir: /mnt/br/output/${{ matrix.arch }}/build | |
| - name: Build the 32-bit companion | |
| run: | | |
| set -eu | |
| FORCE_UNSAFE_CONFIGURE=1 make -C "$BR_B" O="$BR_O" BR2_EXTERNAL="$BR_EXT" \ | |
| BR2_DL_DIR=/mnt/br/dl BR2_CCACHE_DIR="$CCACHE_DIR" -j"$BR_JOBS" | |
| # Kept for anyone who wants a companion tree without access to this | |
| # machine. The image job no longer reads it - it takes $BR_O/target | |
| # directly off the shared disk - so neither of these steps is allowed to | |
| # fail the job: "BlobNotFound" out of GitHub's artifact storage once | |
| # failed this job minutes after the companion had built cleanly, and | |
| # because the image job needs this one, that skipped both images and lost | |
| # the run. A convenience copy should not be able to do that. | |
| - name: Pack the companion target tree | |
| continue-on-error: true | |
| run: tar czf "$GITHUB_WORKSPACE/lib32-${{ matrix.arch }}.tar.gz" -C "$BR_O" target | |
| - name: Upload companion target tree | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: lib32-${{ matrix.arch }} | |
| path: lib32-${{ matrix.arch }}.tar.gz | |
| if-no-files-found: error | |
| - name: ccache stats | |
| if: always() | |
| run: | | |
| ccache -d "$CCACHE_DIR" -s 2>/dev/null || true | |
| df -h /mnt | |
| # See the image job. | |
| - name: Return the workspace to the runner user | |
| if: always() | |
| run: chown -R "$(stat -c '%u:%g' "$GITHUB_WORKSPACE")" "$GITHUB_WORKSPACE" || true | |
| # Fast machinery check without the Swift toolchain: proves the image config | |
| # resolves and generate-config/build-images plumbing is intact on a plain | |
| # runner. Does not build (that needs the container); config-only. | |
| # | |
| # Runs directly on the self-hosted machine rather than in a container, so it | |
| # needs nothing but bash, sed and awk - which is all generate-config.sh uses. | |
| config-check: | |
| name: Config check (${{ inputs.device || matrix.arch }}) | |
| needs: resolve | |
| strategy: | |
| matrix: | |
| arch: ${{ fromJSON(needs.resolve.outputs.arches) }} | |
| # On the same runner as the rest, though it needs nothing from it. Left on | |
| # the hosted pool it would be the one job that can still fail for reasons | |
| # unrelated to the tree, and since nothing depends on it, that failure | |
| # would only ever show up as a red run beside two good images. | |
| # | |
| # The cost is that it can queue behind an image build rather than | |
| # answering in seconds, which blunts it as a fast gate. | |
| runs-on: [self-hosted, Linux, X64] | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Generate image defconfig | |
| env: | |
| DEVICE: ${{ inputs.device }} | |
| run: | | |
| set -eu | |
| # Same --frontend the build job uses, or this checks a defconfig | |
| # nothing builds. "none" is the absence of a fragment, not a file, | |
| # and generate-config.sh takes it as such. | |
| # And the same board, so a device run checks the defconfig it is | |
| # about to build rather than the generic UEFI one for the arch. | |
| ./generate-config.sh --arch ${{ matrix.arch }} --profile image \ | |
| ${DEVICE:+--device "$DEVICE"} \ | |
| --frontend "$FRONTEND" -o /tmp/image.defconfig | |
| echo "----- generated defconfig ($FRONTEND) -----" | |
| grep -E '^BR2_(aarch64|x86_64|PACKAGE_(SWAYWM|FOOT|EMULATIONSTATION|STEAM|BOX64|MESA3D_GALLIUM_DRIVER_VIRGL)|TARGET_GRUB2_.*_EFI|INIT_BUSYBOX)' /tmp/image.defconfig || true | |
| # minimal unwinds the game frontend and the translation layers. It | |
| # does that by negating what earlier fragments turned on, so both | |
| # lines are in the file and only the order decides the outcome: | |
| # kconfig takes the last assignment, and "# X is not set" is how a | |
| # defconfig spells n. Testing for the absence of "X=y" would pass | |
| # on a file where the negation had been emitted first and lost. | |
| # | |
| # Checked here rather than by reading the resolved .config because | |
| # this job has no submodules and so no kconfig to run - the | |
| # ordering is the property that decides the result anyway. | |
| last_state() { | |
| grep -nE "^(# )?BR2_PACKAGE_$1(=y| is not set)\$" /tmp/image.defconfig \ | |
| | tail -1 | sed 's/^[0-9]*://' | |
| } | |
| if [ "$FRONTEND" = minimal ]; then | |
| rc=0 | |
| # Hours of build apiece, and a different image if they came back. | |
| for sym in EMULATIONSTATION RETROARCH BOX64 BOX86 WINE QEMU PORTMASTER; do | |
| s=$(last_state "$sym") | |
| case "$s" in | |
| ''|'# BR2_PACKAGE_'*' is not set') ;; | |
| *) echo "minimal did not unwind BR2_PACKAGE_$sym (last: $s)" >&2; rc=1 ;; | |
| esac | |
| done | |
| # Without these there is no session to boot into at all. | |
| for sym in SWAYWM FOOT DEJAVU; do | |
| [ "$(last_state "$sym")" = "BR2_PACKAGE_$sym=y" ] || { | |
| echo "minimal is missing BR2_PACKAGE_$sym - no session" >&2; rc=1; } | |
| done | |
| [ "$rc" = 0 ] || exit 1 | |
| echo "minimal: sway session present, game frontend and translation layers unwound" | |
| fi |