846 lines
26 KiB
Python
Executable File
846 lines
26 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Generates an alphabetical index of Kconfig symbols with links in index.rst, and
|
|
a separate CONFIG_FOO.rst file for each Kconfig symbol.
|
|
|
|
The generated symbol pages can be referenced in RST as :kconfig:`foo`, and the
|
|
generated index page as `configuration options`_.
|
|
|
|
Optionally, the documentation can be split up based on where symbols are
|
|
defined. See the --modules flag.
|
|
"""
|
|
|
|
import argparse
|
|
import collections
|
|
from operator import attrgetter
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
import textwrap
|
|
|
|
import kconfiglib
|
|
|
|
import gen_helpers
|
|
|
|
NO_MAX_WIDTH = """
|
|
|
|
.. raw:: html
|
|
|
|
<!--
|
|
FIXME: do not limit page width until content uses another representation
|
|
format other than tables
|
|
-->
|
|
<style>.wy-nav-content { max-width: none; !important }</style>
|
|
|
|
"""
|
|
|
|
def escape_inline_rst(text):
|
|
# Escape reStructuredText inline markup characters
|
|
return re.sub(r"(\*|_|`)", r"\\\1", text)
|
|
|
|
def rst_link(sc):
|
|
# Returns an RST link (string) for the symbol/choice 'sc', or the normal
|
|
# Kconfig expression format (e.g. just the name) for 'sc' if it can't be
|
|
# turned into a link.
|
|
|
|
if isinstance(sc, kconfiglib.Symbol):
|
|
# Skip constant and undefined symbols by checking if expr.nodes is
|
|
# empty
|
|
if sc.nodes:
|
|
# The "\ " avoids RST issues for !CONFIG_FOO -- see
|
|
# http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#character-level-inline-markup
|
|
return fr"\ :kconfig:`{sc.name} <CONFIG_{sc.name}>`"
|
|
|
|
elif isinstance(sc, kconfiglib.Choice):
|
|
# Choices appear as dependencies of choice symbols.
|
|
#
|
|
# Use a :ref: instead of an :kconfig:. With an :kconfig:, we'd have to have
|
|
# an '.. kconfig::' in the choice reference page as well. That would make
|
|
# the internal choice ID show up in the documentation.
|
|
#
|
|
# Note that the first pair of <...> is non-syntactic here. We just display
|
|
# choices links within <> in the documentation.
|
|
return fr"\ :ref:`<{choice_desc(sc)}> <{choice_id(sc)}>`"
|
|
|
|
# Can't turn 'sc' into a link. Use the standard Kconfig format.
|
|
return kconfiglib.standard_sc_expr_str(sc)
|
|
|
|
|
|
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 main():
|
|
init()
|
|
|
|
write_index_pages() # Plural since there's more than one in --modules mode
|
|
|
|
if os.getenv("KCONFIG_TURBO_MODE") == "1":
|
|
write_dummy_syms_page()
|
|
else:
|
|
write_sym_pages()
|
|
|
|
|
|
def init():
|
|
# Initializes these globals:
|
|
#
|
|
# kconf:
|
|
# Kconfig instance for the configuration
|
|
#
|
|
# out_dir:
|
|
# Output directory
|
|
#
|
|
# index_desc:
|
|
# Set to the corresponding command-line arguments (or None if
|
|
# missing)
|
|
#
|
|
# modules:
|
|
# A list of (<title>, <suffix>, <path>, <desc. path>) tuples. See the
|
|
# --modules flag. Empty if --modules wasn't passed.
|
|
#
|
|
# <path> is an absolute pathlib.Path instance, which is handy for robust
|
|
# path comparisons.
|
|
#
|
|
# no_index_modules:
|
|
# A list of (<title>, <path>) tuples. See the --no-index-modules flag.
|
|
# Empty if --no-index-modules wasn't passed.
|
|
#
|
|
# <path> is an absolute pathlib.Path instance, which is handy for robust
|
|
# path comparisons.
|
|
#
|
|
# separate_all_index:
|
|
# True if --separate-all-index was passed
|
|
#
|
|
# strip_module_paths:
|
|
# True unless --keep-module-paths was passed
|
|
|
|
global kconf
|
|
global out_dir
|
|
global index_desc
|
|
global modules
|
|
global no_index_modules
|
|
global separate_all_index
|
|
global strip_module_paths
|
|
|
|
args = parse_args()
|
|
|
|
kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True)
|
|
out_dir = args.out_dir
|
|
index_desc = args.index_desc
|
|
separate_all_index = args.separate_all_index
|
|
strip_module_paths = args.strip_module_paths
|
|
|
|
modules = []
|
|
for module_spec in args.modules:
|
|
# Split on ',', but keep any ',,' as a literal ','. Temporarily
|
|
# represent a literal comma with null.
|
|
spec_parts = [part.replace("\0", ",")
|
|
for part in module_spec.replace(",,", "\0").split(",")]
|
|
|
|
if len(spec_parts) == 3:
|
|
title, suffix, path_s = spec_parts
|
|
desc_path = None
|
|
elif len(spec_parts) == 4:
|
|
title, suffix, path_s, desc_path = spec_parts
|
|
else:
|
|
sys.exit(f"error: --modules argument '{module_spec}' should have "
|
|
"the format <title>,<suffix>,<path> or the format "
|
|
"<title>,<suffix>,<path>,<index description filename>. "
|
|
"A doubled ',,' in any part is treated as a literal "
|
|
"comma.")
|
|
|
|
abspath = pathlib.Path(path_s).resolve()
|
|
if not abspath.exists():
|
|
sys.exit(f"error: path '{abspath}' in --modules argument does not exist")
|
|
|
|
modules.append((title, suffix, abspath, desc_path))
|
|
|
|
no_index_modules = []
|
|
for module_spec in args.no_index_modules:
|
|
# Split on ',', but keep any ',,' as a literal ','. Temporarily
|
|
# represent a literal comma with null.
|
|
spec_parts = [part.replace("\0", ",")
|
|
for part in module_spec.replace(",,", "\0").split(",")]
|
|
if len(spec_parts) == 2:
|
|
title, path_s = spec_parts
|
|
else:
|
|
sys.exit(f"error: --no-index-modules argument '{module_spec}' "
|
|
"should have the format <title>,<path>.")
|
|
|
|
abspath = pathlib.Path(path_s).resolve()
|
|
if not abspath.is_dir():
|
|
sys.exit("error: path '{}' in --no-index-modules argument does not"
|
|
" exist".format(abspath))
|
|
|
|
no_index_modules.append((title, abspath))
|
|
|
|
|
|
def parse_args():
|
|
# Parses command-line arguments
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawTextHelpFormatter)
|
|
|
|
parser.add_argument(
|
|
"--kconfig",
|
|
metavar="KCONFIG",
|
|
default="Kconfig",
|
|
help="Top-level Kconfig file (default: Kconfig)")
|
|
|
|
parser.add_argument(
|
|
"--index-desc",
|
|
metavar="RST_FILE",
|
|
help="""\
|
|
Path to an RST file with description text for the top-level
|
|
index.rst index page. If missing, a generic description will
|
|
be used. Used both in --modules and non-modules mode.
|
|
|
|
See <index description path> in the --modules description as
|
|
well.""")
|
|
|
|
parser.add_argument(
|
|
"--modules",
|
|
metavar="MODULE_SPECIFICATION",
|
|
nargs="+",
|
|
default=[],
|
|
help="""\
|
|
Specifies that the documentation should be split into
|
|
several index pages, based on where symbols are defined.
|
|
|
|
Each MODULE_SPECIFICATION has the form
|
|
|
|
<title>,<suffix>,<path>[,<index description path>]
|
|
|
|
, where <index description path> is optional.
|
|
|
|
To insert a literal comma into any of the parts, double it,
|
|
e.g. 'My title,, with a comma'.
|
|
|
|
A separate index-<suffix>.rst symbol index page is generated
|
|
for each MODULE_SPECIFICATION, with links to all symbols
|
|
that are defined inside <path> (possibly more than one level
|
|
deep). The title of the index is "<title> Configuration
|
|
Options", and a 'configuration_options_<suffix>' RST link
|
|
target is inserted at the top of the index page.
|
|
|
|
If <index description path> is given, it should be the path
|
|
to an RST file. The contents of this file will appear under
|
|
at the top of the symbol index page for the module,
|
|
underneath the heading. If no <index description path> is
|
|
given, a generic description is used instead.
|
|
|
|
The top-level index.rst index page contains a TOC tree that
|
|
links to the index-*.rst pages for any modules. It also
|
|
includes a list of all symbols, including symbols that do
|
|
not appear in any module. Pass --separate-all-index to use a
|
|
separate index for the list of all symbols.
|
|
|
|
If a symbol is defined in more than one module, it will be
|
|
listed on several index pages.
|
|
|
|
Passing --modules also tweaks how paths are displayed on
|
|
symbol information pages, showing
|
|
'<title>/path/within/module/Kconfig' for paths that fall
|
|
within modules. This behavior can be disabled by passing
|
|
--keep-module-paths.""")
|
|
|
|
parser.add_argument(
|
|
"--no-index-modules",
|
|
metavar="NO_INDEX_MODULE_SPECIFICATION",
|
|
nargs="+",
|
|
default=[],
|
|
help="""\
|
|
Passing --no-index-modules works similarly to --modules
|
|
but it does not generate index pages. It only tweaks how
|
|
paths are displayed on symbol information pages, showing
|
|
'<title>/path/within/module/Kconfig' for paths that fall
|
|
within modules. This behavior can be disabled by passing
|
|
--keep-module-paths.
|
|
|
|
Each NO_INDEX_MODULE_SPECIFICATION has the form
|
|
|
|
<title>,<path>
|
|
|
|
To insert a literal comma into any of the parts, double it,
|
|
e.g. 'My title,, with a comma'.""")
|
|
|
|
parser.add_argument(
|
|
"--separate-all-index",
|
|
action="store_true",
|
|
help="""\
|
|
Instead of listing all symbols in index.rst, use a separate
|
|
index-all.rst index page, which is linked from index.rst.
|
|
Probably only useful in combination with --modules.
|
|
|
|
index-all.rst has a 'configuration_options_all' RST link
|
|
target.
|
|
|
|
This option can make the documentation build orders of
|
|
magnitude faster when the index.rst generated by this script
|
|
is the top-level page, because Sphinx currently runs into a
|
|
bottleneck with large top-level pages with some themes. See
|
|
https://github.com/sphinx-doc/sphinx/issues/6909.""")
|
|
|
|
parser.add_argument(
|
|
"--keep-module-paths",
|
|
dest="strip_module_paths",
|
|
action="store_false",
|
|
help="Do not rewrite paths that fall within modules. See --modules.")
|
|
|
|
parser.add_argument(
|
|
"out_dir",
|
|
metavar="OUTPUT_DIRECTORY",
|
|
help="Directory to write .rst output files to")
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def write_index_pages():
|
|
# Writes all index pages. --modules will give more than one.
|
|
|
|
# Implementation note: Functions used here add any newlines they want
|
|
# before their output themselves. Try to keep this consistent if you change
|
|
# things.
|
|
|
|
write_main_index_page()
|
|
write_module_index_pages()
|
|
|
|
|
|
def write_main_index_page():
|
|
# Writes the main index page, which lists all symbols. In --modules mode,
|
|
# links to the module index pages are included. If --separate-all-index was
|
|
# passed, a separate index-all.rst index is generated as well.
|
|
|
|
rst = index_header(title="Configuration Options",
|
|
link="configuration_options",
|
|
desc_path=index_desc)
|
|
|
|
if separate_all_index:
|
|
rst += """
|
|
|
|
This index page lists all symbols, regardless of where they are defined:
|
|
|
|
.. toctree::
|
|
:maxdepth: 1
|
|
|
|
index-all.rst\
|
|
"""
|
|
write_if_updated("index-all.rst",
|
|
index_header(title="All Configuration Options",
|
|
link="configuration_options_all",
|
|
desc_path=None) +
|
|
NO_MAX_WIDTH +
|
|
sym_table_rst("Configuration Options",
|
|
kconf.unique_defined_syms))
|
|
|
|
if modules:
|
|
rst += """
|
|
|
|
These index pages only list symbols defined within a particular subsystem:
|
|
|
|
.. toctree::
|
|
:maxdepth: 1
|
|
|
|
""" + "\n".join(" index-" + suffix for _, suffix, _, _, in modules)
|
|
|
|
if not separate_all_index:
|
|
rst += NO_MAX_WIDTH
|
|
# Put index of all symbols in index.rst
|
|
rst += sym_table_rst("All configuration options",
|
|
kconf.unique_defined_syms)
|
|
|
|
write_if_updated("index.rst", rst)
|
|
|
|
|
|
def write_module_index_pages():
|
|
# Writes index index-<suffix>.rst index pages for all modules
|
|
|
|
# Maps each module title to a set of Symbols in the module
|
|
module2syms = collections.defaultdict(set)
|
|
|
|
for sym in kconf.unique_defined_syms:
|
|
# Loop over all definition locations
|
|
for node in sym.nodes:
|
|
mod_title = path2module(node.filename)
|
|
if mod_title is not None:
|
|
module2syms[mod_title].add(node.item)
|
|
|
|
# Iterate 'modules' instead of 'module2syms' so that an index page gets
|
|
# written even if the module has no symbols
|
|
for title, suffix, _, desc_path in modules:
|
|
rst = index_header(title=title + " Configuration Options",
|
|
link="configuration_options_" + suffix,
|
|
desc_path=desc_path)
|
|
|
|
rst += NO_MAX_WIDTH
|
|
rst += sym_table_rst("Configuration Options",
|
|
module2syms[title])
|
|
|
|
write_if_updated(f"index-{suffix}.rst", rst)
|
|
|
|
|
|
def sym_table_rst(title, syms):
|
|
# Returns RST for the list of symbols on index pages. 'title' is the
|
|
# heading to use.
|
|
|
|
rst = f"""
|
|
|
|
{title}
|
|
{len(title)*'*'}
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: auto
|
|
|
|
* - Symbol name
|
|
- Prompt
|
|
"""
|
|
|
|
for sym in sorted(syms, key=attrgetter("name")):
|
|
rst += f"""\
|
|
* - :kconfig:`CONFIG_{sym.name}`
|
|
- {sym_index_desc(sym)}
|
|
"""
|
|
|
|
return rst
|
|
|
|
|
|
def sym_index_desc(sym):
|
|
# Returns the description used for 'sym' on the index page
|
|
|
|
for node in sym.nodes:
|
|
if node.prompt:
|
|
return escape_inline_rst(node.prompt[0])
|
|
|
|
# No help text or prompt
|
|
return ""
|
|
|
|
|
|
def index_header(title, link, desc_path):
|
|
# Returns the RST for the beginning of a symbol index page.
|
|
#
|
|
# title:
|
|
# Page title
|
|
#
|
|
# link:
|
|
# Link target string
|
|
#
|
|
# desc_path:
|
|
# Path to file with RST to put at the of the page, underneath the
|
|
# heading. If None, a generic description is used.
|
|
|
|
if desc_path is None:
|
|
desc = DEFAULT_INDEX_DESC
|
|
else:
|
|
try:
|
|
with open(desc_path, encoding="utf-8") as f:
|
|
desc = f.read()
|
|
except OSError as e:
|
|
sys.exit("error: failed to open index description file "
|
|
f"'{desc_path}': {e}")
|
|
|
|
return f"""\
|
|
.. _{link}:
|
|
|
|
{title}
|
|
{len(title)*'='}
|
|
|
|
{desc}
|
|
|
|
This documentation is generated automatically from the :file:`Kconfig` files by
|
|
the :file:`{os.path.basename(__file__)}` script. Click on symbols for more
|
|
information."""
|
|
|
|
|
|
DEFAULT_INDEX_DESC = """\
|
|
:file:`Kconfig` files describe build-time configuration options (called symbols
|
|
in Kconfig-speak), how they're grouped into menus and sub-menus, and
|
|
dependencies between them that determine what configurations are valid.
|
|
|
|
:file:`Kconfig` files appear throughout the directory tree. For example,
|
|
:file:`subsys/power/Kconfig` defines power-related options.\
|
|
"""
|
|
|
|
|
|
def write_sym_pages():
|
|
# Writes all symbol and choice pages
|
|
|
|
for sym in kconf.unique_defined_syms:
|
|
write_sym_page(sym)
|
|
|
|
for choice in kconf.unique_choices:
|
|
write_choice_page(choice)
|
|
|
|
|
|
def write_dummy_syms_page():
|
|
# Writes a dummy page that just has targets for all symbol links so that
|
|
# they can be referenced from elsewhere in the documentation. This speeds
|
|
# up builds when we don't need the Kconfig symbol documentation.
|
|
|
|
rst = ":orphan:\n\nDummy symbols page for turbo mode.\n\n"
|
|
for sym in kconf.unique_defined_syms:
|
|
rst += f".. kconfig:: CONFIG_{sym.name}\n"
|
|
|
|
write_if_updated("dummy-syms.rst", rst)
|
|
|
|
|
|
def write_sym_page(sym):
|
|
# Writes documentation for 'sym' to <out_dir>/CONFIG_<sym.name>.rst
|
|
|
|
write_if_updated(f"CONFIG_{sym.name}.rst",
|
|
sym_header_rst(sym) +
|
|
help_rst(sym) +
|
|
direct_deps_rst(sym) +
|
|
defaults_rst(sym) +
|
|
select_imply_rst(sym) +
|
|
selecting_implying_rst(sym) +
|
|
kconfig_definition_rst(sym))
|
|
|
|
|
|
def write_choice_page(choice):
|
|
# Writes documentation for 'choice' to <out_dir>/choice_<n>.rst, where <n>
|
|
# is the index of the choice in kconf.choices (where choices appear in the
|
|
# same order as in the Kconfig files)
|
|
|
|
write_if_updated(choice_id(choice) + ".rst",
|
|
choice_header_rst(choice) +
|
|
help_rst(choice) +
|
|
direct_deps_rst(choice) +
|
|
defaults_rst(choice) +
|
|
choice_syms_rst(choice) +
|
|
kconfig_definition_rst(choice))
|
|
|
|
|
|
def sym_header_rst(sym):
|
|
# Returns RST that appears at the top of symbol reference pages
|
|
|
|
# - :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.
|
|
return ":orphan:\n\n" \
|
|
f".. title:: {sym.name}\n\n" \
|
|
f".. kconfig:: CONFIG_{sym.name}\n\n" \
|
|
f"{prompt_rst(sym)}\n\n" \
|
|
f"Type: ``{kconfiglib.TYPE_TO_STR[sym.type]}``\n\n"
|
|
|
|
|
|
def choice_header_rst(choice):
|
|
# Returns RST that appears at the top of choice reference pages
|
|
|
|
return ":orphan:\n\n" \
|
|
f".. title:: {choice_desc(choice)}\n\n" \
|
|
f".. _{choice_id(choice)}:\n\n" \
|
|
f".. describe:: {choice_desc(choice)}\n\n" \
|
|
f"{prompt_rst(choice)}\n\n" \
|
|
f"Type: ``{kconfiglib.TYPE_TO_STR[choice.type]}``\n\n"
|
|
|
|
|
|
def prompt_rst(sc):
|
|
# Returns RST that lists the prompts of 'sc' (symbol or choice)
|
|
|
|
return "\n\n".join(f"*{escape_inline_rst(node.prompt[0])}*"
|
|
for node in sc.nodes if node.prompt) \
|
|
or "*(No prompt -- not directly user assignable.)*"
|
|
|
|
|
|
def help_rst(sc):
|
|
# Returns RST that lists the help text(s) of 'sc' (symbol or choice).
|
|
# Symbols and choices with multiple definitions can have multiple help
|
|
# texts.
|
|
|
|
rst = ""
|
|
|
|
for node in sc.nodes:
|
|
if node.help is not None:
|
|
rst += "Help\n" \
|
|
"====\n\n" \
|
|
".. code-block:: none\n\n" \
|
|
f"{textwrap.indent(node.help, 4 * ' ')}\n\n"
|
|
|
|
return rst
|
|
|
|
|
|
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" \
|
|
f"{expr_str(sc.direct_dep)}\n\n" \
|
|
"*(Includes any dependencies from ifs and menus.)*\n\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 ""
|
|
|
|
heading = "Default"
|
|
if len(sc.defaults) != 1:
|
|
heading += "s"
|
|
rst = f"{heading}\n{len(heading)*'='}\n\n"
|
|
|
|
if sc.defaults:
|
|
for value, cond in sc.orig_defaults:
|
|
rst += "- " + expr_str(value)
|
|
if cond is not sc.kconfig.y:
|
|
rst += " if " + 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 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 += f"- {expr_str(sym)}\n"
|
|
|
|
return rst + "\n"
|
|
|
|
|
|
def select_imply_rst(sym):
|
|
# Returns RST that lists the symbols 'select'ed or 'imply'd by the symbol
|
|
|
|
rst = ""
|
|
|
|
def add_select_imply_rst(type_str, lst):
|
|
# Adds RST that lists the selects/implies from 'lst', which holds
|
|
# (<symbol>, <condition>) tuples, if any. Also adds a heading derived
|
|
# from 'type_str' if there any selects/implies.
|
|
|
|
nonlocal rst
|
|
|
|
if lst:
|
|
heading = f"Symbols {type_str} by this symbol"
|
|
rst += f"{heading}\n{len(heading)*'='}\n\n"
|
|
|
|
for select, cond in lst:
|
|
rst += "- " + rst_link(select)
|
|
if cond is not sym.kconfig.y:
|
|
rst += " if " + expr_str(cond)
|
|
rst += "\n"
|
|
|
|
rst += "\n"
|
|
|
|
add_select_imply_rst("selected", sym.orig_selects)
|
|
add_select_imply_rst("implied", sym.orig_implies)
|
|
|
|
return rst
|
|
|
|
|
|
def selecting_implying_rst(sym):
|
|
# Returns RST that lists the symbols that are 'select'ing or 'imply'ing the
|
|
# symbol
|
|
|
|
rst = ""
|
|
|
|
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 = f"Symbols that {type_str} this symbol"
|
|
rst += f"{heading}\n{len(heading)*'='}\n\n"
|
|
|
|
# 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"
|
|
|
|
add_selecting_implying_rst("select", sym.rev_dep)
|
|
add_selecting_implying_rst("imply", sym.weak_rev_dep)
|
|
|
|
return rst
|
|
|
|
|
|
def kconfig_definition_rst(sc):
|
|
# Returns RST that lists the Kconfig definition location, include path,
|
|
# menu path, and Kconfig definition for each node (definition location) of
|
|
# 'sc' (symbol or choice)
|
|
|
|
# Fancy Unicode arrow. Added in '93, so ought to be pretty safe.
|
|
arrow = " \N{RIGHTWARDS ARROW} "
|
|
|
|
def include_path(node):
|
|
if not node.include_path:
|
|
# In the top-level Kconfig file
|
|
return ""
|
|
|
|
return "Included via {}\n\n".format(
|
|
arrow.join(f"``{strip_module_path(filename)}:{linenr}``"
|
|
for filename, linenr in node.include_path))
|
|
|
|
def menu_path(node):
|
|
path = ""
|
|
|
|
while node.parent is not node.kconfig.top_node:
|
|
node = node.parent
|
|
|
|
# Promptless choices can show up as parents, e.g. when people
|
|
# define choices in multiple locations to add symbols. Use
|
|
# standard_sc_expr_str() to show them. That way they show up as
|
|
# '<choice (name if any)>'.
|
|
path = arrow + \
|
|
(node.prompt[0] if node.prompt else
|
|
kconfiglib.standard_sc_expr_str(node.item)) + \
|
|
path
|
|
|
|
return "(Top)" + path
|
|
|
|
heading = "Kconfig definition"
|
|
if len(sc.nodes) > 1: heading += "s"
|
|
rst = f"{heading}\n{len(heading)*'='}\n\n"
|
|
|
|
for node in sc.nodes:
|
|
rst += "\n\n" \
|
|
f"At ``{strip_module_path(node.filename)}:{node.linenr}``\n\n" \
|
|
f"{include_path(node)}" \
|
|
f"Menu path: {menu_path(node)}\n\n" \
|
|
".. code-block:: kconfig\n\n" \
|
|
f"{textwrap.indent(str(node), 4*' ')}"
|
|
|
|
# Not the last node?
|
|
if node is not sc.nodes[-1]:
|
|
# Add a horizontal line between multiple definitions
|
|
rst += "\n\n----"
|
|
|
|
rst += "\n\n*(The 'depends on' condition includes propagated " \
|
|
"dependencies from ifs and menus.)*"
|
|
|
|
return rst
|
|
|
|
|
|
def choice_id(choice):
|
|
# Returns "choice_<n>", where <n> is the index of the choice in the Kconfig
|
|
# files. The choice that appears first has index 0, the next one index 1,
|
|
# etc.
|
|
#
|
|
# This gives each choice a unique ID, which is used to generate its RST
|
|
# filename and in cross-references. Choices (usually) don't have names, so
|
|
# we can't use that, and the prompt isn't guaranteed to be unique.
|
|
|
|
# Pretty slow, but fast enough
|
|
return f"choice_{choice.kconfig.unique_choices.index(choice)}"
|
|
|
|
|
|
def choice_desc(choice):
|
|
# Returns a description of the choice, used as the title of choice
|
|
# reference pages and in link texts. The format is
|
|
# "choice <name, if any>: <prompt text>"
|
|
|
|
desc = "choice"
|
|
|
|
if choice.name:
|
|
desc += " " + choice.name
|
|
|
|
# The choice might be defined in multiple locations. Use the prompt from
|
|
# the first location that has a prompt.
|
|
for node in choice.nodes:
|
|
if node.prompt:
|
|
desc += ": " + node.prompt[0]
|
|
break
|
|
|
|
return desc
|
|
|
|
|
|
def path2module(path):
|
|
# Returns the name of module that 'path' appears in, or None if it does not
|
|
# appear in a module. 'path' is assumed to be relative to 'srctree'.
|
|
|
|
# Have to be careful here so that e.g. foo/barbaz/qaz isn't assumed to be
|
|
# part of a module with path foo/bar/. Play it safe with pathlib.
|
|
|
|
abspath = pathlib.Path(kconf.srctree).joinpath(path).resolve()
|
|
for name, _, mod_path, _ in modules:
|
|
try:
|
|
abspath.relative_to(mod_path)
|
|
except ValueError:
|
|
# Not within the module
|
|
continue
|
|
|
|
return name
|
|
|
|
return None
|
|
|
|
|
|
def strip_module_path(path):
|
|
# If 'path' is within a module, strips the module path from it, and adds a
|
|
# '<module name>/' prefix. Otherwise, returns 'path' unchanged. 'path' is
|
|
# assumed to be relative to 'srctree'.
|
|
|
|
if strip_module_paths:
|
|
abspath = pathlib.Path(kconf.srctree).joinpath(path).resolve()
|
|
for title, _, mod_path, _ in modules:
|
|
try:
|
|
relpath = abspath.relative_to(mod_path)
|
|
except ValueError:
|
|
# Not within the module
|
|
continue
|
|
|
|
return f"<{title}>{os.path.sep}{relpath}"
|
|
|
|
for title, mod_path in no_index_modules:
|
|
try:
|
|
relpath = abspath.relative_to(mod_path)
|
|
except ValueError:
|
|
continue
|
|
|
|
return f"<{title}>{os.path.sep}{relpath}"
|
|
|
|
return path
|
|
|
|
|
|
def write_if_updated(filename, s):
|
|
# Wrapper around gen_helpers.write_if_updated() that uses 'out_dir'.
|
|
|
|
gen_helpers.write_if_updated(os.path.join(out_dir, filename), s)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|