From 050057f6ac45294e5a7c38d5d0539f5311b5f299 Mon Sep 17 00:00:00 2001 From: Richard Braun Date: Mon, 25 Sep 2017 22:58:35 +0200 Subject: tools/build_configs: new script This script is used to handle the generation and build test of a large number of configurations. --- tools/build_configs.py | 222 ++++++++++++++++++++++++++++++++++++++++++ tools/kconfig/merge_config.sh | 8 +- 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100755 tools/build_configs.py diff --git a/tools/build_configs.py b/tools/build_configs.py new file mode 100755 index 00000000..cbdbc1c0 --- /dev/null +++ b/tools/build_configs.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +# +# Generate a large number of valid configurations, build them concurrently, +# and report. + +import itertools +import multiprocessing +import os +import re +import shutil +import subprocess +import tempfile + +def quote_if_needed(value): + if not isinstance(value, int) and value != 'y' and value != 'n': + value = '"' + value + '"' + + return value + +def gen_config_line(name, value): + return name + '=' + quote_if_needed(value) + +def gen_config_content(config_dict): + lines = [] + + for name, value in config_dict.iteritems(): + lines.append(gen_config_line(name, value) + '\n') + + return lines + +def test_config_run(command, check, buildlog): + buildlog.writelines(['$ ' + command + '\n']) + buildlog.flush() + + if check: + return subprocess.check_call(command.split(), stdout = buildlog, stderr = subprocess.STDOUT) + else: + return subprocess.call(command.split(), stdout = buildlog, stderr = subprocess.STDOUT) + +def test_config(topbuilddir, config_dict): + try: + srctree = os.path.abspath(os.getcwd()) + buildtree = tempfile.mkdtemp(dir = topbuilddir) + os.chdir(buildtree) + buildlog = open('build.log', 'w') + f = open('.testconfig', 'w') + f.writelines(gen_config_content(config_dict)) + f.close() + test_config_run(srctree + '/tools/kconfig/merge_config.sh -f ' + srctree + '/Makefile .testconfig', True, buildlog) + test_config_run('make -f ' + srctree + '/Makefile olddefconfig', True, buildlog) + result = test_config_run('make -f ' + srctree + '/Makefile x15', False, buildlog) + except: + result = -1 + finally: + buildlog.close() + os.chdir(srctree) + + if result == 0: + shutil.rmtree(buildtree) + + return [result, buildtree] + +def test_config_star(args): + return test_config(*args) + +def gen_all_configs_recursive(options_dict, config_dict = {}): + configs_list = [] + + if (len(options_dict) == 0): + return [config_dict] + + options_dict_copy = options_dict.copy() + name, values = options_dict_copy.popitem() + + for v in values: + config_dict_copy = config_dict.copy() + config_dict_copy[name] = v + configs_list += gen_all_configs_recursive(options_dict_copy, config_dict_copy) + + return configs_list + +# TODO Better name. +def check_filter(config_dict, filter_dict): + for name, value in filter_dict.iteritems(): + if not name in config_dict: + return True + + if isinstance(value, str): + if config_dict[name] != value: + return True + else: + if value[0] != bool(value[1].match(config_dict[name])): + return True + + return False + +def check_filters(config_dict, filters_list): + for filter_dict in filters_list: + if (not check_filter(config_dict, filter_dict)): + return False + + return True + +def gen_all_configs(options_dict, filters_list): + configs_list = [] + + for config_dict in gen_all_configs_recursive(options_dict): + if (check_filters(config_dict, filters_list)): + configs_list.append(config_dict) + + return configs_list + +def gen_all_combinations_str(options_dict): + return [' '.join([value for value in config_dict.itervalues()]) + for config_dict in gen_all_configs_recursive(options_dict)] + +def gen_boolean_exclusive_filters_list(options_list): + filters_triplets = [] + + # Forge all (name, value) triplet combinations, filter those where a name + # appears more than once, and then those where there isn't exactly one + # value 'y'. + for x in itertools.combinations(itertools.product(options_list, ['y', 'n']), 3): + if (len(x) == len(set([y[0] for y in x])) + and len(filter(lambda x: x == 'y', [y[1] for y in x])) != 1): + filters_triplets.append(x) + + filters_list = [] + + for filters_triplet in filters_triplets: + filters_list.append(dict(filters_triplet)) + + return filters_list + +# Dictionary of compiler options. +# +# Each entry describes a single compiler option. The key is mostly ignored +# and serves as description, but must be present in order to reuse the +# gen_all_configs_recursive() function. The value is a list of all values +# that may be used for this compiler option when building a configuration. +all_cc_options_dict = { + 'O' : ['-O0', '-O2', '-Os'], + 'LTO' : ['-flto', '-fno-lto'], + 'SSP' : ['-fno-stack-protector', '-fstack-protector'], +} + +# Dictionary of options. +# +# Each entry describes a single option. The key matches an option name +# whereas the value is a list of all values that may be used for this +# option when building a configuration. +all_options_dict = { + 'CONFIG_CC_EXE' : ['gcc', 'clang'], + 'CONFIG_CC_OPTIONS' : gen_all_combinations_str(all_cc_options_dict), + 'CONFIG_ASSERT' : ['y', 'n'], + 'CONFIG_64BITS' : ['y', 'n'], + 'CONFIG_X86_PAE' : ['y', 'n'], + 'CONFIG_MULTIPROCESSOR' : ['y', 'n'], + 'CONFIG_MAX_CPUS' : ['1', '128'], + 'CONFIG_MUTEX_ADAPTIVE' : ['y', 'n'], + 'CONFIG_MUTEX_PI' : ['y', 'n'], + 'CONFIG_MUTEX_PLAIN' : ['y', 'n'], + 'CONFIG_SHELL' : ['y', 'n'], + 'CONFIG_THREAD_STACK_GUARD' : ['y', 'n'], + # TODO Test modules. +} + +# List of filters used to determine valid configurations. +# +# Each entry is a dictionary of options. The key matches an option name +# whereas the value may either be a string for exact matching, or a regular +# expression for more powerful matching. +all_filters_list = [ + # XXX Clang currently cannot build the kernel with LTO. + { + 'CONFIG_CC_EXE' : 'clang', + 'CONFIG_CC_OPTIONS' : [True, re.compile('-flto')], + }, + { + 'CONFIG_MULTIPROCESSOR' : 'n', + 'CONFIG_MAX_CPUS' : [False, re.compile('^0*1$')], + }, + { + 'CONFIG_64BITS' : 'y', + 'CONFIG_X86_PAE' : 'y', + }, +] +all_filters_list += gen_boolean_exclusive_filters_list([ + 'CONFIG_MUTEX_ADAPTIVE', + 'CONFIG_MUTEX_PI', + 'CONFIG_MUTEX_PLAIN' +]) + +if __name__ == '__main__': + # This tool performs out-of-tree builds and requires a clean source tree + print 'cleaning source tree...' + subprocess.check_call(['make', 'distclean']) + topbuilddir = os.path.abspath(tempfile.mkdtemp(prefix = 'build', dir = '.')) + print 'using ' + topbuilddir + + pool = multiprocessing.Pool() + configs_list = gen_all_configs(all_options_dict, all_filters_list) + nr_configs = len(configs_list) + print 'total: ' + str(nr_configs) + results = pool.map(test_config_star, [(topbuilddir, config_dict) + for config_dict in configs_list]) + pool.close() + pool.join() + + for result, buildtree in results: + if result != 0: + print 'failed: ' + buildtree + '/.config (' + buildtree + '/build.log)' + + nr_failures = len(filter(None, [result[0] for result in results])) + nr_successes = nr_configs - nr_failures + print 'passed: ' + str(nr_successes) + print 'failed: ' + str(nr_failures) + + try: + os.rmdir(topbuilddir) + except: + pass diff --git a/tools/kconfig/merge_config.sh b/tools/kconfig/merge_config.sh index 67d13144..c435c371 100755 --- a/tools/kconfig/merge_config.sh +++ b/tools/kconfig/merge_config.sh @@ -33,6 +33,7 @@ usage() { echo " -n use allnoconfig instead of alldefconfig" echo " -r list redundant entries when merging fragments" echo " -O dir to put generated output files. Consider setting \$KCONFIG_CONFIG instead." + echo " -f path to root Makefile" } RUNMAKE=true @@ -71,6 +72,11 @@ while true; do shift 2 continue ;; + "-f") + ROOT_MAKEFILE="-f $2" + shift 2 + continue + ;; *) break ;; @@ -151,7 +157,7 @@ fi # 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 -make KCONFIG_ALLCONFIG=$TMP_FILE $OUTPUT_ARG $ALLTARGET +make KCONFIG_ALLCONFIG=$TMP_FILE $OUTPUT_ARG $ROOT_MAKEFILE $ALLTARGET # Check all specified config values took (might have missed-dependency issues) -- cgit v1.2.3