342 lines
12 KiB
Python
Executable File
342 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Implements a simple configuration interface on top of Kconfiglib to
|
|
# demonstrate concepts for building a menuconfig-like. Emulates how the
|
|
# standard menuconfig prints menu entries.
|
|
#
|
|
# Always displays the entire Kconfig tree to keep things as simple as possible
|
|
# (all symbols, choices, menus, and comments).
|
|
#
|
|
# Usage:
|
|
#
|
|
# $ python(3) Kconfiglib/examples/menuconfig.py <Kconfig file>
|
|
#
|
|
# A sample Kconfig is available in Kconfiglib/examples/Kmenuconfig.
|
|
#
|
|
# Here's a notation guide. The notation matches the one used by menuconfig
|
|
# (scripts/kconfig/mconf):
|
|
#
|
|
# [ ] prompt - Bool
|
|
# < > prompt - Tristate
|
|
# {M} prompt - Tristate selected to m. Can only be set to m or y.
|
|
# -*- prompt - Bool/tristate selected to y, pinning it
|
|
# -M- prompt - Tristate selected to m that also has m visibility,
|
|
# pinning it to m
|
|
# (foo) prompt - String/int/hex symbol with value "foo"
|
|
# --> prompt - The selected symbol in a choice in y mode. This
|
|
# syntax is unique to this example.
|
|
#
|
|
# When modules are disabled, the .type attribute of TRISTATE symbols and
|
|
# choices automatically changes to BOOL. This trick is used by the C
|
|
# implementation as well, and gives the expected behavior without having to do
|
|
# anything extra here. The original type is available in .orig_type if needed.
|
|
#
|
|
# The Kconfiglib/examples/Kmenuconfig example uses named choices to be able to
|
|
# refer to choices by name. Named choices are supported in the C tools too, but
|
|
# I don't think I've ever seen them used in the wild.
|
|
#
|
|
# Sample session:
|
|
#
|
|
# $ python Kconfiglib/examples/menuconfig.py Kconfiglib/examples/Kmenuconfig
|
|
#
|
|
# ======== Example Kconfig configuration ========
|
|
#
|
|
# [*] Enable loadable module support (MODULES)
|
|
# Bool and tristate symbols
|
|
# [*] Bool symbol (BOOL)
|
|
# [ ] Dependent bool symbol (BOOL_DEP)
|
|
# < > Dependent tristate symbol (TRI_DEP)
|
|
# [ ] First prompt (TWO_MENU_NODES)
|
|
# < > Tristate symbol (TRI)
|
|
# [ ] Second prompt (TWO_MENU_NODES)
|
|
# *** These are selected by TRI_DEP ***
|
|
# < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP)
|
|
# < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP)
|
|
# String, int, and hex symbols
|
|
# (foo) String symbol (STRING)
|
|
# (747) Int symbol (INT)
|
|
# (0xABC) Hex symbol (HEX)
|
|
# Various choices
|
|
# -*- Bool choice (BOOL_CHOICE)
|
|
# --> Bool choice sym 1 (BOOL_CHOICE_SYM_1)
|
|
# Bool choice sym 2 (BOOL_CHOICE_SYM_2)
|
|
# {M} Tristate choice (TRI_CHOICE)
|
|
# < > Tristate choice sym 1 (TRI_CHOICE_SYM_1)
|
|
# < > Tristate choice sym 2 (TRI_CHOICE_SYM_2)
|
|
# [ ] Optional bool choice (OPT_BOOL_CHOICE)
|
|
#
|
|
# Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): BOOL
|
|
# Value for BOOL (available: n, y): n
|
|
#
|
|
# ======== Example Kconfig configuration ========
|
|
#
|
|
# [*] Enable loadable module support (MODULES)
|
|
# Bool and tristate symbols
|
|
# [ ] Bool symbol (BOOL)
|
|
# < > Tristate symbol (TRI)
|
|
# [ ] Second prompt (TWO_MENU_NODES)
|
|
# *** These are selected by TRI_DEP ***
|
|
# < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP)
|
|
# < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP)
|
|
# String, int, and hex symbols
|
|
# (foo) String symbol (STRING)
|
|
# (747) Int symbol (INT)
|
|
# (0xABC) Hex symbol (HEX)
|
|
# Various choices
|
|
# -*- Bool choice (BOOL_CHOICE)
|
|
# --> Bool choice sym 1 (BOOL_CHOICE_SYM_1)
|
|
# Bool choice sym 2 (BOOL_CHOICE_SYM_2)
|
|
# {M} Tristate choice (TRI_CHOICE)
|
|
# < > Tristate choice sym 1 (TRI_CHOICE_SYM_1)
|
|
# < > Tristate choice sym 2 (TRI_CHOICE_SYM_2)
|
|
# [ ] Optional bool choice (OPT_BOOL_CHOICE)
|
|
#
|
|
# Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): MODULES
|
|
# Value for MODULES (available: n, y): n
|
|
#
|
|
# ======== Example Kconfig configuration ========
|
|
#
|
|
# [ ] Enable loadable module support (MODULES)
|
|
# Bool and tristate symbols
|
|
# [ ] Bool symbol (BOOL)
|
|
# [ ] Tristate symbol (TRI)
|
|
# [ ] Second prompt (TWO_MENU_NODES)
|
|
# *** These are selected by TRI_DEP ***
|
|
# [ ] Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP)
|
|
# [ ] Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP)
|
|
# String, int, and hex symbols
|
|
# (foo) String symbol (STRING)
|
|
# (747) Int symbol (INT)
|
|
# (0xABC) Hex symbol (HEX)
|
|
# Various choices
|
|
# -*- Bool choice (BOOL_CHOICE)
|
|
# --> Bool choice sym 1 (BOOL_CHOICE_SYM_1)
|
|
# Bool choice sym 2 (BOOL_CHOICE_SYM_2)
|
|
# -*- Tristate choice (TRI_CHOICE)
|
|
# --> Tristate choice sym 1 (TRI_CHOICE_SYM_1)
|
|
# Tristate choice sym 2 (TRI_CHOICE_SYM_2)
|
|
# [ ] Optional bool choice (OPT_BOOL_CHOICE)
|
|
#
|
|
# Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): ^D
|
|
|
|
from __future__ import print_function
|
|
import readline
|
|
import sys
|
|
|
|
from kconfiglib import Kconfig, \
|
|
Symbol, MENU, COMMENT, \
|
|
BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN, \
|
|
expr_value, \
|
|
TRI_TO_STR
|
|
|
|
|
|
# Python 2/3 compatibility hack
|
|
if sys.version_info[0] < 3:
|
|
input = raw_input
|
|
|
|
|
|
def indent_print(s, indent):
|
|
print(indent*" " + s)
|
|
|
|
|
|
def value_str(sc):
|
|
"""
|
|
Returns the value part ("[*]", "<M>", "(foo)" etc.) of a menu entry.
|
|
|
|
sc: Symbol or Choice.
|
|
"""
|
|
if sc.type in (STRING, INT, HEX):
|
|
return "({})".format(sc.str_value)
|
|
|
|
# BOOL or TRISTATE
|
|
|
|
# The choice mode is an upper bound on the visibility of choice symbols, so
|
|
# we can check the choice symbols' own visibility to see if the choice is
|
|
# in y mode
|
|
if isinstance(sc, Symbol) and sc.choice and sc.visibility == 2:
|
|
# For choices in y mode, print '-->' next to the selected symbol
|
|
return "-->" if sc.choice.selection is sc else " "
|
|
|
|
tri_val_str = (" ", "M", "*")[sc.tri_value]
|
|
|
|
if len(sc.assignable) == 1:
|
|
# Pinned to a single value
|
|
return "-{}-".format(tri_val_str)
|
|
|
|
if sc.type == BOOL:
|
|
return "[{}]".format(tri_val_str)
|
|
|
|
if sc.type == TRISTATE:
|
|
if sc.assignable == (1, 2):
|
|
# m and y available
|
|
return "{" + tri_val_str + "}" # Gets a bit confusing with .format()
|
|
return "<{}>".format(tri_val_str)
|
|
|
|
|
|
def node_str(node):
|
|
"""
|
|
Returns the complete menu entry text for a menu node, or "" for invisible
|
|
menu nodes. Invisible menu nodes are those that lack a prompt or that do
|
|
not have a satisfied prompt condition.
|
|
|
|
Example return value: "[*] Bool symbol (BOOL)"
|
|
|
|
The symbol name is printed in parentheses to the right of the prompt. This
|
|
is so that symbols can easily be referred to in the configuration
|
|
interface.
|
|
"""
|
|
if not node.prompt:
|
|
return ""
|
|
|
|
# Even for menu nodes for symbols and choices, it's wrong to check
|
|
# Symbol.visibility / Choice.visibility here. The reason is that a symbol
|
|
# (and a choice, in theory) can be defined in multiple locations, giving it
|
|
# multiple menu nodes, which do not necessarily all have the same prompt
|
|
# visibility. Symbol.visibility / Choice.visibility is calculated as the OR
|
|
# of the visibility of all the prompts.
|
|
prompt, prompt_cond = node.prompt
|
|
if not expr_value(prompt_cond):
|
|
return ""
|
|
|
|
if node.item == MENU:
|
|
return " " + prompt
|
|
|
|
if node.item == COMMENT:
|
|
return " *** {} ***".format(prompt)
|
|
|
|
# Symbol or Choice
|
|
|
|
sc = node.item
|
|
|
|
if sc.type == UNKNOWN:
|
|
# Skip symbols defined without a type (these are obscure and generate
|
|
# a warning)
|
|
return ""
|
|
|
|
# {:3} sets the field width to three. Gives nice alignment for empty string
|
|
# values.
|
|
res = "{:3} {}".format(value_str(sc), prompt)
|
|
|
|
# Don't print the name for unnamed choices (the normal kind)
|
|
if sc.name is not None:
|
|
res += " ({})".format(sc.name)
|
|
|
|
return res
|
|
|
|
|
|
def print_menuconfig_nodes(node, indent):
|
|
"""
|
|
Prints a tree with all the menu entries rooted at 'node'. Child menu
|
|
entries are indented.
|
|
"""
|
|
while node:
|
|
string = node_str(node)
|
|
if string:
|
|
indent_print(string, indent)
|
|
|
|
if node.list:
|
|
print_menuconfig_nodes(node.list, indent + 8)
|
|
|
|
node = node.next
|
|
|
|
|
|
def print_menuconfig(kconf):
|
|
"""
|
|
Prints all menu entries for the configuration.
|
|
"""
|
|
# Print the expanded mainmenu text at the top. This is the same as
|
|
# kconf.top_node.prompt[0], but with variable references expanded.
|
|
print("\n======== {} ========\n".format(kconf.mainmenu_text))
|
|
|
|
print_menuconfig_nodes(kconf.top_node.list, 0)
|
|
print("")
|
|
|
|
|
|
def get_value_from_user(sc):
|
|
"""
|
|
Prompts the user for a value for the symbol or choice 'sc'. For
|
|
bool/tristate symbols and choices, provides a list of all the assignable
|
|
values.
|
|
"""
|
|
if not sc.visibility:
|
|
print(sc.name + " is not currently visible")
|
|
return False
|
|
|
|
prompt = "Value for {}".format(sc.name)
|
|
if sc.type in (BOOL, TRISTATE):
|
|
prompt += " (available: {})" \
|
|
.format(", ".join(TRI_TO_STR[val] for val in sc.assignable))
|
|
prompt += ": "
|
|
|
|
val = input(prompt)
|
|
|
|
# Automatically add a "0x" prefix for hex symbols, like the menuconfig
|
|
# interface does. This isn't done when loading .config files, hence why
|
|
# set_value() doesn't do it automatically.
|
|
if sc.type == HEX and not val.startswith(("0x", "0X")):
|
|
val = "0x" + val
|
|
|
|
# Let Kconfiglib itself print a warning here if the value is invalid. We
|
|
# could also disable warnings temporarily with 'kconf.warn = False' and
|
|
# print our own warning.
|
|
return sc.set_value(val)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) != 2:
|
|
sys.exit("usage: menuconfig.py <Kconfig file>")
|
|
|
|
# Load Kconfig configuration files
|
|
kconf = Kconfig(sys.argv[1])
|
|
|
|
# Print the initial configuration tree
|
|
print_menuconfig(kconf)
|
|
|
|
while True:
|
|
try:
|
|
cmd = input('Enter a symbol/choice name, "load_config", or '
|
|
'"write_config" (or press CTRL+D to exit): ').strip()
|
|
except EOFError:
|
|
print("")
|
|
break
|
|
|
|
if cmd == "load_config":
|
|
config_filename = input(".config file to load: ")
|
|
try:
|
|
# Returns a message telling which file got loaded
|
|
print(kconf.load_config(config_filename))
|
|
except EnvironmentError as e:
|
|
print(e, file=sys.stderr)
|
|
|
|
print_menuconfig(kconf)
|
|
continue
|
|
|
|
if cmd == "write_config":
|
|
config_filename = input("To this file: ")
|
|
try:
|
|
# Returns a message telling which file got saved
|
|
print(kconf.write_config(config_filename))
|
|
except EnvironmentError as e:
|
|
print(e, file=sys.stderr)
|
|
|
|
continue
|
|
|
|
# Assume 'cmd' is the name of a symbol or choice if it isn't one of the
|
|
# commands above, prompt the user for a value for it, and print the new
|
|
# configuration tree
|
|
|
|
if cmd in kconf.syms:
|
|
if get_value_from_user(kconf.syms[cmd]):
|
|
print_menuconfig(kconf)
|
|
|
|
continue
|
|
|
|
if cmd in kconf.named_choices:
|
|
if get_value_from_user(kconf.named_choices[cmd]):
|
|
print_menuconfig(kconf)
|
|
|
|
continue
|
|
|
|
print("No symbol/choice named '{}' in the configuration".format(cmd),
|
|
file=sys.stderr)
|