diff --git a/doc/scripts/genrest/genrest.py b/doc/scripts/genrest/genrest.py index 02c2a855b60..28a61a1e851 100644 --- a/doc/scripts/genrest/genrest.py +++ b/doc/scripts/genrest/genrest.py @@ -2,11 +2,13 @@ # CONFIG_FOO.rst file for each symbol, and an alphabetical index with links in # index.rst. -import kconfiglib +import errno import os import sys import textwrap +import kconfiglib + # "Extend" the standard kconfiglib.expr_str() to turn references to defined # Kconfig symbols into RST links. Symbol.__str__() will then use the extended # version. @@ -71,142 +73,159 @@ def write_kconfig_rst(): kconf = kconfiglib.Kconfig(sys.argv[1]) out_dir = sys.argv[2] - with open(os.path.join(out_dir, "index.rst"), "w") as index_rst: - index_rst.write(INDEX_RST_HEADER) + # String with the RST for the index page + index_rst = INDEX_RST_HEADER - # - Sort the symbols by name so that they end up in sorted order in - # index.rst + # - Sort the symbols by name so that they end up in sorted order in + # index.rst + # + # - Use set() to get rid of duplicates for symbols defined in multiple + # locations. + for sym in sorted(set(kconf.defined_syms), key=lambda sym: sym.name): + # Write an RST file for the symbol + write_sym_rst(sym, out_dir) + + # Add an index entry for the symbol that links to its RST file. Also + # list its prompt(s), if any. (A symbol can have multiple prompts if it + # has multiple definitions.) # - # - Use set() to get rid of duplicates for symbols defined in multiple - # locations. - for sym in sorted(set(kconf.defined_syms), key=lambda sym: sym.name): - # Write an RST file for the symbol - write_sym_rst(sym, out_dir) + # The strip() avoids RST choking on stuff like *foo *, when people + # accidentally include leading/trailing whitespace in prompts. + index_rst += " * - :option:`CONFIG_{}`\n - {}\n".format( + sym.name, + " / ".join(node.prompt[0].strip() + for node in sym.nodes if node.prompt)) - # Add an index entry for the symbol that links to its RST file. - # Also list its prompt(s), if any. (A symbol can have multiple - # prompts if it has multiple definitions.) - # - # The strip() avoids RST choking on stuff like *foo *, when people - # accidentally include leading/trailing whitespace in prompts. - index_rst.write(" * - :option:`CONFIG_{}`\n - {}\n" - .format(sym.name, - " / ".join(node.prompt[0].strip() - for node in sym.nodes - if node.prompt))) + write_if_updated(os.path.join(out_dir, "index.rst"), index_rst) def write_sym_rst(sym, out_dir): # Writes documentation for 'sym' to /CONFIG_.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.)*" - # 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.)*" + # 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. ). 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]) - # - :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 += "Help\n" \ + "====\n\n" \ + "{}\n\n" \ + .format(node.help) - # 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 += "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.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 += "Defaults\n" \ + "========\n\n" - 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 += " - {}\n".format(default_str) - 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 += "\n" - sym_rst.write("\n") + def add_select_imply_rst(type_str, expr): + # Writes a link for each selecting symbol (if 'expr' is sym.rev_dep) or + # each implying symbol (if 'expr' is sym.weak_rev_dep). Also adds a + # heading at the top, derived from type_str ("select"/"imply"). - 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) + nonlocal sym_rst - # 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)) + heading = "Symbols that ``{}`` this symbol".format(type_str) + sym_rst += "{}\n{}\n\n".format(heading, len(heading)*"=") - sym_rst.write("\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. + sym_rst += " - :option:`CONFIG_{}`\n".format( + kconfiglib.split_expr(select, kconfiglib.AND)[0].name) - 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) + sym_rst += "\n" - 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) + if sym.rev_dep is not kconf.n: + add_select_imply_rst("select", sym.rev_dep) - def menu_path(node): - path = "" + if sym.weak_rev_dep is not kconf.n: + add_select_imply_rst("imply", sym.weak_rev_dep) - menu = node.parent - while menu is not kconf.top_node: - # Fancy Unicode arrow. Added in '93, so ought to be pretty - # safe. - path = " → " + menu.prompt[0] + path - menu = menu.parent + def menu_path(node): + path = "" - # The strip() avoids RST choking on leading/trailing whitespace in - # prompts - return ("(top menu)" + path).strip() + menu = node.parent + while menu is not kconf.top_node: + # Fancy Unicode arrow. Added in '93, so ought to be pretty + # safe. + path = " → " + menu.prompt[0] + path + menu = menu.parent - heading = "Kconfig definition" - if len(sym.nodes) > 1: - heading += "s" + # The strip() avoids RST choking on leading/trailing whitespace in + # prompts + return ("(top menu)" + path).strip() - # Add ==... below heading. Adding too many '=' would be okay too, but - # this looks a bit neater in the RST file. - sym_rst.write("{}\n{}\n\n".format(heading, len(heading)*"=")) + heading = "Kconfig definition" + if len(sym.nodes) > 1: + heading += "s" + sym_rst += "{}\n{}\n\n".format(heading, len(heading)*"=") - sym_rst.write("\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".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) + + +def write_if_updated(filename, s): + # Writes 's' as the contents of 'filename', but only if it differs from the + # current contents of the file. This avoids unnecessary timestamp updates, + # which trigger documentation rebuilds. + + try: + with open(filename) as f: + if s == f.read(): + return + except OSError as e: + if e.errno != errno.ENOENT: + raise + + with open(filename, "w") as f: + f.write(s) - sym_rst.write("\n\n*(Definitions include propagated dependencies, " - "including from if's and menus.)*") if __name__ == "__main__": write_kconfig_rst()