Files
AutoFilm-ESP32/docs/CICD.md
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

255 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```bash
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:
```yaml
- 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`
```yaml
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
```bash
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.