[PATCH RFC 1/2] docs: Add documentation generation for Kconfig symbols
From: Nícolas F. R. A. Prado
Date: Fri Apr 04 2025 - 10:05:46 EST
Add the contents of all Kconfig files to the Documentation to both
increase their visibility and allow for cross-referencing throughout the
documentation. In order to achieve this:
* Add a new script 'kconfig2rst' that converts a Kconfig file into a
reStructuredText document.
* Add an extra step to the documentation building that runs the script
for every Kconfig in the source tree, generating a documentation page
for each one.
* Add a new "Kconfig symbols" page in the documentation, that is listed
on the "Kernel Build System" page, which contains an index of all
Kconfig files and their Kconfig symbols, linking to the corresponding
pages.
The generated documentation pages have the config symbols as sections
with labels that can be referenced from anywhere in the documentation.
The exceptions are configs that appear multiple times. Those don't get
labels, as that would generate 'duplicate label' warnings from sphinx.
To allow this, a list of configs that appear more than once is embedded
in the kconfig2rst script. When a config appears more than once in the
same Kconfig file, a count is appended in the section to prevent
sphinx's auto-labeling to cause the same warning.
The paths in 'source' directives in the Kconfig files are turned into
links to the generated documentation page to allow for navigation to
included Kconfig files.
Config symbols on 'depends'/'select'/etc lines are prepended by
'CONFIG_' to allow them to be cross-referenced by automarkup, though no
cross-references are created in this commit.
Signed-off-by: Nícolas F. R. A. Prado <nfraprado@xxxxxxxxxxxxx>
---
Documentation/.gitignore | 2 +
Documentation/Config/index.rst | 17 +++
Documentation/Makefile | 12 +-
Documentation/kbuild/index.rst | 2 +
scripts/kconfig2rst.py | 336 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 368 insertions(+), 1 deletion(-)
diff --git a/Documentation/.gitignore b/Documentation/.gitignore
index d6dc7c9b8e25020f1f3b28811df2291c38695d5f..2fc70a398dc874fcb83834cb6337f602c64a070a 100644
--- a/Documentation/.gitignore
+++ b/Documentation/.gitignore
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
output
*.pyc
+Config/
+!Config/index.rst
diff --git a/Documentation/Config/index.rst b/Documentation/Config/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2abaa9844dd2a9f57bed0a8d050da3538865b1a5
--- /dev/null
+++ b/Documentation/Config/index.rst
@@ -0,0 +1,17 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+Kconfig symbols
+===============
+
+.. toctree::
+ :glob:
+ :maxdepth: 2
+
+ *
+ */*
+ */*/*
+ */*/*/*
+ */*/*/*/*
+ */*/*/*/*/*
+ */*/*/*/*/*/*
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 63094646df2890a788542a273e4a828a844b2932..74ebc5303b47f0837a9ab31d39b5464af5f17995 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -115,7 +115,7 @@ $(YNL_INDEX): $(YNL_RST_FILES)
$(YNL_RST_DIR)/%.rst: $(YNL_YAML_DIR)/%.yaml $(YNL_TOOL)
$(Q)$(YNL_TOOL) -i $< -o $@
-htmldocs texinfodocs latexdocs epubdocs xmldocs: $(YNL_INDEX)
+htmldocs texinfodocs latexdocs epubdocs xmldocs: $(YNL_INDEX) kconfigdocs
htmldocs:
@$(srctree)/scripts/sphinx-pre-install --version-check
@@ -182,9 +182,19 @@ endif # HAVE_SPHINX
refcheckdocs:
$(Q)cd $(srctree);scripts/documentation-file-ref-check
+KCONFIG_DOC_DIR=$(srctree)/Documentation/Config
+KCONFIGS := $(shell find $(srctree) -name Kconfig -type f)
+KCONFIGS_RST := $(patsubst %, $(KCONFIG_DOC_DIR)/%.rst, $(KCONFIGS))
+
+$(KCONFIGS_RST): $(KCONFIGS)
+ $(Q)cd $(srctree); $(foreach var,$^,$(shell mkdir -p $(KCONFIG_DOC_DIR)/$(shell dirname $(var)); scripts/kconfig2rst.py $(var) >$(KCONFIG_DOC_DIR)/$(var).rst))
+
+kconfigdocs: $(KCONFIGS_RST)
+
cleandocs:
$(Q)rm -f $(YNL_INDEX) $(YNL_RST_FILES)
$(Q)rm -rf $(BUILDDIR)
+ $(Q)rm -rf $(filter-out %index.rst,$(wildcard $(KCONFIG_DOC_DIR)/*))
$(Q)$(MAKE) BUILDDIR=$(abspath $(BUILDDIR)) $(build)=Documentation/userspace-api/media clean
dochelp:
diff --git a/Documentation/kbuild/index.rst b/Documentation/kbuild/index.rst
index 3731ab22bfe745c5c51963cffe58fb652dadf88c..47a1d9753a9fb7b55b8a7141da8123ca97b15cfb 100644
--- a/Documentation/kbuild/index.rst
+++ b/Documentation/kbuild/index.rst
@@ -15,6 +15,8 @@ Kernel Build System
makefiles
modules
+ /Config/index
+
headers_install
issues
diff --git a/scripts/kconfig2rst.py b/scripts/kconfig2rst.py
new file mode 100755
index 0000000000000000000000000000000000000000..5af073a1c669ac43c95bb7af00099dcd9473a6ae
--- /dev/null
+++ b/scripts/kconfig2rst.py
@@ -0,0 +1,336 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2025 Collabora Ltd
+
+import sys
+import re
+import os
+
+import argparse
+
+BASE_PATH_DEFAULT = "Documentation/Config/"
+CFG_LEN = 60
+RE_indentation = r"^[ \t]*"
+in_help_txt = False
+help_txt = ""
+
+# These configs appear more than once, thus we don't generate labels or xrefs to
+# them to avoid duplicate label warnings from Sphinx
+REPEATED_CONFIGS = [
+ "32BIT",
+ "4KSTACKS",
+ "64BIT",
+ "A",
+ "ADVANCED_OPTIONS",
+ "ALPHA_LEGACY_START_ADDRESS",
+ "ARCH_AIROHA",
+ "ARCH_ALPINE",
+ "ARCH_BCM2835",
+ "ARCH_BCM_IPROC",
+ "ARCH_BRCMSTB",
+ "ARCH_DEFAULT_CRASH_DUMP",
+ "ARCH_FLATMEM_ENABLE",
+ "ARCH_FORCE_MAX_ORDER",
+ "ARCH_HAS_ADD_PAGES",
+ "ARCH_HAS_CACHE_LINE_SIZE",
+ "ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION",
+ "ARCH_HAS_ILOG2_U32",
+ "ARCH_HAS_ILOG2_U64",
+ "ARCH_HIBERNATION_HEADER",
+ "ARCH_HIBERNATION_POSSIBLE",
+ "ARCH_HISI",
+ "ARCH_MAY_HAVE_PC_FDC",
+ "ARCH_MEMORY_PROBE",
+ "ARCH_MMAP_RND_BITS_MAX",
+ "ARCH_MMAP_RND_BITS_MIN",
+ "ARCH_MMAP_RND_COMPAT_BITS_MAX",
+ "ARCH_MMAP_RND_COMPAT_BITS_MIN",
+ "ARCH_MTD_XIP",
+ "ARCH_OMAP",
+ "ARCH_PKEY_BITS",
+ "ARCH_PROC_KCORE_TEXT",
+ "ARCH_R9A07G043",
+ "ARCH_RENESAS",
+ "ARCH_ROCKCHIP",
+ "ARCH_SELECT_MEMORY_MODEL",
+ "ARCH_SELECTS_CRASH_DUMP",
+ "ARCH_SELECTS_KEXEC_FILE",
+ "ARCH_SPARSEMEM_DEFAULT",
+ "ARCH_SPARSEMEM_ENABLE",
+ "ARCH_SUNXI",
+ "ARCH_SUPPORTS_CRASH_DUMP",
+ "ARCH_SUPPORTS_CRASH_HOTPLUG",
+ "ARCH_SUPPORTS_KEXEC",
+ "ARCH_SUPPORTS_KEXEC_FILE",
+ "ARCH_SUPPORTS_KEXEC_JUMP",
+ "ARCH_SUPPORTS_KEXEC_PURGATORY",
+ "ARCH_SUPPORTS_KEXEC_SIG",
+ "ARCH_SUPPORTS_UPROBES",
+ "ARCH_SUSPEND_POSSIBLE",
+ "ARCH_UNIPHIER",
+ "ARCH_VIRT",
+ "AUDIT_ARCH",
+ "B",
+ "BCH_CONST_M",
+ "BCH_CONST_T",
+ "BUILTIN_DTB",
+ "BUILTIN_DTB_NAME",
+ "C",
+ "CC_HAVE_STACKPROTECTOR_TLS",
+ "CHOICE_B",
+ "CHOICE_C",
+ "CMDLINE",
+ "CMDLINE_BOOL",
+ "CMDLINE_EXTEND",
+ "CMDLINE_FORCE",
+ "CMDLINE_FROM_BOOTLOADER",
+ "CMDLINE_OVERRIDE",
+ "CMM",
+ "COMPAT",
+ "COMPAT_VDSO",
+ "CORE",
+ "CORE_BELL_A",
+ "CORE_BELL_A_ADVANCED",
+ "CPU_BIG_ENDIAN",
+ "CPU_HAS_FPU",
+ "CPU_HAS_PREFETCH",
+ "CPU_LITTLE_ENDIAN",
+ "CRYPTO_CHACHA20_NEON",
+ "CRYPTO_JITTERENTROPY_MEMORY_BLOCKS",
+ "CRYPTO_JITTERENTROPY_MEMORY_BLOCKSIZE",
+ "CRYPTO_JITTERENTROPY_OSR",
+ "CRYPTO_JITTERENTROPY_TESTINTERFACE",
+ "CRYPTO_NHPOLY1305_NEON",
+ "DEBUG_ENTRY",
+ "DMA_NONCOHERENT",
+ "DMI",
+ "DRAM_BASE",
+ "DUMMY",
+ "DUMMY_CONSOLE",
+ "EARLY_PRINTK",
+ "EFI",
+ "EFI_STUB",
+ "FIT_IMAGE_FDT_EPM5",
+ "FIX_EARLYCON_MEM",
+ "FPU",
+ "GENERIC_BUG",
+ "GENERIC_BUG_RELATIVE_POINTERS",
+ "GENERIC_CALIBRATE_DELAY",
+ "GENERIC_CSUM",
+ "GENERIC_HWEIGHT",
+ "GENERIC_ISA_DMA",
+ "GENERIC_LOCKBREAK",
+ "HAS_IOMEM",
+ "HAVE_SMP",
+ "HAVE_TCM",
+ "HEARTBEAT",
+ "HIGHMEM",
+ "HOTPLUG_CPU",
+ "HW_PERF_EVENTS",
+ "HZ",
+ "HZ_100",
+ "HZ_1000",
+ "HZ_1024",
+ "HZ_128",
+ "HZ_250",
+ "HZ_256",
+ "ILLEGAL_POINTER_VALUE",
+ "IRQSTACKS",
+ "ISA",
+ "ISA_DMA_API",
+ "KASAN_SHADOW_OFFSET",
+ "KERNEL_MODE_NEON",
+ "KERNEL_START",
+ "KERNEL_START_BOOL",
+ "KUSER_HELPERS",
+ "KVM",
+ "KVM_GUEST",
+ "L1_CACHE_SHIFT",
+ "LEDS_EXPRESSWIRE",
+ "LOCKDEP_SUPPORT",
+ "LOWMEM_SIZE",
+ "LOWMEM_SIZE_BOOL",
+ "MACH_LOONGSON32",
+ "MACH_LOONGSON64",
+ "MACH_TX49XX",
+ "MAGIC_SYSRQ",
+ "MATH_EMULATION",
+ "MCOUNT",
+ "MMU",
+ "NODES_SHIFT",
+ "NO_IOPORT_MAP",
+ "NR_CPUS",
+ "NR_CPUS_DEFAULT",
+ "NR_CPUS_RANGE_END",
+ "NUMA",
+ "PAGE_OFFSET",
+ "PANIC_TIMEOUT",
+ "PARAVIRT",
+ "PARAVIRT_SPINLOCKS",
+ "PARAVIRT_TIME_ACCOUNTING",
+ "PFAULT",
+ "PGTABLE_LEVELS",
+ "PHYSICAL_ALIGN",
+ "PHYSICAL_START",
+ "PID_IN_CONTEXTIDR",
+ "PM",
+ "POWERPC64_CPU",
+ "PRINT_STACK_DEPTH",
+ "RANDOMIZE_BASE",
+ "RANDOMIZE_BASE_MAX_OFFSET",
+ "RELOCATABLE",
+ "SBUS",
+ "SCHED_CLUSTER",
+ "SCHED_HRTICK",
+ "SCHED_MC",
+ "SCHED_OMIT_FRAME_POINTER",
+ "SCHED_SMT",
+ "SERIAL_CONSOLE",
+ "SMP",
+ "STACKPROTECTOR_PER_TASK",
+ "STACKTRACE_SUPPORT",
+ "SWAP_IO_SPACE",
+ "SYS_SUPPORTS_APM_EMULATION",
+ "SYS_SUPPORTS_NUMA",
+ "SYS_SUPPORTS_SMP",
+ "TASK_SIZE",
+ "TASK_SIZE_BOOL",
+ "TCP_CONG_CUBIC",
+ "TIME_LOW_RES",
+ "UNWINDER_FRAME_POINTER",
+ "UNWINDER_GUESS",
+ "UNWINDER_ORC",
+ "USE_OF",
+ "VMSPLIT_1G",
+ "VMSPLIT_2G",
+ "VMSPLIT_3G",
+ "VMSPLIT_3G_OPT",
+ "X",
+ "X86_32",
+ "X86_64",
+ "XEN",
+ "XEN_DOM0",
+ "XIP_KERNEL",
+ "XIP_PHYS_ADDR",
+ "ARCH_BCM",
+ "VIRTUALIZATION",
+]
+
+
+def print_title(title):
+ heading = "=" * len(title)
+ print(heading)
+ print(title)
+ print(heading)
+ print()
+
+
+parser = argparse.ArgumentParser(
+ prog="kconfig2rst", description="Convert a Kconfig file into ReStructuredText"
+)
+
+parser.add_argument("kconfig", help="Path to input Kconfig file")
+parser.add_argument(
+ "--base-doc-path",
+ default=BASE_PATH_DEFAULT,
+ help="Base path of generated rST files for usage in 'source' links",
+)
+args = parser.parse_args()
+
+print_title(args.kconfig)
+
+line_accum = ""
+continued_line = False
+
+repeated_config_count = {}
+
+with open(args.kconfig) as f:
+ for il in f:
+ # If line ends with \, accumulate it and handle full line
+ if re.search(r"\\\n$", il):
+ continued_line = True
+ line_accum += il[:-2] # accumulate without backslash and newline
+ continue
+
+ if continued_line:
+ continued_line = False
+ l = line_accum + il
+ line_accum = ""
+ else:
+ l = il
+
+ if in_help_txt:
+ if l == "\n":
+ help_txt += l
+ continue
+ if first_line_help_txt:
+ help_txt_indentation = re.match(RE_indentation, l).group(0).expandtabs()
+ first_line_help_txt = False
+ # Consider any line with same or more indentation as part of help text
+ if (
+ help_txt_indentation
+ in re.match(RE_indentation, l).group(0).expandtabs()
+ ):
+ help_txt += l
+ continue
+ else:
+ in_help_txt = False
+ print(help_txt)
+ help_txt = ""
+ else:
+ l = re.sub(r"[*]", r"\*", l) # Escape asterisks
+
+ if re.match(r"^[ \t]*#.*", l):
+ # Skip comments
+ continue
+
+ if re.match(r"^[ \t]*help", l):
+ in_help_txt = True
+ first_line_help_txt = True
+ print("* help::\n")
+ continue
+
+ m = re.match("^[ \t]*(menu)?config (?P<cfgname>[A-Za-z0-9_]+)", l)
+ if m:
+ section_name = f"\nCONFIG_{m.group('cfgname')}"
+ underline = f"\n{'='*CFG_LEN}\n"
+ if m.group("cfgname") in REPEATED_CONFIGS:
+ repeated_config_count[m.group("cfgname")] = (
+ repeated_config_count.get(m.group("cfgname"), 0) + 1
+ )
+ if repeated_config_count[m.group("cfgname")] > 1:
+ section_name += f"({repeated_config_count[m.group('cfgname')]})"
+ print(section_name + underline)
+ else:
+ print(f"\n.. _CONFIG_{m.group('cfgname')}:\n\n" + section_name + underline)
+ continue
+
+ m = re.match(
+ r"^[ \t]*(def_bool|def_tristate|depends on|select|range|visible if|imply|default|prompt|bool|tristate|string|hex|int|modules)( \"(.*)\")?(?P<expr> [^\"]*)?",
+ l,
+ )
+ if m:
+ expr = m.group('expr') if m.group('expr') else ''
+ not_expr = l
+ if expr:
+ expr = re.sub(r'[A-Z0-9_]{2,}', rf" CONFIG_\g<0> ", expr)
+ not_expr = l[:m.start('expr')]
+ print("* " + not_expr.lstrip() + expr.rstrip())
+ continue
+
+ m = re.match(r'^[ \t]*source "(.*)"', l)
+ if m:
+ # Format Kconfig file paths as Documentation/... so they can be turned
+ # into links by the automarkup plugin
+ print(f"\nsource {args.base_doc_path + m.group(1)}.rst\n")
+ continue
+
+ m = re.match(r"[^ \t]*choice|endchoice|comment|menu|endmenu|if|endif", l)
+ if m:
+ print("\n" + l.strip() + "\n")
+ continue
+
+ print(l.strip())
+
+if help_txt:
+ print(help_txt) # Flush any pending help text
--
2.49.0