summaryrefslogtreecommitdiff
path: root/.github
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-08-25 09:46:42 +0700
committerGitHub <[email protected]>2026-08-25 09:46:42 +0700
commitda255b1d2db10b8f31332a779b2a526f579acee1 (patch)
tree154c8b8b9cf48f4a111edd385b8df7cc6919f3b8 /.github
parentaa0f4a40b59012e6ab421949c153088ca4984df4 (diff)
ci: an empty selection must build nothing, plus selector follow-ups (#3845)
ci: an empty selection must build nothing, plus selector follow-ups A PR whose build axis legitimately selected nothing rebuilt everything. build.yml reads .build.families twice - as a |-joined regex, and implicitly as "is anything selected" - but tested only -z "$FAMILY_REGEX", which an empty list and a charset-rejected one both satisfy while meaning opposite things. ci_set_matrix had already returned the correct all-empty matrix; the fall-open branch discarded it. #3842 and #3840 each spent 74 cmake legs on it. Branch on the two cases instead, rename FAM_* to FAMILY_*, and cover the block with a test that extracts it from build.yml and executes it - it had no test at all, which is how this shipped through two merges. Follow-ups to the same machinery: glob.escape the repo root at five sites, so a checkout path containing [ or * stops failing closed; drop the ci-full label, read after the matrix was already computed and so never functional; delete 13 mcu:MKL25ZXX / mcu:SAME5X skip tokens matching no board; carry the rule table in the module docstring, guarded against drift; and pin six selection behaviours a mutation pass proved untested. Cut the selector's cost 1.8x (26.0s -> 14.6s) with 0 divergences over 260 paths, and stop scoping the membrowse upload by the PR example filter.
Diffstat (limited to '.github')
-rwxr-xr-x.github/scripts/ci_set_matrix.py8
-rw-r--r--.github/workflows/build.yml35
-rw-r--r--.github/workflows/build_util.yml13
3 files changed, 33 insertions, 23 deletions
diff --git a/.github/scripts/ci_set_matrix.py b/.github/scripts/ci_set_matrix.py
index 79f466893..409e6dbc1 100755
--- a/.github/scripts/ci_set_matrix.py
+++ b/.github/scripts/ci_set_matrix.py
@@ -131,7 +131,13 @@ def set_matrix_json(select=None):
# a family this file does not list builds on no toolchain, so it contributes no
# leg. hw/bsp holds several CI has never built (efm32, py32f0, same7x, ...) plus
# espressif, whose boards hil-build-esp builds by name.
- unbuilt = sorted(f for f in sel_fams if f not in family_list)
+ # espressif is not a gap: its examples need the ESP-IDF environment
+ # (CLAUDE.md: `. "$IDF_PATH/export.sh"` before any build), which the cmake legs
+ # do not have - that is why it is commented out of family_list above. Its
+ # coverage comes from hil-build-esp, which builds those boards BY NAME in an IDF
+ # container, so an espressif-only PR is already validated and falling open to the
+ # full matrix would add 74 legs, none of which can compile espressif.
+ unbuilt = sorted(f for f in sel_fams if f not in family_list and f != 'espressif')
if unbuilt and not any(matrix.values()):
# NONE of the selected families is buildable here, so every leg would skip
# and the PR would go green from a build job that ran no compiler. That is
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 39a4e7afd..c26fe5cf8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,14 +68,9 @@ jobs:
with:
fetch-depth: 0
- # The `ci-full` PR label turns the scoping off for one PR: no selection file is
- # written, so both matrices and every rig job fall back to the unscoped behaviour.
- # An escape hatch is the point - a selector bug under-selects SILENTLY, and without
- # a label the only routes back to a full matrix are accidental (touch an
- # unclassified path, or break the selector badly enough that it falls open).
- name: CI selection (PR only)
id: hil-select
- if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci-full')
+ if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: |
@@ -179,29 +174,43 @@ jobs:
# treats false like null, so .build.full is compared explicitly.
EXAMPLE_MAP='{}'
BUILD_FILTERED='false'
- FAM_REGEX=''
+ FAMILY_REGEX=''
if [ -n "$BUILD_SELECT_FILE" ]; then
EXAMPLE_MAP=$(jq -c '.build.family_examples // {}' "$BUILD_SELECT_FILE") || EXAMPLE_MAP='{}'
BUILD_FILTERED=$(jq -r 'if (.build? | type) == "object" and .build.full == false then "true" else "false" end' "$BUILD_SELECT_FILE") || BUILD_FILTERED='false'
if [ "$BUILD_FILTERED" = "true" ]; then
- FAM_REGEX=$(jq -r '.build.families | join("|")' "$BUILD_SELECT_FILE") || FAM_REGEX=''
+ FAMILY_COUNT=$(jq -r '.build.families | length' "$BUILD_SELECT_FILE") || FAMILY_COUNT=0
+ FAMILY_REGEX=$(jq -r '.build.families | join("|")' "$BUILD_SELECT_FILE") || FAMILY_REGEX=''
# family names come from hw/bsp dir names, which rule 6 reads straight out
# of the PR's diff path - and this is interpolated raw into a
# `name_is_regexp` artifact pattern, so a regex metacharacter there would
# silently match another family's baseline
- case "$FAM_REGEX" in
+ FAMILY_REJECTED=0
+ case "$FAMILY_REGEX" in
*[!-A-Za-z0-9_\|]*)
echo "::warning::unexpected characters in the family list - dropping the scoping"
- FAM_REGEX='' ;;
+ FAMILY_REGEX=''; FAMILY_REJECTED=1 ;;
esac
- if [ -z "$FAM_REGEX" ]; then
- # all three drop together, as CircleCI's fall-open does. Resetting only
+ # An EMPTY families list and a REJECTED one both leave FAMILY_REGEX empty and
+ # mean opposite things, so branch on which happened. Testing `-z` alone sent
+ # every nothing-selected PR down the fall-open path: a docs/.gitignore diff
+ # (#3842) and a test/hil-only diff (#3840) each rebuilt all 74 cmake legs
+ # after the selector had correctly chosen none.
+ if [ "$FAMILY_REJECTED" = "1" ]; then
+ # unusable: fall open, and all three drop together. Resetting only
# build_filtered leaves the build scoped while code-metrics takes the
# UNSCOPED branch, diffing a 1-family run against the full averaged
# baseline and publishing that as the PR's code-size impact.
BUILD_FILTERED='false'
EXAMPLE_MAP='{}'
MATRIX_JSON=$(python .github/scripts/ci_set_matrix.py)
+ elif [ "$FAMILY_COUNT" = "0" ]; then
+ # legitimate nothing-selected. MATRIX_JSON already holds the all-empty
+ # matrix ci_set_matrix produced from this selection - keep it, so every
+ # leg skips. Nothing is built, so there is nothing to compare a baseline
+ # against: build_filtered goes false to keep code-metrics off the scoped
+ # path, and EXAMPLE_MAP stays '{}' (family_examples is empty anyway).
+ BUILD_FILTERED='false'
fi
fi
fi
@@ -210,7 +219,7 @@ jobs:
echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT
echo "example_map=$EXAMPLE_MAP" >> $GITHUB_OUTPUT
echo "build_filtered=$BUILD_FILTERED" >> $GITHUB_OUTPUT
- echo "build_families_regex=$FAM_REGEX" >> $GITHUB_OUTPUT
+ echo "build_families_regex=$FAMILY_REGEX" >> $GITHUB_OUTPUT
# HIL matrix (merged from tinyusb + hifiphile configs), scoped on PRs.
# Scoping is best-effort too: fall back to the unscoped (full) matrix.
diff --git a/.github/workflows/build_util.yml b/.github/workflows/build_util.yml
index 52999616d..407ed1e71 100644
--- a/.github/workflows/build_util.yml
+++ b/.github/workflows/build_util.yml
@@ -126,16 +126,11 @@ jobs:
MEMBROWSE_API_KEY: ${{ secrets.MEMBROWSE_API_KEY }}
run: |
# if code-changed is false --> there is no elf -> membrowse target upload with --identical flag
- # $EX_ARGS is passed for the BOARD it picks, not to scope the targets:
- # --one-first now chooses a board that can build the -e set (tools/build.py),
- # so omitting it here would configure a DIFFERENT, empty build dir and upload
- # --identical for a board that was never compiled. The target list is not
- # scoped by it - `examples-membrowse-upload` is not `all`, so it passes
- # through as the aggregate, which has no DEPENDS (hw/bsp/family_support.cmake):
- # it rebuilds nothing and still records every example, --identical for the
- # ones without an elf.
+ # deliberately unscoped by $EX_ARGS: keeps the size history on a stable board
+ # per family, at the cost of an --identical-only upload where that board is not
+ # the one the Build step picked (test_ci_metrics pins which families those are)
BUILD_PY_ARGS="-s ${{ inputs.build-system }} ${{ steps.setup-toolchain.outputs.build_option }} ${{ inputs.build-options }}"
- python tools/build.py $BUILD_PY_ARGS --target examples-membrowse-upload -j 1 ${{ matrix.arg }} $EX_ARGS
+ python tools/build.py $BUILD_PY_ARGS --target examples-membrowse-upload -j 1 ${{ matrix.arg }}
shell: bash
- name: Upload Artifacts for Metrics