Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add oldaskconfig the default conf program #134

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ available in the C tools.

- `guiconfig <https://github.com/ulfalizer/Kconfiglib/blob/master/guiconfig.py>`_

- `oldaskconfig <https://github.com/ulfalizer/Kconfiglib/blob/master/oldaskconfig.py>`_

- `oldconfig <https://github.com/ulfalizer/Kconfiglib/blob/master/oldconfig.py>`_

- `olddefconfig <https://github.com/ulfalizer/Kconfiglib/blob/master/olddefconfig.py>`_
Expand Down Expand Up @@ -214,6 +216,7 @@ Getting started

5. To update an old ``.config`` file after the Kconfig files have changed (e.g.
to add new options), run ``oldconfig`` (prompts for values for new options)
or ``oldaskconfig`` (shows all options with any old value as the default)
or ``olddefconfig`` (gives new options their default value). Entering the
``menuconfig`` or ``guiconfig`` interface and saving the configuration will
also update it (the configuration interfaces always prompt for saving
Expand Down
248 changes: 248 additions & 0 deletions oldaskconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#!/usr/bin/env python3

# Copyright (c) 2018-2019, Ulf Magnusson
# SPDX-License-Identifier: ISC

"""
Implements oldaskconfig functionality.

1. Loads existing .config
2. Prompts for the value of all modifiable symbols/choices
3. Writes an updated .config

The default input/output filename is '.config'. A different filename can be
passed in the KCONFIG_CONFIG environment variable.

When overwriting a configuration file, the old version is saved to
<filename>.old (e.g. .config.old).

Entering '?' displays the help text of the symbol/choice, if any.

Unlike 'make oldconfig', this script doesn't print menu titles and comments,
but gives Kconfig definition locations. Printing menus and comments would be
pretty easy to add: Look at the parents of each item, and print all menu
prompts and comments unless they have already been printed (assuming you want
to skip "irrelevant" menus).
"""
from __future__ import print_function

import sys

from kconfiglib import Symbol, Choice, BOOL, TRISTATE, HEX, standard_kconfig


# Python 2/3 compatibility hack
if sys.version_info[0] < 3:
input = raw_input


def _main():
# Earlier symbols in Kconfig files might depend on later symbols and become
# visible if their values change. This flag is set to True if the value of
# any symbol changes, in which case we rerun the oldconfig to check for new
# visible symbols.
global conf_changed

kconf = standard_kconfig(__doc__)
print(kconf.load_config())

while True:
conf_changed = False

for node in kconf.node_iter():
oldaskconfig(node)

if not conf_changed:
break

print(kconf.write_config())


def oldaskconfig(node):
"""
Prompts the user for a value if node.item is a visible symbol/choice
"""
# See main()
global conf_changed

# Only symbols and choices can be configured
if not isinstance(node.item, (Symbol, Choice)):
return

# Skip symbols and choices that aren't visible
if not node.item.visibility:
return

# Skip symbols and choices that don't have a prompt (at this location)
if not node.prompt:
return

if isinstance(node.item, Symbol):
sym = node.item

new = sym.user_value is None

# Skip symbols that can only have a single value, due to selects
if len(sym.assignable) == 1:
return

# Skip symbols in choices in y mode. We ask once for the entire choice
# instead.
if sym.choice and sym.choice.tri_value == 2:
return

# Loop until the user enters a valid value or enters a blank string
# (for the default value)
while True:
val = input("{} ({}) [{}]{} ".format(
node.prompt[0], _name_and_loc_str(sym),
_default_value_str(sym), _new_value_str(new)))

if val == "?":
_print_help(node)
continue

# Substitute a blank string with the default value the symbol
# would get
if not val:
val = sym.str_value

# 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 sym.type == HEX and not val.startswith(("0x", "0X")):
val = "0x" + val

old_str_val = sym.str_value

# Kconfiglib itself will print a warning here if the value
# is invalid, so we don't need to bother
if sym.set_value(val):
# Valid value input. We're done with this node.

if sym.str_value != old_str_val:
conf_changed = True

return

else:
choice = node.item

# Skip choices that already have a visible user selection...
if choice.user_selection and choice.user_selection.visibility == 2:
# ...unless there are new visible symbols in the choice. (We know
# they have y (2) visibility in that case, because m-visible
# symbols get demoted to n-visibility in y-mode choices, and the
# user-selected symbol had visibility y.)
for sym in choice.syms:
if sym is not choice.user_selection and sym.visibility and \
sym.user_value is None:
# New visible symbols in the choice
break
else:
# No new visible symbols in the choice
return

# Get a list of available selections. The mode of the choice limits
# the visibility of the choice value symbols, so this will indirectly
# skip choices in n and m mode.
options = [sym for sym in choice.syms if sym.visibility == 2]

if not options:
# No y-visible choice value symbols
return

# Loop until the user enters a valid selection or a blank string (for
# the default selection)
while True:
print("{} ({})".format(node.prompt[0], _name_and_loc_str(choice)))

for i, sym in enumerate(options, 1):
print("{} {}. {} ({})".format(
">" if sym is choice.selection else " ",
i,
# Assume people don't define choice symbols with multiple
# prompts. That generates a warning anyway.
sym.nodes[0].prompt[0],
sym.name))

sel_index = input("choice[1-{}]: ".format(len(options)))

if sel_index == "?":
_print_help(node)
continue

# Pick the default selection if the string is blank
if not sel_index:
choice.selection.set_value(2)
break

try:
sel_index = int(sel_index)
except ValueError:
print("Bad index", file=sys.stderr)
continue

if not 1 <= sel_index <= len(options):
print("Bad index", file=sys.stderr)
continue

# Valid selection

if options[sel_index - 1].tri_value != 2:
conf_changed = True

options[sel_index - 1].set_value(2)
break

# Give all of the non-selected visible choice symbols the user value n.
# This makes it so that the choice is no longer considered new once we
# do additional passes, if the reason that it was considered new was
# that it had new visible choice symbols.
#
# Only giving visible choice symbols the user value n means we will
# prompt for the choice again if later user selections make more new
# choice symbols visible, which is correct.
for sym in choice.syms:
if sym is not choice.user_selection and sym.visibility:
sym.set_value(0)


def _name_and_loc_str(sc):
# Helper for printing the name of the symbol/choice 'sc' along with the
# location(s) in the Kconfig files where it is defined. Unnamed choices
# return "choice" instead of the name.

return "{}, defined at {}".format(
sc.name or "choice",
", ".join("{}:{}".format(node.filename, node.linenr)
for node in sc.nodes))


def _print_help(node):
print("\n" + (node.help or "No help text\n"))


def _default_value_str(sym):
# Returns the "m/M/y" string in e.g.
#
# TRISTATE_SYM prompt (TRISTATE_SYM, defined at Kconfig:9) [n/M/y]:
#
# For string/int/hex, returns the default value as-is.

if sym.type in (BOOL, TRISTATE):
return "/".join(("NMY" if sym.tri_value == tri else "nmy")[tri]
for tri in sym.assignable)

# string/int/hex
return sym.str_value


def _new_value_str(new):
if new:
return " (NEW)"
return ""


if __name__ == "__main__":
_main()
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"menuconfig",
"guiconfig",
"genconfig",
"oldaskconfig",
"oldconfig",
"olddefconfig",
"savedefconfig",
Expand All @@ -48,6 +49,7 @@
"menuconfig = menuconfig:_main",
"guiconfig = guiconfig:_main",
"genconfig = genconfig:main",
"oldaskconfig = oldaskconfig:_main",
"oldconfig = oldconfig:_main",
"olddefconfig = olddefconfig:main",
"savedefconfig = savedefconfig:main",
Expand Down