def defaults_rst(sc): # Returns RST that lists the 'default' properties of 'sc' (symbol or # choice) if isinstance(sc, kconfiglib.Symbol) and sc.choice: # 'default's on choice symbols have no effect (and generate a warning). # The implicit value hint below would be misleading as well. return "" rst = "Defaults\n" \ "========\n\n" if sc.defaults: for value, cond in sc.defaults: rst += "- " + kconfiglib.expr_str(value) if cond is not sc.kconfig.y: rst += " if " + kconfiglib.expr_str(cond) rst += "\n" else: rst += "No defaults. Implicitly defaults to " if isinstance(sc, kconfiglib.Choice): rst += "the first (visible) choice option.\n" elif sc.orig_type in (kconfiglib.BOOL, kconfiglib.TRISTATE): rst += "``n``.\n" else: # This is accurate even for int/hex symbols, though an active # 'range' might clamp the value (which is then treated as zero) rst += "the empty string.\n" return rst + "\n"
def defaults_rst(sc): # Returns RST that lists the 'default' properties of 'sc' (symbol or # choice) if isinstance(sc, kconfiglib.Symbol) and sc.choice: # 'default's on choice symbols have no effect (and generate a warning). # The implicit value hint below would be misleading as well. return "" rst = "Defaults\n" \ "========\n\n" if sc.defaults: for value, cond in sc.defaults: rst += "- " + kconfiglib.expr_str(value) if cond is not sc.kconfig.y: rst += " if " + kconfiglib.expr_str(cond) rst += "\n" else: rst += "No defaults. Implicitly defaults to " if isinstance(sc, kconfiglib.Choice): rst += "the first (visible) choice option.\n" elif sc.orig_type in (kconfiglib.BOOL, kconfiglib.TRISTATE): rst += "``n``.\n" else: # This is accurate even for int/hex symbols, though an active # 'range' might clamp the value (which is then treated as zero) rst += "the empty string.\n" return rst + "\n"
def show_help(self): node = self.node item = self.node.item if isinstance(item, (kconfiglib.Symbol, kconfiglib.Choice)): title = 'Help for symbol: {}'.format(item.name) if node.help: help = node.help else: help = 'There is no help available for this option.\n' lines = [] lines.append(help) lines.append( 'Symbol: {} [={}]'.format( item.name if item.name else '<UNNAMED>', item.str_value ) ) lines.append('Type : {}'.format(kconfiglib.TYPE_TO_STR[item.type])) for n in item.nodes: lines.append('Prompt: {}'.format(n.prompt[0] if n.prompt else '<EMPTY>')) lines.append(' Defined at {}:{}'.format(n.filename, n.linenr)) lines.append(' Depends on: {}'.format(kconfiglib.expr_str(n.dep))) text = '\n'.join(lines) else: title = 'Help' text = 'Help not available for this menu node.\n' self.menuconfig.show_text(text, title) self.menuconfig.refresh_display()
def defaults_rst(sc): # Returns RST that lists the 'default' properties of 'sc' (symbol or # choice) if not sc.defaults: return "" rst = "Defaults\n" \ "========\n\n" for value, cond in sc.defaults: default_str = kconfiglib.expr_str(value) if cond is not sc.kconfig.y: default_str += " if " + kconfiglib.expr_str(cond) rst += "- {}\n".format(default_str) return rst + "\n"
def direct_deps_rst(sc): # Returns RST that lists the direct dependencies of 'sc' (symbol or choice) if sc.direct_dep is sc.kconfig.y: return "" return "Direct dependencies\n" \ "===================\n\n" \ "{}\n\n" \ "*(Includes any dependencies from if's and menus.)*\n\n" \ .format(kconfiglib.expr_str(sc.direct_dep))
def direct_deps_rst(sc): # Returns RST that lists the direct dependencies of 'sc' (symbol or choice) if sc.direct_dep is sc.kconfig.y: return "" return "Direct dependencies\n" \ "===================\n\n" \ "{}\n\n" \ "*(Includes any dependencies from if's and menus.)*\n\n" \ .format(kconfiglib.expr_str(sc.direct_dep))
def choice_syms_rst(choice): # Returns RST that lists the symbols contained in the choice if not choice.syms: return "" rst = "Choice options\n" \ "==============\n\n" for sym in choice.syms: # Generates a link rst += "- {}\n".format(kconfiglib.expr_str(sym)) return rst + "\n"
def choice_syms_rst(choice): # Returns RST that lists the symbols contained in the choice if not choice.syms: return "" rst = "Choice options\n" \ "==============\n\n" for sym in choice.syms: # Generates a link rst += "- {}\n".format(kconfiglib.expr_str(sym)) return rst + "\n"
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): cond_str = "" if cond is not sym.kconfig.y: cond_str = "if {}".format(kconfiglib.expr_str(cond)) return (low.str_value, high.str_value, cond_str) return None
def _dependencies_to_bdd_expr(self, depends_on): depends_on = kconfiglib.expr_str(depends_on) choices = list() for match in re.finditer(r"<choice ([^>]+)>", depends_on): choices.append(match.group(1)) for choice in choices: depends_on = re.sub(f"<choice {choice}>", f"_choice_{choice}", depends_on) depends_on = re.sub("&& <([^>]+)>", "", depends_on) if depends_on == "n" or depends_on == "m" or depends_on == "y": return None depends_on = re.sub("&&", "&", depends_on) depends_on = re.sub("\|\|", "|", depends_on) return depends_on
def check_assigned_sym_values(kconf): # Verifies that the values assigned to symbols "took" (matches the value # the symbols actually got), printing warnings otherwise. Choice symbols # are checked separately, in check_assigned_choice_values(). for sym in kconf.unique_defined_syms: if sym.choice: continue user_value = sym.user_value if user_value is None: continue # 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[user_value] if user_value != sym.str_value: msg = f"{sym.name_and_loc} was assigned the value '{user_value}' " \ f"but got the value '{sym.str_value}'. " # List any unsatisfied 'depends on' dependencies in the warning mdeps = missing_deps(sym) if mdeps: expr_strs = [] for expr in mdeps: estr = expr_str(expr) if isinstance(expr, tuple): # Add () around dependencies that aren't plain symbols. # Gives '(FOO || BAR) (=n)' instead of # 'FOO || BAR (=n)', which might be clearer. estr = f"({estr})" expr_strs.append( f"{estr} (={TRI_TO_STR[expr_value(expr)]})") msg += "Check these unsatisfied dependencies: " + \ ", ".join(expr_strs) + ". " warn(msg + SYM_INFO_HINT.format(sym))
def add_select_imply_rst(type_str, expr): # Writes a link for each selecting symbol (if 'expr' is sym.rev_dep) or # each implying symbol (if 'expr' is sym.weak_rev_dep). Also adds a # heading at the top, derived from type_str ("select"/"imply"). nonlocal rst heading = "Symbols that ``{}`` this symbol".format(type_str) rst += "{}\n{}\n\n".format(heading, len(heading)*"=") # The reverse dependencies from each select/imply are ORed together for select in kconfiglib.split_expr(expr, kconfiglib.OR): # - 'select/imply A if B' turns into A && B # - 'select/imply A' just turns into A # # In both cases, we can split on AND and pick the first # operand. # kconfiglib.expr_str() generates a link rst += "- {}\n".format(kconfiglib.expr_str( kconfiglib.split_expr(select, kconfiglib.AND)[0])) rst += "\n"
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 add_select_imply_rst(type_str, expr): # Writes a link for each selecting symbol (if 'expr' is sym.rev_dep) or # each implying symbol (if 'expr' is sym.weak_rev_dep). Also adds a # heading at the top, derived from type_str ("select"/"imply"). nonlocal rst heading = "Symbols that ``{}`` this symbol".format(type_str) rst += "{}\n{}\n\n".format(heading, len(heading)*"=") # The reverse dependencies from each select/imply are ORed together for select in kconfiglib.split_expr(expr, kconfiglib.OR): # - 'select/imply A if B' turns into A && B # - 'select/imply A' just turns into A # # In both cases, we can split on AND and pick the first # operand. # kconfiglib.expr_str() generates a link rst += "- {}\n".format(kconfiglib.expr_str( kconfiglib.split_expr(select, kconfiglib.AND)[0])) rst += "\n"
def kconfig_build_resources(app: Sphinx) -> None: """Build the Kconfig database and install HTML resources.""" if not app.config.kconfig_generate_db: return with progress_message("Building Kconfig database..."): kconfig, module_paths = kconfig_load(app) db = list() for sc in chain(kconfig.unique_defined_syms, kconfig.unique_choices): # skip nameless symbols if not sc.name: continue # store alternative defaults (from defconfig files) alt_defaults = list() for node in sc.nodes: if "defconfig" not in node.filename: continue for value, cond in node.orig_defaults: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" alt_defaults.append([fmt, node.filename]) # build list of symbols that select/imply the current one # note: all reverse dependencies are ORed together, and conditionals # (e.g. select/imply A if B) turns into A && B. So we first split # by OR to include all entries, and we split each one by AND to just # take the first entry. selected_by = list() if isinstance(sc, kconfiglib.Symbol) and sc.rev_dep != sc.kconfig.n: for select in kconfiglib.split_expr(sc.rev_dep, kconfiglib.OR): sym = kconfiglib.split_expr(select, kconfiglib.AND)[0] selected_by.append(f"CONFIG_{sym.name}") implied_by = list() if isinstance( sc, kconfiglib.Symbol) and sc.weak_rev_dep != sc.kconfig.n: for select in kconfiglib.split_expr(sc.weak_rev_dep, kconfiglib.OR): sym = kconfiglib.split_expr(select, kconfiglib.AND)[0] implied_by.append(f"CONFIG_{sym.name}") # only process nodes with prompt or help nodes = [node for node in sc.nodes if node.prompt or node.help] inserted_paths = list() for node in nodes: # avoid duplicate symbols by forcing unique paths. this can # happen due to dependencies on 0, a trick used by some modules path = f"{node.filename}:{node.linenr}" if path in inserted_paths: continue inserted_paths.append(path) dependencies = None if node.dep is not sc.kconfig.y: dependencies = kconfiglib.expr_str(node.dep, sc_fmt) defaults = list() for value, cond in node.orig_defaults: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" defaults.append(fmt) selects = list() for value, cond in node.orig_selects: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" selects.append(fmt) implies = list() for value, cond in node.orig_implies: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" implies.append(fmt) ranges = list() for min, max, cond in node.orig_ranges: fmt = (f"[{kconfiglib.expr_str(min, sc_fmt)}, " f"{kconfiglib.expr_str(max, sc_fmt)}]") if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" ranges.append(fmt) choices = list() if isinstance(sc, kconfiglib.Choice): for sym in sc.syms: choices.append(kconfiglib.expr_str(sym, sc_fmt)) menupath = "" iternode = node while iternode.parent is not iternode.kconfig.top_node: iternode = iternode.parent menupath = f" > {iternode.prompt[0]}" + menupath menupath = "(Top)" + menupath filename = node.filename for name, path in module_paths.items(): if node.filename.startswith(path): filename = node.filename.replace( path, f"<module:{name}>") break db.append({ "name": f"CONFIG_{sc.name}", "prompt": node.prompt[0] if node.prompt else None, "type": kconfiglib.TYPE_TO_STR[sc.type], "help": node.help, "dependencies": dependencies, "defaults": defaults, "alt_defaults": alt_defaults, "selects": selects, "selected_by": selected_by, "implies": implies, "implied_by": implied_by, "ranges": ranges, "choices": choices, "filename": filename, "linenr": node.linenr, "menupath": menupath, }) app.env.kconfig_db = db # type: ignore outdir = Path(app.outdir) / "kconfig" outdir.mkdir(exist_ok=True) kconfig_db_file = outdir / "kconfig.json" with open(kconfig_db_file, "w") as f: json.dump(db, f) app.config.html_extra_path.append(kconfig_db_file.as_posix()) app.config.html_static_path.append(RESOURCES_DIR.as_posix())
def write_sym_rst(sym, out_dir): # Writes documentation for 'sym' to <out_dir>/CONFIG_<sym.name>.rst kconf = sym.kconfig with open(os.path.join(out_dir, "CONFIG_{}.rst".format(sym.name)), "w") as sym_rst: # List all prompts on separate lines prompt_str = "\n\n".join("*{}*".format(node.prompt[0].strip()) for node in sym.nodes if node.prompt) \ or "*(No prompt -- not directly user assignable.)*" # - :orphan: suppresses warnings for the symbol RST files not being # included in any toctree # # - '.. title::' sets the title of the document (e.g. <title>). This # seems to be poorly documented at the moment. sym_rst.write(":orphan:\n\n" ".. title:: {0}\n\n" ".. option:: CONFIG_{0}\n\n" "{1}\n\n" "Type: ``{2}``\n\n".format( sym.name, prompt_str, kconfiglib.TYPE_TO_STR[sym.type])) # Symbols with multiple definitions can have multiple help texts for node in sym.nodes: if node.help is not None: sym_rst.write("Help\n" "====\n\n" "{}\n\n".format(node.help)) if sym.direct_dep is not kconf.y: sym_rst.write( "Direct dependencies\n" "===================\n\n" "{}\n\n" "*(Includes any dependencies from if's and menus.)*\n\n". format(kconfiglib.expr_str(sym.direct_dep))) if sym.defaults: sym_rst.write("Defaults\n" "========\n\n") for value, cond in sym.defaults: default_str = kconfiglib.expr_str(value) if cond is not kconf.y: default_str += " if " + kconfiglib.expr_str(cond) sym_rst.write(" - {}\n".format(default_str)) sym_rst.write("\n") def write_select_imply_rst(expr): # Writes a link for each selecting symbol (if 'expr' is # sym.rev_dep) or each implying symbol (if 'expr' is # sym.weak_rev_dep) # The reverse dependencies from each select/imply are ORed together for select in kconfiglib.split_expr(expr, kconfiglib.OR): # - 'select/imply A if B' turns into A && B # - 'select/imply A' just turns into A # # In both cases, we can split on AND and pick the first # operand. sym_rst.write(" - :option:`CONFIG_{}`\n".format( kconfiglib.split_expr(select, kconfiglib.AND)[0].name)) sym_rst.write("\n") if sym.rev_dep is not kconf.n: sym_rst.write("Symbols that ``select`` this symbol\n" "===================================\n\n") write_select_imply_rst(sym.rev_dep) if sym.weak_rev_dep is not kconf.n: sym_rst.write("Symbols that ``imply`` this symbol\n" "==================================\n\n") write_select_imply_rst(sym.weak_rev_dep) # parsed-literal supports links in the definition sym_rst.write( "Kconfig definition\n" "==================\n\n" ".. parsed-literal::\n\n" "{}\n\n" "*(Includes propagated dependencies, including from if's and menus.)*\n\n" .format(textwrap.indent(str(sym), " " * 4))) sym_rst.write("Definition location{}\n" "====================\n\n" "{}".format( "s" if len(sym.nodes) > 1 else "", "\n".join( " - ``{}:{}``".format(node.filename, node.linenr) for node in sym.nodes)))
def _expr_str(expr): # Custom expression printer that shows symbol values return expr_str(expr, _name_and_val_str)
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 kconfig_build_resources(app: Sphinx) -> None: """Build the Kconfig database and install HTML resources.""" if not app.config.kconfig_generate_db: return with progress_message("Building Kconfig database..."): kconfig, module_paths = kconfig_load(app) db = list() for sc in chain(kconfig.unique_defined_syms, kconfig.unique_choices): # skip nameless symbols if not sc.name: continue # store alternative defaults (from defconfig files) alt_defaults = list() for node in sc.nodes: if "defconfig" not in node.filename: continue for value, cond in node.orig_defaults: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" alt_defaults.append([fmt, node.filename]) # only process nodes with prompt or help nodes = [node for node in sc.nodes if node.prompt or node.help] inserted_paths = list() for node in nodes: # avoid duplicate symbols by forcing unique paths. this can # happen due to dependencies on 0, a trick used by some modules path = f"{node.filename}:{node.linenr}" if path in inserted_paths: continue inserted_paths.append(path) dependencies = None if node.dep is not sc.kconfig.y: dependencies = kconfiglib.expr_str(node.dep, sc_fmt) defaults = list() for value, cond in node.orig_defaults: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" defaults.append(fmt) selects = list() for value, cond in node.orig_selects: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" selects.append(fmt) implies = list() for value, cond in node.orig_implies: fmt = kconfiglib.expr_str(value, sc_fmt) if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" implies.append(fmt) ranges = list() for min, max, cond in node.orig_ranges: fmt = (f"[{kconfiglib.expr_str(min, sc_fmt)}, " f"{kconfiglib.expr_str(max, sc_fmt)}]") if cond is not sc.kconfig.y: fmt += f" if {kconfiglib.expr_str(cond, sc_fmt)}" ranges.append(fmt) choices = list() if isinstance(sc, kconfiglib.Choice): for sym in sc.syms: choices.append(kconfiglib.expr_str(sym, sc_fmt)) filename = node.filename for name, path in module_paths.items(): if node.filename.startswith(path): filename = node.filename.replace( path, f"<module:{name}>") break db.append({ "name": f"CONFIG_{sc.name}", "prompt": node.prompt[0] if node.prompt else None, "type": kconfiglib.TYPE_TO_STR[sc.type], "help": node.help, "dependencies": dependencies, "defaults": defaults, "alt_defaults": alt_defaults, "selects": selects, "implies": implies, "ranges": ranges, "choices": choices, "filename": filename, "linenr": node.linenr, }) app.env.kconfig_db = db # type: ignore outdir = Path(app.outdir) / "kconfig" outdir.mkdir(exist_ok=True) kconfig_db_file = outdir / "kconfig.json" with open(kconfig_db_file, "w") as f: json.dump(db, f) app.config.html_extra_path.append(kconfig_db_file.as_posix()) app.config.html_static_path.append(RESOURCES_DIR.as_posix())
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 expr_str(expr): # Returns the Kconfig representation of 'expr', with symbols/choices turned # into RST links return kconfiglib.expr_str(expr, rst_link)
def write_sym_rst(sym, out_dir): # Writes documentation for 'sym' to <out_dir>/CONFIG_<sym.name>.rst kconf = sym.kconfig # List all prompts on separate lines prompt_str = "\n\n".join("*{}*".format(node.prompt[0]) for node in sym.nodes if node.prompt) \ or "*(No prompt -- not directly user assignable.)*" # String with the RST for the symbol page # # - :orphan: suppresses warnings for the symbol RST files not being # included in any toctree # # - '.. title::' sets the title of the document (e.g. <title>). This seems # to be poorly documented at the moment. sym_rst = ":orphan:\n\n" \ ".. title:: {0}\n\n" \ ".. option:: CONFIG_{0}\n\n" \ "{1}\n\n" \ "Type: ``{2}``\n\n" \ .format(sym.name, prompt_str, kconfiglib.TYPE_TO_STR[sym.type]) # Symbols with multiple definitions can have multiple help texts for node in sym.nodes: if node.help is not None: sym_rst += "Help\n" \ "====\n\n" \ "{}\n\n" \ .format(node.help) if sym.direct_dep is not kconf.y: sym_rst += "Direct dependencies\n" \ "===================\n\n" \ "{}\n\n" \ "*(Includes any dependencies from if's and menus.)*\n\n" \ .format(kconfiglib.expr_str(sym.direct_dep)) if sym.defaults: sym_rst += "Defaults\n" \ "========\n\n" for value, cond in sym.defaults: default_str = kconfiglib.expr_str(value) if cond is not kconf.y: default_str += " if " + kconfiglib.expr_str(cond) sym_rst += " - {}\n".format(default_str) sym_rst += "\n" def add_select_imply_rst(type_str, expr): # Writes a link for each selecting symbol (if 'expr' is sym.rev_dep) or # each implying symbol (if 'expr' is sym.weak_rev_dep). Also adds a # heading at the top, derived from type_str ("select"/"imply"). nonlocal sym_rst heading = "Symbols that ``{}`` this symbol".format(type_str) sym_rst += "{}\n{}\n\n".format(heading, len(heading) * "=") # The reverse dependencies from each select/imply are ORed together for select in kconfiglib.split_expr(expr, kconfiglib.OR): # - 'select/imply A if B' turns into A && B # - 'select/imply A' just turns into A # # In both cases, we can split on AND and pick the first # operand. sym_rst += " - :option:`CONFIG_{}`\n".format( kconfiglib.split_expr(select, kconfiglib.AND)[0].name) sym_rst += "\n" if sym.rev_dep is not kconf.n: add_select_imply_rst("select", sym.rev_dep) if sym.weak_rev_dep is not kconf.n: add_select_imply_rst("imply", sym.weak_rev_dep) def menu_path(node): path = "" while True: # This excludes indented submenus created in the menuconfig # interface when items depend on the preceding symbol. # is_menuconfig means anything that would be shown as a separate # menu (not indented): proper 'menu's, menuconfig symbols, and # choices. node = node.parent while not node.is_menuconfig: node = node.parent if node is kconf.top_node: break # Fancy Unicode arrow. Added in '93, so ought to be pretty safe. path = " → " + node.prompt[0] + path return "(top menu)" + path heading = "Kconfig definition" if len(sym.nodes) > 1: heading += "s" sym_rst += "{}\n{}\n\n".format(heading, len(heading) * "=") sym_rst += "\n\n".join( "At ``{}:{}``, in menu ``{}``:\n\n" ".. parsed-literal::\n\n" "{}".format(node.filename, node.linenr, menu_path(node), textwrap.indent(str(node), " " * 4)) for node in sym.nodes) sym_rst += "\n\n*(Definitions include propagated dependencies, " \ "including from if's and menus.)*" write_if_updated(os.path.join(out_dir, "CONFIG_{}.rst".format(sym.name)), sym_rst)
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_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 expr_str(expr): # Returns the Kconfig representation of 'expr', with symbols/choices turned # into RST links return kconfiglib.expr_str(expr, rst_link)
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