Files
gronod 674d067872
ci / test (push) Successful in 32s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 51s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 51s
Install cmake and gcc in Gitea host-test job
gitea/runner-images:ubuntu-latest has no cmake; test job exited 127
before firmware matrix could run.
2026-09-16 12:31:37 +00:00

10 KiB
Raw Permalink Blame History

CI/CD — Gitea Actions

Status: planned on refactor/esp-idf-modular-ui. Implementation follows the IDF skeleton (migration step 2 in docs/TARGET_ARCHITECTURE.md). Workflows live in .gitea/workflows/ (Gitea Actions, GitHub-compatible YAML). PRs target develop.

Purpose

Every change that lands on develop must:

  1. Run host unit tests for recipe tables and the machine state machine (no hardware).
  2. Build two firmware images: board_wroom (ESP32 classic) and board_jc4827w543 (ESP32-S3).
  3. Publish flashable artifacts (app binary, bootloader, partition table, merged image, size report).

Legacy PlatformIO (platformio.ini) is not a CI target.

Runner requirements

Gitea act_runner must be registered against git.i3omb.com with labels that match runs-on.

Workflow runs-on Typical runner label Notes
ubuntu-latest ubuntu-latest:docker://gitea/runner-images:ubuntu-latest Host tests, checkout, artifact upload. Image has no cmake/gcc — the test job must apt-get install them.
(job container) Docker socket available on the runner Firmware jobs use espressif/idf:<tag> as the job container

Firmware builds need:

  • Docker on the runner (job-level container:)
  • Enough disk for the IDF image (~several GB) and two build/ trees
  • Outbound pull of docker.io/espressif/idf (or a mirror on the Gitea registry)

If the instance cannot pull Docker Hub, mirror espressif/idf:v5.4 into the Gitea container registry and point container.image at that.

Do not depend on espressif/esp-idf-ci-action or other GitHub-hosted actions for the compile step. Those often assume GitHub path layout. Drive idf.py inside espressif/idf instead. actions/checkout and actions/upload-artifact (or Gitea’s equivalents) are fine.

Triggers

Event What runs
pull_request into develop tests + both firmware builds (required check)
push to develop tests + both firmware builds; keep artifacts
push to refactor/** and feature/** same, so the branch is green before the PR
push tags v* tests + builds + attach binaries to the Gitea release
workflow_dispatch manual rebuild

main is not a CI integration branch for this refactor.

Jobs

test (host) ──► firmware [wroom / jc4827w543] ──► (tag only) release

Firmware must needs: test. A failing recipe or state-machine test must not produce a binary.

1. test — host unit tests

Runs on ubuntu-latest without the IDF container so the gate is fast.

  • Toolchain: CMake + GCC (C11), CTest.
  • Tree: tests/host/ linking components/app_process and components/app_machine against stub HAL (tests/host/stubs/: motor, temp, audio, display no-ops; queues replaced with a tiny fake or a vendored FreeRTOS POSIX port if the machine task is exercised).
  • Framework: Unity (can be vendored) or plain assert + CTest. Prefer Unity so on-target tests later share the same style.
  • Must cover:
    • find / lookup of all six process names
    • frozen default times, temps, CW/CCW for every step (values from docs/CURRENT_STATE.md)
    • session ±5 s overlay does not mutate the const table
    • state machine: Idle → Select → Arm → Start → Stop (EN disabled) → Resume → Complete → Return
    • Stop is accepted in StepRunning and during a complete-alarm
  • Recipe-value tests fail the build if defaults change without an explicit doc update.

Command sketch:

cmake -S tests/host -B build/host
cmake --build build/host
ctest --test-dir build/host --output-on-failure

Until tests/host exists, the workflow file is not merged as a required check — or the job is if’d on path existence. Do not ship a red pipeline on docs-only commits after the workflow is enabled; use:

- name: Host tests
  if: hashFiles('tests/host/CMakeLists.txt') != ''

2. firmware — matrix build

Job container: espressif/idf:v5.4 (pin the tag; bump in one place when IDF is upgraded).

Matrix:

Name IDF_TARGET Board cmake/sdkconfig
wroom esp32 -DAUTOFILM_BOARD=wroom + sdkconfig.defaults + sdkconfig.wroom
jc4827w543 esp32s3 -DAUTOFILM_BOARD=jc4827w543 + sdkconfig.defaults + sdkconfig.s3

Steps:

  1. Checkout (full history not required).
  2. source /opt/esp/idf/export.sh (already true in the official image entrypoint; use idf.py directly).
  3. idf.py set-target ${IDF_TARGET} then
    idf.py @sdkconfig.defaults @sdkconfig.<board> -DAUTOFILM_BOARD=<board> build
  4. idf.py size (fail the job only if the binary will not fit flash; log the report either way).
  5. Merge a single flash image:
    esptool.py --chip ${IDF_TARGET} merge_bin -o autofilm-${board}-${GIT_SHA}.bin @build/flash_args
  6. Upload artifacts (see below).

S3 build must enable octal PSRAM in sdkconfig.s3. WROOM must not.

Arduino-as-component is pulled by the IDF CMake on the WROOM image first; the S3 image may share it until debt burn-down. Cache managed_components/ by lock hash when a idf_component.yml exists.

3. release (tags only)

needs: [test, firmware]. Download both artifact sets and attach to the Gitea release for tag v*:

  • autofilm-wroom-<version>.bin (merged)
  • autofilm-jc4827w543-<version>.bin (merged)
  • raw app.bin / bootloader.bin / partition-table.bin per board
  • sdkconfig snapshot per board
  • size.txt per board

Version string is the git tag. CI also stamps version in firmware via -DAUTOFILM_GIT_DESC=$(git describe --always --dirty).

Artifacts

File Why
autofilm-<board>-<sha>.bin Merged image for esptool write_flash 0x0
build/autofilm.bin (app) OTA / partial flash
build/bootloader/bootloader.bin First-time flash
build/partition_table/partition-table.bin First-time flash
build/flasher_args.json Exact offsets
size.txt / size.json Flash/RAM budget
sdkconfig Repro the build flags

Retention: 14 days on branch builds, keep release attachments. Artifact names must include board + short SHA so WROOM and S3 cannot overwrite each other.

Example workflow (target shape)

Path: .gitea/workflows/ci.yml

name: ci

on:
  pull_request:
    branches: [develop]
  push:
    branches: [develop, "refactor/**", "feature/**"]
    tags: ["v*"]
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install host toolchain
        if: hashFiles('tests/host/CMakeLists.txt') != ''
        run: |
          export DEBIAN_FRONTEND=noninteractive
          if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then
            SUDO=sudo
          else
            SUDO=
          fi
          $SUDO apt-get update
          $SUDO apt-get install -y --no-install-recommends cmake gcc g++ make
      - name: Host unit tests
        if: hashFiles('tests/host/CMakeLists.txt') != ''
        run: |
          cmake -S tests/host -B build/host
          cmake --build build/host
          ctest --test-dir build/host --output-on-failure

  firmware:
    needs: test
    runs-on: ubuntu-latest
    container:
      image: docker.io/espressif/idf:v5.4
    strategy:
      fail-fast: false
      matrix:
        include:
          - board: wroom
            target: esp32
            sdkconfig: sdkconfig.wroom
          - board: jc4827w543
            target: esp32s3
            sdkconfig: sdkconfig.s3
    steps:
      - uses: actions/checkout@v4
      - name: Build
        if: hashFiles('CMakeLists.txt') != ''
        run: |
          git config --global --add safe.directory '*'
          idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }}
          idf.py -B build/${{ matrix.board }} \
            -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;${{ matrix.sdkconfig }}" \
            -D AUTOFILM_BOARD=${{ matrix.board }} \
            build
          idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt
          python $IDF_PATH/components/esptool_py/esptool/esptool.py \
            --chip ${{ matrix.target }} merge_bin \
            -o autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
            @build/${{ matrix.board }}/flash_args
      - uses: actions/upload-artifact@v3
        if: hashFiles('CMakeLists.txt') != ''
        with:
          name: firmware-${{ matrix.board }}
          path: |
            autofilm-${{ matrix.board }}-*.bin
            build/${{ matrix.board }}/*.bin
            build/${{ matrix.board }}/bootloader/bootloader.bin
            build/${{ matrix.board }}/partition_table/partition-table.bin
            build/${{ matrix.board }}/flasher_args.json
            build/${{ matrix.board }}/sdkconfig
            size-${{ matrix.board }}.txt

Context substitutions: Gitea accepts both github.* and gitea.* in many versions; prefer gitea.sha / gitea.ref and fall back if the runner is older.

hashFiles guards keep docs-only stages of this branch from going red before the IDF tree exists.

Required status on PRs

Once the IDF skeleton compiles, mark on the develop branch protection (Gitea repo settings):

  • test
  • firmware (wroom)
  • firmware (jc4827w543)

No merge to develop with a red firmware matrix.

Caching (optional, second pass)

  • Cache ~/.cache/pip and IDF tools if not using the pre-baked espressif/idf image.
  • Cache managed_components/ keyed on idf_component.yml + idf.lock.
  • Do not cache build/ across boards or targets.

Secrets

CI must not need device tokens or Wi-Fi credentials. Firmware builds are offline-configurable. If a later job publishes to a hardware farm, use Gitea secrets — never commit them.

Local equivalent

cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure

docker run --rm -v "$PWD":/project -w /project espressif/idf:v5.4 \
  bash -lc 'idf.py -B build/wroom set-target esp32 && idf.py -B build/wroom -D AUTOFILM_BOARD=wroom build'

Agents must run the host tests before pushing once tests/host exists.