diff --git a/Documentation/components/tools/index.rst b/Documentation/components/tools/index.rst index f380acfbe20..65ccb8e0545 100644 --- a/Documentation/components/tools/index.rst +++ b/Documentation/components/tools/index.rst @@ -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 + LIBC_ARCH_ELF y n + LIBC_MODLIB y n + MODLIB_ALIGN_LOG2 2 + MODLIB_BUFFERINCR 32 + MODLIB_BUFFERSIZE 32 + MODLIB_MAXDEPEND 2 + MODLIB_RELOCATION_BUFFERCOUNT 256 + MODLIB_SYMBOL_CACHECOUNT 256 + +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 ------------- diff --git a/tools/checkkconfig.py b/tools/checkkconfig.py new file mode 100755 index 00000000000..74c5c0415c4 --- /dev/null +++ b/tools/checkkconfig.py @@ -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 +# LIBC_ARCH_ELF y n +# LIBC_MODLIB y n +# MODLIB_ALIGN_LOG2 2 +# MODLIB_BUFFERINCR 32 +# MODLIB_BUFFERSIZE 32 +# MODLIB_MAXDEPEND 2 +# MODLIB_RELOCATION_BUFFERCOUNT 256 +# MODLIB_SYMBOL_CACHECOUNT 256 +# +# 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 +# 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 +# BINFMT_LOADABLE y n +# ELF y n +# ELF_STACKSIZE 8192 +# LIBC_ARCH_ELF y n +# LIBC_MODLIB y n +# MODLIB_ALIGN_LOG2 2 +# MODLIB_BUFFERINCR 32 +# MODLIB_BUFFERSIZE 32 +# MODLIB_MAXDEPEND 2 +# MODLIB_RELOCATION_BUFFERCOUNT 256 +# MODLIB_SYMBOL_CACHECOUNT 256 +# NXPLAYER_COMMAND_LINE y n +# NXPLAYER_DEFAULT_MEDIADIR /music +# 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 +# NXPLAYER_PLAYTHREAD_STACKSIZE 8192 +# NXRECORDER_COMMAND_LINE y n +# NXRECORDER_FMT_FROM_EXT y n +# NXRECORDER_INCLUDE_HELP y n +# NXRECORDER_MAINTHREAD_STACKSIZE 8192 +# NXRECORDER_RECORDTHREAD_STACKSIZE 8192 +# 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 '':<20} {new or '':<20}")