Skip to content

Commit 9ab243d

Browse files
edersondisouzacarlescufi
authored andcommitted
doc: Generate develop/api/overview.rst API table from doxygen
A new extension, api_overview.py, is used to, leveraging doxygen's Python module doxmlparser, parse the doxygen generated XML files. All groups ('defgroup' and 'addtogroup' tags) are collected, alongside their 'version' and 'since' info. From there, a new Sphinx directive `api-overview-table` is populated, including the name of the group, and if available, their 'since' and 'version' information. Signed-off-by: Ederson de Souza <ederson.desouza@intel.com> Signed-off-by: Anas Nashif <anas.nashif@intel.com>
1 parent 4fe0a1d commit 9ab243d

File tree

4 files changed

+236
-373
lines changed

4 files changed

+236
-373
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Copyright (c) 2023 Intel Corporation
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import doxmlparser
5+
6+
from docutils import nodes
7+
from doxmlparser.compound import DoxCompoundKind
8+
from pathlib import Path
9+
from sphinx.application import Sphinx
10+
from sphinx.util.docutils import SphinxDirective
11+
from typing import Any, Dict
12+
13+
14+
class ApiOverview(SphinxDirective):
15+
"""
16+
This is a Zephyr directive to generate a table containing an overview
17+
of all APIs. This table will show the API name, version and since which
18+
version it is present - all information extracted from Doxygen XML output.
19+
20+
It is exclusively used by the doc/develop/api/overview.rst page.
21+
22+
Configuration options:
23+
24+
api_overview_doxygen_xml_dir: Doxygen xml output directory
25+
api_overview_doxygen_base_url: Doxygen base html directory
26+
"""
27+
28+
def run(self):
29+
return [self.env.api_overview_table]
30+
31+
32+
def get_group(innergroup, all_groups):
33+
try:
34+
return [
35+
g
36+
for g in all_groups
37+
if g.get_compounddef()[0].get_id() == innergroup.get_refid()
38+
][0]
39+
except IndexError as e:
40+
raise Exception(f"Unexpected group {innergroup.get_refid()}") from e
41+
42+
43+
def visit_group(app, group, all_groups, rows, indent=0):
44+
version = since = ""
45+
github_uri = "https://github.com/zephyrproject-rtos/zephyr/releases/tag/"
46+
cdef = group.get_compounddef()[0]
47+
48+
ssects = [
49+
s for p in cdef.get_detaileddescription().get_para() for s in p.get_simplesect()
50+
]
51+
for sect in ssects:
52+
if sect.get_kind() == "since":
53+
since = sect.get_para()[0].get_valueOf_()
54+
elif sect.get_kind() == "version":
55+
version = sect.get_para()[0].get_valueOf_()
56+
57+
if since:
58+
since_url = nodes.inline()
59+
reference = nodes.reference(text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0")
60+
reference.attributes["internal"] = True
61+
since_url += reference
62+
else:
63+
since_url = nodes.Text("")
64+
65+
url_base = Path(app.config.api_overview_doxygen_base_url)
66+
url = url_base / f"{cdef.get_id()}.html"
67+
68+
title = cdef.get_title()
69+
70+
row_node = nodes.row()
71+
72+
# Next entry will contain the spacer and the link with API name
73+
entry = nodes.entry()
74+
span = nodes.Text("".join(["\U000000A0"] * indent))
75+
entry += span
76+
77+
# API name with link
78+
inline = nodes.inline()
79+
reference = nodes.reference(text=title, refuri=str(url))
80+
reference.attributes["internal"] = True
81+
inline += reference
82+
entry += inline
83+
row_node += entry
84+
85+
version_node = nodes.Text(version)
86+
# Finally, add version and since
87+
for cell in [version_node, since_url]:
88+
entry = nodes.entry()
89+
entry += cell
90+
row_node += entry
91+
rows.append(row_node)
92+
93+
for innergroup in cdef.get_innergroup():
94+
visit_group(
95+
app, get_group(innergroup, all_groups), all_groups, rows, indent + 6
96+
)
97+
98+
99+
def parse_xml_dir(dir_name):
100+
groups = []
101+
root = doxmlparser.index.parse(Path(dir_name) / "index.xml", True)
102+
for compound in root.get_compound():
103+
if compound.get_kind() == DoxCompoundKind.GROUP:
104+
file_name = Path(dir_name) / f"{compound.get_refid()}.xml"
105+
groups.append(doxmlparser.compound.parse(file_name, True))
106+
107+
return groups
108+
109+
110+
def generate_table(app, toplevel, groups):
111+
table = nodes.table()
112+
tgroup = nodes.tgroup()
113+
114+
thead = nodes.thead()
115+
thead_row = nodes.row()
116+
for header_name in ["API", "Version", "Available in Zephyr Since"]:
117+
colspec = nodes.colspec()
118+
tgroup += colspec
119+
120+
entry = nodes.entry()
121+
entry += nodes.Text(header_name)
122+
thead_row += entry
123+
thead += thead_row
124+
tgroup += thead
125+
126+
rows = []
127+
tbody = nodes.tbody()
128+
for t in toplevel:
129+
visit_group(app, t, groups, rows)
130+
tbody.extend(rows)
131+
tgroup += tbody
132+
133+
table += tgroup
134+
135+
return table
136+
137+
138+
def sync_contents(app: Sphinx) -> None:
139+
if app.config.doxyrunner_outdir:
140+
doxygen_out_dir = Path(app.config.doxyrunner_outdir)
141+
else:
142+
doxygen_out_dir = Path(app.outdir) / "_doxygen"
143+
144+
doxygen_xml_dir = doxygen_out_dir / "xml"
145+
groups = parse_xml_dir(doxygen_xml_dir)
146+
147+
toplevel = [
148+
g
149+
for g in groups
150+
if g.get_compounddef()[0].get_id()
151+
not in [
152+
i.get_refid()
153+
for h in [j.get_compounddef()[0].get_innergroup() for j in groups]
154+
for i in h
155+
]
156+
]
157+
158+
app.builder.env.api_overview_table = generate_table(app, toplevel, groups)
159+
160+
161+
def setup(app) -> Dict[str, Any]:
162+
app.add_config_value("api_overview_doxygen_xml_dir", "html/doxygen/xml", "env")
163+
app.add_config_value("api_overview_doxygen_base_url", "../../doxygen/html", "env")
164+
165+
app.add_directive("api-overview-table", ApiOverview)
166+
167+
app.connect("builder-inited", sync_contents)
168+
169+
return {
170+
"version": "0.1",
171+
"parallel_read_safe": True,
172+
"parallel_write_safe": True,
173+
}

doc/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"sphinx_togglebutton",
9696
"zephyr.external_content",
9797
"zephyr.domain",
98+
"zephyr.api_overview",
9899
]
99100

100101
# Only use SVG converter when it is really needed, e.g. LaTeX.
@@ -364,6 +365,9 @@
364365
linkcheck_workers = 10
365366
linkcheck_anchors = False
366367

368+
# -- Options for zephyr.api_overview --------------------------------------
369+
370+
api_overview_doxygen_base_url = "../../doxygen/html"
367371

368372
def setup(app):
369373
# theme customizations

doc/develop/api/api_lifecycle.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ no longer optimal or supported by the underlying platforms.
2020
An up-to-date table of all APIs and their maturity level can be found in the
2121
:ref:`api_overview` page.
2222

23+
24+
.. _api_lifecycle_experimental:
25+
2326
Experimental
2427
*************
2528

@@ -36,6 +39,10 @@ The following requirements apply to all new APIs:
3639
of said API (in the case of peripheral APIs, this corresponds to one driver)
3740
- At least one sample using the new API (may only build on one single board)
3841

42+
When introducing a new and experimental API, mark the API version in the headers
43+
where the API is defined. An experimental API shall have a version where the minor
44+
version is up to one (0.1.z). (see `api overview <api_overview>`)
45+
3946
Peripheral APIs (Hardware Related)
4047
==================================
4148

@@ -46,13 +53,19 @@ the Architecture working group consisting of representatives from different vend
4653
The API shall be promoted to ``unstable`` when it has at least two
4754
implementations on different hardware platforms.
4855

56+
.. _api_lifecycle_unstable:
57+
4958
Unstable
5059
********
5160

5261
The API is in the process of settling, but has not yet had sufficient real-world
5362
testing to be considered stable. The API is considered generic in nature and can
5463
be used on different hardware platforms.
5564

65+
When the API changes status to unstable API, mark the API version in the headers
66+
where the API is defined. Unstable APIs shall have a version where the minor
67+
version is larger than one (0.y.z | y > 1 ). (see `api overview <api_overview>`)
68+
5669
.. note::
5770

5871
Changes will not be announced.
@@ -69,6 +82,8 @@ Hardware Agnostic APIs
6982
For hardware agnostic APIs, multiple applications using it are required to
7083
promote an API from ``experimental`` to ``unstable``.
7184

85+
.. _api_lifecycle_stable:
86+
7287
Stable
7388
*******
7489

@@ -94,6 +109,11 @@ In order to declare an API ``stable``, the following steps need to be followed:
94109
`Zephyr Architecture meeting`_ where, barring any objections, the Pull Request
95110
will be merged
96111

112+
113+
When the API changes status to stable API, mark the API version in the headers
114+
where the API is defined. Stable APIs shall have a version where the major
115+
version is one or larger (x.y.z | x >= 1 ). (see `api overview <api_overview>`)
116+
97117
.. _breaking_api_changes:
98118

99119
Introducing breaking API changes
@@ -177,6 +197,11 @@ for it to be discussed and ultimately even voted on in the `Zephyr TSC meeting`_
177197
If the Pull Request is merged then an email must be sent to the ``devel`` and
178198
``user`` mailing lists informing them of the change.
179199

200+
The API version shall be changed to signal backward incompatible changes. This
201+
is achieved by incrementing the major version (X.y.z | X > 1). It MAY also
202+
include minor and patch level changes. Patch and minor versions MUST be reset to
203+
0 when major version is incremented. (see `api overview <api_overview>`)
204+
180205
.. note::
181206

182207
Breaking API changes will be listed and described in the migration guide.

0 commit comments

Comments
 (0)