ec7044437e
Disables allowing the python argparse library from automatically shortening command line arguments, this prevents issues whereby a new command is added and code that wrongly uses the shortened command of an existing argument which is the same as the new command being added will silently change script behaviour. Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
293 lines
11 KiB
Python
Executable file
293 lines
11 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
# Writes/updates the zephyr/.config configuration file by merging configuration
|
|
# files passed as arguments, e.g. board *_defconfig and application prj.conf
|
|
# files.
|
|
#
|
|
# When fragments haven't changed, zephyr/.config is both the input and the
|
|
# output, which just updates it. This is handled in the CMake files.
|
|
#
|
|
# Also does various checks (most via Kconfiglib warnings).
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import textwrap
|
|
|
|
# Zephyr doesn't use tristate symbols. They're supported here just to make the
|
|
# script a bit more generic.
|
|
from kconfiglib import Kconfig, split_expr, expr_value, expr_str, BOOL, \
|
|
TRISTATE, TRI_TO_STR, AND, OR
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
if args.zephyr_base:
|
|
os.environ['ZEPHYR_BASE'] = args.zephyr_base
|
|
|
|
print("Parsing " + args.kconfig_file)
|
|
kconf = Kconfig(args.kconfig_file, warn_to_stderr=False,
|
|
suppress_traceback=True)
|
|
|
|
if args.handwritten_input_configs:
|
|
# Warn for assignments to undefined symbols, but only for handwritten
|
|
# fragments, to avoid warnings-turned-errors when using an old
|
|
# configuration file together with updated Kconfig files
|
|
kconf.warn_assign_undef = True
|
|
|
|
# prj.conf may override settings from the board configuration, so
|
|
# disable warnings about symbols being assigned more than once
|
|
kconf.warn_assign_override = False
|
|
kconf.warn_assign_redun = False
|
|
|
|
# Load configuration files
|
|
print(kconf.load_config(args.configs_in[0]))
|
|
for config in args.configs_in[1:]:
|
|
# replace=False creates a merged configuration
|
|
print(kconf.load_config(config, replace=False))
|
|
|
|
if args.handwritten_input_configs:
|
|
# Check that there are no assignments to promptless symbols, which
|
|
# have no effect.
|
|
#
|
|
# This only makes sense when loading handwritten fragments and not when
|
|
# loading zephyr/.config, because zephyr/.config is configuration
|
|
# output and also assigns promptless symbols.
|
|
check_no_promptless_assign(kconf)
|
|
|
|
# Print warnings for symbols that didn't get the assigned value. Only
|
|
# do this for handwritten input too, to avoid likely unhelpful warnings
|
|
# when using an old configuration and updating Kconfig files.
|
|
check_assigned_sym_values(kconf)
|
|
check_assigned_choice_values(kconf)
|
|
|
|
if kconf.syms['WARN_DEPRECATED'].tri_value == 2:
|
|
check_deprecated(kconf)
|
|
|
|
if kconf.syms['WARN_EXPERIMENTAL'].tri_value == 2:
|
|
check_experimental(kconf)
|
|
|
|
# Hack: Force all symbols to be evaluated, to catch warnings generated
|
|
# during evaluation. Wait till the end to write the actual output files, so
|
|
# that we don't generate any output if there are warnings-turned-errors.
|
|
#
|
|
# Kconfiglib caches calculated symbol values internally, so this is still
|
|
# fast.
|
|
kconf.write_config(os.devnull)
|
|
|
|
if kconf.warnings:
|
|
# Put a blank line between warnings to make them easier to read
|
|
for warning in kconf.warnings:
|
|
print("\n" + warning, file=sys.stderr)
|
|
|
|
# Turn all warnings into errors, so that e.g. assignments to undefined
|
|
# Kconfig symbols become errors.
|
|
#
|
|
# A warning is generated by this script whenever a symbol gets a
|
|
# different value than the one it was assigned. Keep that one as just a
|
|
# warning for now.
|
|
err("Aborting due to Kconfig warnings")
|
|
|
|
# Write the merged configuration and the C header
|
|
print(kconf.write_config(args.config_out))
|
|
print(kconf.write_autoconf(args.header_out))
|
|
|
|
# Write the list of parsed Kconfig files to a file
|
|
write_kconfig_filenames(kconf, args.kconfig_list_out)
|
|
|
|
|
|
def check_no_promptless_assign(kconf):
|
|
# Checks that no promptless symbols are assigned
|
|
|
|
for sym in kconf.unique_defined_syms:
|
|
if sym.user_value is not None and promptless(sym):
|
|
err(f"""\
|
|
{sym.name_and_loc} is assigned in a configuration file, but is not directly
|
|
user-configurable (has no prompt). It gets its value indirectly from other
|
|
symbols. """ + SYM_INFO_HINT.format(sym))
|
|
|
|
|
|
def check_assigned_sym_values(kconf):
|
|
# Verifies that the values assigned to symbols "took" (matches the value
|
|
# the symbols actually got), printing warnings otherwise. Choice symbols
|
|
# are checked separately, in check_assigned_choice_values().
|
|
|
|
for sym in kconf.unique_defined_syms:
|
|
if sym.choice:
|
|
continue
|
|
|
|
user_value = sym.user_value
|
|
if user_value is None:
|
|
continue
|
|
|
|
# Tristate values are represented as 0, 1, 2. Having them as "n", "m",
|
|
# "y" is more convenient here, so convert.
|
|
if sym.type in (BOOL, TRISTATE):
|
|
user_value = TRI_TO_STR[user_value]
|
|
|
|
if user_value != sym.str_value:
|
|
msg = f"{sym.name_and_loc} was assigned the value '{user_value}'" \
|
|
f" but got the value '{sym.str_value}'. "
|
|
|
|
# List any unsatisfied 'depends on' dependencies in the warning
|
|
mdeps = missing_deps(sym)
|
|
if mdeps:
|
|
expr_strs = []
|
|
for expr in mdeps:
|
|
estr = expr_str(expr)
|
|
if isinstance(expr, tuple):
|
|
# Add () around dependencies that aren't plain symbols.
|
|
# Gives '(FOO || BAR) (=n)' instead of
|
|
# 'FOO || BAR (=n)', which might be clearer.
|
|
estr = f"({estr})"
|
|
expr_strs.append(f"{estr} "
|
|
f"(={TRI_TO_STR[expr_value(expr)]})")
|
|
|
|
msg += "Check these unsatisfied dependencies: " + \
|
|
", ".join(expr_strs) + ". "
|
|
|
|
warn(msg + SYM_INFO_HINT.format(sym))
|
|
|
|
|
|
def missing_deps(sym):
|
|
# check_assigned_sym_values() helper for finding unsatisfied dependencies.
|
|
#
|
|
# Given direct dependencies
|
|
#
|
|
# depends on <expr> && <expr> && ... && <expr>
|
|
#
|
|
# on 'sym' (which can also come from e.g. a surrounding 'if'), returns a
|
|
# list of all <expr>s with a value less than the value 'sym' was assigned
|
|
# ("less" instead of "not equal" just to be general and handle tristates,
|
|
# even though Zephyr doesn't use them).
|
|
#
|
|
# For string/int/hex symbols, just looks for <expr> = n.
|
|
#
|
|
# Note that <expr>s can be something more complicated than just a symbol,
|
|
# like 'FOO || BAR' or 'FOO = "string"'.
|
|
|
|
deps = split_expr(sym.direct_dep, AND)
|
|
|
|
if sym.type in (BOOL, TRISTATE):
|
|
return [dep for dep in deps if expr_value(dep) < sym.user_value]
|
|
# string/int/hex
|
|
return [dep for dep in deps if expr_value(dep) == 0]
|
|
|
|
|
|
def check_assigned_choice_values(kconf):
|
|
# Verifies that any choice symbols that were selected (by setting them to
|
|
# y) ended up as the selection, printing warnings otherwise.
|
|
#
|
|
# We check choice symbols separately to avoid warnings when two different
|
|
# choice symbols within the same choice are set to y. This might happen if
|
|
# a choice selection from a board defconfig is overridden in a prj.conf,
|
|
# for example. The last choice symbol set to y becomes the selection (and
|
|
# all other choice symbols get the value n).
|
|
#
|
|
# Without special-casing choices, we'd detect that the first symbol set to
|
|
# y ended up as n, and print a spurious warning.
|
|
|
|
for choice in kconf.unique_choices:
|
|
if choice.user_selection and \
|
|
choice.user_selection is not choice.selection:
|
|
|
|
warn(f"""\
|
|
The choice symbol {choice.user_selection.name_and_loc} was selected (set =y),
|
|
but {choice.selection.name_and_loc if choice.selection else "no symbol"} ended
|
|
up as the choice selection. """ + SYM_INFO_HINT.format(choice.user_selection))
|
|
|
|
|
|
# Hint on where to find symbol information. Used like
|
|
# SYM_INFO_HINT.format(sym).
|
|
SYM_INFO_HINT = """\
|
|
See http://docs.zephyrproject.org/latest/kconfig.html#CONFIG_{0.name} and/or
|
|
look up {0.name} in the menuconfig/guiconfig interface. The Application
|
|
Development Primer, Setting Configuration Values, and Kconfig - Tips and Best
|
|
Practices sections of the manual might be helpful too.\
|
|
"""
|
|
|
|
|
|
def check_deprecated(kconf):
|
|
deprecated = kconf.syms['DEPRECATED']
|
|
dep_expr = deprecated.rev_dep
|
|
|
|
if dep_expr is not kconf.n:
|
|
selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2]
|
|
for selector in selectors:
|
|
selector_name = split_expr(selector, AND)[0].name
|
|
warn(f'Deprecated symbol {selector_name} is enabled.')
|
|
|
|
|
|
def check_experimental(kconf):
|
|
experimental = kconf.syms['EXPERIMENTAL']
|
|
dep_expr = experimental.rev_dep
|
|
|
|
if dep_expr is not kconf.n:
|
|
selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2]
|
|
for selector in selectors:
|
|
selector_name = split_expr(selector, AND)[0].name
|
|
warn(f'Experimental symbol {selector_name} is enabled.')
|
|
|
|
|
|
def promptless(sym):
|
|
# Returns True if 'sym' has no prompt. Since the symbol might be defined in
|
|
# multiple locations, we need to check all locations.
|
|
|
|
return not any(node.prompt for node in sym.nodes)
|
|
|
|
|
|
def write_kconfig_filenames(kconf, kconfig_list_path):
|
|
# Writes a sorted list with the absolute paths of all parsed Kconfig files
|
|
# to 'kconfig_list_path'. The paths are realpath()'d, and duplicates are
|
|
# removed. This file is used by CMake to look for changed Kconfig files. It
|
|
# needs to be deterministic.
|
|
|
|
with open(kconfig_list_path, 'w') as out:
|
|
for path in sorted({os.path.realpath(os.path.join(kconf.srctree, path))
|
|
for path in kconf.kconfig_filenames}):
|
|
print(path, file=out)
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
|
|
|
parser.add_argument("--handwritten-input-configs",
|
|
action="store_true",
|
|
help="Assume the input configuration fragments are "
|
|
"handwritten fragments and do additional checks "
|
|
"on them, like no promptless symbols being "
|
|
"assigned")
|
|
parser.add_argument("--zephyr-base",
|
|
help="Path to current Zephyr installation")
|
|
parser.add_argument("kconfig_file",
|
|
help="Top-level Kconfig file")
|
|
parser.add_argument("config_out",
|
|
help="Output configuration file")
|
|
parser.add_argument("header_out",
|
|
help="Output header file")
|
|
parser.add_argument("kconfig_list_out",
|
|
help="Output file for list of parsed Kconfig files")
|
|
parser.add_argument("configs_in",
|
|
nargs="+",
|
|
help="Input configuration fragments. Will be merged "
|
|
"together.")
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def warn(msg):
|
|
# Use a large fill() width to try to avoid linebreaks in the symbol
|
|
# reference link, and add some extra newlines to set the message off from
|
|
# surrounding text (this usually gets printed as part of spammy CMake
|
|
# output)
|
|
print("\n" + textwrap.fill("warning: " + msg, 100) + "\n", file=sys.stderr)
|
|
|
|
|
|
def err(msg):
|
|
sys.exit("\n" + textwrap.fill("error: " + msg, 100) + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|