| #!/usr/bin/env python3 |
| # Modified from: https://github.com/ulfalizer/Kconfiglib/blob/master/examples/merge_config.py |
| 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 Kconfig tree in " + args.kconfig_root) |
| kconf = Kconfig(args.kconfig_root, warn_to_stderr=False) |
| |
| # prj.conf may override settings from the board configuration, so disable |
| # warnings about symbols being assigned more than once |
| kconf.disable_override_warnings() |
| kconf.disable_redun_warnings() |
| # Warn for assignments to undefined symbols |
| kconf.enable_undef_warnings() |
| |
| for i, config in enumerate(args.conf_fragments): |
| print(("Loading {} as base" if i == 0 else "Merging {}") |
| .format(config)) |
| # replace=False creates a merged configuration |
| kconf.load_config(config, replace=False) |
| |
| # 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: |
| verify_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: |
| verify_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 explicity 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): |
| sys.exit("\n" + textwrap.fill( |
| "Error: Aborting due to non-whitelisted Kconfig " |
| "warning '{}'.\nNote: 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]), |
| 100) + "\n") |
| |
| # Write the merged configuration and the C header |
| kconf.write_config(args.dotconfig) |
| print("Configuration written to '{}'".format(args.dotconfig)) |
| kconf.write_autoconf(args.autoconf) |
| |
| # Write the list of processed Kconfig sources to a file |
| write_kconfig_filenames(kconf.kconfig_filenames, kconf.srctree, args.sources) |
| |
| |
| # Message printed when a promptless symbol is assigned (and doesn't get the |
| # assigned value) |
| PROMPTLESS_HINT = """ |
| This symbol has no prompt, meaning assignments in configuration files have no |
| effect on it. It can only be set indirectly, via Kconfig defaults (e.g. in a |
| Kconfig.defconfig file) or through being 'select'ed or 'imply'd (note: try to |
| avoid Kconfig 'select's except for trivial promptless "helper" symbols without |
| dependencies, as it ignores dependencies and forces symbols on).""" |
| |
| # Message about where to look up symbol information |
| SYM_INFO_HINT = """ |
| You can check symbol information (including dependencies) in the 'menuconfig' |
| interface (see the Application Development Primer section of the manual), or in |
| the Kconfig reference at |
| http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_{}.html (which is |
| updated regularly from the master branch). See the 'Setting configuration |
| values' section of the Board Porting Guide as well.""" |
| |
| PROMPTLESS_HINT_EXTRA = """ |
| It covers Kconfig.defconfig files.""" |
| |
| def verify_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: |
| msg = "warning: {} was assigned the value '{}' but got the " \ |
| "value '{}'." \ |
| .format(name_and_loc(sym), user_value, sym.str_value) |
| |
| if promptless(sym): msg += PROMPTLESS_HINT |
| msg += SYM_INFO_HINT.format(sym.name) |
| if promptless(sym): msg += PROMPTLESS_HINT_EXTRA |
| |
| # Use a large fill() width to try to avoid linebreaks in the symbol |
| # reference link |
| print("\n" + textwrap.fill(msg, 100), file=sys.stderr) |
| |
| |
| def verify_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 overriden 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: |
| msg = "warning: the choice symbol {} was selected (set =y), but {} " \ |
| "ended up as the choice selection. {}" \ |
| .format(name_and_loc(choice.user_selection), |
| name_and_loc(choice.selection) if choice.selection |
| else "no symbol", |
| SYM_INFO_HINT.format(choice.user_selection.name)) |
| |
| print("\n" + textwrap.fill(msg, 100), file=sys.stderr) |
| |
| |
| def name_and_loc(sym): |
| # Helper for printing the name and Kconfig file location(s) for a symbol |
| |
| if not sym.nodes: |
| return sym.name + " (undefined)" |
| |
| return "{} (defined at {})".format( |
| sym.name, |
| ", ".join("{}:{}".format(node.filename, node.linenr) |
| for node in sym.nodes)) |
| |
| |
| 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(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'. |
| |
| # 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 should also have absolute paths instead of |
| # relative paths, and it should not have duplicates. |
| |
| # Remove duplicates |
| paths_uniq = set(paths) |
| |
| with open(output_file_path, 'w') as out: |
| # sort to be deterministic |
| for path in sorted(paths_uniq): |
| # Change from relative to absolute path (do nothing for |
| # absolute paths) |
| abs_path = os.path.join(root_path, path) |
| |
| # Assert that the file exists, since it was sourced, it |
| # must surely also exist. |
| assert os.path.isfile(abs_path), "Internal error" |
| |
| out.write("{}\n".format(abs_path)) |
| |
| |
| def parse_args(): |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter |
| ) |
| |
| parser.add_argument("kconfig_root") |
| parser.add_argument("dotconfig") |
| parser.add_argument("autoconf") |
| parser.add_argument("sources") |
| parser.add_argument("conf_fragments", metavar='conf', type=str, nargs='+') |
| |
| return parser.parse_args() |
| |
| |
| if __name__ == "__main__": |
| main() |