Contributing¶
Requirements¶
ESP-IDF v5.5.5 or newer. Sendspin, which is built into almost every image by default,
needs the WebSocket post-handshake callback that landed in v5.5.5; on an older 5.5.x
release build with CONFIG_SENDSPIN_ENABLE=n. PlatformIO gets a matching toolchain from
the pioarduino platform pinned in platformio.ini, so no manual ESP-IDF install is needed
for that route. Clone with submodules:
git clone --recursive https://github.com/rbouteiller/airplay-esp32
Formatting¶
C and header files are formatted with clang-format 22.1.4 in LLVM style: 2-space indent,
80-column limit. See .clang-format.
python3 -m pip install --user -r requirements-dev.txt
scripts/format.sh # format everything
scripts/format.sh --check # check without modifying
Linting¶
clang-tidy runs with the bugprone, performance, portability and readability checks
configured in .clang-tidy. It needs build/compile_commands.json, so build once first.
scripts/lint.sh # check
scripts/lint.sh --fix # attempt auto-fixes
Pre-commit hook¶
A hook auto-formats staged C and H files and runs clang-tidy. Enable it once per clone:
git config core.hooksPath .githooks
Branches¶
Open pull requests against staging, not main.
staging is the integration branch. Every push to it rebuilds the firmware matrix and
replaces the rolling beta pre-release, so anything merged there is immediately
installable from the browser installer and can
be tried on real hardware before it reaches anyone running a release.
main carries stable releases. It is what the documentation site is published from and
what the release install buttons serve, and it moves only when staging has proved itself
and a version is tagged.
version.txt on staging must stay ahead of the latest release or the beta job fails, so
bump it as soon as a release goes out.
Rebase before merging
A pull request is checked against staging as it was when the check ran. If
another PR merges in the meantime, a green tick can go red on merge — two branches
that touch different files merge without conflict but can still break the build
together, which is exactly how the SPIFFS partition overflowed once already.
Enable Require branches to be up to date before merging on staging, or rebase
and wait for a fresh run before merging anything non-trivial.
CI¶
On every pull request to main or staging, and on every push to main:
| Job | What it does |
|---|---|
format-check |
clang-format dry run over all C/H files, excluding components/u8g2 |
lint-check |
clang-tidy against the build output |
output-backends |
Builds the S/PDIF and USB output backends so every backend keeps linking |
build |
Builds the target matrix |
A pull request that touches only Markdown skips the firmware jobs, so a docs typo does not cost an ESP-IDF toolchain build.
The target list lives in .github/workflows/targets.json. A pull request builds only the
entries flagged "core": true — enough to cover every chip and every board support
directory — while a push to main and a push to staging build all of them. The matrix
does not fail fast, and the beta job publishes whatever succeeded, so one board failing to
compile does not withhold every other board's build.
Adding a target means an entry in targets.json and a matching
docs/firmware/<name>.json manifest whose parts[0].path is
airplay2-receiver-<name>.bin. The docs workflow silently drops a manifest whose binary is
missing from the release, so a target added here shows up in the browser installer only
once a release actually carries it.
Tagging vMAJOR.MINOR.PATCH triggers a release, which validates the tag against
version.txt and publishes merged firmware binaries.
Testing¶
There is no unit test framework — this is embedded firmware and testing means flashing real hardware. When submitting a change, say which board and build environment you tested on.
Editing these docs¶
The site is built with Zensical from Markdown in docs/. Every
page has an edit link in its top-right corner that takes you straight to the GitHub
editor, so small corrections need no local setup at all.
To preview locally:
python3 -m pip install -r docs/requirements.txt
zensical serve
Then open http://127.0.0.1:8000.
Configuration lives in mkdocs.yml. Zensical reads that format natively — it is the
successor to Material for MkDocs by the same team, so the file is unchanged from a
Material setup and switching back is just a dependency change.
The build runs in strict mode, so a broken internal link fails CI rather than shipping
a dead link. Adding a page means adding it to the nav section of mkdocs.yml.
Docs and code live in the same repository on purpose: a pull request that changes a GPIO default or a Kconfig option should update the corresponding page in the same diff.