Ejemplo n.º 1
0
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"
Ejemplo n.º 2
0
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"
Ejemplo n.º 3
0
 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()
Ejemplo n.º 4
0
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"
Ejemplo n.º 5
0
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))
Ejemplo n.º 6
0
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))
Ejemplo n.º 7
0
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"
Ejemplo n.º 8
0
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"
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
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))
Ejemplo n.º 12
0
    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"
Ejemplo n.º 13
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
Ejemplo n.º 14
0
    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"
Ejemplo n.º 15
0
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())
Ejemplo n.º 16
0
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)))
Ejemplo n.º 17
0
def _expr_str(expr):
    # Custom expression printer that shows symbol values
    return expr_str(expr, _name_and_val_str)
Ejemplo n.º 18
0
    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
Ejemplo n.º 19
0
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())
Ejemplo n.º 20
0
    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
Ejemplo n.º 21
0
def expr_str(expr):
    # Returns the Kconfig representation of 'expr', with symbols/choices turned
    # into RST links

    return kconfiglib.expr_str(expr, rst_link)
Ejemplo n.º 22
0
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)
Ejemplo n.º 23
0
    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
Ejemplo n.º 24
0
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')
Ejemplo n.º 25
0
def expr_str(expr):
    # Returns the Kconfig representation of 'expr', with symbols/choices turned
    # into RST links

    return kconfiglib.expr_str(expr, rst_link)
Ejemplo n.º 26
0
    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