def add_selecting_implying_rst(type_str, expr): # Writes a link for each symbol that selects the symbol (if 'expr' is # sym.rev_dep) or each symbol that imply's the symbol (if 'expr' is # sym.weak_rev_dep). Also adds a heading at the top derived from # type_str ("select"/"imply"), if there are any selecting/implying # symbols. nonlocal rst if expr is not sym.kconfig.n: 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. rst += "- {}\n".format(rst_link( kconfiglib.split_expr(select, kconfiglib.AND)[0])) rst += "\n"
def add_selecting_implying_rst(type_str, expr): # Writes a link for each symbol that selects the symbol (if 'expr' is # sym.rev_dep) or each symbol that imply's the symbol (if 'expr' is # sym.weak_rev_dep). Also adds a heading at the top derived from # type_str ("select"/"imply"), if there are any selecting/implying # symbols. nonlocal rst if expr is not sym.kconfig.n: 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. rst += "- {}\n".format( rst_link(kconfiglib.split_expr(select, kconfiglib.AND)[0])) rst += "\n"
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 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 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 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 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")
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"
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 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 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 try_fix_deps(symbols): while symbols: check = set() for sym in symbols: if deps_met(sym): continue deps = K.split_expr(sym.direct_dep, K.AND) for dep in deps: if isinstance(dep, tuple): if dep[0] == K.EQUAL and (dep[1].is_constant or dep[2].is_constant): if dep[1].is_constant: tgt, src = dep[2], dep[1] else: tgt, src = dep[1], dep[2] v_old = K.TRI_TO_STR[user_value(tgt)] v_new = K.TRI_TO_STR[user_value(src)] eprint( f"warning: Changing symbol value: {tgt.name} {v_old} -> {v_new}" ) tgt.set_value(user_value(src)) check.add(tgt) continue if dep[0] == K.UNEQUAL and (dep[1].is_constant or dep[2].is_constant): if dep[1].is_constant: tgt, src = dep[2], dep[1] else: tgt, src = dep[1], dep[2] values = set([1, 2]) # assume that 'n' is not an option values.remove(user_value(src)) value = (*values, )[0] v_old = K.TRI_TO_STR[user_value(tgt)] v_new = K.TRI_TO_STR[value] eprint( f"warning: Changing symbol value: {tgt.name} {v_old} -> {v_new}" ) tgt.set_value(value) check.add(tgt) continue eprint( "error: Cannot fix dependencies: Complex dependency statements not suppoted" ) eprint(f" On symbol: {sym.name}") exit(1) if dep_satisfied(sym, dep): continue if dep.orig_type not in K._BOOL_TRISTATE: eprint( "error: Cannot fix dependencies: Non-boolean and non-tristate dependency" ) if dep.orig_type == K.TRISTATE and sym.orig_type == K.TRISTATE: value = user_value(sym) # copy tristate value elif dep.orig_type == K.TRISTATE: value = 1 # prefer M for bool dependents else: value = 2 # set bool dependency to Y if value > user_value(dep): v_old = K.TRI_TO_STR[user_value(dep)] v_new = K.TRI_TO_STR[value] eprint( f"warning: Changing symbol value: {dep.name} {v_old} -> {v_new}" ) dep.set_value(value) check.add(dep) symbols = check