def _defaults_info(sc): # Returns a string describing the defaults of 'sc' (Symbol or Choice) if not sc.defaults: return "" s = "Default" if len(sc.defaults) > 1: s += "s" s += ":\n" for val, cond in sc.orig_defaults: s += " - " if isinstance(sc, Symbol): s += _expr_str(val) # Skip the tristate value hint if the expression is just a single # symbol. _expr_str() already shows its value as a string. # # This also avoids showing the tristate value for string/int/hex # defaults, which wouldn't make any sense. if isinstance(val, tuple): s += ' (={})'.format(TRI_TO_STR[expr_value(val)]) else: # Don't print the value next to the symbol name for choice # defaults, as it looks a bit confusing s += val.name s += "\n" if cond is not _kconfig.y: s += " Condition (={}):\n{}" \ .format(TRI_TO_STR[expr_value(cond)], _split_expr_info(cond, 4)) return s + "\n"
def _minimize_expr(expr, visibility): def expr_nodes_invisible(e): return hasattr(e, 'nodes') and len(e.nodes) > 0 and all(not visibility.visible(i) for i in e.nodes) if isinstance(expr, tuple): if expr[0] == kconfiglib.NOT: new_expr = _minimize_expr(expr[1], visibility) return kconfiglib.Kconfig.y if new_expr == kconfiglib.Kconfig.n else new_expr else: new_expr1 = _minimize_expr(expr[1], visibility) new_expr2 = _minimize_expr(expr[2], visibility) if expr[0] == kconfiglib.AND: if new_expr1 == kconfiglib.Kconfig.n or new_expr2 == kconfiglib.Kconfig.n: return kconfiglib.Kconfig.n if new_expr1 == kconfiglib.Kconfig.y: return new_expr2 if new_expr2 == kconfiglib.Kconfig.y: return new_expr1 elif expr[0] == kconfiglib.OR: if new_expr1 == kconfiglib.Kconfig.y or new_expr2 == kconfiglib.Kconfig.y: return kconfiglib.Kconfig.y if new_expr1 == kconfiglib.Kconfig.n: return new_expr2 if new_expr2 == kconfiglib.Kconfig.n: return new_expr1 elif expr[0] == kconfiglib.EQUAL: if not isinstance(new_expr1, type(new_expr2)): return kconfiglib.Kconfig.n if new_expr1 == new_expr2: return kconfiglib.Kconfig.y elif expr[0] == kconfiglib.UNEQUAL: if not isinstance(new_expr1, type(new_expr2)): return kconfiglib.Kconfig.y if new_expr1 != new_expr2: return kconfiglib.Kconfig.n else: # <, <=, >, >= if not isinstance(new_expr1, type(new_expr2)): return kconfiglib.Kconfig.n # e.g "True < 2" if expr_nodes_invisible(new_expr1) or expr_nodes_invisible(new_expr2): return kconfiglib.Kconfig.y if kconfiglib.expr_value(expr) else kconfiglib.Kconfig.n return (expr[0], new_expr1, new_expr2) if (not kconfiglib.expr_value(expr) and len(expr.config_string) == 0 and expr_nodes_invisible(expr)): # nodes which are invisible # len(expr.nodes) > 0 avoids constant symbols without actual node definitions, e.g. integer constants # len(expr.config_string) == 0 avoids hidden configs which reflects the values of choices return kconfiglib.Kconfig.n if (kconfiglib.expr_value(expr) and len(expr.config_string) > 0 and expr_nodes_invisible(expr)): # hidden config dependencies which will be written to sdkconfig as enabled ones. return kconfiglib.Kconfig.y if any(node.item.name.startswith(visibility.target_env_var) for node in expr.nodes): # We know the actual values for IDF_TARGETs return kconfiglib.Kconfig.y if kconfiglib.expr_value(expr) else kconfiglib.Kconfig.n return expr
def select(self): """ Called when <Return> key is pressed and SELECT action is selected """ item = self.node.item # - Menu: dive into submenu # - INT, HEX, STRING symbol: raise prompt to enter symbol value # - BOOL, TRISTATE symbol inside 'y'-valued Choice: set 'y' value if (item is kconfiglib.MENU or isinstance(item, kconfiglib.Symbol) and self.node.is_menuconfig or isinstance(item, kconfiglib.Choice)): # Dive into submenu self.menuconfig.show_submenu(self.node) elif (isinstance(item, kconfiglib.Symbol) and item.type in (kconfiglib.INT, kconfiglib.HEX, kconfiglib.STRING)): # Raise prompt to enter symbol value ident = self.node.prompt[0] if self.node.prompt is not None else None title = 'Symbol: {}'.format(item.name) if item.type is kconfiglib.INT: # Find enabled ranges ranges = [ (int(start.str_value), int(end.str_value)) for start, end, expr in item.ranges if kconfiglib.expr_value(expr) > 0 ] # Raise prompt self.set_str_value(str(self.menuconfig.ask_for_int( ident=ident, title=title, value=item.str_value, ranges=ranges ))) elif item.type is kconfiglib.HEX: # Find enabled ranges ranges = [ (int(start.str_value, base=16), int(end.str_value, base=16)) for start, end, expr in item.ranges if kconfiglib.expr_value(expr) > 0 ] # Raise prompt self.set_str_value(hex(self.menuconfig.ask_for_hex( ident=ident, title=title, value=item.str_value, ranges=ranges ))) elif item.type is kconfiglib.STRING: # Raise prompt self.set_str_value(self.menuconfig.ask_for_string( ident=ident, title=title, value=item.str_value )) elif (isinstance(item, kconfiglib.Symbol) and item.choice is not None and item.choice.tri_value == 2): # Symbol inside choice -> set symbol value to 'y' self.set_tristate_value(2)
def _implies_invisibility(self, item): if isinstance(item, tuple): if item[0] == kconfiglib.NOT: (invisibility, source) = self._implies_invisibility(item[1]) if source is not None and source.startswith(self.target_env_var): return (not invisibility, source) else: # we want to be visible all configs which are not dependent on target variables, # e.g. "depends on XY" and "depends on !XY" as well return (False, None) elif item[0] == kconfiglib.AND: (invisibility, source) = self._implies_invisibility(item[1]) if invisibility: return (True, source) (invisibility, source) = self._implies_invisibility(item[2]) if invisibility: return (True, source) return (False, None) elif item[0] == kconfiglib.OR: implication_list = [self._implies_invisibility(item[1]), self._implies_invisibility(item[2])] if all([implies for (implies, _) in implication_list]): source_list = [s for (_, s) in implication_list if s.startswith(self.target_env_var)] if len(set(source_list)) != 1: # set removes the duplicates print('[WARNING] list contains targets: {}'.format(source_list)) return (True, source_list[0]) return (False, None) elif item[0] in self.direct_eval_set: def node_is_invisible(item): return all([node.prompt is None for node in item.nodes]) if node_is_invisible(item[1]) or node_is_invisible(item[1]): # it makes no sense to call self._implies_invisibility() here because it won't generate any useful # "source" return (not kconfiglib.expr_value(item), None) else: # expressions with visible configs can be changed to make the item visible return (False, None) else: raise RuntimeError('Unimplemented operation in {}'.format(item)) else: # Symbol or Choice vis_list = [self._visible(node) for node in item.nodes] if len(vis_list) > 0 and all([not visible for (visible, _) in vis_list]): source_list = [s for (_, s) in vis_list if s is not None and s.startswith(self.target_env_var)] if len(set(source_list)) != 1: # set removes the duplicates print('[WARNING] list contains targets: {}'.format(source_list)) return (True, source_list[0]) if item.name.startswith(self.target_env_var): return (not kconfiglib.expr_value(item), item.name) if len(vis_list) == 1: (visible, source) = vis_list[0] if visible: return (False, item.name) # item.name is important here in case the result will be inverted: if # the dependency is on another config then it can be still visible return (False, None)
def _check_is_visible(self, node): v = True v = v and node.prompt is not None # It should be enough to check if prompt expression is not false and # for menu nodes whether 'visible if' is not false v = v and kconfiglib.expr_value(node.prompt[1]) > 0 if node.item == kconfiglib.MENU: v = v and kconfiglib.expr_value(node.visibility) > 0 # If node references Symbol, then we also account for symbol visibility # TODO: need to re-think whether this is needed if isinstance(node.item, kconfiglib.Symbol): if node.item.type in (kconfiglib.BOOL, kconfiglib.TRISTATE): v = v and len(node.item.assignable) > 0 else: v = v and node.item.visibility > 0 return v
def _split_expr_info(expr, indent): # Returns a string with 'expr' split into its top-level && or || operands, # with one operand per line, together with the operand's value. This is # usually enough to get something readable for long expressions. A fancier # recursive thingy would be possible too. # # indent: # Number of leading spaces to add before the split expression. if len(split_expr(expr, AND)) > 1: split_op = AND op_str = "&&" else: split_op = OR op_str = "||" s = "" for i, term in enumerate(split_expr(expr, split_op)): s += "{}{} {}".format(indent * " ", " " if i == 0 else op_str, _expr_str(term)) # Don't bother showing the value hint if the expression is just a # single symbol. _expr_str() already shows its value. if isinstance(term, tuple): s += " (={})".format(TRI_TO_STR[expr_value(term)]) s += "\n" return s
def node2str(node): prompt, cond = node.prompt if not kconfiglib.expr_value(cond): return None, None if node.item == kconfiglib.MENU: return f'=== {prompt} ===', None if node.item == kconfiglib.COMMENT: return f'*** {prompt} ***', None # Now node.item is a symbol or a choice if node.item.type == kconfiglib.UNKNOWN: return None, None res = f'{value_str(node.item):3} {prompt}' if node.item.name is not None: res += f' ({node.item.name})' if hasattr(node, 'help') and node.help is not None: help_lines = [l for l in node.help.split('\n') if l.strip()] return res, help_lines return res, None
def check_deprecated(kconf): deprecated = kconf.syms['DEPRECATED'] dep_expr = deprecated.rev_dep if dep_expr is not kconf.n: selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2] for selector in selectors: selector_name = split_expr(selector, AND)[0].name warn(f'Deprecated symbol {selector_name} is enabled.')
def check_experimental(kconf): experimental = kconf.syms['EXPERIMENTAL'] dep_expr = experimental.rev_dep if dep_expr is not kconf.n: selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2] for selector in selectors: selector_name = split_expr(selector, AND)[0].name warn(f'Experimental symbol {selector_name} is enabled.')
def run_exploration_from_file(self, config_file, with_initial_config=True): kconfig_file = f"{self.cwd}/{self.kconfig}" kconfig_hash = self.file_hash(kconfig_file) with cd(self.cwd): kconf = kconfiglib.Kconfig(kconfig_file) kconf.load_config(config_file) if with_initial_config: experiment = ExploreConfig() shutil.copyfile(config_file, f"{self.cwd}/.config") experiment([ "--config_hash", self.file_hash(config_file), "--kconfig_hash", kconfig_hash, "--project_version", self.git_commit_id(), "--project_root", self.cwd, "--clean_command", self.clean_command, "--build_command", self.build_command, "--attr_command", self.attribute_command, ]) for symbol in kconf.syms.values(): if kconfiglib.TYPE_TO_STR[symbol.type] == "bool": if symbol.tri_value == 0 and 2 in symbol.assignable: logger.debug(f"Set {symbol.name} to y") symbol.set_value(2) self._run_explore_experiment(kconf, kconfig_hash, config_file) elif symbol.tri_value == 2 and 0 in symbol.assignable: logger.debug(f"Set {symbol.name} to n") symbol.set_value(0) self._run_explore_experiment(kconf, kconfig_hash, config_file) elif (kconfiglib.TYPE_TO_STR[symbol.type] == "int" and symbol.visibility and symbol.ranges): for min_val, max_val, condition in symbol.ranges: if kconfiglib.expr_value(condition): min_val = int(min_val.str_value, 0) max_val = int(max_val.str_value, 0) step_size = (max_val - min_val) // 5 if step_size == 0: step_size = 1 for val in range(min_val, max_val + 1, step_size): print(f"Set {symbol.name} to {val}") symbol.set_value(str(val)) self._run_explore_experiment( kconf, kconfig_hash, config_file) break else: continue
def _direct_dep_info(sc): # Returns a string describing the direct dependencies of 'sc' (Symbol or # Choice). The direct dependencies are the OR of the dependencies from each # definition location. The dependencies at each definition location come # from 'depends on' and dependencies inherited from parent items. return "" if sc.direct_dep is _kconfig.y else \ 'Direct dependencies (={}):\n{}\n' \ .format(TRI_TO_STR[expr_value(sc.direct_dep)], _split_expr_info(sc.direct_dep, 2))
def sis(expr, val, title): # sis = selects/implies sis = [si for si in split_expr(expr, OR) if expr_value(si) == val] if not sis: return "" res = title for si in sis: res += " - {}\n".format(split_expr(si, AND)[0].name) return res + "\n"
def node_str(node, indent): """ 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 " " + indent * " " + prompt + " --->" if type(node.item) == Choice: return " " + indent * " " + prompt if node.item == COMMENT: return " " + indent * " " + "*** {} ***".format(prompt) # Symbol sym = node.item if sym.type == UNKNOWN: return "" # {:3} sets the field width to three. Gives nice alignment for empty # string values. res = "{:3} {}{}".format(Menuconfig.value_str(sym), indent * " ", prompt) # Append a sub-menu arrow if menuconfig and enabled if node.is_menuconfig: res += " ---" + (">" if sym.tri_value > 0 else "-") return res
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. """ 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 "" # Add help text if WITH_HELP_DESC: prompt += ' - ' + str(node.help).replace('\n', ' ').replace('\r', '') # {: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 missing_deps(sym): # check_assigned_sym_values() helper for finding unsatisfied dependencies. # # Given direct dependencies # # depends on <expr> && <expr> && ... && <expr> # # on 'sym' (which can also come from e.g. a surrounding 'if'), returns a # list of all <expr>s with a value less than the value 'sym' was assigned # ("less" instead of "not equal" just to be general and handle tristates, # even though Zephyr doesn't use them). # # For string/int/hex symbols, just looks for <expr> = n. # # Note that <expr>s can be something more complicated than just a symbol, # like 'FOO || BAR' or 'FOO = "string"'. deps = split_expr(sym.direct_dep, AND) if sym.type in (BOOL, TRISTATE): return [dep for dep in deps if expr_value(dep) < sym.user_value] # string/int/hex return [dep for dep in deps if expr_value(dep) == 0]
def get_sym_missing_deps(sym): """ Returns an array of strings, where each element is the string representation of the expressions on which `sym` depends and are missing. """ # this splits the top expressions that are connected via AND (&&) # this will be the case, for example, for expressions that are defined in # multiple lines top_deps = kconfiglib.split_expr(sym.direct_dep, kconfiglib.AND) # we only need the expressions that are not met expr = [dep for dep in top_deps if kconfiglib.expr_value(dep) == 0] # convert each expression to strings and add the value for a friendlier # output message expr_str = [] for expr in expr: s = kconfiglib.expr_str(expr) if isinstance(expr, tuple): s = "({})".format(s) expr_str.append("{} (={})".format(s, kconfiglib.TRI_TO_STR[kconfiglib.expr_value(expr)])) return expr_str
def get_active_range(sym): """ Returns a tuple of (low, high) integer values if a range limit is active for this symbol, or (None, None) if no range limit exists. """ base = kconfiglib._TYPE_TO_BASE[sym.orig_type] if sym.orig_type in kconfiglib._TYPE_TO_BASE else 0 try: for low_expr, high_expr, cond in sym.ranges: if kconfiglib.expr_value(cond): low = int(low_expr.str_value, base) if is_base_n(low_expr.str_value, base) else 0 high = int(high_expr.str_value, base) if is_base_n(high_expr.str_value, base) else 0 return (low, high) except ValueError: pass return (None, None)
def get_sym_applying_range(sym): """ Returns the first range that applies to a symbol (the active range) and the condition when it is not a constant. The return value is a tuple holding string representations of the range like so: (low, high, condition) When the condition is a constant (e.g. 'y' when there is no condition) it will be an empty string. """ # multiple ranges could apply to a symbol for rng in sym.ranges: (low, high, cond) = rng # get the first active one if kconfiglib.expr_value(cond): return (low.str_value, high.str_value, "(if {})".format(cond.name) if not cond.is_constant else "") return None
def _add_symbol_if_nontrivial(self, sym, trivialize=True): if sym.__class__ is ExprSymbol and not symbol_can_be_user_assigned( sym.sym): return sympy.true if kconfiglib.expr_value( sym.sym) else sympy.false # If the symbol is aleady satisfied in the current config, # skip it. if trivialize and sym.is_satisfied(): return sympy.true # Return existing symbol if possible for s, sympy_s in self.symbols: if s.__class__ is sym.__class__ is ExprSymbol: if s.sym == sym.sym: return sympy_s # Create new symbol i = len(self.symbols) s = sympy.Symbol(str(i)) self.symbols.append((sym, s)) return s
def save_config(self, filename): kas_includes = [] kas_targets = [] kas_build_system = None kas_vars = {} menu_configuration = {} for symname in self.kconf.syms: if symname == 'MODULES': continue sym = self.kconf.syms[symname] symvalue = sym.str_value if expr_value(sym.direct_dep) < 2: continue if sym.visibility == 2: if sym.type == BOOL: menu_configuration[symname] = symvalue == 'y' elif sym.type == STRING: menu_configuration[symname] = symvalue elif sym.type == INT: menu_configuration[symname] = int(symvalue) elif sym.type == HEX: menu_configuration[symname] = int(symvalue, 16) else: logging.error( 'Configuration variable %s uses unsupported type', symname) sys.exit(1) if symname.startswith('KAS_INCLUDE_'): check_sym_is_string(sym) if symvalue != '': kas_includes.append(symvalue) elif symname.startswith('KAS_TARGET_'): check_sym_is_string(sym) if symvalue != '': kas_targets.append(symvalue) elif symname == 'KAS_BUILD_SYSTEM': check_sym_is_string(sym) if symvalue != '': kas_build_system = symvalue elif sym.type in (STRING, INT, HEX): kas_vars[symname] = symvalue config = { 'header': { 'version': __file_version__, 'includes': kas_includes }, 'menu_configuration': menu_configuration } if kas_build_system: config['build_system'] = kas_build_system if len(kas_targets) > 0: config['target'] = kas_targets if len(kas_vars) > 0: config['local_conf_header'] = { '__menu_config_vars': '\n'.join([ '{} = "{}"'.format(key, value) for key, value in kas_vars.items() ]) } logging.debug('Menu configuration:\n%s', pprint.pformat(config)) if config != self.orig_config: logging.info('Saving configuration as %s', filename) # format multi-line strings more nicely yaml.add_representer(str, str_representer) try: os.rename(filename, filename + '.old') except FileNotFoundError: pass with open(filename, 'w') as config_file: config_file.write('#\n' '# Automatically generated by kas {}\n' '#\n'.format(__version__)) yaml.dump(config, config_file)
def isVisible(node): """Return the visibility state of the node passed as an argument.""" return node.prompt and expr_value(node.prompt[1]) and not \ (node.item == MENU and not expr_value(node.visibility))
def write_node(node): try: json_parent = node_lookup[node.parent]['children'] except KeyError: assert node.parent not in node_lookup # if fails, we have a parent node with no "children" entity (ie a bug) json_parent = result # root level node # node.kconfig.y means node has no dependency, if node.dep is node.kconfig.y: depends = None else: depends = kconfiglib.expr_str(node.dep) try: # node.is_menuconfig is True in newer kconfiglibs for menus and choices as well is_menuconfig = node.is_menuconfig and isinstance(node.item, kconfiglib.Symbol) except AttributeError: is_menuconfig = False new_json = None if node.item == kconfiglib.MENU or is_menuconfig: new_json = {'type': 'menu', 'title': node.prompt[0], 'depends_on': depends, 'children': [], } if is_menuconfig: sym = node.item new_json['name'] = sym.name new_json['help'] = node.help new_json['is_menuconfig'] = is_menuconfig greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one condition which is true for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr): greatest_range = [min_range, max_range] new_json['range'] = greatest_range elif isinstance(node.item, kconfiglib.Symbol): sym = node.item greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one condition which is true for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr): base = 16 if sym.type == kconfiglib.HEX else 10 greatest_range = [int(min_range.str_value, base), int(max_range.str_value, base)] break new_json = { 'type': kconfiglib.TYPE_TO_STR[sym.type], 'name': sym.name, 'title': node.prompt[0] if node.prompt else None, 'depends_on': depends, 'help': node.help, 'range': greatest_range, 'children': [], } elif isinstance(node.item, kconfiglib.Choice): choice = node.item new_json = { 'type': 'choice', 'title': node.prompt[0], 'name': choice.name, 'depends_on': depends, 'help': node.help, 'children': [] } if new_json: node_id = get_menu_node_id(node) if node_id in existing_ids: raise RuntimeError('Config file contains two items with the same id: %s (%s). ' + 'Please rename one of these items to avoid ambiguity.' % (node_id, node.prompt[0])) new_json['id'] = node_id json_parent.append(new_json) node_lookup[node] = new_json
def write_node(node): try: json_parent = node_lookup[node.parent]["children"] except KeyError: assert node.parent not in node_lookup # if fails, we have a parent node with no "children" entity (ie a bug) json_parent = result # root level node # node.kconfig.y means node has no dependency, if node.dep is node.kconfig.y: depends = None else: depends = kconfiglib.expr_str(node.dep) try: is_menuconfig = node.is_menuconfig except AttributeError: is_menuconfig = False new_json = None if node.item == kconfiglib.MENU or is_menuconfig: new_json = { "type": "menu", "title": node.prompt[0], "depends_on": depends, "children": [] } if is_menuconfig: sym = node.item new_json["name"] = sym.name new_json["help"] = node.help new_json["is_menuconfig"] = is_menuconfig greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one result different from value 0 ("n"). for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr) != "n": greatest_range = [min_range, max_range] new_json["range"] = greatest_range elif isinstance(node.item, kconfiglib.Symbol): sym = node.item greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one result different from value 0 ("n"). for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr) != "n": greatest_range = [ int(min_range.str_value), int(max_range.str_value) ] new_json = { "type": kconfiglib.TYPE_TO_STR[sym.type], "name": sym.name, "title": node.prompt[0] if node.prompt else None, "depends_on": depends, "help": node.help, "range": greatest_range, "children": [], } elif isinstance(node.item, kconfiglib.Choice): choice = node.item new_json = { "type": "choice", "title": node.prompt[0], "name": choice.name, "depends_on": depends, "help": node.help, "children": [] } if new_json: json_parent.append(new_json) node_lookup[node] = new_json
def write_menu_item(f, node, visibility): def is_choice(node): """ Skip choice nodes, they are handled as part of the parent (see below) """ return isinstance(node.parent.item, kconfiglib.Choice) if is_choice(node) or not visibility.visible(node): return try: name = node.item.name except AttributeError: name = None is_menu = node_is_menu(node) # Heading if name: title = 'CONFIG_%s' % name else: # if no symbol name, use the prompt as the heading title = node.prompt[0] f.write('.. _%s:\n\n' % get_link_anchor(node)) f.write('%s\n' % title) f.write(HEADING_SYMBOLS[get_heading_level(node)] * len(title)) f.write('\n\n') if name: f.write('%s%s\n\n' % (INDENT, node.prompt[0])) f.write('%s:emphasis:`Found in:` %s\n\n' % (INDENT, get_breadcrumbs(node))) try: if node.help: # Help text normally contains newlines, but spaces at the beginning of # each line are stripped by kconfiglib. We need to re-indent the text # to produce valid ReST. f.write(format_rest_text(node.help, INDENT)) f.write('\n') except AttributeError: pass # No help if isinstance(node.item, kconfiglib.Choice): f.write('%sAvailable options:\n' % INDENT) choice_node = node.list while choice_node: # Format available options as a list f.write('%s- %-20s (%s)\n' % (INDENT * 2, choice_node.prompt[0], choice_node.item.name)) if choice_node.help: HELP_INDENT = INDENT * 2 fmt_help = format_rest_text(choice_node.help, ' ' + HELP_INDENT) f.write('%s \n%s\n' % (HELP_INDENT, fmt_help)) choice_node = choice_node.next f.write('\n\n') if isinstance(node.item, kconfiglib.Symbol): def _expr_str(sc): if sc.is_constant or not sc.nodes or sc.choice: return '{}'.format(sc.name) return ':ref:`%s%s`' % (sc.kconfig.config_prefix, sc.name) range_strs = [] for low, high, cond in node.item.ranges: cond = _minimize_expr(cond, visibility) if cond == kconfiglib.Kconfig.n: continue if not isinstance(cond, tuple) and cond != kconfiglib.Kconfig.y: if len(cond.nodes) > 0 and all(not visibility.visible(i) for i in cond.nodes): if not kconfiglib.expr_value(cond): continue range_str = '%s- from %s to %s' % (INDENT * 2, low.str_value, high.str_value) if cond != kconfiglib.Kconfig.y and not kconfiglib.expr_value( cond): range_str += ' if %s' % kconfiglib.expr_str(cond, _expr_str) range_strs.append(range_str) if len(range_strs) > 0: f.write('%sRange:\n' % INDENT) f.write('\n'.join(range_strs)) f.write('\n\n') default_strs = [] for default, cond in node.item.defaults: cond = _minimize_expr(cond, visibility) if cond == kconfiglib.Kconfig.n: continue if not isinstance(cond, tuple) and cond != kconfiglib.Kconfig.y: if len(cond.nodes) > 0 and all(not visibility.visible(i) for i in cond.nodes): if not kconfiglib.expr_value(cond): continue # default.type is mostly UNKNOWN so it cannot be used reliably for detecting the type d = default.str_value if d in ['y', 'Y']: d = 'Yes (enabled)' elif d in ['n', 'N']: d = 'No (disabled)' elif re.search( r'[^0-9a-fA-F]', d): # simple string detection: if it not a valid number d = '"%s"' % d default_str = '%s- %s' % (INDENT * 2, d) if cond != kconfiglib.Kconfig.y and not kconfiglib.expr_value( cond): default_str += ' if %s' % kconfiglib.expr_str(cond, _expr_str) default_strs.append(default_str) if len(default_strs) > 0: f.write('%sDefault value:\n' % INDENT) f.write('\n'.join(default_strs)) f.write('\n\n') if is_menu: # enumerate links to child items child_list = [] child = node.list while child: if not is_choice(child) and child.prompt and visibility.visible( child): child_list.append((child.prompt[0], get_link_anchor(child))) child = child.next if len(child_list) > 0: f.write('Contains:\n\n') sorted_child_list = sorted(child_list, key=lambda pair: pair[0].lower()) ref_list = [ '- :ref:`{}`'.format(anchor) for _, anchor in sorted_child_list ] f.write('\n'.join(ref_list)) f.write('\n\n')
def is_constant_y(sym): return sym.is_constant and (kconfiglib.expr_value(sym) == 2)
def expr_value_bool(expr): """ Evaluates the given expression using kconfiglib.expr_value(expr) and converts the result to a boolean value using tri_to_bool(). """ return tri_to_bool(kconfiglib.expr_value(expr))
def write_node(node): try: json_parent = node_lookup[node.parent]["children"] except KeyError: assert node.parent not in node_lookup # if fails, we have a parent node with no "children" entity (ie a bug) json_parent = result # root level node # node.kconfig.y means node has no dependency, if node.dep is node.kconfig.y: depends = None else: depends = kconfiglib.expr_str(node.dep) try: is_menuconfig = node.is_menuconfig except AttributeError: is_menuconfig = False new_json = None if node.item == kconfiglib.MENU or is_menuconfig: new_json = { "type": "menu", "title": node.prompt[0], "depends_on": depends, "children": [], } if is_menuconfig: sym = node.item new_json["name"] = sym.name new_json["help"] = node.help new_json["is_menuconfig"] = is_menuconfig greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one condition which is true for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr): greatest_range = [min_range, max_range] new_json["range"] = greatest_range elif isinstance(node.item, kconfiglib.Symbol): sym = node.item greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one condition which is true for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr): base = 16 if sym.type == kconfiglib.HEX else 10 greatest_range = [ int(min_range.str_value, base), int(max_range.str_value, base) ] break new_json = { "type": kconfiglib.TYPE_TO_STR[sym.type], "name": sym.name, "title": node.prompt[0] if node.prompt else None, "depends_on": depends, "help": node.help, "range": greatest_range, "children": [], } elif isinstance(node.item, kconfiglib.Choice): choice = node.item new_json = { "type": "choice", "title": node.prompt[0], "name": choice.name, "depends_on": depends, "help": node.help, "children": [] } if new_json: node_id = get_menu_node_id(node) if node_id in existing_ids: raise RuntimeError( "Config file contains two items with the same id: %s (%s). " + "Please rename one of these items to avoid ambiguity." % (node_id, node.prompt[0])) new_json["id"] = node_id json_parent.append(new_json) node_lookup[node] = new_json
def write_node(node): try: json_parent = node_lookup[node.parent]["children"] except KeyError: assert node.parent not in node_lookup # if fails, we have a parent node with no "children" entity (ie a bug) json_parent = result # root level node # node.kconfig.y means node has no dependency, if node.dep is node.kconfig.y: depends = None else: depends = kconfiglib.expr_str(node.dep) try: is_menuconfig = node.is_menuconfig except AttributeError: is_menuconfig = False new_json = None if node.item == kconfiglib.MENU or is_menuconfig: new_json = {"type": "menu", "title": node.prompt[0], "depends_on": depends, "children": [], } if is_menuconfig: sym = node.item new_json["name"] = sym.name new_json["help"] = node.help new_json["is_menuconfig"] = is_menuconfig greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one condition which is true for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr): greatest_range = [min_range, max_range] new_json["range"] = greatest_range elif isinstance(node.item, kconfiglib.Symbol): sym = node.item greatest_range = None if len(sym.ranges) > 0: # Note: Evaluating the condition using kconfiglib's expr_value # should have one condition which is true for min_range, max_range, cond_expr in sym.ranges: if kconfiglib.expr_value(cond_expr): greatest_range = [int(min_range.str_value), int(max_range.str_value)] new_json = { "type": kconfiglib.TYPE_TO_STR[sym.type], "name": sym.name, "title": node.prompt[0] if node.prompt else None, "depends_on": depends, "help": node.help, "range": greatest_range, "children": [], } elif isinstance(node.item, kconfiglib.Choice): choice = node.item new_json = { "type": "choice", "title": node.prompt[0], "name": choice.name, "depends_on": depends, "help": node.help, "children": [] } if new_json: node_id = get_menu_node_id(node) if node_id in existing_ids: raise RuntimeError("Config file contains two items with the same id: %s (%s). " + "Please rename one of these items to avoid ambiguity." % (node_id, node.prompt[0])) new_json["id"] = node_id json_parent.append(new_json) node_lookup[node] = new_json