#!/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}")