mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-10 08:11:56 +00:00
There are some issues with the behavior when rerunning CMake in an already initialized build directory: 1. The check for assignments to promptless symbols in configuration fragments isn't run when reconfiguring, because it only runs if zephyr/.config doesn't exist 2. As outlined in https://github.com/zephyrproject-rtos/zephyr/issues/9573, you can get into situations where zephyr/.config is invalid (e.g. due to being outdated), but menuconfig/guiconfig can't be run to fix it 3. If kconfig.py fails while merging fragments during reconfiguration, it will ignore the fragments during the next reconfiguration and use the existing zephyr/.config instead, because the fragment checksum is calculated and saved before running kconfig.py (Footnote: The input configuration file(s) to kconfig.py can be either a list of configuration fragments, when merging fragments, or just zephyr/.config, if the merged configuration is up-to-date. The output configuration file is always zephyr/.config.) To fix the first two issues, explicitly tell kconfig.py when it's dealing with handwritten configuration input (fragments), via a new --handwritten-input-configs flag. This is more robust than checking whether zephyr/.config exists, which was the old logic. When dealing with handwritten input, there should be no assignments to promptless symbols. Assignments to promptless symbols is expected in zephyr/.config however, because it doubles as configuration output. When running menuconfig/guiconfig, the input configuration is zephyr/.config rather than configuration fragments, so this change also makes sure that menuconfig can always be run as long as zephyr/.config exists and is up-to-date. To fix the last issue, only write the checksum for the configuration fragments if kconfig.py succeeds (which means it wrote a zephyr/.config). Also improve naming a bit, add help texts for the command-line parameters to kconfig.py, and simplify write_kconfig_filenames() by moving logic into it. Partial fix for https://github.com/zephyrproject-rtos/zephyr/issues/9573, without the part in #issuecomment-469701831. Can still run into issues when e.g. when CMake files can't make sense of settings. Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
230 lines
8.7 KiB
Python
Executable File
230 lines
8.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import textwrap
|
|
|
|
from kconfiglib import Kconfig, BOOL, TRISTATE, TRI_TO_STR
|
|
|
|
|
|
# Warnings that won't be turned into errors (but that will still be printed),
|
|
# identified by a substring of the warning. The warning texts from Kconfiglib
|
|
# are guaranteed to not change.
|
|
WARNING_WHITELIST = (
|
|
# Warning generated when a symbol with unsatisfied dependencies is being
|
|
# selected. These should be investigated, but whitelist them for now.
|
|
"y-selected",
|
|
)
|
|
|
|
|
|
def fatal(warning):
|
|
# Returns True if 'warning' is not whitelisted and should be turned into an
|
|
# error
|
|
|
|
return not any(wl_warning in warning for wl_warning in WARNING_WHITELIST)
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
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 whose actual value doesn't match the assigned
|
|
# value
|
|
for sym in kconf.unique_defined_syms:
|
|
# Was the symbol assigned to? Choice symbols are checked separately.
|
|
if sym.user_value is not None and not sym.choice:
|
|
check_assigned_sym_value(sym)
|
|
|
|
# Print warnings for choices whose actual selection doesn't match the user
|
|
# selection
|
|
for choice in kconf.unique_choices:
|
|
if choice.user_selection:
|
|
check_assigned_choice_value(choice)
|
|
|
|
# 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)
|
|
|
|
# Print warnings ourselves so that we can put a blank line between them for
|
|
# readability. We could roll this into the loop below, but it's nice to
|
|
# always print all warnings, even if one of them turns out to be fatal.
|
|
for warning in kconf.warnings:
|
|
print("\n" + warning, file=sys.stderr)
|
|
|
|
# Turn all warnings except for explicitly whitelisted ones into errors. In
|
|
# particular, this will turn assignments to undefined Kconfig variables
|
|
# into 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 as well.
|
|
for warning in kconf.warnings:
|
|
if fatal(warning):
|
|
err("""\
|
|
Aborting due to non-whitelisted Kconfig warning '{}'. If this warning doesn't
|
|
point to an actual problem, you can add it to the whitelist at the top of {}.\
|
|
""".format(warning, sys.argv[0]))
|
|
|
|
# Write the merged configuration and the C header
|
|
print(kconf.write_config(args.config_out))
|
|
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(("""\
|
|
{0.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_value(sym):
|
|
# Verifies that the value assigned to 'sym' "took" (matches the value the
|
|
# symbol actually got), printing a warning otherwise
|
|
|
|
# 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[sym.user_value]
|
|
else:
|
|
user_value = sym.user_value
|
|
|
|
if user_value != sym.str_value:
|
|
warn(("""\
|
|
{0.name_and_loc} was assigned the value '{1}' but got the value
|
|
'{0.str_value}'. Check its dependencies. \
|
|
""" + SYM_INFO_HINT).format(sym, user_value))
|
|
|
|
|
|
def check_assigned_choice_value(choice):
|
|
# Verifies that the choice symbol that was selected (by setting it to y)
|
|
# ended up as the selection, printing a warning 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.
|
|
|
|
if choice.user_selection is not choice.selection:
|
|
warn(("""\
|
|
the choice symbol {0.name_and_loc} was selected (set =y), but {1} ended up as
|
|
the choice selection. \
|
|
""" + SYM_INFO_HINT).format(
|
|
choice.user_selection,
|
|
choice.selection.name_and_loc if choice.selection else "no symbol"))
|
|
|
|
|
|
# Hint on where to find symbol information. Expects the first argument of
|
|
# format() to be the symbol.
|
|
SYM_INFO_HINT = """\
|
|
See http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_{0.name}.html
|
|
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 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()
|
|
|
|
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("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. Add some extra newlines to set the message off from
|
|
# surrounding text (this usually gets printed as part of spammy CMake
|
|
# output).
|
|
print("\nwarning: " + textwrap.fill(msg, 100) + "\n", file=sys.stderr)
|
|
|
|
|
|
def err(msg):
|
|
sys.exit("\nerror: " + textwrap.fill(msg, 100) + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|