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
8 changes: 8 additions & 0 deletions doc/api/python/extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ Re-reads a document when an input from outside the source tree changes.
.. automodule:: input_tracking
:members:

``needs_config_state``
------------------------

Re-reads a document when its sphinx-needs TOML file or the needs it imports change.

.. automodule:: needs_config_state
:members:

``needs_fields``
------------------

Expand Down
5 changes: 5 additions & 0 deletions doc/manual/reference/consumer-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ layout, not toggled from outside it.
link's incoming side. One field is optional: ``depends_on`` (the
``@kconfig_depends`` conditions) is set only if declared here.

A change to the file's content makes every document that reads it re-read
its sources on the next incremental build (the engine's
``needs_config_state`` extension), because sphinx-needs itself rebuilds only
the HTML when a type, link or field changes.

This variable is real and load-bearing — every sample and fixture in this
repository that uses sphinx-needs sets it — but it is not mentioned
alongside the others in ``cmake/zdocs.cmake``'s own "Consumer configuration"
Expand Down
13 changes: 12 additions & 1 deletion doc/manual/reference/registry-schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ Each entry in ``documents:``, keyed by its id:
rejected at configure time as a ``spec:`` target, because ``testreport``
correlates against ``needs.json``, and the synthesized stub is not that.

When the needs that a peer imports from this document change, the peer
reads all its sources again on the next incremental build (the engine's
``needs_config_state`` extension). Otherwise the pages of the peer do not
show a new incoming link from this document.

``doxygen_tag``
Opt-in; publishes this document's needs as Doxygen requirements, so a
``\verifies`` or ``\satisfies`` in any ``kind: doxygen`` peer resolves
Expand Down Expand Up @@ -311,7 +316,13 @@ Each entry in ``documents:``, keyed by its id:

``api_reference``
Same existence/kind requirement as ``doxygen_source``, for where
``@see`` cross-references resolve.
``@see`` cross-references resolve. A symbol Doxygen resolved through
another document's tag file links into that document instead (Doxygen
records the tag file on the reference); ``api_reference`` takes only
the references no registry document's tag file accounts for. A
``@see`` target that no Doxygen project documents has no reference.
It shows as a literal in the "See also" line, without a link. The
line has the targets of all ``@see`` lines of the test, in order.

``spec``
Id of the sphinx document whose exported needs a ``testreport`` document
Expand Down
32 changes: 31 additions & 1 deletion scripts/docrefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ def __init__(
#: never a dict of empty strings. A dict with keys ``xml_dir``,
#: ``doxygen_url``, ``api_url``, ``needs_json`` (each ``""`` when the
#: corresponding ``testmodule:`` field is absent, e.g. no
#: ``api_reference:``).
#: ``api_reference:``), and ``tag_urls`` (see `tag_urls`).
self.testmodule = testmodule
#: This document's own resolved ``symbol_needs:`` block, or ``None``
#: when it has none (then the ``symbol_needs`` extension is not
Expand Down Expand Up @@ -962,6 +962,7 @@ def _nav_href(doc_id, meta):
"doxygen_url": rel_urls.get(doxygen_source, "") if doxygen_source else "",
"api_url": rel_urls.get(api_reference, "") if api_reference else "",
"needs_json": needs_json,
"tag_urls": tag_urls(documents, deploy, rel_urls),
}

symbol_needs = None
Expand All @@ -987,6 +988,35 @@ def _nav_href(doc_id, meta):
)


def tag_urls(documents, deploy, rel_urls):
"""``{tag file path: HTML directory URL}`` for every tag file `tagfiles` can name.

Doxygen marks a reference it resolved through a tag file with
``external="<tag file path>"``, the path exactly as ``TAGFILES`` gave it.
This map sends such a reference to the document whose tag file it is:
for a ``kind: doxygen`` peer its HTML directory relative to this
document's root (``rel_urls``), for a ``kind: doxygen-external`` peer its
absolute remote directory, and for a needs tag its Sphinx root.
"""
urls = {}
for doc_id, meta in documents.items():
kind = meta.get("kind")
path = meta.get("path", f"html/{doc_id}")
if kind == "doxygen" and doc_id in rel_urls:
urls[(deploy / path / DOXYGEN_TAGFILE).as_posix()] = rel_urls[doc_id]
elif kind == "doxygen-external":
# Without its trailing slash: the reader appends "/<page>".
base_dir_url = _intersphinx_target_dir(meta["remote-url"]).rstrip("/")
urls[(deploy / path / DOXYGEN_TAGFILE).as_posix()] = base_dir_url
elif (
kind in (None, "sphinx")
and meta.get("doxygen_tag") is not None
and doc_id in rel_urls
):
urls[(deploy / path / NEEDS_TAGFILE).as_posix()] = rel_urls[doc_id]
return urls


def tagfiles(this_doc, deploy_dir, registry=None):
"""Doxygen ``TAGFILES`` value for ``this_doc``: an entry for every OTHER
``kind: doxygen`` document in the registry, so any doxygen doc can resolve
Expand Down
88 changes: 88 additions & 0 deletions sphinx/_extensions/_tests/test_doxygen_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,91 @@ def test_parse_memberdef_links_symbols_in_brief_details_and_body():
def test_see_to_rst_links_local_symbol_when_testspec_dir_given():
see = ET.fromstring(f"<simplesect kind='see'><para>{LOCAL_REF}</para></simplesect>")
assert "../testspec/group__procs.html#ga3f93" in dp.see_to_rst(see, API, SPEC)


# ---------------------------------------------------------------------------
# see_to_rst: text that is not in a <ref>
#
# `@see irq_offload()` gives no <ref> when no Doxygen project documents the
# symbol. see_to_rst read only the <ref> elements, so the see section gave no
# line, and the reference was lost.
# ---------------------------------------------------------------------------

def _see(body):
return ET.fromstring(f"<simplesect kind='see'><para>{body}</para></simplesect>")


def test_see_to_rst_renders_text_without_ref_as_literal():
assert dp.see_to_rst(_see("irq_offload()"), API, SPEC) == "**See also:** ``irq_offload()``"


def test_see_to_rst_keeps_refs_and_the_text_between_them_in_order():
line = dp.see_to_rst(_see(f"atomic_set(), {EXT_REF}, printk() "), API, SPEC)
assert line == (
"**See also:** ``atomic_set()``, "
"`k_fifo_get() <../api/group__fifo__apis.html#ga1e2c>`__, ``printk()``"
)


def test_see_to_rst_separators_between_refs_give_no_item():
"""A line with only linked refs is the same as before."""
line = dp.see_to_rst(_see(f"{EXT_REF}, {LOCAL_REF}."), API, SPEC)
assert line == (
"**See also:** `k_fifo_get() <../api/group__fifo__apis.html#ga1e2c>`__, "
"`get_scratch_packet() <../testspec/group__procs.html#ga3f93>`__"
)


def test_see_to_rst_a_comma_in_parentheses_does_not_divide_items():
assert dp.see_to_rst(_see("k_foo(a, b), k_bar()"), API) == (
"**See also:** ``k_foo(a, b)``, ``k_bar()``"
)


def test_see_to_rst_links_a_ref_inside_computeroutput():
line = dp.see_to_rst(_see(f"<computeroutput>{EXT_REF}</computeroutput>"), API, SPEC)
assert line == "**See also:** `k_fifo_get() <../api/group__fifo__apis.html#ga1e2c>`__"


def test_see_to_rst_empty_section_gives_no_line():
assert dp.see_to_rst(_see(" , "), API, SPEC) == ""


def test_see_to_rst_space_after_a_call_divides_items():
assert dp.see_to_rst(_see("k_stats_query() k_stats_reset()"), API) == (
"**See also:** ``k_stats_query()``, ``k_stats_reset()``"
)


# ---------------------------------------------------------------------------
# All see sections of a member
#
# Doxygen 1.16 writes one see section for each `@see` line. Only the first was
# read, so `@see k_thread_join()` followed by `@see irq_offload()` lost the
# second reference (TSPEC-THREADS-030).
# ---------------------------------------------------------------------------

def test_see_to_rst_joins_all_sections_in_order():
sections = [_see(EXT_REF), _see("irq_offload()")]
assert dp.see_to_rst(sections, API, SPEC) == (
"**See also:** `k_fifo_get() <../api/group__fifo__apis.html#ga1e2c>`__, ``irq_offload()``"
)


def test_see_to_rst_no_sections_gives_no_line():
assert dp.see_to_rst([], API, SPEC) == ""


def test_parse_memberdef_reads_every_see_section():
md = ET.fromstring(
"<memberdef kind='function' id='group__s_1a1'><name>test_join</name>"
"<briefdescription><para>Join.</para></briefdescription>"
"<detaileddescription><para>"
f"<simplesect kind='see'><para>{EXT_REF}</para></simplesect>"
"<simplesect kind='see'><para>irq_offload()</para></simplesect>"
"</para></detaileddescription>"
"<location file='t.c' line='1'/></memberdef>"
)
info = dp.parse_memberdef(md, "group__s", SPEC, API)
assert "k_fifo_get()" in info["see_rst"]
assert info["see_rst"].endswith(", ``irq_offload()``")
Loading