[PATCH v1 08/10] kconfig-checker: Add support for rule overlays

From: Ahmed S. Darwish

Date: Fri Nov 06 2026 - 04:27:06 EST


An kconfig-checker overlay is a YAML file applied on top of an existing
rules file.

Overlays are intended for distribution kernel packaging scripts, CI
environments, and board support packages; where the base rules may need
local adjustments. Overlays may override any rule's field. They may
also update the documentation URL or introduce totally new rules.

Add support for such an overlay mechanism at the kconfig checker.

Signed-off-by: Ahmed S. Darwish <darwi@xxxxxxxxxxxxx>
---
tools/kconfig-checker/check.py | 89 +++++++++++++++++++++++++++++++++-
1 file changed, 88 insertions(+), 1 deletion(-)

diff --git a/tools/kconfig-checker/check.py b/tools/kconfig-checker/check.py
index 4f79ec487da7..38ae4ed32817 100755
--- a/tools/kconfig-checker/check.py
+++ b/tools/kconfig-checker/check.py
@@ -7,7 +7,7 @@
# Main usage:
#
# $ check.py list-rules
-# $ check.py check -c path/to/linux/.config -r preempt_rt
+# $ check.py check -c path/to/linux/.config -r preempt_rt [-o path/to/overlay.yml]
#
# Notes:
#
@@ -225,6 +225,48 @@ class Rule:
if self.docs_hint:
self.docs_hint = '#' + self.docs_hint

+ def overlay(self, overlay: YamlDict) -> None:
+ """Overlay passed yaml structure over existing data"""
+ if 'expected' in overlay:
+ self.expected = ExpectedValue[overlay['expected']]
+
+ if 'severity' in overlay:
+ self.severity = Severity[overlay['severity']]
+
+ if 'rationale' in overlay:
+ self.rationale = overlay['rationale']
+
+ if 'docs_hint' in overlay:
+ self.docs_hint = overlay['docs_hint']
+ self._normalize_docs_hint()
+
+ @staticmethod
+ def _ensure_required_fields(rule: YamlDict, rule_id: str) -> None:
+ """Ensure that passed rule has all required fields"""
+
+ # Normal rule files require every rule to define both 'expected'
+ # and 'severity'.
+ #
+ # Overlay files are different: an overlay may update only selected
+ # fields of an existing rule, so the overlay schema permits these
+ # fields to be omitted.
+ #
+ # However, an overlay may also introduce a completely new rule,
+ # where such a rule has no existing base rule to inherit from.
+ # Manually check all required fields existence in that case.
+
+ if 'expected' not in rule:
+ error(
+ f'Required field `expected` is missing from {rule_id}',
+ os.EX_DATAERR
+ )
+
+ if 'severity' not in rule:
+ error(
+ f'Required field `severity` is missing from {rule_id}',
+ os.EX_DATAERR
+ )
+
class SymbolRule(Rule):
"""Rule entry for one .config symbol"""

@@ -232,6 +274,8 @@ class SymbolRule(Rule):

def __init__(self, rule: YamlDict):
self.symbol = rule['name']
+
+ super()._ensure_required_fields(rule, f'symbol {self.symbol}')
super().__init__(rule)

def _normalize_docs_hint(self) -> None:
@@ -253,6 +297,7 @@ class PatternRule(Rule):
except re.error as e:
error(f'Invalid symbol regex {regex}: {e}', os.EX_DATAERR)

+ super()._ensure_required_fields(rule, f'pattern {regex}')
super().__init__(rule)

@dataclass
@@ -333,6 +378,36 @@ class Issue:
severity: Severity
rationale: Optional[str]

+class OverlayFile:
+ """YAML overlay file access"""
+
+ overlays: YamlDict
+
+ def __init__(self, overlay_f: Path, schema: Schema):
+ self.overlays = Helpers.load_yaml(overlay_f)
+ Helpers.validate_yaml(self.overlays, schema.validator, overlay_f.name)
+
+ def apply(self, rules: Rules) -> Rules:
+ if 'docs_url' in self.overlays:
+ rules.docs_url = self.overlays['docs_url']
+
+ for overlay in self.overlays.get('rules', []):
+ if 'name' in overlay:
+ if rule := rules.get_symbol_rule(overlay['name']):
+ rule.overlay(overlay)
+ else:
+ rules.add_rule(SymbolRule(overlay))
+ continue
+
+ if 'name_pattern' in overlay:
+ if prule := rules.get_pattern_rule(overlay['name_pattern']):
+ prule.overlay(overlay)
+ else:
+ rules.add_rule(PatternRule(overlay))
+ continue
+
+ return rules
+
@dataclass
class SymbolIssue(Issue):
"""Checker issue concerning a single .config symbol"""
@@ -563,6 +638,14 @@ def parse_args(rules: list[str]) -> argparse.Namespace:
help='rules file to check against',
)

+ check_parser.add_argument(
+ '-o',
+ '--overlay',
+ metavar='OVERLAY_FILE',
+ type=Path,
+ help='file overlay over picked rules',
+ )
+
return parser.parse_args()

def main() -> None:
@@ -586,6 +669,10 @@ def main() -> None:
rules_f = rules_dir / f'{args.rules}.yml'
rules = RulesFile(rules_f, schema).parse()

+ if args.overlay:
+ schema = Schema(schema_dir / 'overlay.yml')
+ rules = OverlayFile(args.overlay, schema).apply(rules)
+
checker = Checker(kconfig, rules)
report = checker.find_issues()

--
2.56.0