Files
openipc-builder/.github/scripts
edc0a0f88f ci: build pull requests, and only the devices they reach (#120)
* ci: build pull requests, and only the devices they reach

This repo had no pull_request trigger at all. A change landed unbuilt,
and the nightly found out the next morning -- or a user did. Turning PR
CI on meant 107 device builds per push, which is why it stayed off.

Almost every change here is one device: 639 of the file touches in the
last 200 commits sit under a single devices/<dir>/, and the median
commit reaches 2 devices. So narrowing first is what makes PR CI
affordable at all. .github/scripts/ci-matrix.py maps the paths a PR
touches to the devices that build them, and master.yml takes its matrix
from it.

The mapping is not written down, it is read off the tree the same way
builder.sh reads it. builder.sh line 121 locates a device by its
defconfig and copies that WHOLE devices/<dir>/ tree over the firmware
clone, so the devices a file affects are exactly the devices whose
defconfig shares its directory. That distinction is load-bearing:
devices/common/ backs 18 targets and devices/apfpv/ backs 2, and
treating a directory as one device would skip 17 real builds. A
defconfig still names one target even inside a shared directory.

THE NIGHTLY IS NOT NARROWED. builder.sh re-clones OpenIPC/firmware at
HEAD on every run, so what a nightly builds is decided mostly outside
this repo; master.yml already carries the scar of an earlier gate that
skipped on this repo's HEAD and left upstream fixes invisible to users.
Every event except pull_request gets the whole matrix, and --self-test
asserts that for schedule, workflow_dispatch and push.

Nothing on a pull_request may write a release. The three upload steps
were gated only on an image existing, so with a pull_request trigger
added they would have clobbered the published nightly and latest assets
that users flash. They and the Telegram post are now guarded on the
event.

master.yml and check-adjacent plumbing get a 15-device smoke set rather
than all 107: they decide how a build runs, not what it produces.
--self-test enforces the cover -- every vendor, architecture, toolchain
tuple and variant, plus both shared device directories -- rather than
the list, so the set can be re-picked while the property holds. It
already earned itself: the first run rejected my hand-picked set for
missing arm-openipc-linux-gnueabi, which exactly one target in the tree
has, and musleabihf.

Also adds the CI Gate umbrella check, since a dynamic matrix cannot be
named in branch protection and nothing reported a verdict before; and
freezes the 7 defconfigs that exist but are in no matrix, so a device
falling out of CI is a test failure rather than a silent skip.

Replaying the last 200 commits: 20330 device-builds if every PR built
everything, 5930 with the selector, 70.8% less, no coverage leaks. 43
of the 49 that still widen do so on paths that no longer exist
(devices/ssc338q_apfpv/ before it became devices/apfpv/, the old
top-level common/, a deleted builder-local.sh); restricted to commits
whose paths survive, it is 78.2%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci: derive the device list instead of writing it down

Adding a camera IS the work in this repo: 8 of the 10 PRs open right now
add a device, and 14% of recent commits do. With the list written down
in ci-matrix.py, every one of those PRs also had to edit that file --
and a change to that file cannot narrow, because it is what does the
narrowing. So registering one camera cost a full 107-device build to
prove one device. The most common change got the worst outcome.

The matrix is now every defconfig under devices/, minus a NOT_BUILT
opt-out that keeps the 7 already in the tree but out of CI. Adding a
device is creating its directory, nothing else, and it builds exactly
that device.

This is the opposite call to OpenIPC/firmware's ci-matrix.py, on
purpose. There the matrix is curated -- 29 defconfigs are deliberately
never built and boards are not the product -- so an explicit list is the
honest description and an opt-out list would be noise. Here devices are
the product and the list is just "all of them".

The derived matrix is identical to the 107 master.yml carried: nothing
lost, nothing gained. --self-test now checks the opt-outs still name
real defconfigs, so a rename cannot leave a device silently building
under its new name while its old name sits in NOT_BUILT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci-matrix: own $GITHUB_OUTPUT instead of being redirected into it

The docstring said the default mode writes $GITHUB_OUTPUT; it printed
key=value lines to stdout and left the workflow to redirect them there.
Raised in review on #120.

Fixing the prose would have made the file honest, but the redirect
itself is the weaker half. Under `>> ${GITHUB_OUTPUT}` every print() in
this file is one keystroke away from corrupting the step outputs, and a
crash between the first line and the last leaves a half-written file
that Actions still reads. That the diagnostics go to stderr is what
keeps it safe today -- a rule that has to hold forever, rather than a
property of the code.

So the script appends the three lines itself when $GITHUB_OUTPUT is set,
and prints them when it is not, so a local run still shows its decision.
--stdin never touches the file: it is the mode for reading with your
eyes. master.yml drops the redirect, though leaving one in place would
have been harmless -- with the file written directly, stdout is empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 07:05:30 +03:00
..