Skip to content

[libc][docs] Generate implementation status dashboard - #241945

Merged
kaladron merged 1 commit into
llvm:mainfrom
kaladron:howclose-pr9
Nov 12, 2026
Merged

kaladron merged 1 commit into
llvm:mainfrom
kaladron:howclose-pr9

Conversation

@kaladron

Copy link
Copy Markdown
Member

The top-level headers/index.md page currently renders a plain static list of header names without indicating overall C23 or POSIX.1-2024 coverage or per-header completion across the library.

Replace static headers/index.md with docgen.py --index (invoked by the CMake libc-docgen-index target) to generate top-level C23, POSIX.1-2024, and symbol-category completeness cards alongside colour-coded progress rows for every catalogued header, while hiding top-of-page source links on the aggregated index page in conf.py.

Assisted-by: Automated tooling, human reviewed.

The top-level headers/index.md page currently renders a plain static
list of header names without indicating overall C23 or POSIX.1-2024
coverage or per-header completion across the library.

Replace static headers/index.md with docgen.py --index (invoked by the
CMake libc-docgen-index target) to generate top-level C23, POSIX.1-2024,
and symbol-category completeness cards alongside colour-coded progress
rows for every catalogued header, while hiding top-of-page source links
on the aggregated index page in conf.py.

Assisted-by: Automated tooling, human reviewed.
@kaladron

Copy link
Copy Markdown
Member Author

@kaladron
kaladron marked this pull request as ready for review November 12, 2026 16:36
@kaladron
kaladron requested a review from a team as a code owner November 12, 2026 16:36
@kaladron
kaladron requested a review from lntue November 12, 2026 16:36
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-libc

Author: Jeff Bailey (kaladron)

Changes

The top-level headers/index.md page currently renders a plain static list of header names without indicating overall C23 or POSIX.1-2024 coverage or per-header completion across the library.

Replace static headers/index.md with docgen.py --index (invoked by the CMake libc-docgen-index target) to generate top-level C23, POSIX.1-2024, and symbol-category completeness cards alongside colour-coded progress rows for every catalogued header, while hiding top-of-page source links on the aggregated index page in conf.py.

Assisted-by: Automated tooling, human reviewed.


Full diff: https://github.com/llvm/llvm-project/pull/241945.diff

6 Files Affected:

  • (modified) libc/docs/CMakeLists.txt (+6-1)
  • (modified) libc/docs/_static/status.css (+25)
  • (modified) libc/docs/conf.py (+3)
  • (removed) libc/docs/headers/index.md (-94)
  • (modified) libc/utils/docgen/docgen.py (+133-19)
  • (modified) libc/utils/docgen/docgen_test.py (+23-1)
diff --git a/libc/docs/CMakeLists.txt b/libc/docs/CMakeLists.txt
index bd71375f98055..6d67aef96091d 100644
--- a/libc/docs/CMakeLists.txt
+++ b/libc/docs/CMakeLists.txt
@@ -97,7 +97,7 @@ if(LLVM_ENABLE_SPHINX)
       foreach(stem IN LISTS docgen_list)
         list(APPEND libc_generated_docs "headers/${stem}.md")
       endforeach()
-      list(APPEND libc_generated_docs configure.rst)
+      list(APPEND libc_generated_docs headers/index.md configure.rst)
       add_sphinx_source_sync_target(copy-libc-rst-docs
         "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}"
         PRESERVE_DOCS ${libc_generated_docs})
@@ -141,6 +141,11 @@ if(LLVM_ENABLE_SPHINX)
         # depend on the docgen invocation.
         add_dependencies(docs-libc-html ${docgen_target_name})
       endforeach()
+      add_custom_target(libc-docgen-index
+        COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../utils/docgen/docgen.py --index >
+          ${CMAKE_CURRENT_BINARY_DIR}/headers/index.md)
+      add_dependencies(libc-docgen-index copy-libc-rst-docs)
+      add_dependencies(docs-libc-html libc-docgen-index)
     endif()
   endif()
 endif()
diff --git a/libc/docs/_static/status.css b/libc/docs/_static/status.css
index 632f0df8ab237..66795308a1325 100644
--- a/libc/docs/_static/status.css
+++ b/libc/docs/_static/status.css
@@ -244,3 +244,28 @@ body[data-theme="dark"] {
 .hc-mini-val { font-size: 1.1rem; font-weight: 700; margin: 3px 0 6px 0; }
 .hc-mini-val span { font-size: 0.78em; font-weight: 600; }
 .hc-subcard-title { font-size: 0.88rem; font-weight: 600; margin-bottom: 10px; }
+.hc-grid-standards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 14px; margin: 16px 0 12px 0; }
+.hc-grid-standards .hc-card { margin: 0; }
+.hc-std-title { font-size: 0.95rem; font-weight: 600; color: var(--color-foreground-muted, #57606a); }
+.hc-std-val { font-size: 1.85rem; font-weight: 700; margin: 4px 0 8px 0; }
+.hc-std-val span { font-size: 0.52em; font-weight: 500; color: var(--color-foreground-muted, #57606a); }
+.hc-headers-list { display: flex; flex-direction: column; gap: 8px; margin: 14px 0 24px 0; }
+.header-card { border: 1px solid var(--color-background-border, #e1e4e8); border-radius: 6px; background: var(--color-background-secondary, #f6f8fa); }
+.header-card > summary { list-style: none; display: flex; align-items: center; justify-content: space-between; padding: 9px 14px; gap: 12px; user-select: none; }
+.header-card > summary::-webkit-details-marker { display: none; }
+.header-card:has(.hc-drawer) > summary { cursor: pointer; }
+.header-card > summary:focus-visible, .hc-page-link:focus-visible { outline: 2px solid var(--hc-blue-text); outline-offset: 2px; }
+.hc-header-title-col { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; width: 240px; flex-shrink: 0; }
+.hc-thermometer-col { flex: 1; min-width: 140px; display: flex; align-items: center; gap: 10px; }
+.hc-thermometer-col .hc-progress-track { flex: 1; height: 10px; }
+.hc-row-pct { font-size: 0.85rem; font-weight: 700; width: 48px; text-align: right; }
+.hc-row-frac { font-size: 0.78rem; color: var(--color-foreground-muted, #57606a); width: 64px; text-align: right; }
+.hc-action-col { display: flex; align-items: center; flex-shrink: 0; }
+.hc-page-link { display: inline-flex; align-items: center; gap: 3px; padding: 3px 8px; border: 1px solid var(--color-background-border, #d0d7de); border-radius: 4px; font-size: 0.76rem; font-weight: 500; text-decoration: none; color: var(--color-foreground-secondary, #57606a); background: var(--color-background-primary, #fff); white-space: nowrap; }
+.hc-page-link:hover { border-color: var(--hc-blue-border); color: var(--hc-blue-text); text-decoration: none; }
+@media (max-width: 640px) {
+  .header-card > summary { flex-wrap: wrap; padding: 9px 12px; gap: 8px; }
+  .hc-header-title-col { width: auto; flex: 1 1 auto; min-width: 0; }
+  .hc-action-col { margin-left: auto; }
+  .hc-thermometer-col { width: 100%; flex: 1 1 100%; order: 3; }
+}
diff --git a/libc/docs/conf.py b/libc/docs/conf.py
index d20346799174b..b02b62bf9a706 100644
--- a/libc/docs/conf.py
+++ b/libc/docs/conf.py
@@ -255,6 +255,9 @@
 
 
 def _configure_generated_header_links(app, pagename, templatename, context, doctree):
+    if pagename == "headers/index":
+        context["theme_top_of_page_buttons"] = []
+        return
     if not pagename.startswith("headers/"):
         return
     stem = pagename[len("headers/") :]
diff --git a/libc/docs/headers/index.md b/libc/docs/headers/index.md
deleted file mode 100644
index 872a86a3cb049..0000000000000
--- a/libc/docs/headers/index.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# Implementation Status
-
-Purely compiler-provided freestanding C headers (`iso646.h`, `stdalign.h`,
-`stdarg.h`, `stdatomic.h`, `stdbool.h`, `stddef.h`, `stdnoreturn.h`, and
-`tgmath.h`) come from the host compiler (`Clang` or `GCC`) rather than
-LLVM-libc and are omitted below.
-
-```{toctree}
-:maxdepth: 1
-
-aio
-arpa/inet
-assert
-byteswap
-complex
-cpio
-ctype
-dirent
-dlfcn
-endian
-errno
-fcntl
-fenv
-float
-fmtmsg
-fnmatch
-ftw
-glob
-grp
-iconv
-inttypes
-langinfo
-libgen
-limits
-locale
-math
-monetary
-mqueue
-ndbm
-net/if
-netdb
-netinet/in
-netinet/tcp
-nl_types
-poll
-pwd
-regex
-sched
-search
-semaphore
-setjmp
-signal
-spawn
-stdbit
-stdckdint
-stdfix
-stdint
-stdio
-stdlib
-string
-strings
-sys/ipc
-sys/mman
-sys/msg
-sys/resource
-sys/select
-sys/sem
-sys/shm
-sys/socket
-sys/stat
-sys/statvfs
-sys/time
-sys/times
-sys/types
-sys/uio
-sys/un
-sys/utsname
-sys/wait
-syslog
-tar
-termios
-threads
-time
-uchar
-ulimit
-unistd
-utime
-utmpx
-wchar
-wctype
-pthread
-wordexp
-```
-
diff --git a/libc/utils/docgen/docgen.py b/libc/utils/docgen/docgen.py
index ae5b24d2c54ef..94b34b0be1b60 100755
--- a/libc/utils/docgen/docgen.py
+++ b/libc/utils/docgen/docgen.py
@@ -381,6 +381,26 @@ def _precision_grid_html(header_name: str, precisions: List[Dict]) -> str:
     return f'<div class="hc-grid-prec">{cards}</div>'
 
 
+def _header_pills_html(stats: Dict, short: bool = False) -> str:
+    pills = []
+    if stats["standard"] == "c23":
+        lbl = "C23" if short else "C23 Standard"
+        pills.append(f'<span class="hc-pill hc-pill-c23">{lbl}</span>')
+    elif stats["standard"] == "iso-tr-18037":
+        lbl = "TR 18037" if short else "ISO/IEC TR 18037"
+        pills.append(f'<span class="hc-pill hc-pill-ext">{lbl}</span>')
+    if stats["posix"] == "posix-2024":
+        lbl = "POSIX" if short else "POSIX.1-2024"
+        pills.append(f'<span class="hc-pill hc-pill-posix">{lbl}</span>')
+    if not pills:
+        if stats["removed_posix"]:
+            lbl = "Removed" if short else "Removed in POSIX.1-2024"
+        else:
+            lbl = "Ext" if short else "Extension Header"
+        pills.append(f'<span class="hc-pill hc-pill-ext">{lbl}</span>')
+    return "".join(pills)
+
+
 def print_summary_card(header: Header, api: Dict):
     stats = header.get_summary_stats(api)
     imp, tot = stats["overall"]["implemented"], stats["overall"]["total"]
@@ -388,19 +408,7 @@ def print_summary_card(header: Header, api: Dict):
         return
     pct = imp / tot * 100.0
     tone = _tone(pct)
-
-    pills = []
-    if stats["standard"] == "c23":
-        pills.append('<span class="hc-pill hc-pill-c23">C23 Standard</span>')
-    elif stats["standard"] == "iso-tr-18037":
-        pills.append('<span class="hc-pill hc-pill-ext">ISO/IEC TR 18037</span>')
-    if stats["posix"] == "posix-2024":
-        pills.append('<span class="hc-pill hc-pill-posix">POSIX.1-2024</span>')
-    if not pills:
-        ext_lbl = (
-            "Removed in POSIX.1-2024" if stats["removed_posix"] else "Extension Header"
-        )
-        pills.append(f'<span class="hc-pill hc-pill-ext">{ext_lbl}</span>')
+    pills_html = _header_pills_html(stats)
 
     cards = "".join(
         _mini_card_html(
@@ -424,7 +432,7 @@ def print_summary_card(header: Header, api: Dict):
     print(
         "```{raw} html\n"
         f'<div class="hc-card"><div class="hc-summary-top">'
-        f'<div class="hc-pill-row">{"".join(pills)}</div>'
+        f'<div class="hc-pill-row">{pills_html}</div>'
         f'<div class="hc-overall-stat">{imp:,} / {tot:,} '
         f'<span style="color:var(--hc-{tone}-text)">({pct:.1f}%)</span></div></div>'
         f'{overall_bar}<div class="hc-grid-cards">{cards}</div></div>'
@@ -503,15 +511,121 @@ def get_choices() -> list:
     return sorted(choices)
 
 
+def _std_card_html(title: str, imp: int, tot: int, tone: str) -> str:
+    pct = (imp / tot * 100.0) if tot else 0.0
+    bar = _progress_bar_html(pct, title, tone, "hc-progress-lg")
+    return (
+        f'<div class="hc-card"><div class="hc-std-title">{title}</div>'
+        f'<div class="hc-std-val" style="color:var(--hc-{tone}-text)">'
+        f"{pct:.1f}% <span>({imp:,} / {tot:,})</span></div>{bar}</div>"
+    )
+
+
+def generate_index_md():
+    headers_data = []
+    totals = {
+        k: [0, 0]
+        for k in ("c23", "posix_spec", "functions", "macros", "types", "objects")
+    }
+    for hname in get_choices():
+        hdr = Header(hname)
+        check_api(hdr, hdr.api)
+        stats = hdr.get_summary_stats(hdr.api)
+        headers_data.append((hdr, hdr.api, stats))
+        for k, pair in totals.items():
+            pair[0] += stats[k]["implemented"]
+            pair[1] += stats[k]["total"]
+
+    n_c23 = sum(1 for _, _, s in headers_data if s["standard"] == "c23")
+    n_posix = sum(1 for _, _, s in headers_data if s["posix"] == "posix-2024")
+    c23_card = _std_card_html("C23 Standard Completeness", *totals["c23"], "green")
+    pos_card = _std_card_html(
+        "POSIX.1-2024 Completeness", *totals["posix_spec"], "blue"
+    )
+    cat_cards = "".join(
+        _mini_card_html("Overall", title, *totals[k], tone)
+        for k, title, tone in (
+            ("functions", "Functions", "green"),
+            ("macros", "Macros", "blue"),
+            ("types", "Types", "purple"),
+            ("objects", "Global Variables", "amber"),
+        )
+    )
+    print(
+        "(implementation_status)=\n\n(headers_index)=\n\n# Implementation Status\n\n"
+        f"Live symbol coverage across {len(headers_data)} headers ({n_c23} C23 "
+        f"standard headers, {n_posix} POSIX.1-2024 headers, and extensions), verified "
+        "directly against LLVM-libc source files, header generation specifications, "
+        "and exported entrypoints.  Purely compiler-provided freestanding C headers "
+        "(`iso646.h`, `stdalign.h`, `stdarg.h`, `stdatomic.h`, `stdbool.h`, "
+        "`stddef.h`, `stdnoreturn.h`, and `tgmath.h`) come from the host compiler "
+        "(`Clang` or `GCC`) rather than LLVM-libc and are omitted below.\n\n"
+        "```{raw} html\n"
+        f'<div class="hc-grid-standards">{c23_card}{pos_card}</div>'
+        f'<div class="hc-grid-cards">{cat_cards}</div>\n```\n\n'
+        "## All Headers\n\n```{raw} html\n"
+        '<div id="headers-list" class="hc-headers-list">'
+    )
+    for hdr, api, stats in headers_data:
+        imp, tot = stats["overall"]["implemented"], stats["overall"]["total"]
+        pct = (imp / tot * 100.0) if tot else 0.0
+        std_tags = []
+        if stats["standard"] == "c23":
+            std_tags.append("c23")
+        if stats["posix"] == "posix-2024":
+            std_tags.append("posix")
+        if not std_tags:
+            std_tags.append("ext")
+        status_attr = (
+            "complete" if tot and imp == tot else ("inprogress" if imp else "planned")
+        )
+        syms = [
+            s
+            for sec in ("families", "functions", "macros", "types", "objects")
+            for s in (api.get(sec) or {})
+        ] + [
+            v
+            for fd in (api.get("families") or {}).values()
+            for v in ((fd or {}).get("variants") or {}).values()
+            if v
+        ]
+        stem = html.escape(hdr.stem, quote=True)
+        hname = html.escape(hdr.name, quote=True)
+        syms_attr = html.escape(" ".join(syms).lower(), quote=True)
+        bar = _progress_bar_html(pct, f"{hdr.name} implementation progress", _tone(pct))
+        print(
+            f'<details class="header-card" data-stem="{stem}" data-name="{hname}" '
+            f'data-standard="{" ".join(std_tags)}" data-status="{status_attr}" '
+            f'data-pct="{pct:.2f}" data-missing="{tot - imp}" '
+            f'data-symbols="{syms_attr}"><summary>'
+            f'<div class="hc-header-title-col"><code class="hc-sym">&lt;{hname}&gt;'
+            f"</code>{_header_pills_html(stats, short=True)}</div>"
+            f'<div class="hc-thermometer-col">{bar}'
+            f'<span class="hc-row-pct">{pct:.1f}%</span>'
+            f'<span class="hc-row-frac">({imp}/{tot})</span></div>'
+            f'<div class="hc-action-col"><a class="hc-page-link" href="{stem}.html" '
+            f'onclick="event.stopPropagation();" '
+            f'aria-label="Open {hname} status page">'
+            'Page <span aria-hidden="true">&#8599;</span></a></div>'
+            "</summary></details>"
+        )
+    stems = "\n".join(hdr.stem for hdr, _, _ in headers_data)
+    print(f"</div>\n```\n\n```{{toctree}}\n:hidden: true\n:maxdepth: 1\n\n{stems}\n```")
+
+
 def parse_args() -> Namespace:
     parser = ArgumentParser()
-    parser.add_argument("header_name", choices=get_choices())
+    group = parser.add_mutually_exclusive_group(required=True)
+    group.add_argument("header_name", nargs="?", choices=get_choices())
+    group.add_argument("--index", action="store_true")
     return parser.parse_args()
 
 
 if __name__ == "__main__":
     args = parse_args()
-    header = Header(args.header_name)
-    check_api(header, header.api)
-
-    print_header_status_md(header, header.api)
+    if args.index:
+        generate_index_md()
+    else:
+        header = Header(args.header_name)
+        check_api(header, header.api)
+        print_header_status_md(header, header.api)
diff --git a/libc/utils/docgen/docgen_test.py b/libc/utils/docgen/docgen_test.py
index 33490ca8eddab..a34c63b6cd7c7 100644
--- a/libc/utils/docgen/docgen_test.py
+++ b/libc/utils/docgen/docgen_test.py
@@ -16,7 +16,13 @@
 import unittest
 
 sys.path.insert(0, str(Path(__file__).parent))
-from docgen import check_api, cppreference_url, posix_url, print_header_status_md
+from docgen import (
+    check_api,
+    cppreference_url,
+    generate_index_md,
+    posix_url,
+    print_header_status_md,
+)
 from header import Header, _is_stub_file
 
 
@@ -385,6 +391,22 @@ def test_summary_stats(self):
         self.assertGreater(fx_stats["tr18037"]["implemented"], 0)
         self.assertEqual(fx_stats["c23"]["total"], 0)
 
+    def test_index_output_generation(self):
+        buf = io.StringIO()
+        with redirect_stdout(buf), redirect_stderr(io.StringIO()):
+            generate_index_md()
+        out = buf.getvalue()
+        self.assertIn("(implementation_status)=", out)
+        self.assertIn("(headers_index)=", out)
+        self.assertIn("C23 Standard Completeness", out)
+        self.assertIn("POSIX.1-2024 Completeness", out)
+        self.assertIn("Global Variables", out)
+        self.assertIn("`stdarg.h`", out)
+        self.assertIn('data-stem="stdio"', out)
+        self.assertIn('data-stem="arpa/inet"', out)
+        self.assertIn("acosf128", out)
+        self.assertIn("```{toctree}\n:hidden: true", out)
+
 
 if __name__ == "__main__":
     unittest.main()

@kaladron
kaladron merged commit bd85f36 into llvm:main Nov 12, 2026
51 checks passed
@kaladron
kaladron deleted the howclose-pr9 branch November 12, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants