diff --git a/tools/kconfig_new/kconfiglib.py b/tools/kconfig_new/kconfiglib.py index 700ce7a63..c0a5b2ab6 100644 --- a/tools/kconfig_new/kconfiglib.py +++ b/tools/kconfig_new/kconfiglib.py @@ -554,7 +554,7 @@ from glob import iglob from os.path import dirname, exists, expandvars, islink, join, realpath -VERSION = (13, 3, 2) +VERSION = (13, 7, 1) # File layout: @@ -865,7 +865,7 @@ class Kconfig(object): # def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, - encoding="utf-8"): + encoding="utf-8", suppress_traceback=False): """ Creates a new Kconfig object by parsing Kconfig files. Note that Kconfig files are not the same as .config files (which store @@ -930,7 +930,35 @@ class Kconfig(object): anyway. Related PEP: https://www.python.org/dev/peps/pep-0538/ + + suppress_traceback (default: False): + Helper for tools. When True, any EnvironmentError or KconfigError + generated during parsing is caught, the exception message is printed + to stderr together with the command name, and sys.exit(1) is called + (which generates SystemExit). + + This hides the Python traceback for "expected" errors like syntax + errors in Kconfig files. + + Other exceptions besides EnvironmentError and KconfigError are still + propagated when suppress_traceback is True. """ + try: + self._init(filename, warn, warn_to_stderr, encoding) + except (EnvironmentError, KconfigError) as e: + if suppress_traceback: + cmd = sys.argv[0] # Empty string if missing + if cmd: + cmd += ": " + # Some long exception messages have extra newlines for better + # formatting when reported as an unhandled exception. Strip + # them here. + sys.exit(cmd + str(e).strip()) + raise + + def _init(self, filename, warn, warn_to_stderr, encoding): + # See __init__() + self._encoding = encoding self.srctree = os.getenv("srctree", "") @@ -1052,8 +1080,9 @@ class Kconfig(object): self._readline = self._open(join(self.srctree, filename), "r").readline try: - # Parse the Kconfig files - self._parse_block(None, self.top_node, self.top_node) + # Parse the Kconfig files. Returns the last node, which we + # terminate with '.next = None'. + self._parse_block(None, self.top_node, self.top_node).next = None self.top_node.list = self.top_node.next self.top_node.next = None except UnicodeDecodeError as e: @@ -1363,6 +1392,28 @@ class Kconfig(object): elif self.warn_assign_override: self._warn(msg, filename, linenr) + def load_allconfig(self, filename): + """ + Helper for all*config. Loads (merges) the configuration file specified + by KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in + the Linux kernel. + + Disables warnings for duplicated assignments within configuration files + for the duration of the call + (kconf.warn_assign_override/warn_assign_redun = False), and restores + the previous warning settings at the end. The KCONFIG_ALLCONFIG + configuration file is expected to override symbols. + + Exits with sys.exit() (which raises a SystemExit exception) and prints + an error to stderr if KCONFIG_ALLCONFIG is set but the configuration + file can't be opened. + + filename: + Command-specific configuration filename - "allyes.config", + "allno.config", etc. + """ + load_allconfig(self, filename) + def write_autoconf(self, filename=None, header=None): r""" Writes out symbol values as a C header file, matching the format used @@ -2163,9 +2214,9 @@ class Kconfig(object): # it's part of a different construct if self._reuse_tokens: self._reuse_tokens = False - # self._tokens_i is known to be 1 here, because _parse_properties() - # leaves it like that when it can't recognize a line (or parses - # a help text) + # self._tokens_i is known to be 1 here, because _parse_props() + # leaves it like that when it can't recognize a line (or parses a + # help text) return True # readline() returns '' over and over at EOF, which we rely on for help @@ -2182,7 +2233,7 @@ class Kconfig(object): self._tokens = self._tokenize(line) # Initialize to 1 instead of 0 to factor out code from _parse_block() - # and _parse_properties(). They immediately fetch self._tokens[0]. + # and _parse_props(). They immediately fetch self._tokens[0]. self._tokens_i = 1 return True @@ -2844,7 +2895,7 @@ class Kconfig(object): # # prev: # The previous menu node. New nodes will be added after this one (by - # modifying their 'next' pointer). + # modifying 'next' pointers). # # 'prev' is reused to parse a list of child menu nodes (for a menu or # Choice): After parsing the children, the 'next' pointer is assigned @@ -2880,7 +2931,7 @@ class Kconfig(object): sym.nodes.append(node) - self._parse_properties(node) + self._parse_props(node) if node.is_menuconfig and not node.prompt: self._warn("the menuconfig symbol {} has no prompt" @@ -2966,7 +3017,7 @@ class Kconfig(object): self.menus.append(node) - self._parse_properties(node) + self._parse_props(node) self._parse_block(_T_ENDMENU, node, node) node.list = node.next @@ -2986,7 +3037,7 @@ class Kconfig(object): self.comments.append(node) - self._parse_properties(node) + self._parse_props(node) prev.next = prev = node @@ -3018,7 +3069,7 @@ class Kconfig(object): choice.nodes.append(node) - self._parse_properties(node) + self._parse_props(node) self._parse_block(_T_ENDCHOICE, node, node) node.list = node.next @@ -3036,17 +3087,16 @@ class Kconfig(object): "no corresponding 'menu'" if t0 is _T_ENDMENU else "unrecognized construct") - # End of file reached. Terminate the final node and return it. + # End of file reached. Return the last node. if end_token: raise KconfigError( - "expected '{}' at end of '{}'" + "error: expected '{}' at end of '{}'" .format("endchoice" if end_token is _T_ENDCHOICE else "endif" if end_token is _T_ENDIF else "endmenu", self.filename)) - prev.next = None return prev def _parse_cond(self): @@ -3060,7 +3110,7 @@ class Kconfig(object): return expr - def _parse_properties(self, node): + def _parse_props(self, node): # Parses and adds properties to the MenuNode 'node' (type, 'prompt', # 'default's, etc.) Properties are later copied up to symbols and # choices in a separate pass after parsing, in e.g. @@ -3086,7 +3136,7 @@ class Kconfig(object): if t0 in _TYPE_TOKENS: # Relies on '_T_BOOL is BOOL', etc., to save a conversion - self._set_type(node, t0) + self._set_type(node.item, t0) if self._tokens[1] is not None: self._parse_prompt(node) @@ -3116,7 +3166,7 @@ class Kconfig(object): self._parse_cond())) elif t0 in _DEF_TOKEN_TO_TYPE: - self._set_type(node, _DEF_TOKEN_TO_TYPE[t0]) + self._set_type(node.item, _DEF_TOKEN_TO_TYPE[t0]) node.defaults.append((self._parse_expr(False), self._parse_cond())) @@ -3217,13 +3267,15 @@ class Kconfig(object): self._reuse_tokens = True return - def _set_type(self, node, new_type): - # UNKNOWN is falsy - if node.item.orig_type and node.item.orig_type is not new_type: - self._warn("{} defined with multiple types, {} will be used" - .format(node.item.name_and_loc, TYPE_TO_STR[new_type])) + def _set_type(self, sc, new_type): + # Sets the type of 'sc' (symbol or choice) to 'new_type' - node.item.orig_type = new_type + # UNKNOWN is falsy + if sc.orig_type and sc.orig_type is not new_type: + self._warn("{} defined with multiple types, {} will be used" + .format(sc.name_and_loc, TYPE_TO_STR[new_type])) + + sc.orig_type = new_type def _parse_prompt(self, node): # 'prompt' properties override each other within a single definition of @@ -3413,7 +3465,7 @@ class Kconfig(object): # The calculated sets might be larger than necessary as we don't do any # complex analysis of the expressions. - make_depend_on = _make_depend_on # Micro-optimization + depend_on = _depend_on # Micro-optimization # Only calculate _dependents for defined symbols. Constant and # undefined symbols could theoretically be selected/implied, but it @@ -3424,29 +3476,29 @@ class Kconfig(object): # The prompt conditions for node in sym.nodes: if node.prompt: - make_depend_on(sym, node.prompt[1]) + depend_on(sym, node.prompt[1]) # The default values and their conditions for value, cond in sym.defaults: - make_depend_on(sym, value) - make_depend_on(sym, cond) + depend_on(sym, value) + depend_on(sym, cond) # The reverse and weak reverse dependencies - make_depend_on(sym, sym.rev_dep) - make_depend_on(sym, sym.weak_rev_dep) + depend_on(sym, sym.rev_dep) + depend_on(sym, sym.weak_rev_dep) # The ranges along with their conditions for low, high, cond in sym.ranges: - make_depend_on(sym, low) - make_depend_on(sym, high) - make_depend_on(sym, cond) + depend_on(sym, low) + depend_on(sym, high) + depend_on(sym, cond) # The direct dependencies. This is usually redundant, as the direct # dependencies get propagated to properties, but it's needed to get # invalidation solid for 'imply', which only checks the direct # dependencies (even if there are no properties to propagate it # to). - make_depend_on(sym, sym.direct_dep) + depend_on(sym, sym.direct_dep) # In addition to the above, choice symbols depend on the choice # they're in, but that's handled automatically since the Choice is @@ -3459,11 +3511,11 @@ class Kconfig(object): # The prompt conditions for node in choice.nodes: if node.prompt: - make_depend_on(choice, node.prompt[1]) + depend_on(choice, node.prompt[1]) # The default symbol conditions for _, cond in choice.defaults: - make_depend_on(choice, cond) + depend_on(choice, cond) def _add_choice_deps(self): # Choices also depend on the choice symbols themselves, because the @@ -3817,7 +3869,7 @@ class Kconfig(object): .format(sym.name_and_loc)) def _parse_error(self, msg): - raise KconfigError("{}couldn't parse '{}': {}".format( + raise KconfigError("{}error: couldn't parse '{}': {}".format( "" if self.filename is None else "{}:{}: ".format(self.filename, self.linenr), self._line.strip(), msg)) @@ -5336,8 +5388,8 @@ class Choice(object): self._cached_selection = _NO_CACHED_SELECTION - # is_constant is checked by _make_depend_on(). Just set it to avoid - # having to special-case choices. + # is_constant is checked by _depend_on(). Just set it to avoid having + # to special-case choices. self.is_constant = self.is_optional = False # See Kconfig._build_dep() @@ -6158,17 +6210,9 @@ def standard_kconfig(description=None): metavar="KCONFIG", default="Kconfig", nargs="?", - help="Kconfig file (default: Kconfig)") + help="Top-level Kconfig file (default: Kconfig)") - args = parser.parse_args() - - # Suppress backtraces for expected exceptions - try: - return Kconfig(args.kconfig) - except (EnvironmentError, KconfigError) as e: - # Some long exception messages have extra newlines for better - # formatting when reported as an unhandled exception. Strip them here. - sys.exit(str(e).strip()) + return Kconfig(parser.parse_args().kconfig, suppress_traceback=True) def standard_config_filename(): @@ -6184,25 +6228,9 @@ def standard_config_filename(): def load_allconfig(kconf, filename): """ - Helper for all*config. Loads (merges) the configuration file specified by - KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in the - Linux kernel. - - Disables warnings for duplicated assignments within configuration files for - the duration of the call (kconf.warn_assign_override/warn_assign_redun = False), - and restores the previous warning settings at the end. The - KCONFIG_ALLCONFIG configuration file is expected to override symbols. - - Exits with sys.exit() (which raises a SystemExit exception) and prints an - error to stderr if KCONFIG_ALLCONFIG is set but the configuration file - can't be opened. - - kconf: - Kconfig instance to load the configuration in. - - filename: - Command-specific configuration filename - "allyes.config", - "allno.config", etc. + Use Kconfig.load_allconfig() instead, which was added in Kconfiglib 13.4.0. + Supported for backwards compatibility. Might be removed at some point after + a long period of deprecation warnings. """ allconfig = os.getenv("KCONFIG_ALLCONFIG") if allconfig is None: @@ -6278,7 +6306,7 @@ def _visibility(sc): return vis -def _make_depend_on(sc, expr): +def _depend_on(sc, expr): # Adds 'sc' (symbol or choice) as a "dependee" to all symbols in 'expr'. # Constant symbols in 'expr' are skipped as they can never change value # anyway. @@ -6286,11 +6314,11 @@ def _make_depend_on(sc, expr): if expr.__class__ is tuple: # AND, OR, NOT, or relation - _make_depend_on(sc, expr[1]) + _depend_on(sc, expr[1]) # NOTs only have a single operand if expr[0] is not NOT: - _make_depend_on(sc, expr[2]) + _depend_on(sc, expr[2]) elif not expr.is_constant: # Non-constant symbol, or choice @@ -6744,8 +6772,7 @@ def _error_if_fn(kconf, _, cond, msg): def _shell_fn(kconf, _, command): - # Only import as needed, to save some startup time - import subprocess + import subprocess # Only import as needed, to save some startup time stdout, stderr = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE diff --git a/tools/kconfig_new/menuconfig.py b/tools/kconfig_new/menuconfig.py index b26e2aa14..d5456dd87 100644 --- a/tools/kconfig_new/menuconfig.py +++ b/tools/kconfig_new/menuconfig.py @@ -328,10 +328,10 @@ _STYLES = { # Blue-tinted style loosely resembling lxdialog "aquatic": """ - path=fg:cyan,bg:blue,bold - separator=fg:white,bg:cyan,bold + path=fg:white,bg:blue + separator=fg:white,bg:cyan help=path - frame=fg:white,bg:cyan,bold + frame=fg:white,bg:cyan body=fg:white,bg:blue edit=fg:black,bg:white """ @@ -1107,8 +1107,7 @@ def _enter_menu(menu): global _menu_scroll if not menu.is_menuconfig: - # Not a menu - return False + return False # Not a menu shown_sub = _shown_nodes(menu) # Never enter empty menus. We depend on having a current node. @@ -1349,7 +1348,6 @@ def _draw_main(): term_width = _width(_stdscr) - # # Update the separator row below the menu path # @@ -1396,7 +1394,6 @@ def _draw_main(): _menu_win.noutrefresh() - # # Update the bottom separator window # @@ -1421,7 +1418,6 @@ def _draw_main(): _bot_sep_win.noutrefresh() - # # Update the help window, which shows either key bindings or help texts # @@ -1442,7 +1438,6 @@ def _draw_main(): _help_win.noutrefresh() - # # Update the top row with the menu path. # @@ -2302,7 +2297,6 @@ def _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win, edit_width = _width(edit_box) - 2 - # # Update list of matches # @@ -2333,7 +2327,6 @@ def _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win, matches_win.noutrefresh() - # # Update bottom separator line # @@ -2346,7 +2339,6 @@ def _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win, bot_sep_win.noutrefresh() - # # Update help window at bottom # @@ -2358,7 +2350,6 @@ def _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win, help_win.noutrefresh() - # # Update edit box. We do this last since it makes it handy to position the # cursor. @@ -2448,12 +2439,10 @@ def _info_dialog(node, from_jump_to_dialog): # Support starting a search from within the information dialog if from_jump_to_dialog: - # Avoid recursion - return + return # Avoid recursion if _jump_to_dialog(): - # Jumped to a symbol. Cancel the information dialog. - return + return # Jumped to a symbol. Cancel the information dialog. # Stay in the information dialog if the jump-to dialog was # canceled. Resize it in case the terminal was resized while the @@ -2500,7 +2489,6 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win, text_win_height, text_win_width = text_win.getmaxyx() - # Note: The top row is deliberately updated last. See _draw_main(). # @@ -2514,7 +2502,6 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win, text_win.noutrefresh() - # # Update bottom separator line # @@ -2527,7 +2514,6 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win, bot_sep_win.noutrefresh() - # # Update help window at bottom # @@ -2539,7 +2525,6 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win, help_win.noutrefresh() - # # Update top row # @@ -2598,8 +2583,7 @@ def _info_str(node): _kconfig_def_info(choice) ) - # node.item in (MENU, COMMENT) - return _kconfig_def_info(node) + return _kconfig_def_info(node) # node.item in (MENU, COMMENT) def _name_info(sc): @@ -2956,7 +2940,6 @@ def _edit_text(c, s, i, hscroll, width): max_scroll = max(len(s) - width + 1, 0) hscroll = min(i - width + _SCROLL_OFFSET + 1, max_scroll) - return s, i, hscroll @@ -3106,8 +3089,7 @@ def _check_valid(sym, s): # Otherwise, displays an error and returns False. if sym.orig_type not in (INT, HEX): - # Anything goes for non-int/hex symbols - return True + return True # Anything goes for non-int/hex symbols base = 10 if sym.orig_type == INT else 16 try: @@ -3164,12 +3146,17 @@ def _is_num(name): def _getch_compat(win): - # Uses get_wch() if available (Python 3.3+) and getch() otherwise. Also - # handles a PDCurses resizing quirk. + # Uses get_wch() if available (Python 3.3+) and getch() otherwise. + # + # Also falls back on getch() if get_wch() raises curses.error, to work + # around an issue when resizing the terminal on at least macOS Catalina. + # See https://github.com/ulfalizer/Kconfiglib/issues/84. + # + # Also handles a PDCurses resizing quirk. - if hasattr(win, "get_wch"): + try: c = win.get_wch() - else: + except (AttributeError, curses.error): c = win.getch() if 0 <= c <= 255: c = chr(c)