Skip to content

validate-board-config: follow source ${SRC}/config/boards inheritance#9748

Merged
igorpecovnik merged 1 commit intomainfrom
validate-board-config-follow-source
May 2, 2026
Merged

validate-board-config: follow source ${SRC}/config/boards inheritance#9748
igorpecovnik merged 1 commit intomainfrom
validate-board-config-follow-source

Conversation

@igorpecovnik
Copy link
Copy Markdown
Member

@igorpecovnik igorpecovnik commented May 2, 2026

Summary

Extend tools/validate-board-config.py to recognise the source "${SRC}/config/boards/<parent>.csc" pattern that derivative boards use, and treat fields the parent declares as inherited by the child. Today three Ayn-Odin2 derivative boards (ayn-odin2mini.csc, ayn-odin2portal.csc, ayn-thor.csc) use this pattern: each is one source line plus a handful of overrides, with BOARDFAMILY / KERNEL_TARGET / KERNEL_TEST_TARGET defined exclusively in the parent ayn-odin2.csc. The validator's regex-only parser couldn't see them and would emit:

ERROR: config/boards/ayn-odin2mini.csc: BOARDFAMILY: required, ...
ERROR: config/boards/ayn-odin2mini.csc: KERNEL_TARGET: required, ...
(same pair on ayn-odin2portal.csc and ayn-thor.csc)

This is a pre-existing latent bug — it would fire on any future change to those files. PR #9747 (which adds explicit ARCH=arm64 to those boards among others) is what surfaced it.

Why not just source the bash file

The validator's own header explains:

The validator does NOT source the bash file (boards have side-effecty function bodies). It extracts top-level KEY=value assignments via regex.

So the fix has to stay regex-only. Adding a parallel regex for the source pattern keeps that constraint.

Implementation

New collect_inherited_assignments(path):

  • Scans for ^source\s+["']?\$\{?SRC\}?/config/boards/(<file>)["']? (anchored to start-of-line so source calls inside function bodies — which are always indented — aren't followed).
  • Loads the sourced file's top-level assignments and lays them behind the child's own via dict.setdefault. Child's explicit values still win.
  • Recursion-guarded by a visited set keyed on resolved paths — self-source or parent↔child cycle terminates instead of looping.
  • Missing or unresolvable source targets are silently skipped — the main required-field checks will still flag any field that ends up unset, so a typo'd source path doesn't mask a real gap.

Verified

$ python3 tools/validate-board-config.py config/boards/ayn-odin2mini.csc config/boards/ayn-odin2portal.csc config/boards/ayn-thor.csc
0 error(s), 0 warning(s) across 3 file(s)

Pre-fix: 6 errors, 3 KERNEL_TEST_TARGET warnings (KERNEL_TEST_TARGET="current" is set on the parent — now correctly inherited).

Self-contained boards (musepipro.conf, bananapif3.conf, musebook.conf, orangepi5.conf, …) produce byte-identical validator output to before this change — they have no source directive so the inheritance walk is a no-op.

Cycle protection verified with a synthetic fixture (a file sourcing itself → terminates cleanly, no infinite loop).

Companion PR

#9747 declares ARCH=arm64 explicitly on five inheriting boards. Its Validate changed board configs job is currently failing on the same three .csc derivatives this PR fixes; #9747 needs this PR (or its content) on main before the boards PR can pass CI.

Test plan

  • Five PR-changed files in boards: declare ARCH=arm64 explicitly on five inheriting boards #9747 validate cleanly with this fix in place.
  • Non-inheriting boards produce identical validator output pre-/post-change.
  • Cyclic source case terminates instead of looping.
  • Missing-source case (typo'd path) doesn't crash the validator and still surfaces the real missing field.

Summary by CodeRabbit

  • New Features
    • Board configuration validation now resolves inherited configuration fields from source statements, with child configuration values taking precedence.
    • Added cycle detection to prevent infinite inheritance loops.
    • Missing or unresolvable sources are gracefully skipped without validation failures.

…eritance

`Validate changed board configs` (PR #9747 CI run 25258999953) failed
on the three Ayn-Odin2 derivative boards with:

    ERROR: config/boards/ayn-odin2mini.csc: BOARDFAMILY: required, ...
    ERROR: config/boards/ayn-odin2mini.csc: KERNEL_TARGET: required, ...
    (same pair on ayn-odin2portal.csc and ayn-thor.csc)

The validator's header explicitly says it does NOT source the file
("boards have side-effecty function bodies"), and the regex
`parse_assignments` only sees top-level `KEY=value` lines. Boards
that consist of one `source "${SRC}/config/boards/<parent>.csc"` line
plus a handful of overrides (BOARD_NAME, BOARD_VENDOR, BOARD_MAINTAINER,
ARCH) inherit BOARDFAMILY / KERNEL_TARGET / KERNEL_TEST_TARGET from
the parent, so the validator saw them as missing and erred out.

The errors are pre-existing — they would fire on any future change
to those .csc files. PR #9747 just made them visible by being the
first PR to touch an inheriting board since the validator landed.

Add `collect_inherited_assignments`: when a top-level `source` line
points at another file under config/boards/ (matched by a regex
anchored to start-of-line so `source` calls inside function bodies —
which are always indented — aren't followed), parse that file's
top-level assignments and lay them behind the child's own. Child's
explicit values still win via dict.setdefault.

Recursion is guarded by a visited set keyed on resolved paths, so
a self-source or a parent-source-child cycle terminates instead of
looping. Missing source targets are silently skipped — the main
required-field checks will still flag any field that ends up unset
after the merge, so a typo'd source path doesn't mask a real gap.

Verified against the five PR-changed files: 6 errors → 0 errors,
4 warnings → 1 warning (the unmaintained-board warning on
aml-t95z-plus.tvb, which is unrelated to inheritance and was
already there). Self-contained boards (musepipro.conf etc.)
produce identical output to before this change.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

📝 Walkthrough

Walkthrough

This pull request adds support for resolving inherited board configuration fields referenced via source ${SRC}/config/boards/<...> statements. A new regex pattern identifies source directives, a new recursive function collects and merges inherited assignments with cycle detection, and the validator integrates inherited fields while preserving child assignment precedence.

Changes

Board Configuration Inheritance

Layer / File(s) Summary
Pattern Recognition
tools/validate-board-config.py (lines 45–53)
_SOURCE_RE regex matches source ${SRC}/config/boards/<file>.<conf|csc|tvb|wip> statements and captures the referenced board config path.
Inheritance Resolution
tools/validate-board-config.py (lines 96–143)
New collect_inherited_assignments() recursively resolves parent config files, follows source chains with cycle detection via _visited, parses assignments from each parent, and merges them so nearest parents take precedence; unresolvable paths and cycles return {}.
Integration
tools/validate-board-config.py (lines 151–156)
validate() augments board fields by calling collect_inherited_assignments() and filling missing keys with inherited values, ensuring child-declared fields override inherited ones.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through config chains,
Finding sources, breaking cycles' reins—
Child fields shine through parent's care,
Inherited values everywhere! 🐰📋

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: adding support for following source inheritance from parent board configuration files via ${SRC}/config/boards statements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch validate-board-config-follow-source

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 11 minutes and 53 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added Needs review Seeking for review Framework Framework components labels May 2, 2026
@github-actions github-actions Bot added 05 Milestone: Second quarter release size/medium PR with more then 50 and less then 250 lines labels May 2, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tools/validate-board-config.py (1)

134-135: 💤 Low value

Minor: sourced file is read twice.

The file at sourced is read inside the recursive collect_inherited_assignments() call (line 134, at its line 124), and then again at line 135 for parse_assignments(). For deeply inherited chains or large files, this doubles I/O.

You could refactor to return both the parsed assignments and the source matches from a single read, or have collect_inherited_assignments return the text as well. Low priority given typical file sizes.

♻️ Possible optimization
 def collect_inherited_assignments(path: Path, _visited: set[Path] | None = None) -> dict[str, str]:
     ...
     text = path.read_text(errors="replace")
+    own_fields = parse_assignments(text)
     inherited: dict[str, str] = {}
     for m in _SOURCE_RE.finditer(text):
         sourced = path.parent / m.group(1)
         if not sourced.is_file():
             continue
         ancestors = collect_inherited_assignments(sourced, _visited)
-        parent_fields = parse_assignments(sourced.read_text(errors="replace"))
-        for k, v in parent_fields.items():
-            inherited.setdefault(k, v)
         for k, v in ancestors.items():
             inherited.setdefault(k, v)
+    # Merge own fields last so they take precedence
+    for k, v in own_fields.items():
+        inherited.setdefault(k, v)
     return inherited

This reads each file once and lets the recursion handle parent parsing. Caller would then just use the returned dict directly without calling parse_assignments separately in validate().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/validate-board-config.py` around lines 134 - 135, The code reads the
same file twice by calling collect_inherited_assignments(sourced, _visited)
(which itself reads sourced) and then calling
parse_assignments(sourced.read_text(...)) again; refactor
collect_inherited_assignments to return the parsed assignments (or the file
text) along with the current ancestors so callers can reuse that result, then
replace the second call to parse_assignments(...) in validate() with the parsed
data returned from collect_inherited_assignments; update recursive calls inside
collect_inherited_assignments to propagate the parsed/text return value so each
file is read only once (refer to functions collect_inherited_assignments and
parse_assignments and the variable parent_fields).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tools/validate-board-config.py`:
- Around line 134-135: The code reads the same file twice by calling
collect_inherited_assignments(sourced, _visited) (which itself reads sourced)
and then calling parse_assignments(sourced.read_text(...)) again; refactor
collect_inherited_assignments to return the parsed assignments (or the file
text) along with the current ancestors so callers can reuse that result, then
replace the second call to parse_assignments(...) in validate() with the parsed
data returned from collect_inherited_assignments; update recursive calls inside
collect_inherited_assignments to propagate the parsed/text return value so each
file is read only once (refer to functions collect_inherited_assignments and
parse_assignments and the variable parent_fields).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e56c5fb1-bf3c-492f-acb5-2f23e17eda22

📥 Commits

Reviewing files that changed from the base of the PR and between e825695 and 78c4ff6.

📒 Files selected for processing (1)
  • tools/validate-board-config.py

@igorpecovnik igorpecovnik merged commit 3640a9d into main May 2, 2026
14 checks passed
@igorpecovnik igorpecovnik deleted the validate-board-config-follow-source branch May 2, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

05 Milestone: Second quarter release Framework Framework components Needs review Seeking for review size/medium PR with more then 50 and less then 250 lines

Development

Successfully merging this pull request may close these issues.

1 participant