The merge_config.py script is fully compatible by command line options with merge_config.sh script and provide better performance and solve several issues that present in the merge_config.sh script.
Improvements comparing with merge_config.sh: 1. Preformance was greatly improved: let start=0 end=100 for file in `seq 10`; do for n in `seq $start $end`; do echo "CONFIG_X$n=$file" >> $file.cfg done let start+=100 end+=100 done time merge_config.sh -m *.cfg # Takes 15.3 seconds time merge_config.py -m *.cfg # Takes 0.03 seconds 2. merge_config.sh produce false positive warnings when config name used as config value: $ echo 'CONFIG_NAME="CONFIG_OTHER"' > 1.cfg $ echo 'CONFIG_OTHER="value"' > 2.cfg $ merge_config.sh -m 1.cfg 2.cfg ... Value of CONFIG_OTHER is redefined by fragment 2.cfg: Previous value: CONFIG_NAME="CONFIG_OTHER" New value: CONFIG_OTHER="value" ... 3. merge_config.sh can incorrectly process commentary as a configuration value: $ echo "CONFIG_NAME=value" > 1.cfg $ echo "# CONFIG_NAME was defined in 1.cfg" > 2.cfg $ merge_config.sh -m 1.cfg 2.cfg ... Value of CONFIG_NAME is redefined by fragment 2.cfg: Previous value: CONFIG_NAME=value New value: # CONFIG_NAME was defined in 1.cfg ... $ cat .config # CONFIG_NAME was defined in 1.cfg 4. merge_config.py able to find re-definition inside of the same file: $ echo "CONFIG_NAME=1" > 1.cfg $ echo "CONFIG_NAME=2" >> 1.cfg $ merge_config.py -m 1.cfg ... Value of CONFIG_NAME is redefined by fragment 1.cfg: Previous value: CONFIG_NAME=1 New value: CONFIG_NAME=2 ... Signed-off-by: Nikolai Merinov <n.meri...@inango-systems.com> Signed-off-by: Oleksandr Hnatiuk <o.hnat...@inango-systems.com> --- Makefile | 2 +- tools/merge_config.py | 166 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100755 tools/merge_config.py diff --git a/Makefile b/Makefile index eb3daac..df2fca7 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ kern_tools_LIST = kgit kgit-meta \ kconf_check \ get_defconfig scc \ merge_config.sh spp kgit-s2q \ - symbol_why.py + symbol_why.py merge_config.py cmds := $(wildcard tools/scc-cmds/*) libs := Kconfiglib/kconfiglib.py diff --git a/tools/merge_config.py b/tools/merge_config.py new file mode 100755 index 0000000..50e32eb --- /dev/null +++ b/tools/merge_config.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only + +""" +merge_config.py - Takes a list of config fragment values, and merges +them one by one. Provides warnings on overridden values, and specified +values that did not make it to the resulting .config file (due to missed +dependencies or config symbol removal). +""" + +# Portions reused from merge_config.sh: +# http://git.yoctoproject.org/cgit/cgit.cgi/yocto-kernel-tools/tree/tools/merge_config.sh +# +# Copyright (c) 2009-2010 Wind River Systems, Inc. +# Copyright 2011 Linaro +# Copyright (c) 2020 Inango Systems Ltd. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. + +import sys +import re +import os +import subprocess +from collections import OrderedDict +import argparse + +CONFIG_REGEX = r'^((# (?=.* is not set))?(CONFIG_[a-zA-Z0-9_]*)(=.*| is not set))$' + +def parse_arguments(): + """Parse argv and return extracted options and list of the config files for merging""" + parser = argparse.ArgumentParser() + parser.add_argument('configs', metavar='CONFIG', type=str, nargs='+', + help='path to the config fragment') + parser.add_argument('-m', dest='run_make', action='store_false', + help='only merge the fragments, do not execute the make command') + parser.add_argument('-n', dest='alltarget', action='store_const', + default='alldefconfig', const='allnoconfig', + help='use allnoconfig instead of alldefconfig') + parser.add_argument('-r', dest='warn_redundant', action='store_true', + help='list redundant entries when merging fragments') + parser.add_argument('-O', dest='output_dir', metavar='DIR', type=str, + action='store', default='.', + help='dir to put generated output files. ' + 'Consider setting $KCONFIG_CONFIG instead.') + + args = parser.parse_args() + + if not os.path.isdir(args.output_dir): + print("output directory {} does not exist".format(args.output_dir), file=sys.stderr) + else: + args.output_dir = args.output_dir.rstrip('/') + + return args + +def merge_config(config_dict, file_name, warn_redundant=False): + """Add all configuration options from file_name to the config_dict dictionary. + This function warns about all configuration in config_dict that was redefined. + Optionally it warns about redifinition to the same value if warn_redundant is True""" + matches = re.findall(CONFIG_REGEX, open(file_name, "r").read(), re.MULTILINE) + for match in matches: + config_option = match[2] + new_value = match[0] + previous_value = config_dict.get(config_option) + if previous_value != None: + if previous_value != new_value: + print("Value of {} is redefined by fragment {}:".format(config_option, file_name)) + print("Previous value: {}".format(previous_value)) + print("New value: {}".format(new_value)) + print() + elif warn_redundant: + print("Value of {} is redundant by fragment {}:".format(config_option, file_name)) + del config_dict[config_option] + config_dict[config_option] = new_value + +def compare_configs(passed_config, resulting_config): + """Compare two dictionaries with config values and print warnings if + resulting_config changed any values presented in passed_config. + + This function assumes that resulting_config is a result of processing of the + passed_config by make defconfig command.""" + for config_option, requested_value in passed_config.items(): + actual_value = resulting_config.get(config_option) + if actual_value is None: + print("Value requested for {} not in final .config".format(config_option)) + print("Requested value: {}".format(requested_value)) + print("There is no value for this config in .config") + print("") + elif requested_value != actual_value: + print("Value requested for {} not in final .config".format(config_option)) + print("Requested value: {}".format(requested_value)) + print("Actual value: {}".format(actual_value)) + print("") + +def dump_config(dictionary, file_name): + """Write data stored in the dictionary to the file_name.""" + out_file = open(file_name, "w") + for val in dictionary.values(): + out_file.write("{}\n".format(val)) + out_file.close() + +def exit_if_not_exists(file_name): + """Exit if the file_name do not point to an existed file.""" + if not os.path.isfile(file_name): + print("The file '{}' does not exist. Exit.".format(file_name), file=sys.stderr) + sys.exit(1) + +def main(): + """Main function of this module.""" + args = parse_arguments() + + kconfig_config = os.getenv("KCONFIG_CONFIG") + if kconfig_config is None: + if args.output_dir != ".": + kconfig_config = os.path.realpath("{}/.config".format(args.output_dir)) + else: + kconfig_config = ".config" + os.putenv("KCONFIG_CONFIG", kconfig_config) + + final_config = OrderedDict() + + base_config = args.configs[0] + merge_list = args.configs[1:] + + exit_if_not_exists(base_config) + print("Using {} as base".format(base_config)) + merge_config(final_config, base_config, args.warn_redundant) + + # Merge files, printing warnings on overridden values + for merge_file in merge_list: + exit_if_not_exists(merge_file) + print("Merging {}".format(merge_file)) + merge_config(final_config, merge_file) + + dump_config(final_config, kconfig_config) + + if not args.run_make: + print("#\n# merged configuration written to {} (needs make)\n#".format(kconfig_config)) + sys.exit() + + # If we have an output dir, setup the O= argument, otherwise leave + # it blank, since O=. will create an unnecessary ./source softlink + output_arg = "" + if args.output_dir != ".": + output_arg = "O={}".format(args.output_dir) + + + # Use the merged file as the starting point for: + # alldefconfig: Fills in any missing symbols with Kconfig default + # allnoconfig: Fills in any missing symbols with # CONFIG_* is not set + subprocess.run("make {} {}".format(output_arg, args.alltarget), shell=True) + + + # Check all specified config values took (might have missed-dependency issues) + kconfig_values = OrderedDict() + merge_config(kconfig_values, kconfig_config) + + compare_configs(final_config, kconfig_values) + +main() -- 2.17.1
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#47988): https://lists.yoctoproject.org/g/yocto/message/47988 Mute This Topic: https://lists.yoctoproject.org/mt/69745945/21656 Group Owner: yocto+ow...@lists.yoctoproject.org Unsubscribe: https://lists.yoctoproject.org/g/yocto/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-