mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-24 07:26:24 +00:00
Extend the specifications passed to --modules to add the form <title>:<suffix>:<path>:<index description filename> <index description filename> points to a file that contains RST that is inserted at the top of the index page. If no filename is passed, the old default description is used. Also add three flags --top-index-desc, --non-module-index-desc, and --all-index-desc for customizing the text at the top of non-module index pages. This functionality is currently unused in Zephyr, but will probably be used later. It's being added for a downstream project. Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
800 lines
25 KiB
Python
800 lines
25 KiB
Python
"""
|
|
Generates a Kconfig symbol reference in RST format, with a separate
|
|
CONFIG_FOO.rst file for each symbol, and an alphabetical index with links in
|
|
index.rst.
|
|
|
|
The generated symbol pages can be referenced in RST as :option:`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
|
|
import errno
|
|
from operator import attrgetter
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
import textwrap
|
|
|
|
import kconfiglib
|
|
|
|
|
|
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 r"\ :option:`{0} <CONFIG_{0}>`".format(sc.name)
|
|
|
|
elif isinstance(sc, kconfiglib.Choice):
|
|
# Choices appear as dependencies of choice symbols.
|
|
#
|
|
# Use a :ref: instead of an :option:. With an :option:, we'd have to have
|
|
# an '.. option::' 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 r"\ :ref:`<{}> <{}>`" \
|
|
.format(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():
|
|
# Writes index.rst and the symbol RST files
|
|
|
|
init()
|
|
|
|
# Writes index page(s)
|
|
if modules:
|
|
write_module_index_pages()
|
|
else:
|
|
write_index_page(kconf.unique_defined_syms, None, None,
|
|
desc_from_file(top_index_desc))
|
|
|
|
# Write symbol pages
|
|
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
|
|
#
|
|
# non_module_title:
|
|
# Title for index of non-module symbols, as passed via
|
|
# --non-module-title
|
|
#
|
|
# top_index_desc/non_module_index_desc/all_index_desc:
|
|
# Set to the corresponding command-line arguments (or None if
|
|
# missing)
|
|
#
|
|
# modules:
|
|
# A list of (<title>, <suffix>, <path>, <index description>) tuples. See
|
|
# the --modules argument. Empty if --modules wasn't passed.
|
|
#
|
|
# <path> is an absolute pathlib.Path instance, which is handy for robust
|
|
# path comparisons.
|
|
#
|
|
# strip_module_paths:
|
|
# True unless --keep-module-paths was passed
|
|
|
|
global kconf
|
|
global out_dir
|
|
global non_module_title
|
|
global top_index_desc
|
|
global non_module_index_desc
|
|
global all_index_desc
|
|
global modules
|
|
global strip_module_paths
|
|
|
|
args = parse_args()
|
|
|
|
kconf = kconfiglib.Kconfig(args.kconfig)
|
|
out_dir = args.out_dir
|
|
non_module_title = args.non_module_title
|
|
top_index_desc = args.top_index_desc
|
|
non_module_index_desc = args.non_module_index_desc
|
|
all_index_desc = args.all_index_desc
|
|
strip_module_paths = args.strip_module_paths
|
|
|
|
modules = []
|
|
for module_spec in args.modules:
|
|
if module_spec.count(":") == 2:
|
|
title, suffix, path_s = module_spec.split(":")
|
|
index_text = DEFAULT_INDEX_DESCRIPTION
|
|
elif module_spec.count(":") == 3:
|
|
title, suffix, path_s, index_text_fname = module_spec.split(":")
|
|
index_text = desc_from_file(index_text_fname)
|
|
else:
|
|
sys.exit("error: --modules argument '{}' should have the format "
|
|
"<title>:<suffix>:<path> or the format "
|
|
"<title>:<suffix>:<path>:<index description filename>"
|
|
.format(module_spec))
|
|
|
|
path = pathlib.Path(path_s).resolve()
|
|
if not path.exists():
|
|
sys.exit("error: path '{}' in --modules argument does not exist"
|
|
.format(path))
|
|
|
|
modules.append((title, suffix, path, index_text))
|
|
|
|
|
|
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(
|
|
"--non-module-title",
|
|
metavar="NON_MODULE_TITLE",
|
|
default="Zephyr",
|
|
help="""\
|
|
The title used for the index page that lists the symbols
|
|
that do not appear in any module (index-main.rst). Only
|
|
meaningful in --modules mode.""")
|
|
|
|
parser.add_argument(
|
|
"--top-index-desc",
|
|
metavar="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 filename> in the --modules
|
|
description as well.""")
|
|
|
|
parser.add_argument(
|
|
"--non-module-index-desc",
|
|
metavar="FILE",
|
|
help="""\
|
|
Like --top-index-desc, but for the index page that lists the
|
|
non-module symbols in --modules mode (index-main.rst).""")
|
|
|
|
parser.add_argument(
|
|
"--all-index-desc",
|
|
metavar="FILE",
|
|
help="""\
|
|
Like --top-index-desc, but for the index page that lists all
|
|
symbols in --modules mode (index-all.rst).""")
|
|
|
|
parser.add_argument(
|
|
"--modules",
|
|
metavar="MODULE_SPECIFICATION",
|
|
nargs="+",
|
|
default=[],
|
|
help="""\
|
|
Used to split the documentation into several index pages,
|
|
based on where symbols are defined.
|
|
|
|
MODULE_SPECIFICATION is either a <title>:<suffix>:<path>
|
|
tuple or a
|
|
<title>:<suffix>:<path>:<index description filename> tuple.
|
|
If the second form is used, <index description filename>
|
|
should be the path to an RST file, the contents of which
|
|
will appear on the index page that lists the symbols for the
|
|
module (under an automatically-inserted Overview heading).
|
|
If the first form is used, a generic description will be
|
|
used instead.
|
|
|
|
A separate index-<suffix>.rst index page is generated for
|
|
each tuple, with the title "<title> Configuration Options",
|
|
a 'configuration_options_<suffix>' RST link target, and
|
|
links to all symbols that appear under the tuple's <path>
|
|
(possibly more than one level deep). Symbols that do not
|
|
appear in any module are added to index-main.rst.
|
|
|
|
A separate index-all.rst page is generated that lists all
|
|
symbols, regardless of whether they're from a module or not.
|
|
|
|
The generated index.rst contains a TOC tree that links to
|
|
the other index-*.rst pages.
|
|
|
|
If a symbol is defined in more than one module (or both
|
|
inside and outside a 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 path rewriting can be disabled with
|
|
--keep-module-paths.""")
|
|
|
|
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_module_index_pages():
|
|
# Generate all index pages. Passing --modules will generate more than one.
|
|
|
|
write_toplevel_index()
|
|
|
|
# Maps each module title to a set of Symbols in the module
|
|
module2syms = collections.defaultdict(set)
|
|
# Symbols that do not appear in any module
|
|
non_module_syms = 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 None:
|
|
non_module_syms.add(node.item)
|
|
else:
|
|
module2syms[mod_title].add(node.item)
|
|
|
|
# Write the index-main.rst index page, which lists the symbols that aren't
|
|
# from a module
|
|
write_index_page(non_module_syms, non_module_title, "main",
|
|
desc_from_file(non_module_index_desc))
|
|
|
|
# Write the index-<suffix>.rst index pages, which list symbols from
|
|
# modules. Iterate 'modules' instead of 'module2syms' so that an index page
|
|
# gets written even if a module has no symbols, for consistency.
|
|
for title, suffix, _, text in modules:
|
|
write_index_page(module2syms[title], title, suffix, text)
|
|
|
|
# Write the index-all.rst index page, which lists all symbols, including
|
|
# both module and non-module symbols
|
|
write_index_page(kconf.unique_defined_syms, "All", "all",
|
|
desc_from_file(all_index_desc))
|
|
|
|
|
|
def write_toplevel_index():
|
|
# Used in --modules mode. Writes an index.rst with a TOC tree that links to
|
|
# index-main.rst and the index-<suffix>.rst pages.
|
|
|
|
rst = index_page_header(None, None, desc_from_file(top_index_desc)) + """
|
|
Subsystems
|
|
**********
|
|
|
|
.. toctree::
|
|
:maxdepth: 1
|
|
|
|
"""
|
|
|
|
rst += " index-main\n"
|
|
for _, suffix, _, _ in modules:
|
|
rst += " index-{}\n".format(suffix)
|
|
rst += " index-all\n"
|
|
|
|
write_if_updated("index.rst", rst)
|
|
|
|
|
|
def write_index_page(syms, title, suffix, text):
|
|
# Writes an index page for the Symbols in 'syms' to 'index-<suffix>.rst'
|
|
# (or index.rst if 'suffix' is None). 'title', 'suffix', and 'text' are
|
|
# also used for to generate the index page header. See index_page_header().
|
|
|
|
rst = index_page_header(title, suffix, text)
|
|
|
|
rst += """
|
|
Configuration symbols
|
|
*********************
|
|
|
|
.. list-table:: Alphabetized Index of Configuration Options
|
|
:header-rows: 1
|
|
|
|
* - Kconfig Symbol
|
|
- Description
|
|
"""
|
|
|
|
for sym in sorted(syms, key=attrgetter("name")):
|
|
# Add an index entry for the symbol that links to its page. Also list
|
|
# its prompt(s), if any. (A symbol can have multiple prompts if it has
|
|
# multiple definitions.)
|
|
rst += " * - :option:`CONFIG_{}`\n - {}\n".format(
|
|
sym.name, " / ".join(node.prompt[0] for node in sym.nodes
|
|
if node.prompt))
|
|
|
|
if suffix is None:
|
|
fname = "index.rst"
|
|
else:
|
|
fname = "index-{}.rst".format(suffix)
|
|
|
|
write_if_updated(fname, rst)
|
|
|
|
|
|
def index_page_header(title, link, description):
|
|
# write_index_page() helper. Returns the RST for the beginning of a symbol
|
|
# index page.
|
|
#
|
|
# title:
|
|
# String used for the page title, as '<title> Configuration Options'. If
|
|
# None, just 'Configuration Options' is used as the title.
|
|
#
|
|
# link:
|
|
# String used for link target, as 'configuration_options_<link>'. If
|
|
# None, the link will be 'configuration_options'.
|
|
#
|
|
# description:
|
|
# RST put into an Overview section at the beginning of the page
|
|
|
|
if title is None:
|
|
title = "Configuration Options"
|
|
else:
|
|
title = title + " Configuration Options"
|
|
|
|
if link is None:
|
|
link = "configuration_options"
|
|
else:
|
|
link = "configuration_options_" + link
|
|
|
|
title += "\n" + len(title)*"="
|
|
|
|
return """\
|
|
.. _{}:
|
|
|
|
{}
|
|
|
|
Overview
|
|
********
|
|
|
|
{}
|
|
|
|
This documentation is generated automatically from the :file:`Kconfig` files by
|
|
the :file:`{}` script. Click on symbols for more information.
|
|
""".format(link, title, description, os.path.basename(__file__))
|
|
|
|
|
|
DEFAULT_INDEX_DESCRIPTION = """\
|
|
: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 desc_from_file(fname):
|
|
# Helper for loading files with descriptions for index pages. Returns
|
|
# DEFAULT_INDEX_DESCRIPTION if 'fname' is None, and the contents of the
|
|
# file otherwise.
|
|
|
|
if fname is None:
|
|
return DEFAULT_INDEX_DESCRIPTION
|
|
|
|
try:
|
|
with open(fname, "r", encoding="utf-8") as f:
|
|
return f.read()
|
|
except OSError as e:
|
|
sys.exit("error: failed to open index description file '{}': {}"
|
|
.format(fname, e))
|
|
|
|
|
|
def write_sym_pages():
|
|
# Generates 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, to speed 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 += ".. option:: CONFIG_{}\n".format(sym.name)
|
|
|
|
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("CONFIG_{}.rst".format(sym.name),
|
|
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" \
|
|
".. title:: {0}\n\n" \
|
|
".. option:: CONFIG_{0}\n\n" \
|
|
"{1}\n\n" \
|
|
"Type: ``{2}``\n\n" \
|
|
.format(sym.name, prompt_rst(sym),
|
|
kconfiglib.TYPE_TO_STR[sym.type])
|
|
|
|
|
|
def choice_header_rst(choice):
|
|
# Returns RST that appears at the top of choice reference pages
|
|
|
|
return ":orphan:\n\n" \
|
|
".. title:: {0}\n\n" \
|
|
".. _{1}:\n\n" \
|
|
".. describe:: {0}\n\n" \
|
|
"{2}\n\n" \
|
|
"Type: ``{3}``\n\n" \
|
|
.format(choice_desc(choice), choice_id(choice),
|
|
prompt_rst(choice), kconfiglib.TYPE_TO_STR[choice.type])
|
|
|
|
|
|
def prompt_rst(sc):
|
|
# Returns RST that lists the prompts of 'sc' (symbol or choice)
|
|
|
|
return "\n\n".join("*{}*".format(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" \
|
|
"{}\n\n" \
|
|
.format(node.help)
|
|
|
|
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" \
|
|
"{}\n\n" \
|
|
"*(Includes any dependencies from ifs and menus.)*\n\n" \
|
|
.format(expr_str(sc.direct_dep))
|
|
|
|
|
|
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 = "{}\n{}\n\n".format(heading, len(heading)*"=")
|
|
|
|
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 += "- {}\n".format(expr_str(sym))
|
|
|
|
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 = "Symbols {} by this symbol".format(type_str)
|
|
rst += "{}\n{}\n\n".format(heading, len(heading)*"=")
|
|
|
|
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 = "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"
|
|
|
|
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("``{}:{}``".format(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 = "{}\n{}\n\n".format(heading, len(heading)*"=")
|
|
|
|
rst += ".. highlight:: kconfig"
|
|
|
|
for node in sc.nodes:
|
|
rst += "\n\n" \
|
|
"At ``{}:{}``\n\n" \
|
|
"{}" \
|
|
"Menu path: {}\n\n" \
|
|
".. parsed-literal::\n\n{}" \
|
|
.format(strip_module_path(node.filename), node.linenr,
|
|
include_path(node), menu_path(node),
|
|
textwrap.indent(node.custom_str(rst_link), 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 "choice_{}".format(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 "<{}>{}{}".format(title, os.path.sep, relpath)
|
|
|
|
return path
|
|
|
|
|
|
def write_if_updated(filename, s):
|
|
# Writes 's' as the contents of <out_dir>/<filename>, but only if it
|
|
# differs from the current contents of the file. This avoids unnecessary
|
|
# timestamp updates, which trigger documentation rebuilds.
|
|
|
|
path = os.path.join(out_dir, filename)
|
|
|
|
try:
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
if s == f.read():
|
|
return
|
|
except OSError as e:
|
|
if e.errno != errno.ENOENT:
|
|
raise
|
|
|
|
with open(path, "w", encoding="utf-8") as f:
|
|
f.write(s)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|