Compare commits

...
Author SHA1 Message Date
druppy 1c596cf097 Keep image globs outside quotes so they expand
Build RPi image / build (push) Successful in 10s
Build RPi image / build (pull_request) Successful in 10s
2026-10-24 19:41:59 +02:00
druppy 16d63d95cb Refactor CI into composite actions + repo variables
Build RPi image / build (push) Failing after 9s
Build RPi image / build (pull_request) Failing after 8s
The workflow was mostly inline bash. Moves the logic into reusable
composite actions under .gitea/actions/, keeps host paths in the
YOCTO_HOME repo variable, adds a workflow_dispatch skip-build option
and publishes step outputs (build dir, image list, kas path).
2026-10-24 19:39:31 +02:00
druppy d4ca8fe7c5 Add pull_request trigger so PRs into main post a status check
Build RPi image / build (push) Successful in 10s
Build RPi image / build (pull_request) Successful in 10s
2026-10-24 19:31:07 +02:00
druppy 6f8456f2b6 Trigger CI on ci/* branches so main can stay protected
Build RPi image / build (push) Successful in 10s
2026-10-24 19:30:09 +02:00
druppy 571c8ad70c Fix artifact collection: workspace-relative dist, canonical image names, checksums
Build RPi image / build (push) Successful in 11s
2026-10-24 19:14:09 +02:00
druppy 5396dd1d7e Give each kas repo its own path (all three shared extern/, clobbering each other)
Build RPi image / build (push) Failing after 19m24s
2026-10-24 18:52:23 +02:00
druppy a71b188289 Use https for meta-openembedded (git:// port 9418 unreachable)
Build RPi image / build (push) Failing after 1s
2026-10-24 18:47:37 +02:00
5 changed files with 244 additions and 88 deletions

No files matched your search

+8 -6
View File
@@ -12,21 +12,21 @@ target: rpilinux-image
repos: repos:
meta-raspberrypi: meta-raspberrypi:
url: http://git.yoctoproject.org/git/meta-raspberrypi url: https://git.yoctoproject.org/git/meta-raspberrypi
path: extern/ path: extern/meta-raspberrypi
tag: scarthgap tag: scarthgap
poky: poky:
url: http://git.yoctoproject.org/git/poky url: https://git.yoctoproject.org/git/poky
tag: scarthgap tag: scarthgap
path: extern/ path: extern/poky
layers: layers:
meta: meta:
meta-poky: meta-poky:
meta-yocto-bsp: meta-yocto-bsp:
meta-openembedded: meta-openembedded:
url: git://git.openembedded.org/meta-openembedded url: https://git.openembedded.org/meta-openembedded
tag: scarthgap tag: scarthgap
path: extern/ path: extern/meta-openembedded
layers: layers:
meta-oe: meta-oe:
meta-multimedia: meta-multimedia:
@@ -34,6 +34,8 @@ repos:
meta-python: meta-python:
local: local:
path: meta-rpilinux path: meta-rpilinux
local:
path: meta-rpilinux
local_conf_header: local_conf_header:
ipk: | ipk: |
+118
View File
@@ -0,0 +1,118 @@
name: build-image
description: >-
Build a kas project in a persistent directory and expose the flashable
images. The workspace is synced into the build dir instead of built in
place, because checkout cleans ignored files (build/, extern/) and bitbake
reuse depends on sstate-cache surviving between runs.
inputs:
project:
description: kas configuration file, relative to the repository
required: false
default: .config.yaml
machine:
description: Machine the images are deployed for
required: false
default: raspberrypi4-64
image-prefix:
description: Artifact filename prefix in the deploy dir
required: false
default: rpilinux-image
skip-build:
description: Set to true to only collect images from an existing build dir
required: false
default: "false"
outputs:
build-dir:
description: Persistent project directory on the runner
value: ${{ steps.locate.outputs.build-dir }}
image-dir:
description: Deploy dir bitbake wrote to
value: ${{ steps.locate.outputs.image-dir }}
images:
description: Newline separated artifact filenames
value: ${{ steps.collect.outputs.images }}
runs:
using: composite
steps:
- name: Sync workspace into persistent build dir
if: ${{ inputs.skip-build != 'true' }}
shell: bash
run: |
set -euo pipefail
SRC="$(pwd)"
DST="${YOCTO_HOME:-$HOME/yocto-build}/$(basename "$GITHUB_REPOSITORY")"
# checkout deletes ignored files, so the build tree must live elsewhere
if [ "$SRC" = "$DST" ]; then
echo "::error::workspace == build dir ($SRC); point YOCTO_HOME somewhere else"
exit 1
fi
echo "syncing $SRC -> $DST"
mkdir -p "$DST"
# .git stays (kas requires a repo); extern/ + build/ only exist in DST,
# so --delete cannot reach them
rsync -a --delete --exclude /build/ --exclude /extern/ ./ "$DST/"
- name: kas build
if: ${{ inputs.skip-build != 'true' }}
shell: bash
run: |
set -euo pipefail
cd "${YOCTO_HOME:-$HOME/yocto-build}/$(basename "$GITHUB_REPOSITORY")"
git config --global --add safe.directory "$PWD" || true
"${YOCTO_HOME:-$HOME/yocto-build}/venv/bin/kas" build
- name: Locate deploy dir
id: locate
shell: bash
run: |
set -euo pipefail
BUILD_DIR="${YOCTO_HOME:-$HOME/yocto-build}/$(basename "$GITHUB_REPOSITORY")"
IMAGE_DIR="$(find "$BUILD_DIR/build" -type d -path "*/tmp/deploy/images/${{ inputs.machine }}" | head -1)"
if [ -z "$IMAGE_DIR" ]; then
echo "::error::no deploy dir for ${{ inputs.machine }} under $BUILD_DIR/build"
exit 1
fi
echo "build-dir=$BUILD_DIR" >> "$GITHUB_OUTPUT"
echo "image-dir=$IMAGE_DIR" >> "$GITHUB_OUTPUT"
- name: Collect artifacts
id: collect
shell: bash
run: |
set -euo pipefail
IMAGE_DIR="${{ steps.locate.outputs.image-dir }}"
shopt -s nullglob
# only the timestamped names: the short ones are hardlinks to the same
# inode and would upload every image twice.
# NOTE: the glob must stay OUTSIDE the quotes - "$DIR/pat*" never expands.
FILES=("${IMAGE_DIR}/${{ inputs.image-prefix }}."*.rootfs-[0-9]*.wic.* \
"${IMAGE_DIR}/${{ inputs.image-prefix }}-"*.rootfs-[0-9]*.wic.*)
if [ "${#FILES[@]}" -eq 0 ]; then
echo "::error::no flashable .wic image for ${{ inputs.machine }} in $IMAGE_DIR"
exit 1
fi
mkdir -p dist
cp -v "${FILES[@]}" dist/
( cd dist && sha256sum * > SHA256SUMS )
ls -lh dist
printf 'images=%s\n' "$(ls dist | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
- name: Summarise
shell: bash
run: |
set -euo pipefail
SUMMARY="${GITHUB_STEP_SUMMARY:-}"
[ -n "$SUMMARY" ] || exit 0
{
echo "## ${{ inputs.image-prefix }} for ${{ inputs.machine }}"
echo
echo "| artifact | sha256 |"
echo "| --- | --- |"
( cd dist && while read -r sum file; do
[ "$file" = SHA256SUMS ] || echo "| \`$file\` | \`${sum:0:16}\` |"
done < SHA256SUMS )
echo
echo "Flash with \`bzcat <image> | sudo dd of=/dev/sdX bs=4M status=progress\`."
echo
echo "Incremental build tree: \`${{ steps.locate.outputs.build-dir }}\`"
} >> "$SUMMARY"
@@ -0,0 +1,37 @@
name: check-build-host
description: >-
Report the build host and fail fast when executables, CPU or disk that the
build needs are missing - a mistake should cost seconds, not hours.
inputs:
tools:
description: Space separated executables that must be on PATH
required: false
default: git python3
min-disk-gb:
description: Warn when the build filesystem has less than this much free
required: false
default: "100"
runs:
using: composite
steps:
- name: Host report
shell: bash
run: |
set -euo pipefail
echo "os : $(. /etc/os-release && echo "$PRETTY_NAME") ($(uname -m), $(uname -r))"
echo "user : $(id -un) home=$HOME"
echo "cpu : $(nproc) cores / $(awk '/MemTotal/{printf "%.0fG", $2/1048576}' /proc/meminfo) ram"
df -h . | tail -1 | sed 's/^/disk : /'
missing=""
for tool in ${{ inputs.tools }}; do
command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
done
if [ -n "$missing" ]; then
echo "::error::Host is missing:$missing"
echo "Install them on the runner host, e.g. sudo apt-get install -y $missing"
exit 1
fi
free_gb="$(df --output=avail -BG . | tail -1 | tr -d ' ')"
if [ "$free_gb" -lt "${{ inputs.min-disk-gb }}" ]; then
echo "::warning::only ${free_gb}G free on $(df --output=target . | tail -1); expected >= ${{ inputs.min-disk-gb }}G"
fi
+34
View File
@@ -0,0 +1,34 @@
name: setup-kas
description: Create or reuse the persistent python venv that holds kas.
inputs:
requirement:
description: pip requirement for kas (pinned, e.g. kas==5.5)
required: false
default: kas
outputs:
kas:
description: Absolute path of the kas executable
value: ${{ steps.venv.outputs.kas }}
reused:
description: "true when the venv already existed (warm run)"
value: ${{ steps.venv.outputs.reused }}
runs:
using: composite
steps:
- name: Prepare venv
id: venv
shell: bash
run: |
set -euo pipefail
BUILD_ROOT="${YOCTO_HOME:-$HOME/yocto-build}"
VENV="$BUILD_ROOT/venv"
mkdir -p "$BUILD_ROOT"
if [ -d "$VENV" ]; then
echo "reused=true" >> "$GITHUB_OUTPUT"
else
echo "reused=false" >> "$GITHUB_OUTPUT"
python3 -m venv "$VENV"
fi
"$VENV/bin/pip" install --quiet --upgrade "${{ inputs.requirement }}"
echo "kas=$VENV/bin/kas" >> "$GITHUB_OUTPUT"
echo "kas $($VENV/bin/kas --version) in $VENV"
+47 -82
View File
@@ -1,23 +1,29 @@
name: Build RPi image name: Build RPi image
# Yocto (kas) build of rpilinux-image for raspberrypi4-64. # Yocto (kas) build of rpilinux-image. Steps live in .gitea/actions/* so this
# file stays a description of the pipeline, not a shell script.
# #
# Runs on the dedicated host runner (label "yocto:host", Ryzen 9 box "ada"). # Host-specific paths come from repo settings (Settings -> Actions ->
# Everything persists in $YOCTO_HOME: extern/ (layer clones) + build/ (sstate) # Variables), so the workflow is not tied to one machine:
# survive between runs -> incremental builds after the first. # YOCTO_HOME persistent build dir on the runner
# $YOCTO_HOME comes from the runner config (runner.envs.YOCTO_HOME = # Order of precedence for the build dir: this variable, then the runner's
# /home/bl/yocto-build, on the 1.8T disk); falls back to $HOME/yocto-build. # own YOCTO_HOME (runner.envs), then $HOME/yocto-build.
#
# First build takes hours (poky fetch + full image) -> runner.timeout >= 8h.
#
# First build takes hours (poky fetch + full image) -> runner.timeout >= 8h.
on: on:
push: push:
# main is release protected, ci/* branches are free scratch space
branches: [main, 'ci/*']
pull_request:
# gates merges into main and posts the commit status that branch
# protection waits for
branches: [main] branches: [main]
workflow_dispatch: workflow_dispatch:
inputs:
skip-build:
description: Collect images from the existing build dir instead of rebuilding
type: boolean
default: false
# Never run two builds at once on the same (single) runner.
concurrency: concurrency:
group: yocto-rpi-build group: yocto-rpi-build
cancel-in-progress: false cancel-in-progress: false
@@ -25,87 +31,46 @@ concurrency:
permissions: permissions:
contents: read contents: read
env:
YOCTO_HOME: ${{ vars.YOCTO_HOME }}
MACHINE: raspberrypi4-64
IMAGE_PREFIX: rpilinux-image
defaults:
run:
shell: bash
jobs: jobs:
build: build:
runs-on: yocto runs-on: yocto
timeout-minutes: 600 timeout-minutes: 600
steps: steps:
- uses: builtin:checkout - name: Check out sources
uses: builtin:checkout
- name: Probe environment and host dependencies - name: Verify build host
run: | uses: $/.gitea/actions/check-build-host
echo "=== runner environment ===" with:
uname -a tools: git python3 curl wget rsync gawk xz diffstat chrpath bison flex node
[ -r /etc/os-release ] && . /etc/os-release && echo "$PRETTY_NAME" min-disk-gb: "200"
echo "HOME=$HOME workspace=$GITEA_WORKSPACE nproc=$(nproc)"
df -h "$HOME" | tail -1
free -h 2>/dev/null | sed -n 2p || true
command -v docker >/dev/null && docker info --format 'docker: {{.ServerVersion}}' 2>/dev/null || echo "no docker daemon"
echo "=== dependency check ==="
set -e
missing=""
for t in git python3 curl wget rsync gawk xz diffstat chrpath node npm; do
command -v "$t" >/dev/null 2>&1 || missing="$missing $t"
done
if [ -n "$missing" ]; then
echo "::error::Missing host tools:$missing"
echo "Install on the runner host, e.g.:"
echo ' sudo apt-get install -y --no-install-recommends build-essential \
bison flex chrpath curl diffstat file gawk git libpython3-dev libssl-dev \
make python3 python3-venv python3-pip python3-dev rsync swig unzip \
wget xz-utils zstd bzip2 nodejs npm'
exit 1
fi
- name: Set up kas (persistent venv) - name: Set up kas
run: | id: kas
set -e uses: $/.gitea/actions/setup-kas
export YOCTO_HOME="${YOCTO_HOME:-$HOME/yocto-build}" with:
mkdir -p "$YOCTO_HOME" requirement: kas==5.5
[ -d "$YOCTO_HOME/venv" ] || python3 -m venv "$YOCTO_HOME/venv"
"$YOCTO_HOME/venv/bin/pip" -q install -U kas
"$YOCTO_HOME/venv/bin/kas" --version
- name: Sync to persistent build dir and build - name: Build image
run: | id: image
set -euo pipefail uses: $/.gitea/actions/build-image
export YOCTO_HOME="${YOCTO_HOME:-$HOME/yocto-build}" with:
SRC="$(pwd)" machine: ${{ env.MACHINE }}
DST="$YOCTO_HOME/yocto_rpi" image-prefix: ${{ env.IMAGE_PREFIX }}
# Safety: builtin:checkout cleans ignored files (build/, extern/), skip-build: ${{ github.event.inputs.skip-build }}
# so the build tree must NOT be the ephemeral workspace.
if [ "$SRC" = "$DST" ]; then
echo "::error::workspace == persistent dir ($SRC); keep the runner WorkingDirectory outside YOCTO_HOME"
exit 1
fi
echo "syncing $SRC -> $DST"
mkdir -p "$DST"
# .git is copied (kas expects a git repo); extern/ and build/ exist
# only in DST and are excluded, so --delete cannot touch them.
rsync -a --delete --exclude /build/ --exclude /extern/ ./ "$DST/"
cd "$DST"
git config --global --add safe.directory "$DST" || true
"$YOCTO_HOME/venv/bin/kas" build
- name: Collect images - name: Upload image
run: |
set -euo pipefail
export YOCTO_HOME="${YOCTO_HOME:-$HOME/yocto-build}"
cd "$YOCTO_HOME/yocto_rpi"
IMG_DIR="build/*/tmp/deploy/images/raspberrypi4-64"
ls -lh $IMG_DIR
mkdir -p "$GITEA_WORKSPACE/dist"
cp $IMG_DIR/* "$GITEA_WORKSPACE/dist/"
{
echo "## RPi4 image built"
echo
echo "- commit: ${GITEA_SHA}"
echo "- images also on the runner host at: \`$YOCTO_HOME/yocto_rpi/$IMG_DIR\`"
} >> "$GITEA_STEP_SUMMARY"
- name: Upload images
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: rpilinux-image-rpi4 name: ${{ env.IMAGE_PREFIX }}-${{ env.MACHINE }}
path: dist/ path: dist/
retention-days: 14 retention-days: 14