Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions doc/_extensions/zephyr/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"""
from typing import Any, Dict, Iterator, List, Tuple

from breathe.directives.content_block import DoxygenGroupDirective
from docutils import nodes
from docutils.nodes import Node
from docutils.parsers.rst import Directive, directives
Expand All @@ -59,6 +58,7 @@
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging
from sphinx.util.nodes import NodeMatcher, make_refnode
from zephyr.doxybridge import DoxygenGroupDirective
from zephyr.gh_utils import gh_link_get_url

import json
Expand Down Expand Up @@ -310,7 +310,7 @@ def run(self) -> List[Node]:
nodes = super().run()

if self.config.zephyr_breathe_insert_related_samples:
return [RelatedCodeSamplesNode(id=self.arguments[0]), *nodes]
return [*nodes, RelatedCodeSamplesNode(id=self.arguments[0])]
else:
return nodes

Expand All @@ -323,7 +323,7 @@ def setup(app):
app.add_transform(ConvertCodeSampleNode)
app.add_post_transform(ProcessRelatedCodeSamplesNode)

# monkey-patching of Breathe's DoxygenGroupDirective
# monkey-patching of the DoxygenGroupDirective
app.add_directive("doxygengroup", CustomDoxygenGroupDirective, override=True)

return {
Expand Down
235 changes: 235 additions & 0 deletions doc/_extensions/zephyr/doxybridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
"""
Copyright (c) 2021 Nordic Semiconductor ASA
Copyright (c) 2024 The Linux Foundation
SPDX-License-Identifier: Apache-2.0
"""

import os
from typing import Any, Dict

import concurrent.futures

from docutils import nodes

from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.domains.c import CXRefRole

import doxmlparser
from doxmlparser.compound import DoxCompoundKind, DoxMemberKind

logger = logging.getLogger(__name__)


KIND_D2S = {
DoxMemberKind.DEFINE: "macro",
DoxMemberKind.VARIABLE: "var",
DoxMemberKind.TYPEDEF: "type",
DoxMemberKind.ENUM: "enum",
DoxMemberKind.FUNCTION: "func",
}


class DoxygenGroupDirective(SphinxDirective):
has_content = False
required_arguments = 1
optional_arguments = 0

def run(self):

desc_node = addnodes.desc()
desc_node["domain"] = "c"
desc_node["objtype"] = "group"

title_signode = addnodes.desc_signature()
group_xref = addnodes.pending_xref(
"",
refdomain="c",
reftype="group",
reftarget=self.arguments[0],
refwarn=True,
)
group_xref += nodes.Text(self.arguments[0])
title_signode += group_xref

desc_node.append(title_signode)

return [desc_node]


class DoxygenReferencer(SphinxPostTransform):
"""Mapping between Doxygen memberdef kind and Sphinx kinds"""

default_priority = 5

def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(addnodes.pending_xref):
if node.get("refdomain") != "c":
continue

reftype = node.get("reftype")

# "member", "data" and "var" are equivalent as per Sphinx documentation for C domain
if reftype in ("member", "data"):
reftype = "var"

entry = self.app.env.doxybridge_cache.get(reftype)
if not entry:
continue

reftarget = node.get("reftarget").replace(".", "::").rstrip("()")
id = entry.get(reftarget)
if not id:
if reftype == "func":
# macros are sometimes referenced as functions, so try that
id = self.app.env.doxybridge_cache.get("macro").get(reftarget)
if not id:
continue
else:
continue

if reftype in ("struct", "union", "group"):
doxygen_target = f"{id}.html"
else:
split = id.split("_")
doxygen_target = f"{'_'.join(split[:-1])}.html#{split[-1][1:]}"

doxygen_target = str(self.app.config.doxybridge_dir) + "/html/" + doxygen_target

doc_dir = os.path.dirname(self.document.get("source"))
doc_dest = os.path.join(
self.app.outdir,
os.path.relpath(doc_dir, self.app.srcdir),
)
rel_uri = os.path.relpath(doxygen_target, doc_dest)

refnode = nodes.reference("", "", internal=True, refuri=rel_uri, reftitle="")

refnode.append(node[0].deepcopy())

if reftype == "group":
refnode["classes"].append("doxygroup")
title = self.app.env.doxybridge_group_titles.get(reftarget, "group")
refnode[0] = nodes.Text(title)

node.replace_self([refnode])


def parse_members(sectiondef):
cache = {}

for memberdef in sectiondef.get_memberdef():
kind = KIND_D2S.get(memberdef.get_kind())
if not kind:
continue

id = memberdef.get_id()
if memberdef.get_kind() == DoxMemberKind.VARIABLE:
name = memberdef.get_qualifiedname() or memberdef.get_name()
else:
name = memberdef.get_name()

cache.setdefault(kind, {})[name] = id

if memberdef.get_kind() == DoxMemberKind.ENUM:
for enumvalue in memberdef.get_enumvalue():
enumname = enumvalue.get_name()
enumid = enumvalue.get_id()
cache.setdefault("enumerator", {})[enumname] = enumid

return cache


def parse_sections(compounddef):
cache = {}

for sectiondef in compounddef.get_sectiondef():
members = parse_members(sectiondef)
for kind, data in members.items():
cache.setdefault(kind, {}).update(data)

return cache


def parse_compound(inDirName, baseName) -> Dict:
rootObj = doxmlparser.compound.parse(inDirName + "/" + baseName + ".xml", True)
cache = {}
group_titles = {}

for compounddef in rootObj.get_compounddef():
name = compounddef.get_compoundname()
id = compounddef.get_id()
kind = None
if compounddef.get_kind() == DoxCompoundKind.STRUCT:
kind = "struct"
elif compounddef.get_kind() == DoxCompoundKind.UNION:
kind = "union"
elif compounddef.get_kind() == DoxCompoundKind.GROUP:
kind = "group"
group_titles[name] = compounddef.get_title()

if kind:
cache.setdefault(kind, {})[name] = id

sections = parse_sections(compounddef)
for kind, data in sections.items():
cache.setdefault(kind, {}).update(data)

return cache, group_titles


def parse_index(app: Sphinx, inDirName):
rootObj = doxmlparser.index.parse(inDirName + "/index.xml", True)
compounds = rootObj.get_compound()

with concurrent.futures.ProcessPoolExecutor() as executor:
futures = [
executor.submit(parse_compound, inDirName, compound.get_refid())
for compound in compounds
]
for future in concurrent.futures.as_completed(futures):
cache, group_titles = future.result()
for kind, data in cache.items():
app.env.doxybridge_cache.setdefault(kind, {}).update(data)
app.env.doxybridge_group_titles.update(group_titles)


def doxygen_parse(app: Sphinx) -> None:
if not app.env.doxygen_input_changed:
return

app.env.doxybridge_cache = {
"macro": {},
"var": {},
"type": {},
"enum": {},
"enumerator": {},
"func": {},
"union": {},
"struct": {},
"group": {},
}

app.env.doxybridge_group_titles = {}

parse_index(app, str(app.config.doxybridge_dir / "xml"))


def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value("doxybridge_dir", None, "env")

app.add_directive("doxygengroup", DoxygenGroupDirective)

app.add_role_to_domain("c", "group", CXRefRole())

app.add_post_transform(DoxygenReferencer)
app.connect("builder-inited", doxygen_parse)

return {
"version": "0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
12 changes: 12 additions & 0 deletions doc/_static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -953,3 +953,15 @@ dark-mode-toggle::part(toggleLabel){
#search-se-menu ul li.selected .fa-check {
display: inline;
}

.doxygroup::after {
content: 'Doxygen';
display: inline-block;
background-color: var(--admonition-note-title-background-color);
color: var(--admonition-note-title-color);
padding: 2px 8px;
border-radius: 12px;
margin-left: 8px;
font-size: 0.875em;
font-weight: bold;
}
1 change: 0 additions & 1 deletion doc/build/dts/api/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,6 @@ system-wide settings. The :c:func:`DT_CHOSEN()` macro can be used to get a node
identifier for a chosen node.

.. doxygengroup:: devicetree-generic-chosen
:project: Zephyr

Zephyr-specific chosen nodes
****************************
Expand Down
29 changes: 3 additions & 26 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
# -- General configuration ------------------------------------------------

extensions = [
"breathe",
"sphinx_rtd_theme",
"sphinx.ext.todo",
"sphinx.ext.extlinks",
Expand All @@ -89,6 +88,7 @@
"sphinx_sitemap",
"zephyr.warnings_filter",
"zephyr.doxyrunner",
"zephyr.doxybridge",
"zephyr.gh_utils",
"zephyr.manifest_projects_table",
"notfound.extension",
Expand Down Expand Up @@ -249,32 +249,9 @@
doxyrunner_fmt_vars = {"ZEPHYR_BASE": str(ZEPHYR_BASE), "ZEPHYR_VERSION": version}
doxyrunner_outdir_var = "DOXY_OUT"

# -- Options for Breathe plugin -------------------------------------------
# -- Options for zephyr.doxybridge plugin ---------------------------------

breathe_projects = {"Zephyr": str(doxyrunner_outdir / "xml")}
breathe_default_project = "Zephyr"
breathe_domain_by_extension = {
"h": "c",
"c": "c",
}
breathe_show_enumvalue_initializer = True
breathe_default_members = ("members", )

cpp_id_attributes = [
"__syscall",
"__syscall_always_inline",
"__deprecated",
"__may_alias",
"__used",
"__unused",
"__weak",
"__attribute_const__",
"__DEPRECATED_MACRO",
"FUNC_NORETURN",
"__subsystem",
"ALWAYS_INLINE",
]
c_id_attributes = cpp_id_attributes
doxybridge_dir = doxyrunner_outdir

# -- Options for html_redirect plugin -------------------------------------

Expand Down
2 changes: 0 additions & 2 deletions doc/connectivity/bluetooth/api/mesh/blob.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,3 @@ API reference
This section contains types and defines common to the BLOB Transfer models.

.. doxygengroup:: bt_mesh_blob
:project: Zephyr
:members:
2 changes: 0 additions & 2 deletions doc/connectivity/bluetooth/api/mesh/blob_cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,3 @@ API reference
*************

.. doxygengroup:: bt_mesh_blob_cli
:project: Zephyr
:members:
1 change: 0 additions & 1 deletion doc/connectivity/bluetooth/api/mesh/blob_flash.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,3 @@ API Reference
*************

.. doxygengroup:: bt_mesh_blob_io_flash
:project: Zephyr
2 changes: 0 additions & 2 deletions doc/connectivity/bluetooth/api/mesh/blob_srv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,3 @@ API reference
*************

.. doxygengroup:: bt_mesh_blob_srv
:project: Zephyr
:members:
2 changes: 0 additions & 2 deletions doc/connectivity/bluetooth/api/mesh/dfd_srv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,3 @@ API reference
*************

.. doxygengroup:: bt_mesh_dfd_srv
:project: Zephyr
:members:
6 changes: 0 additions & 6 deletions doc/connectivity/bluetooth/api/mesh/dfu.rst
Original file line number Diff line number Diff line change
Expand Up @@ -371,13 +371,7 @@ API reference
This section lists the types common to the Device Firmware Update mesh models.

.. doxygengroup:: bt_mesh_dfd
:project: Zephyr
:members:

.. doxygengroup:: bt_mesh_dfu
:project: Zephyr
:members:

.. doxygengroup:: bt_mesh_dfu_metadata
:project: Zephyr
:members:
2 changes: 0 additions & 2 deletions doc/connectivity/bluetooth/api/mesh/dfu_cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,3 @@ API reference
*************

.. doxygengroup:: bt_mesh_dfu_cli
:project: Zephyr
:members:
Loading