-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add script for checking resulting kernel config (#3006)
There is bunch of kernel config options that are not propagated correctly to the kernel configuration after fragments are merged and processed by Kconfig. Current Buildroot tools are not good at discovering these - while we cleaned up most inconsistencies by using linux-diff-config and output from the merge_config.sh script, there are still options that were removed or get a different value than intended because of dependencies, etc. This commit adds a Python script that is using Kconfiglib to parse current kernel's Kconfig files and the generated .config and compare the requested values from individual kernel config fragments. The script can be used manually by running `make linux-check-dotconfig` from the buildroot directory (with path to BR2_EXTERNAL directory set) and it's called also from the CI, where it generates Github Workflow warning annotations when some of the values are not present or when set incorrectly. The kconfiglib.py is checked-in to the repo as well, because the library is currently abandoned on PyPI and packaged version has a bug that causes errors parsing Kconfigs in newer Linux versions, fixed in outstanding pull request ulfalizer/Kconfiglib#119 - so version from this PR is used here. If pypi/support#2526 is ever resolved, we could remove it from our repo and use pip for installing the package as a requirement during build of the build container.
- Loading branch information
Showing
3 changed files
with
7,313 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,12 @@ | ||
include $(sort $(wildcard $(BR2_EXTERNAL_OPENVOICEOS_PATH)/package/*/*.mk)) | ||
|
||
.PHONY: linux-check-dotconfig | ||
linux-check-dotconfig: linux-check-configuration-done | ||
CC=$(TARGET_CC) LD=$(TARGET_LD) srctree=$(LINUX_SRCDIR) \ | ||
ARCH=$(if $(BR2_x86_64),x86,$(if $(BR2_arm)$(BR2_aarch64),arm,$(ARCH))) \ | ||
SRCARCH=$(if $(BR2_x86_64),x86,$(if $(BR2_arm)$(BR2_aarch64),arm,$(ARCH))) \ | ||
$(BR2_EXTERNAL_HASSOS_PATH)/scripts/check-dotconfig.py \ | ||
$(BR2_CHECK_DOTCONFIG_OPTS) \ | ||
--src-kconfig $(LINUX_SRCDIR)Kconfig \ | ||
--actual-config $(LINUX_SRCDIR).config \ | ||
$(shell echo $(BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
#!/usr/bin/env python | ||
|
||
import argparse | ||
from collections import namedtuple | ||
import re | ||
|
||
from kconfiglib import Kconfig | ||
|
||
|
||
# Can be either "CONFIG_OPTION=(y|m|n)" or "# CONFIG_OPTION is not set" | ||
regex = re.compile( | ||
r"^(CONFIG_(?P<option_set>[A-Z0-9_]+)=(?P<value>[mny])" | ||
r"|# CONFIG_(?P<option_unset>[A-Z0-9_]+) is not set)$" | ||
) | ||
|
||
# use namedtuple as a lightweight representation of fragment-defined options | ||
OptionValue = namedtuple("OptionValue", ["option", "value", "file", "line"]) | ||
|
||
|
||
def parse_fragment( | ||
filename: str, strip_path_prefix: str = None | ||
) -> dict[str, OptionValue]: | ||
""" | ||
Parse Buildroot Kconfig fragment and return dict of OptionValue objects. | ||
""" | ||
options: dict[str, OptionValue] = {} | ||
|
||
with open(filename) as f: | ||
if strip_path_prefix and filename.startswith(strip_path_prefix): | ||
filename = filename[len(strip_path_prefix) :] | ||
|
||
for line_number, line in enumerate(f, 1): | ||
if matches := re.match(regex, line): | ||
if matches["option_unset"]: | ||
value = OptionValue( | ||
matches["option_unset"], None, filename, line_number | ||
) | ||
options.update({matches.group("option_unset"): value}) | ||
else: | ||
value = OptionValue( | ||
matches["option_set"], matches["value"], filename, line_number | ||
) | ||
options.update({matches.group("option_set"): value}) | ||
|
||
return options | ||
|
||
|
||
def _format_message( | ||
message: str, file: str, line: int, github_format: bool = False | ||
) -> str: | ||
""" | ||
Format message with source file and line number. | ||
""" | ||
if github_format: | ||
return f"::warning file={file},line={line}::{message}" | ||
return f"{message} (defined in {file}:{line})" | ||
|
||
|
||
def compare_configs( | ||
expected_options: dict[str, OptionValue], | ||
kconfig: Kconfig, | ||
github_format: bool = False, | ||
) -> None: | ||
""" | ||
Compare dictionary of expected options with actual Kconfig representation. | ||
""" | ||
for option, spec in expected_options.items(): | ||
if option not in kconfig.syms: | ||
print( | ||
_format_message( | ||
f"{option}={spec.value} not found", | ||
file=spec.file, | ||
line=spec.line, | ||
github_format=github_format, | ||
) | ||
) | ||
elif (val := kconfig.syms[option].str_value) != spec.value: | ||
if spec.value is None and val == "n": | ||
continue | ||
print( | ||
_format_message( | ||
f"{option}={spec.value} requested, actual = {val}", | ||
file=spec.file, | ||
line=spec.line, | ||
github_format=github_format, | ||
) | ||
) | ||
|
||
|
||
def main() -> None: | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"--src-kconfig", help="Path to top-level Kconfig file", required=True | ||
) | ||
parser.add_argument( | ||
"--actual-config", | ||
help="Path to config with actual config values (.config)", | ||
required=True, | ||
) | ||
parser.add_argument( | ||
"--github-format", | ||
action="store_true", | ||
help="Use Github Workflow commands output format", | ||
) | ||
parser.add_argument( | ||
"-s", | ||
"--strip-path-prefix", | ||
help="Path prefix to strip in the output from config fragment paths", | ||
) | ||
parser.add_argument("fragments", nargs="+", help="Paths to source config fragments") | ||
|
||
args = parser.parse_args() | ||
|
||
expected_options: dict[str, OptionValue] = {} | ||
|
||
for f in args.fragments: | ||
expected_options.update( | ||
parse_fragment(f, strip_path_prefix=args.strip_path_prefix) | ||
) | ||
|
||
kconfig = Kconfig(args.src_kconfig, warn_to_stderr=False) | ||
kconfig.load_config(args.actual_config) | ||
|
||
compare_configs(expected_options, kconfig, github_format=args.github_format) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Oops, something went wrong.