diff --git a/.config.yaml b/.config.yaml index ec1fc8a..4c1babf 100644 --- a/.config.yaml +++ b/.config.yaml @@ -12,21 +12,21 @@ target: rpilinux-image repos: meta-raspberrypi: - url: http://git.yoctoproject.org/git/meta-raspberrypi - path: extern/ + url: https://git.yoctoproject.org/git/meta-raspberrypi + path: extern/meta-raspberrypi tag: scarthgap poky: - url: http://git.yoctoproject.org/git/poky + url: https://git.yoctoproject.org/git/poky tag: scarthgap - path: extern/ + path: extern/poky layers: meta: meta-poky: meta-yocto-bsp: meta-openembedded: - url: git://git.openembedded.org/meta-openembedded + url: https://git.openembedded.org/meta-openembedded tag: scarthgap - path: extern/ + path: extern/meta-openembedded layers: meta-oe: meta-multimedia: @@ -34,6 +34,8 @@ repos: meta-python: local: path: meta-rpilinux + local: + path: meta-rpilinux local_conf_header: ipk: | diff --git a/.gitea/actions/build-image/action.yml b/.gitea/actions/build-image/action.yml new file mode 100644 index 0000000..f890ae1 --- /dev/null +++ b/.gitea/actions/build-image/action.yml @@ -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 | sudo dd of=/dev/sdX bs=4M status=progress\`." + echo + echo "Incremental build tree: \`${{ steps.locate.outputs.build-dir }}\`" + } >> "$SUMMARY" diff --git a/.gitea/actions/check-build-host/action.yml b/.gitea/actions/check-build-host/action.yml new file mode 100644 index 0000000..db54030 --- /dev/null +++ b/.gitea/actions/check-build-host/action.yml @@ -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 diff --git a/.gitea/actions/setup-kas/action.yml b/.gitea/actions/setup-kas/action.yml new file mode 100644 index 0000000..1859f92 --- /dev/null +++ b/.gitea/actions/setup-kas/action.yml @@ -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" diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..a8d566f --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,76 @@ +name: Build RPi image + +# Yocto (kas) build of rpilinux-image. Steps live in .gitea/actions/* so this +# file stays a description of the pipeline, not a shell script. +# +# Host-specific paths come from repo settings (Settings -> Actions -> +# Variables), so the workflow is not tied to one machine: +# YOCTO_HOME persistent build dir on the runner +# Order of precedence for the build dir: this variable, then the runner's +# own YOCTO_HOME (runner.envs), then $HOME/yocto-build. + +on: + 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] + workflow_dispatch: + inputs: + skip-build: + description: Collect images from the existing build dir instead of rebuilding + type: boolean + default: false + +concurrency: + group: yocto-rpi-build + cancel-in-progress: false + +permissions: + contents: read + +env: + YOCTO_HOME: ${{ vars.YOCTO_HOME }} + MACHINE: raspberrypi4-64 + IMAGE_PREFIX: rpilinux-image + +defaults: + run: + shell: bash + +jobs: + build: + runs-on: yocto + timeout-minutes: 600 + steps: + - name: Check out sources + uses: builtin:checkout + + - name: Verify build host + uses: $/.gitea/actions/check-build-host + with: + tools: git python3 curl wget rsync gawk xz diffstat chrpath bison flex node + min-disk-gb: "200" + + - name: Set up kas + id: kas + uses: $/.gitea/actions/setup-kas + with: + requirement: kas==5.5 + + - name: Build image + id: image + uses: $/.gitea/actions/build-image + with: + machine: ${{ env.MACHINE }} + image-prefix: ${{ env.IMAGE_PREFIX }} + skip-build: ${{ github.event.inputs.skip-build }} + + - name: Upload image + uses: actions/upload-artifact@v4 + with: + name: ${{ env.IMAGE_PREFIX }}-${{ env.MACHINE }} + path: dist/ + retention-days: 14 diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..79860b3 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,5 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{}