kconfig/cmake: Improve reconfiguration behavior

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>
This commit is contained in:
Ulf Magnusson 2020-01-20 11:25:13 +01:00 committed by Carles Cufí
parent e1052a0f8d
commit 45050dda48
2 changed files with 63 additions and 48 deletions

View file

@ -189,25 +189,22 @@ if(EXISTS ${DOTCONFIG} AND EXISTS ${merge_config_files_checksum_file})
endif()
if(CREATE_NEW_DOTCONFIG)
file(WRITE
${merge_config_files_checksum_file}
${merge_config_files_checksum}
)
set(merge_fragments ${merge_config_files})
set(input_configs_are_handwritten --handwritten-input-configs)
set(input_configs ${merge_config_files})
else()
set(merge_fragments ${DOTCONFIG})
set(input_configs ${DOTCONFIG})
endif()
execute_process(
COMMAND
${PYTHON_EXECUTABLE}
${ZEPHYR_BASE}/scripts/kconfig/kconfig.py
${input_configs_are_handwritten}
${KCONFIG_ROOT}
${DOTCONFIG}
${AUTOCONF_H}
${PARSED_KCONFIG_SOURCES_TXT}
${merge_fragments}
${input_configs}
WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR}
# The working directory is set to the app dir such that the user
# can use relative paths in CONF_FILE, e.g. CONF_FILE=nrf5.conf
@ -217,6 +214,14 @@ if(NOT "${ret}" STREQUAL "0")
message(FATAL_ERROR "command failed with return code: ${ret}")
endif()
if(CREATE_NEW_DOTCONFIG)
# Write the new configuration fragment checksum. Only do this if kconfig.py
# succeeds, to avoid marking zephyr/.config as up-to-date when it hasn't been
# regenerated.
file(WRITE ${merge_config_files_checksum_file}
${merge_config_files_checksum})
endif()
# Read out the list of 'Kconfig' sources that were used by the engine.
file(STRINGS ${PARSED_KCONFIG_SOURCES_TXT} PARSED_KCONFIG_SOURCES_LIST)

View file

@ -28,31 +28,34 @@ def fatal(warning):
def main():
args = parse_args()
print("Parsing Kconfig tree in " + args.kconfig_root)
kconf = Kconfig(args.kconfig_root, warn_to_stderr=False,
print("Parsing " + args.kconfig_file)
kconf = Kconfig(args.kconfig_file, warn_to_stderr=False,
suppress_traceback=True)
# Warn for assignments to undefined symbols
kconf.warn_assign_undef = 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
# 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
print(kconf.load_config(args.conf_fragments[0]))
for config in args.conf_fragments[1:]:
# 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 not os.path.exists(args.autoconf):
# If zephyr/.config does not exist, it means we just merged
# configuration fragments. Check that there were no assignments to
# promptless symbols in them. Such assignments have no effect.
if args.handwritten_input_configs:
# Check that there are no assignments to promptless symbols, which
# have no effect.
#
# This won't work if zephyr/.config already exists (which means it's
# being loaded), because zephyr/.config is a full configuration file
# that includes values for promptless symbols.
# 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
@ -97,11 +100,11 @@ 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.dotconfig))
kconf.write_autoconf(args.autoconf)
print(kconf.write_config(args.config_out))
kconf.write_autoconf(args.header_out)
# Write the list of processed Kconfig sources to a file
write_kconfig_filenames(kconf.kconfig_filenames, kconf.srctree, args.sources)
# Write the list of parsed Kconfig files to a file
write_kconfig_filenames(kconf, args.kconfig_list_out)
def check_no_promptless_assign(kconf):
@ -173,32 +176,39 @@ def promptless(sym):
return not any(node.prompt for node in sym.nodes)
def write_kconfig_filenames(paths, root_path, output_file_path):
# 'paths' is a list of paths. The list has duplicates and the
# paths are either absolute or relative to 'root_path'.
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.
# We need to write this list, in a format that CMake can easily
# parse, to the output file at 'output_file_path'.
# The written list has sorted real (absolute) paths, and it does not have
# duplicates. The list is sorted to be deterministic. It is realpath()'d
# to ensure that different representations of the same path does not end
# up with two entries, as that could cause the build system to fail.
with open(output_file_path, 'w') as out:
for path in sorted({os.path.realpath(os.path.join(root_path, path))
for path in paths}):
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("kconfig_root")
parser.add_argument("dotconfig")
parser.add_argument("autoconf")
parser.add_argument("sources")
parser.add_argument("conf_fragments", nargs='+')
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()