diff options
Diffstat (limited to 'scripts/multiconfig.py')
-rwxr-xr-x | scripts/multiconfig.py | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/scripts/multiconfig.py b/scripts/multiconfig.py new file mode 100755 index 0000000..749abcb --- /dev/null +++ b/scripts/multiconfig.py @@ -0,0 +1,410 @@ +#!/usr/bin/env python +# +# Copyright (C) 2014, Masahiro Yamada <yamada.m@jp.panasonic.com> +# +# SPDX-License-Identifier: GPL-2.0+ +# + +""" +A wrapper script to adjust Kconfig for U-Boot + +The biggest difference between Linux Kernel and U-Boot in terms of the +board configuration is that U-Boot has to configure multiple boot images +per board: Normal, SPL, TPL. +We need to expand the functions of Kconfig to handle multiple boot +images. + +Instead of touching various parts under the scripts/kconfig/ directory, +pushing necessary adjustments into this single script would be better +for code maintainance. All the make targets related to the configuration +(make %config) should be invoked via this script. + +Let's see what is different from the original Kconfig. + +- config, menuconfig, etc. + +The commands 'make config', 'make menuconfig', etc. are used to create +or modify the .config file, which stores configs for Normal boot image. + +The location of the one for SPL, TPL image is spl/.config, tpl/.config, +respectively. Use 'make spl/config', 'make spl/menuconfig', etc. +to create or modify the spl/.config file, which contains configs +for SPL image. +Do likewise for the tpl/.config file. +The generic syntax for SPL, TPL configuration is +'make <target_image>/<config_command>'. + +- silentoldconfig + +The command 'make silentoldconfig' updates .config, if necessary, and +additionally updates include/generated/autoconf.h and files under +include/configs/ directory. In U-Boot, it should do the same things for +SPL, TPL images for boards supporting them. +Depending on whether CONFIG_SPL, CONFIG_TPL is defined or not, +'make silentoldconfig' iterates three times at most changing the target +directory. + +To sum up, 'make silentoldconfig' possibly updates + - .config, include/generated/autoconf.h, include/config/* + - spl/.config, spl/include/generated/autoconf.h, spl/include/config/* + (in case CONFIG_SPL=y) + - tpl/.config, tpl/include/generated/autoconf.h, tpl/include/config/* + (in case CONFIG_TPL=y) + +- defconfig, <board>_defconfig + +The command 'make <board>_defconfig' creates a new .config based on the +file configs/<board>_defconfig. The command 'make defconfig' is the same +but the difference is it uses the file specified with KBUILD_DEFCONFIG +environment. + +We need to create .config, spl/.config, tpl/.config for boards where SPL +and TPL images are supported. One possible solution for that is to have +multiple defconfig files per board, but it would produce duplication +among the defconfigs. +The approach chosen here is to expand the feature and support +conditional definition in defconfig, that is, each line in defconfig +files has the form of: +<condition>:<macro definition> + +The '<condition>:' prefix specifies which image the line is valid for. +The '<condition>:' is one of: + None - the line is valid only for Normal image + S: - the line is valid only for SPL image + T: - the line is valid only for TPL image + ST: - the line is valid for SPL and TPL images + +S: - the line is valid for Normal and SPL images + +T: - the line is valid for Normal and TPL images + +ST: - the line is valid for Normal, SPL and SPL images + +So, if neither CONFIG_SPL nor CONFIG_TPL is defined, the defconfig file +has no '<condition>:' part and therefore has the same form of that of +Linux Kernel. + +In U-Boot, for example, a defconfig file can be written like this: + + CONFIG_FOO=100 + S:CONFIG_FOO=200 + T:CONFIG_FOO=300 + ST:CONFIG_BAR=y + +S:CONFIG_BAZ=y + +T:CONFIG_QUX=y + +ST:CONFIG_QUUX=y + +The defconfig above is parsed by this script and internally divided into +three temporary defconfig files. + + - Temporary defconfig for Normal image + CONFIG_FOO=100 + CONFIG_BAZ=y + CONFIG_QUX=y + CONFIG_QUUX=y + + - Temporary defconfig for SPL image + CONFIG_FOO=200 + CONFIG_BAR=y + CONFIG_BAZ=y + CONFIG_QUUX=y + + - Temporary defconfig for TPL image + CONFIG_FOO=300 + CONFIG_BAR=y + CONFIG_QUX=y + CONFIG_QUUX=y + +They are passed to scripts/kconfig/conf, each is used for generating +.config, spl/.config, tpl/.config, respectively. + +- savedefconfig + +This is the reverse operation of 'make defconfig'. +If neither CONFIG_SPL nor CONFIG_TPL is defined in the .config file, +it works as 'make savedefconfig' in Linux Kernel: create the minimal set +of config based on the .config and save it into 'defconfig' file. + +If CONFIG_SPL or CONFIG_TPL is defined, the common lines among .config, +spl/.config, tpl/.config are coalesced together and output to the file +'defconfig' in the form like: + + CONFIG_FOO=100 + S:CONFIG_FOO=200 + T:CONFIG_FOO=300 + ST:CONFIG_BAR=y + +S:CONFIG_BAZ=y + +T:CONFIG_QUX=y + +ST:CONFIG_QUUX=y + +This can be used as an input of 'make <board>_defconfig' command. +""" + +import errno +import os +import re +import subprocess +import sys + +# Constant variables +SUB_IMAGES = ('spl', 'tpl') +IMAGES = ('',) + SUB_IMAGES +SYMBOL_MAP = {'': '+', 'spl': 'S', 'tpl': 'T'} +PATTERN_SYMBOL = re.compile(r'(\+?)(S?)(T?):(.*)') + +# Environment variables (should be defined in the top Makefile) +# .get('key', 'default_value') method is useful for standalone testing. +MAKE = os.environ.get('MAKE', 'make') +srctree = os.environ.get('srctree', '.') +KCONFIG_CONFIG = os.environ.get('KCONFIG_CONFIG', '.config') + +# Useful shorthand +build = '%s -f %s/scripts/Makefile.build obj=scripts/kconfig %%s' % (MAKE, srctree) +autoconf = '%s -f %s/scripts/Makefile.autoconf obj=%%s %%s' % (MAKE, srctree) + +### helper functions ### +def mkdirs(*dirs): + """Make directories ignoring 'File exists' error.""" + for d in dirs: + try: + os.makedirs(d) + except OSError as exception: + # Ignore 'File exists' error + if exception.errno != errno.EEXIST: + raise + +def rmfiles(*files): + """Remove files ignoring 'No such file or directory' error.""" + for f in files: + try: + os.remove(f) + except OSError as exception: + # Ignore 'No such file or directory' error + if exception.errno != errno.ENOENT: + raise + +def rmdirs(*dirs): + """Remove directories ignoring 'No such file or directory' + and 'Directory not empty' error. + """ + for d in dirs: + try: + os.rmdir(d) + except OSError as exception: + # Ignore 'No such file or directory' + # and 'Directory not empty' error + if exception.errno != errno.ENOENT and \ + exception.errno != errno.ENOTEMPTY: + raise + +def error(msg): + """Output the given argument to stderr and exit with return code 1.""" + print >> sys.stderr, msg + sys.exit(1) + +def run_command(command, callback_on_error=None): + """Run the given command in a sub-shell (and exit if it fails). + + Arguments: + command: A string of the command + callback_on_error: Callback handler invoked just before exit + when the command fails (Default=None) + """ + retcode = subprocess.call(command, shell=True) + if retcode: + if callback_on_error: + callback_on_error() + error("'%s' Failed" % command) + +def run_make_config(cmd, objdir, callback_on_error=None): + """Run the make command in a sub-shell (and exit if it fails). + + Arguments: + cmd: Make target such as 'config', 'menuconfig', 'defconfig', etc. + objdir: Target directory where the make command is run. + Typically '', 'spl', 'tpl' for Normal, SPL, TPL image, + respectively. + callback_on_error: Callback handler invoked just before exit + when the command fails (Default=None) + """ + # Linux expects defconfig files in arch/$(SRCARCH)/configs/ directory, + # but U-Boot puts them in configs/ directory. + # Give SRCARCH=.. to fake scripts/kconfig/Makefile. + options = 'SRCARCH=.. KCONFIG_OBJDIR=%s' % objdir + if objdir: + options += ' KCONFIG_CONFIG=%s/%s' % (objdir, KCONFIG_CONFIG) + mkdirs(objdir) + run_command(build % cmd + ' ' + options, callback_on_error) + +def get_enabled_subimages(ignore_error=False): + """Parse .config file to detect if CONFIG_SPL, CONFIG_TPL is enabled + and return a tuple of enabled subimages. + + Arguments: + ignore_error: Specify the behavior when '.config' is not found; + Raise an exception if this flag is False. + Return a null tuple if this flag is True. + + Returns: + A tuple of enabled subimages as follows: + () if neither CONFIG_SPL nor CONFIG_TPL is defined + ('spl',) if CONFIG_SPL is defined but CONFIG_TPL is not + ('spl', 'tpl') if both CONFIG_SPL and CONFIG_TPL are defined + """ + enabled = () + match_patterns = [ (img, 'CONFIG_' + img.upper() + '=y\n') + for img in SUB_IMAGES ] + try: + f = open(KCONFIG_CONFIG) + except IOError as exception: + if not ignore_error or exception.errno != errno.ENOENT: + raise + return enabled + with f: + for line in f: + for img, pattern in match_patterns: + if line == pattern: + enabled += (img,) + return enabled + +def do_silentoldconfig(cmd): + """Run 'make silentoldconfig' for all the enabled images. + + Arguments: + cmd: should always be a string 'silentoldconfig' + """ + run_make_config(cmd, '') + subimages = get_enabled_subimages() + for obj in subimages: + mkdirs(os.path.join(obj, 'include', 'config'), + os.path.join(obj, 'include', 'generated')) + run_make_config(cmd, obj) + remove_auto_conf = lambda : rmfiles('include/config/auto.conf') + # If the following part failed, include/config/auto.conf should be deleted + # so 'make silentoldconfig' will be re-run on the next build. + run_command(autoconf % + ('include', 'include/autoconf.mk include/autoconf.mk.dep'), + remove_auto_conf) + # include/config.h has been updated after 'make silentoldconfig'. + # We need to touch include/config/auto.conf so it gets newer + # than include/config.h. + # Otherwise, 'make silentoldconfig' would be invoked twice. + os.utime('include/config/auto.conf', None) + for obj in subimages: + run_command(autoconf % (obj + '/include', + obj + '/include/autoconf.mk'), + remove_auto_conf) + +def do_tmp_defconfig(output_lines, img): + """Helper function for do_board_defconfig(). + + Write the defconfig contents into a file '.tmp_defconfig' and + invoke 'make .tmp_defconfig'. + + Arguments: + output_lines: A sequence of defconfig lines of each image + img: Target image. Typically '', 'spl', 'tpl' for + Normal, SPL, TPL images, respectively. + """ + TMP_DEFCONFIG = '.tmp_defconfig' + TMP_DIRS = ('arch', 'configs') + defconfig_path = os.path.join('configs', TMP_DEFCONFIG) + mkdirs(*TMP_DIRS) + with open(defconfig_path, 'w') as f: + f.write(''.join(output_lines[img])) + cleanup = lambda: (rmfiles(defconfig_path), rmdirs(*TMP_DIRS)) + run_make_config(TMP_DEFCONFIG, img, cleanup) + cleanup() + +def do_board_defconfig(cmd): + """Run 'make <board>_defconfig'. + + Arguments: + cmd: should be a string '<board>_defconfig' + """ + defconfig_path = os.path.join(srctree, 'configs', cmd) + output_lines = dict([ (img, []) for img in IMAGES ]) + with open(defconfig_path) as f: + for line in f: + m = PATTERN_SYMBOL.match(line) + if m: + for idx, img in enumerate(IMAGES): + if m.group(idx + 1): + output_lines[img].append(m.group(4) + '\n') + continue + output_lines[''].append(line) + do_tmp_defconfig(output_lines, '') + for img in get_enabled_subimages(): + do_tmp_defconfig(output_lines, img) + +def do_defconfig(cmd): + """Run 'make defconfig'. + + Arguments: + cmd: should always be a string 'defconfig' + """ + KBUILD_DEFCONFIG = os.environ['KBUILD_DEFCONFIG'] + print "*** Default configuration is based on '%s'" % KBUILD_DEFCONFIG + do_board_defconfig(KBUILD_DEFCONFIG) + +def do_savedefconfig(cmd): + """Run 'make savedefconfig'. + + Arguments: + cmd: should always be a string 'savedefconfig' + """ + DEFCONFIG = 'defconfig' + # Continue even if '.config' does not exist + subimages = get_enabled_subimages(True) + run_make_config(cmd, '') + output_lines = [] + prefix = {} + with open(DEFCONFIG) as f: + for line in f: + output_lines.append(line) + prefix[line] = '+' + for img in subimages: + run_make_config(cmd, img) + unmatched_lines = [] + with open(DEFCONFIG) as f: + for line in f: + if line in output_lines: + index = output_lines.index(line) + output_lines[index:index] = unmatched_lines + unmatched_lines = [] + prefix[line] += SYMBOL_MAP[img] + else: + ummatched_lines.append(line) + prefix[line] = SYMBOL_MAP[img] + with open(DEFCONFIG, 'w') as f: + for line in output_lines: + if prefix[line] == '+': + f.write(line) + else: + f.write(prefix[line] + ':' + line) + +def do_others(cmd): + """Run the make command other than 'silentoldconfig', 'defconfig', + '<board>_defconfig' and 'savedefconfig'. + + Arguments: + cmd: Make target in the form of '<target_image>/<config_command>' + The field '<target_image>/' is typically empty, 'spl/', 'tpl/' + for Normal, SPL, TPL images, respectively. + The field '<config_command>' is make target such as 'config', + 'menuconfig', etc. + """ + objdir, _, cmd = cmd.rpartition('/') + run_make_config(cmd, objdir) + +cmd_list = {'silentoldconfig': do_silentoldconfig, + 'defconfig': do_defconfig, + 'savedefconfig': do_savedefconfig} + +def main(): + cmd = sys.argv[1] + if cmd.endswith('_defconfig'): + do_board_defconfig(cmd) + else: + func = cmd_list.get(cmd, do_others) + func(cmd) + +if __name__ == '__main__': + main() |