diff --git a/doc/_extensions/zephyr/kconfig/__init__.py b/doc/_extensions/zephyr/kconfig/__init__.py index 6bf828a9088..eb1903b1898 100644 --- a/doc/_extensions/zephyr/kconfig/__init__.py +++ b/doc/_extensions/zephyr/kconfig/__init__.py @@ -70,19 +70,24 @@ ZEPHYR_BASE = Path(__file__).parents[4] -def kconfig_load(app: Sphinx) -> tuple[kconfiglib.Kconfig, dict[str, str]]: +def kconfig_load(app: Sphinx) -> tuple[kconfiglib.Kconfig, kconfiglib.Kconfig, dict[str, str]]: """Load Kconfig""" with TemporaryDirectory() as td: modules = zephyr_module.parse_modules(ZEPHYR_BASE) # generate Kconfig.modules file kconfig = "" + sysbuild_kconfig = "" for module in modules: kconfig += zephyr_module.process_kconfig(module.project, module.meta) + sysbuild_kconfig += zephyr_module.process_sysbuildkconfig(module.project, module.meta) with open(Path(td) / "Kconfig.modules", "w") as f: f.write(kconfig) + with open(Path(td) / "Kconfig.sysbuild.modules", "w") as f: + f.write(sysbuild_kconfig) + # generate dummy Kconfig.dts file kconfig = "" @@ -145,6 +150,13 @@ def kconfig_load(app: Sphinx) -> tuple[kconfiglib.Kconfig, dict[str, str]]: os.environ["BOARD"] = "boards" os.environ["KCONFIG_BOARD_DIR"] = str(Path(td) / "boards") + # Sysbuild runs first + os.environ["CONFIG_"] = "SB_CONFIG_" + sysbuild_output = kconfiglib.Kconfig(ZEPHYR_BASE / "share" / "sysbuild" / "Kconfig") + + # Normal Kconfig runs second + os.environ["CONFIG_"] = "CONFIG_" + # insert external Kconfigs to the environment module_paths = dict() for module in modules: @@ -172,7 +184,7 @@ def kconfig_load(app: Sphinx) -> tuple[kconfiglib.Kconfig, dict[str, str]]: if kconfig.exists(): os.environ[f"ZEPHYR_{name_var}_KCONFIG"] = str(kconfig) - return kconfiglib.Kconfig(ZEPHYR_BASE / "Kconfig"), module_paths + return kconfiglib.Kconfig(ZEPHYR_BASE / "Kconfig"), sysbuild_output, module_paths class KconfigSearchNode(nodes.Element): @@ -332,13 +344,15 @@ def add_option(self, option): def sc_fmt(sc): + prefix = os.environ["CONFIG_"] + if isinstance(sc, kconfiglib.Symbol): if sc.nodes: - return f'CONFIG_{sc.name}' + return f'{prefix}{sc.name}' elif isinstance(sc, kconfiglib.Choice): if not sc.name: return "<choice>" - return f'<choice CONFIG_{sc.name}>' + return f'<choice {prefix}{sc.name}>' return kconfiglib.standard_sc_expr_str(sc) @@ -350,137 +364,139 @@ def kconfig_build_resources(app: Sphinx) -> None: return with progress_message("Building Kconfig database..."): - kconfig, module_paths = kconfig_load(app) + kconfig, sysbuild_kconfig, module_paths = kconfig_load(app) db = list() - for sc in sorted( - chain(kconfig.unique_defined_syms, kconfig.unique_choices), - key=lambda sc: sc.name if sc.name else "", - ): - # 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: + for kconfig_obj in [kconfig, sysbuild_kconfig]: + os.environ["CONFIG_"] = kconfig_obj.config_prefix + for sc in sorted( + chain(kconfig_obj.unique_defined_syms, kconfig_obj.unique_choices), + key=lambda sc: sc.name if sc.name else "", + ): + # skip nameless symbols + if not sc.name: 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)}]" + # store alternative defaults (from defconfig files) + alt_defaults = list() + for node in sc.nodes: + if "defconfig" not in str(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"{kconfig_obj.config_prefix}{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"{kconfig_obj.config_prefix}{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 + if iternode.prompt: + title = iternode.prompt[0] + else: + title = kconfiglib.standard_sc_expr_str(iternode.item) + menupath = f" > {title}" + menupath + + menupath = "(Top)" + menupath + + filename = str(node.filename) + for name, path in module_paths.items(): + path += "/" + if str(node.filename).startswith(path): + filename = str(node.filename).replace(path, f"/") + break + + db.append( + { + "name": f"{kconfig_obj.config_prefix}{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, + } ) - 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 - if iternode.prompt: - title = iternode.prompt[0] - else: - title = kconfiglib.standard_sc_expr_str(iternode.item) - menupath = f" > {title}" + menupath - - menupath = "(Top)" + menupath - - filename = node.filename - for name, path in module_paths.items(): - path += "/" - if node.filename.startswith(path): - filename = node.filename.replace(path, f"/") - 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