tools[feat]: add config check tool

[checkkconfig.py] is a tool that simulates the effects of modifying a CONFIG item,
Can be used to check whether my config changes are what I expected.

Signed-off-by: xuxin19 <xuxin19@xiaomi.com>
This commit is contained in:
xuxin19
2025-03-03 22:05:27 +08:00
committed by simbit18
parent 35ae7a761d
commit 81d1126965
2 changed files with 349 additions and 0 deletions
+42
View File
@@ -13,6 +13,48 @@ cmpconfig.c
This C file can be used to build a utility for comparing two NuttX
configuration files.
checkkconfig.py
---------------
``checkkconfig.py`` is a Python script that simulates the effects of modifying a CONFIG item.
It can be used to check whether my config changes are what I expected.
Help message::
$ tools/checkkconfig.py -h
usage: checkkconfig.py [-h] -f FILE (-s CONFIG VALUE | -d DIFF)
optional arguments:
-h, --help show this help message and exit
-f FILE, --file FILE Path to the input defconfig file
-s CONFIG_XXX VALUE, --single CONFIG VALUE
Analyze single change: CONFIG_NAME y/m/n
-d DIFF, --diff DIFF Analyze changes from diff file
example: ./tools/checkkconfig.py -f defconfig -s ELF n
outputs:
Change report for ELF=n
Config Option Old New
----------------------------------------------------------------------
BINFMT_LOADABLE y n
ELF y n
ELF_STACKSIZE 8192 <unset>
LIBC_ARCH_ELF y n
LIBC_MODLIB y n
MODLIB_ALIGN_LOG2 2 <unset>
MODLIB_BUFFERINCR 32 <unset>
MODLIB_BUFFERSIZE 32 <unset>
MODLIB_MAXDEPEND 2 <unset>
MODLIB_RELOCATION_BUFFERCOUNT 256 <unset>
MODLIB_SYMBOL_CACHECOUNT 256 <unset>
As we can see, we can clearly know that
if I turn off ELF in defconfig at this time,
it will bring about the following configuration linkage changes
It can also parse diff files, which can be used to check the changes of multiple configs.
checkpatch.sh
-------------
+307
View File
@@ -0,0 +1,307 @@
#!/usr/bin/env python3
# tools/checkkconfig.py
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
#
# [checkkconfig.py] is a tool that simulates the effects of modifying a CONFIG item,
# Can be used to check whether my config changes are what I expected.
#
# usage: checkkconfig.py [-h] -f FILE (-s CONFIG VALUE | -d DIFF)
#
# optional arguments:
# -h, --help show this help message and exit
# -f FILE, --file FILE Path to the input defconfig file
# -s CONFIG_XXX VALUE, --single CONFIG VALUE
# Analyze single change: CONFIG_NAME y/m/n
# -d DIFF, --diff DIFF Analyze changes from diff file
#
# example: ./tools/checkkconfig.py -f defconfig -s ELF n
#
# outputs:
# Change report for ELF=n
# Config Option Old New
# ----------------------------------------------------------------------
# BINFMT_LOADABLE y n
# ELF y n
# ELF_STACKSIZE 8192 <unset>
# LIBC_ARCH_ELF y n
# LIBC_MODLIB y n
# MODLIB_ALIGN_LOG2 2 <unset>
# MODLIB_BUFFERINCR 32 <unset>
# MODLIB_BUFFERSIZE 32 <unset>
# MODLIB_MAXDEPEND 2 <unset>
# MODLIB_RELOCATION_BUFFERCOUNT 256 <unset>
# MODLIB_SYMBOL_CACHECOUNT 256 <unset>
#
# As we can see, we can clearly know that
# if I turn off ELF in defconfig at this time,
# it will bring about the following configuration linkage changes
#
# It can also parse diff files, which can be used to check the changes of multiple configs.
# diff file example:
# diff --git a/boards/demo/configs/nsh/defconfig b/boards/demo/configs/nsh/defconfig
# index cf7d07c..5de20d4 100644
# --- a/boards/demo/configs/nsh/defconfig
# +++ b/boards/demo/configs/nsh/defconfig
# @@ -51,7 +51,6 @@ CONFIG_ARMV7A_STRING_FUNCTION=y
# CONFIG_ARM_PSCI=y
# CONFIG_ARM_SEMIHOSTING_HOSTFS=y
# CONFIG_ARM_THUMB=y
# -CONFIG_AUDIO=y
# CONFIG_BCH=y
# CONFIG_BINFMT_ELF_EXECUTABLE=y
# CONFIG_BLUETOOTH=y
# @@ -104,7 +103,6 @@ CONFIG_DRIVERS_VIRTIO_SERIAL=y
# CONFIG_DRIVERS_VIRTIO_SOUND=y
# CONFIG_DRIVERS_WIFI_SIM=y
# CONFIG_DRIVERS_WIRELESS=y
# -CONFIG_ELF=y
# CONFIG_ETC_ROMFS=y
# CONFIG_EVENT_FD=y
# CONFIG_EXAMPLES_FB=y
#
# example: ./tools/checkkconfig.py -f defconfig -d changes.diff
#
# outputs:
# Change report for diff: changes.diff
# Config Option Old New
# ----------------------------------------------------------------------
# AUDIO y n
# AUDIO_BUFFER_NUMBYTES 8192 <unset>
# AUDIO_EXCLUDE_EQUALIZER y n
# AUDIO_EXCLUDE_REWIND y n
# AUDIO_FORMAT_AMR y n
# AUDIO_FORMAT_MP3 y n
# AUDIO_FORMAT_OPUS y n
# AUDIO_FORMAT_PCM y n
# AUDIO_FORMAT_SBC y n
# AUDIO_NUM_BUFFERS 2 <unset>
# BINFMT_LOADABLE y n
# ELF y n
# ELF_STACKSIZE 8192 <unset>
# LIBC_ARCH_ELF y n
# LIBC_MODLIB y n
# MODLIB_ALIGN_LOG2 2 <unset>
# MODLIB_BUFFERINCR 32 <unset>
# MODLIB_BUFFERSIZE 32 <unset>
# MODLIB_MAXDEPEND 2 <unset>
# MODLIB_RELOCATION_BUFFERCOUNT 256 <unset>
# MODLIB_SYMBOL_CACHECOUNT 256 <unset>
# NXPLAYER_COMMAND_LINE y n
# NXPLAYER_DEFAULT_MEDIADIR /music <unset>
# NXPLAYER_FMT_FROM_EXT y n
# NXPLAYER_INCLUDE_DEVICE_SEARCH y n
# NXPLAYER_INCLUDE_HELP y n
# NXPLAYER_INCLUDE_MEDIADIR y n
# NXPLAYER_INCLUDE_PREFERRED_DEVICE y n
# NXPLAYER_MAINTHREAD_STACKSIZE 8192 <unset>
# NXPLAYER_PLAYTHREAD_STACKSIZE 8192 <unset>
# NXRECORDER_COMMAND_LINE y n
# NXRECORDER_FMT_FROM_EXT y n
# NXRECORDER_INCLUDE_HELP y n
# NXRECORDER_MAINTHREAD_STACKSIZE 8192 <unset>
# NXRECORDER_RECORDTHREAD_STACKSIZE 8192 <unset>
# SYSTEM_NXPLAYER y n
# SYSTEM_NXRECORDER y n
#
#
# RECAUTION:
# Because NuttX apps Kconfig of menu is generated by build system,
# and arch/board bridge Kconfig is symlink to real arch board dir.
# So it is best to check the defconfig that has been configured.
# If the environment does not generate Kconfig menu, etc.
# the tool will execute `configure.sh` and distclean at the end.
#
import argparse
import os
import re
import subprocess
import sys
from pathlib import Path
try:
from kconfiglib import Kconfig
except ImportError:
print(
"ERROR: checkkconfig tool depends on kconfiglib for parse Kconfig tree.\nplease install it.\npip install kconfiglib",
file=sys.stderr,
)
sys.exit(1)
TOPDIR = Path(__file__).resolve().parent.parent
DPATH = Path("defconfig")
NEED_RESET = False
# Prepare environment for Kconfig
def prepare_env():
global NEED_RESET
# check if we are in the configured Kconfig environment
full_config_file = TOPDIR / Path(".config")
if not full_config_file.exists():
print("apps preconfig do not generate yet \nrun configure.sh first")
result = subprocess.run(
[f"{TOPDIR}/tools/configure.sh", "-e", f"{str(DPATH.parent)}"],
capture_output=True,
text=True,
)
if result.returncode != 0:
print(
f"ERROR: {TOPDIR}/tools/configure.sh run fail\n configure path: {str(DPATH.parent)}",
file=sys.stderr,
)
print(f"STDERROR:{result.stderr}", file=sys.stderr)
sys.exit(1)
NEED_RESET = True
os.environ["APPSDIR"] = "../apps"
os.environ["APPSBINDIR"] = "../apps"
os.environ["EXTERNALDIR"] = "dummy"
os.environ["BINDIR"] = str(TOPDIR)
os.environ["KCONFIG_CONFIG"] = str(DPATH)
# Reset environment to previous
def reset_env():
os.environ.pop("APPSDIR", None)
os.environ.pop("APPSBINDIR", None)
os.environ.pop("EXTERNALDIR", None)
os.environ.pop("BINDIR", None)
os.environ.pop("KCONFIG_CONFIG", None)
if NEED_RESET:
result = subprocess.run(
["make", "distclean"], cwd=TOPDIR, capture_output=True, text=True
)
print(result.stdout)
if result.returncode != 0:
print(
"ERROR: distclean error please clean up your workspace manually",
file=sys.stderr,
)
print(f"STDERROR:{result.stderr}", file=sys.stderr)
# Parse a diff file and return a dict of changes
def parse_diff(diff_path):
changes = {}
diff_pattern = re.compile(r"^([+-])CONFIG_(\w+)=([ymn])$")
with open(diff_path, "r") as f:
for line in f:
line = line.strip()
match = diff_pattern.match(line)
if not match:
continue
op, name, value = match.groups()
full_name = f"{name}"
if op == "-":
changes[full_name] = "n"
elif op == "+":
changes[full_name] = value.lower()
return changes
# Apply a set of changes to a Kconfig tree and return a list of changed symbols
def apply_changes(kconf, changes):
# step 1: keep track of original values
orig_state = {sym.name: sym.str_value for sym in kconf.defined_syms}
# step 2: apply the config settings
value_map = {"n": 0, "m": 1, "y": 2}
for target, value in changes.items():
sym = kconf.syms.get(target)
if not sym:
print(f"Warning: {target} not found, skipped")
continue
if value not in value_map:
print(f"Invalid value {value} for {target}, skipped")
continue
sym.set_value(value_map[value])
# step 3: check for changes
changed = []
for sym in kconf.defined_syms:
orig = orig_state.get(sym.name, "")
curr = sym.str_value
if orig != curr:
changed.append((sym.name, orig, curr))
return changed
def track_single_change(target, value):
kconf = Kconfig()
kconf.load_config()
return apply_changes(kconf, {target: value})
def track_diff_changes(diff_path):
kconf = Kconfig()
kconf.load_config()
changes = parse_diff(diff_path)
return apply_changes(kconf, changes)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-f", "--file", required=True, help="Path to the input defconfig file"
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-s",
"--single",
nargs=2,
metavar=("CONFIG", "VALUE"),
help="Analyze single change: CONFIG_NAME y/m/n",
)
group.add_argument("-d", "--diff", help="Analyze changes from diff file")
args = parser.parse_args()
DPATH = Path(args.file)
if not DPATH.is_absolute():
DPATH = TOPDIR / DPATH
if not DPATH.exists:
print("ERROR: defconfig file DO NOT exist", file=sys.stderr)
sys.exit(1)
prepare_env()
if args.single:
target, value = args.single
changes = track_single_change(target, value.lower())
title = f"Change report for {target}={value}"
elif args.diff:
changes = track_diff_changes(args.diff)
title = f"Change report for diff: {args.diff}"
reset_env()
print(f"\n{title}")
print(f"{'Config Option':<40} {'Old':<20} {'New':<20}")
print("-" * 70)
for name, old, new in sorted(changes, key=lambda x: x[0]):
print(f"{name:<40} {old or '<unset>':<20} {new or '<unset>':<20}")