aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Hajnoczi <stefanha@redhat.com>2025-03-16 02:44:56 -0400
committerStefan Hajnoczi <stefanha@redhat.com>2025-03-16 02:44:57 -0400
commit9beccc2df03026dc2979f0f28b8ff952e356164e (patch)
tree1731d2e51996ec21c0d0907d85cc8dbcbee552e6
parent0462a32b4f63b2448b4a196381138afd50719dc4 (diff)
parenta6af54434400099b8afd59ba036cf9a662006d1e (diff)
downloadqemu-9beccc2df03026dc2979f0f28b8ff952e356164e.zip
qemu-9beccc2df03026dc2979f0f28b8ff952e356164e.tar.gz
qemu-9beccc2df03026dc2979f0f28b8ff952e356164e.tar.bz2
Merge tag 'pull-qapi-2025-03-14' of https://repo.or.cz/qemu/armbru into staging
QAPI patches patches for 2025-03-14 # -----BEGIN PGP SIGNATURE----- # # iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmfT/U0SHGFybWJydUBy # ZWRoYXQuY29tAAoJEDhwtADrkYZThb4P/i2FNedYYeU+qOAtjKwCE0bnbtxWdthj # Zd+0u0LOXxkK7+nqgva+2+Szl4Ee0rYrbwVjd26nYRtB/m1/q1Glj1GTTAO+Xzpb # 3q4/ByFTDG3/mFktfVkE5HAJ7RGbjI3toRFWbpw1C4RabkX+dyZZ0MVwkfBwiyY7 # bEW7cW9OZlIXbMS867n7gURqEsD+LWXzxX5ozeWZGQVTp5nbQdubulYTkxJTXK+A # as2Q+RJhfB2lVJHAY3xN6R+gjHUNCBfwzfSFGMTMr+tYPeHZVssWeypXJJ9Qh7aA # dVLfVCY6PbstrGD1dGybIY1HfUTjJQNiyZ3qIoRfkxsfZcO7ru6Q5CMfEgxwcu53 # FaXLB3ra3R5cmYKFVeasEKHo/xsXeb3MAKCGLLqp7gC2GGdGvZAyHJevFZJslC+Q # /AbGtbmNYOYCkJdbT3r8bu9Qc7p2llw24Pjw/9I/qvwkKy3xdDyZQS+lT/vyYZvS # zc/hnlJR8UQvGXtzf0OrNCf8lDswNP6r51eTpno0OCQatrDi0ZjZqIOxHUUOn1pr # AE4JRDjtDoOqw8ltZxrulsiySSHewM4ouS3MXylpMk1PoWNq/6v8nUYL7p2RGgMq # FKyEdInExe1dWEjwaqPABBHdAWpZbmH0wmRLgeFaDvgmqqrOqFFeBKbgLFC2xcX5 # pgR35cz28GUh # =0HX3 # -----END PGP SIGNATURE----- # gpg: Signature made Fri 14 Mar 2025 05:56:29 EDT # gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653 # gpg: issuer "armbru@redhat.com" # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full] # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full] # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * tag 'pull-qapi-2025-03-14' of https://repo.or.cz/qemu/armbru: docs: enable transmogrifier for QSD and QGA docs: disambiguate references in qapi-domain.rst docs: add QAPI namespace "QMP" to qemu-qmp-ref docs/qapi-domain: add namespaced index support docs/qapi_domain: add namespace support to cross-references docs/qapidoc: add :namespace: option to qapi-doc directive docs/qapi-domain: add qapi:namespace directive docs/qapi-domain: add :namespace: override option docs/qapi_domain: add namespace support to FQN docs/qapi-domain: always store fully qualified name in signode docs/qapi_domain: isolate TYPE_CHECKING imports qapi/block-core: Improve x-blockdev-change documentation Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-rw-r--r--docs/conf.py7
-rw-r--r--docs/devel/qapi-domain.rst70
-rw-r--r--docs/interop/qemu-ga-ref.rst2
-rw-r--r--docs/interop/qemu-qmp-ref.rst1
-rw-r--r--docs/interop/qemu-storage-daemon-qmp-ref.rst2
-rw-r--r--docs/sphinx/qapi_domain.py297
-rw-r--r--docs/sphinx/qapidoc.py12
-rw-r--r--qapi/block-core.json28
-rw-r--r--qapi/qapi-schema.json2
-rw-r--r--qga/qapi-schema.json3
-rw-r--r--storage-daemon/qapi/qapi-schema.json8
11 files changed, 315 insertions, 117 deletions
diff --git a/docs/conf.py b/docs/conf.py
index a3f9fa6..7b5712e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -161,6 +161,13 @@ qapi_allowed_fields = {
"see also",
}
+# Due to a limitation in Sphinx, we need to know which indices to
+# generate in advance. Adding a namespace here allows that generation.
+qapi_namespaces = {
+ "QGA",
+ "QMP",
+ "QSD",
+}
# -- Options for HTML output ----------------------------------------------
diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst
index 1475870..a748529 100644
--- a/docs/devel/qapi-domain.rst
+++ b/docs/devel/qapi-domain.rst
@@ -385,13 +385,13 @@ Type names in references can be surrounded by brackets, like
``[typename]``, to indicate an array of that type. The cross-reference
will apply only to the type name between the brackets. For example;
``:qapi:type:`[Qcow2BitmapInfoFlags]``` renders to:
-:qapi:type:`[Qcow2BitmapInfoFlags]`
+:qapi:type:`[QMP:Qcow2BitmapInfoFlags]`
To indicate an optional argument/member in a field list, the type name
can be suffixed with ``?``. The cross-reference will be transformed to
"type, Optional" with the link applying only to the type name. For
example; ``:qapi:type:`BitmapSyncMode?``` renders to:
-:qapi:type:`BitmapSyncMode?`
+:qapi:type:`QMP:BitmapSyncMode?`
Namespaces
@@ -400,17 +400,38 @@ Namespaces
Mimicking the `Python domain target specification syntax
<https://www.sphinx-doc.org/en/master/usage/domains/python.html#target-specification>`_,
QAPI allows you to specify the fully qualified path for a data
-type. QAPI enforces globally unique names, so it's unlikely you'll need
-this specific feature, but it may be extended in the near future to
-allow referencing identically named commands and data types from
-different utilities; i.e. QEMU Storage Daemon vs QMP.
+type.
+* A namespace can be explicitly provided;
+ e.g. ``:qapi:type:`QMP:BitmapSyncMode``
* A module can be explicitly provided;
- ``:qapi:type:`block-core.BitmapSyncMode``` will render to:
- :qapi:type:`block-core.BitmapSyncMode`
+ ``:qapi:type:`QMP:block-core.BitmapSyncMode``` will render to:
+ :qapi:type:`QMP:block-core.BitmapSyncMode`
* If you don't want to display the "fully qualified" name, it can be
- prefixed with a tilde; ``:qapi:type:`~block-core.BitmapSyncMode```
- will render to: :qapi:type:`~block-core.BitmapSyncMode`
+ prefixed with a tilde; ``:qapi:type:`~QMP:block-core.BitmapSyncMode```
+ will render to: :qapi:type:`~QMP:block-core.BitmapSyncMode`
+
+
+Target resolution
+-----------------
+
+Any cross-reference to a QAPI type, whether using the ```any``` style of
+reference or the more explicit ```:qapi:any:`target``` syntax, allows
+for the presence or absence of either the namespace or module
+information.
+
+When absent, their value will be inferred from context by the presence
+of any ``qapi:namespace`` or ``qapi:module`` directives preceding the
+cross-reference.
+
+If no results are found when using the inferred values, other
+namespaces/modules will be searched as a last resort; but any explicitly
+provided values must always match in order to succeed.
+
+This allows for efficient cross-referencing with a minimum of syntax in
+the large majority of cases, but additional context or namespace markup
+may be required outside of the QAPI reference documents when linking to
+items that share a name across multiple documented QAPI schema.
Custom link text
@@ -423,7 +444,7 @@ using the ``custom text <target>`` syntax.
For example, ``:qapi:cmd:`Merge dirty bitmaps
<block-dirty-bitmap-merge>``` will render as: :qapi:cmd:`Merge dirty
-bitmaps <block-dirty-bitmap-merge>`
+bitmaps <QMP:block-dirty-bitmap-merge>`
Directives
@@ -464,8 +485,11 @@ removed in a future version.
QAPI standard options
---------------------
-All QAPI directives -- *except* for module -- support these common options.
+All QAPI directives -- *except* for namespace and module -- support
+these common options.
+* ``:namespace: name`` -- This option allows you to override the
+ namespace association of a given definition.
* ``:module: modname`` -- Borrowed from the Python domain, this option allows
you to override the module association of a given definition.
* ``:since: x.y`` -- Allows the documenting of "Since" information, which is
@@ -480,6 +504,28 @@ All QAPI directives -- *except* for module -- support these common options.
production code.
+qapi:namespace
+--------------
+
+The ``qapi:namespace`` directive marks the start of a QAPI namespace. It
+does not take a content body, nor any options. All subsequent QAPI
+directives are associated with the most recent namespace. This affects
+the definition's "fully qualified name", allowing two different
+namespaces to create an otherwise identically named definition.
+
+This directive also influences how reference resolution works for any
+references that do not explicity specify a namespace, so this directive
+can be used to nudge references into preferring targets from within that
+namespace.
+
+Example::
+
+ .. qapi:namespace:: QMP
+
+
+This directive has no visible effect.
+
+
qapi:module
-----------
diff --git a/docs/interop/qemu-ga-ref.rst b/docs/interop/qemu-ga-ref.rst
index 032d492..19b5c7a 100644
--- a/docs/interop/qemu-ga-ref.rst
+++ b/docs/interop/qemu-ga-ref.rst
@@ -5,3 +5,5 @@ QEMU Guest Agent Protocol Reference
:depth: 3
.. qapi-doc:: qga/qapi-schema.json
+ :transmogrify:
+ :namespace: QGA
diff --git a/docs/interop/qemu-qmp-ref.rst b/docs/interop/qemu-qmp-ref.rst
index e95eeac..ef8792b 100644
--- a/docs/interop/qemu-qmp-ref.rst
+++ b/docs/interop/qemu-qmp-ref.rst
@@ -8,3 +8,4 @@ QEMU QMP Reference Manual
.. qapi-doc:: qapi/qapi-schema.json
:transmogrify:
+ :namespace: QMP
diff --git a/docs/interop/qemu-storage-daemon-qmp-ref.rst b/docs/interop/qemu-storage-daemon-qmp-ref.rst
index 9fed681..d0228d6 100644
--- a/docs/interop/qemu-storage-daemon-qmp-ref.rst
+++ b/docs/interop/qemu-storage-daemon-qmp-ref.rst
@@ -5,3 +5,5 @@ QEMU Storage Daemon QMP Reference Manual
:depth: 3
.. qapi-doc:: storage-daemon/qapi/qapi-schema.json
+ :transmogrify:
+ :namespace: QSD
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index 7ff618d..c94af57 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -7,17 +7,14 @@ QAPI domain extension.
from __future__ import annotations
+import re
+import types
from typing import (
TYPE_CHECKING,
- AbstractSet,
- Any,
- Dict,
- Iterable,
List,
NamedTuple,
- Optional,
Tuple,
- Union,
+ Type,
cast,
)
@@ -34,7 +31,6 @@ from compat import (
SpaceNode,
)
from sphinx import addnodes
-from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
from sphinx.domains import (
Domain,
@@ -45,17 +41,29 @@ from sphinx.domains import (
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
+from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_id, make_refnode
if TYPE_CHECKING:
+ from typing import (
+ AbstractSet,
+ Any,
+ Dict,
+ Iterable,
+ Optional,
+ Union,
+ )
+
from docutils.nodes import Element, Node
+ from sphinx.addnodes import desc_signature, pending_xref
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
from sphinx.util.typing import OptionSpec
+
logger = logging.getLogger(__name__)
@@ -89,6 +97,7 @@ class QAPIXRefRole(XRefRole):
title: str,
target: str,
) -> tuple[str, str]:
+ refnode["qapi:namespace"] = env.ref_context.get("qapi:namespace")
refnode["qapi:module"] = env.ref_context.get("qapi:module")
# Cross-references that begin with a tilde adjust the title to
@@ -174,6 +183,30 @@ class QAPIDescription(ParserFix):
# NB: this is used for the global index, not the QAPI index.
return ("single", f"{name} (QMP {self.objtype})")
+ def _get_context(self) -> Tuple[str, str]:
+ namespace = self.options.get(
+ "namespace", self.env.ref_context.get("qapi:namespace", "")
+ )
+ modname = self.options.get(
+ "module", self.env.ref_context.get("qapi:module", "")
+ )
+
+ return namespace, modname
+
+ def _get_fqn(self, name: Signature) -> str:
+ namespace, modname = self._get_context()
+
+ # If we're documenting a module, don't include the module as
+ # part of the FQN; we ARE the module!
+ if self.objtype == "module":
+ modname = ""
+
+ if modname:
+ name = f"{modname}.{name}"
+ if namespace:
+ name = f"{namespace}:{name}"
+ return name
+
def add_target_and_index(
self, name: Signature, sig: str, signode: desc_signature
) -> None:
@@ -183,14 +216,8 @@ class QAPIDescription(ParserFix):
assert self.objtype
- # If we're documenting a module, don't include the module as
- # part of the FQN.
- modname = ""
- if self.objtype != "module":
- modname = self.options.get(
- "module", self.env.ref_context.get("qapi:module")
- )
- fullname = (modname + "." if modname else "") + name
+ if not (fullname := signode.get("fullname", "")):
+ fullname = self._get_fqn(name)
node_id = make_id(
self.env, self.state.document, self.objtype, fullname
@@ -209,18 +236,26 @@ class QAPIDescription(ParserFix):
(arity, indextext, node_id, "", None)
)
+ @staticmethod
+ def split_fqn(name: str) -> Tuple[str, str, str]:
+ if ":" in name:
+ ns, name = name.split(":")
+ else:
+ ns = ""
+
+ if "." in name:
+ module, name = name.split(".")
+ else:
+ module = ""
+
+ return (ns, module, name)
+
def _object_hierarchy_parts(
self, sig_node: desc_signature
) -> Tuple[str, ...]:
if "fullname" not in sig_node:
return ()
- modname = sig_node.get("module")
- fullname = sig_node["fullname"]
-
- if modname:
- return (modname, *fullname.split("."))
-
- return tuple(fullname.split("."))
+ return self.split_fqn(sig_node["fullname"])
def _toc_entry_name(self, sig_node: desc_signature) -> str:
# This controls the name in the TOC and on the sidebar.
@@ -231,13 +266,23 @@ class QAPIDescription(ParserFix):
return ""
config = self.env.app.config
- *parents, name = toc_parts
+ namespace, modname, name = toc_parts
+
if config.toc_object_entries_show_parents == "domain":
- return sig_node.get("fullname", name)
+ ret = name
+ if modname and modname != self.env.ref_context.get(
+ "qapi:module", ""
+ ):
+ ret = f"{modname}.{name}"
+ if namespace and namespace != self.env.ref_context.get(
+ "qapi:namespace", ""
+ ):
+ ret = f"{namespace}:{ret}"
+ return ret
if config.toc_object_entries_show_parents == "hide":
return name
if config.toc_object_entries_show_parents == "all":
- return ".".join(parents + [name])
+ return sig_node.get("fullname", name)
return ""
@@ -254,8 +299,9 @@ class QAPIObject(QAPIDescription):
)
option_spec.update(
{
- # Borrowed from the Python domain:
- "module": directives.unchanged, # Override contextual module name
+ # Context overrides:
+ "namespace": directives.unchanged,
+ "module": directives.unchanged,
# These are QAPI originals:
"since": directives.unchanged,
"ifcond": directives.unchanged,
@@ -308,12 +354,15 @@ class QAPIObject(QAPIDescription):
As such, the only argument here is "sig", which is just the QAPI
definition name.
"""
- modname = self.options.get(
- "module", self.env.ref_context.get("qapi:module")
- )
+ # No module or domain info allowed in the signature!
+ assert ":" not in sig
+ assert "." not in sig
- signode["fullname"] = sig
+ namespace, modname = self._get_context()
+ signode["fullname"] = self._get_fqn(sig)
+ signode["namespace"] = namespace
signode["module"] = modname
+
sig_prefix = self.get_signature_prefix()
if sig_prefix:
signode += addnodes.desc_annotation(
@@ -601,6 +650,17 @@ class QAPIModule(QAPIDescription):
return ret
+class QAPINamespace(SphinxDirective):
+ has_content = False
+ required_arguments = 1
+
+ def run(self) -> List[Node]:
+ namespace = self.arguments[0].strip()
+ self.env.ref_context["qapi:namespace"] = namespace
+
+ return []
+
+
class QAPIIndex(Index):
"""
Index subclass to provide the QAPI definition index.
@@ -611,6 +671,7 @@ class QAPIIndex(Index):
name = "index"
localname = _("QAPI Index")
shortname = _("QAPI Index")
+ namespace = ""
def generate(
self,
@@ -620,25 +681,20 @@ class QAPIIndex(Index):
content: Dict[str, List[IndexEntry]] = {}
collapse = False
- # list of all object (name, ObjectEntry) pairs, sorted by name
- # (ignoring the module)
- objects = sorted(
- self.domain.objects.items(),
- key=lambda x: x[0].split(".")[-1].lower(),
- )
-
- for objname, obj in objects:
+ for objname, obj in self.domain.objects.items():
if docnames and obj.docname not in docnames:
continue
- # Strip the module name out:
- objname = objname.split(".")[-1]
+ ns, _mod, name = QAPIDescription.split_fqn(objname)
+
+ if self.namespace != ns:
+ continue
# Add an alphabetical entry:
- entries = content.setdefault(objname[0].upper(), [])
+ entries = content.setdefault(name[0].upper(), [])
entries.append(
IndexEntry(
- objname, 0, obj.docname, obj.node_id, obj.objtype, "", ""
+ name, 0, obj.docname, obj.node_id, obj.objtype, "", ""
)
)
@@ -646,10 +702,14 @@ class QAPIIndex(Index):
category = obj.objtype.title() + "s"
entries = content.setdefault(category, [])
entries.append(
- IndexEntry(objname, 0, obj.docname, obj.node_id, "", "", "")
+ IndexEntry(name, 0, obj.docname, obj.node_id, "", "", "")
)
- # alphabetically sort categories; type names first, ABC entries last.
+ # Sort entries within each category alphabetically
+ for category in content:
+ content[category] = sorted(content[category])
+
+ # Sort the categories themselves; type names first, ABC entries last.
sorted_content = sorted(
content.items(),
key=lambda x: (len(x[0]) == 1, x[0]),
@@ -682,6 +742,7 @@ class QAPIDomain(Domain):
# Each of these provides a rST directive,
# e.g. .. qapi:module:: block-core
directives = {
+ "namespace": QAPINamespace,
"module": QAPIModule,
"command": QAPICommand,
"event": QAPIEvent,
@@ -721,6 +782,21 @@ class QAPIDomain(Domain):
ret = self.data.setdefault("objects", {})
return ret # type: ignore[no-any-return]
+ def setup(self) -> None:
+ namespaces = set(self.env.app.config.qapi_namespaces)
+ for namespace in namespaces:
+ new_index: Type[QAPIIndex] = types.new_class(
+ f"{namespace}Index", bases=(QAPIIndex,)
+ )
+ new_index.name = f"{namespace.lower()}-index"
+ new_index.localname = _(f"{namespace} Index")
+ new_index.shortname = _(f"{namespace} Index")
+ new_index.namespace = namespace
+
+ self.indices.append(new_index)
+
+ super().setup()
+
def note_object(
self,
name: str,
@@ -773,40 +849,44 @@ class QAPIDomain(Domain):
self.objects[fullname] = obj
def find_obj(
- self, modname: str, name: str, typ: Optional[str]
- ) -> list[tuple[str, ObjectEntry]]:
+ self, namespace: str, modname: str, name: str, typ: Optional[str]
+ ) -> List[Tuple[str, ObjectEntry]]:
"""
- Find a QAPI object for "name", perhaps using the given module.
+ Find a QAPI object for "name", maybe using contextual information.
Returns a list of (name, object entry) tuples.
- :param modname: The current module context (if any!)
- under which we are searching.
- :param name: The name of the x-ref to resolve;
- may or may not include a leading module.
- :param type: The role name of the x-ref we're resolving, if provided.
- (This is absent for "any" lookups.)
+ :param namespace: The current namespace context (if any!) under
+ which we are searching.
+ :param modname: The current module context (if any!) under
+ which we are searching.
+ :param name: The name of the x-ref to resolve; may or may not
+ include leading context.
+ :param type: The role name of the x-ref we're resolving, if
+ provided. This is absent for "any" role lookups.
"""
if not name:
return []
- names: list[str] = []
- matches: list[tuple[str, ObjectEntry]] = []
+ # ##
+ # what to search for
+ # ##
- fullname = name
- if "." in fullname:
- # We're searching for a fully qualified reference;
- # ignore the contextual module.
- pass
- elif modname:
- # We're searching for something from somewhere;
- # try searching the current module first.
- # e.g. :qapi:cmd:`query-block` or `query-block` is being searched.
- fullname = f"{modname}.{name}"
+ parts = list(QAPIDescription.split_fqn(name))
+ explicit = tuple(bool(x) for x in parts)
+
+ # Fill in the blanks where possible:
+ if namespace and not parts[0]:
+ parts[0] = namespace
+ if modname and not parts[1]:
+ parts[1] = modname
+
+ implicit_fqn = ""
+ if all(parts):
+ implicit_fqn = f"{parts[0]}:{parts[1]}.{parts[2]}"
if typ is None:
- # type isn't specified, this is a generic xref.
- # search *all* qapi-specific object types.
+ # :any: lookup, search everything:
objtypes: List[str] = list(self.object_types)
else:
# type is specified and will be a role (e.g. obj, mod, cmd)
@@ -814,25 +894,57 @@ class QAPIDomain(Domain):
# using the QAPIDomain.object_types table.
objtypes = self.objtypes_for_role(typ, [])
- if name in self.objects and self.objects[name].objtype in objtypes:
- names = [name]
- elif (
- fullname in self.objects
- and self.objects[fullname].objtype in objtypes
- ):
- names = [fullname]
- else:
- # exact match wasn't found; e.g. we are searching for
- # `query-block` from a different (or no) module.
- searchname = "." + name
- names = [
- oname
- for oname in self.objects
- if oname.endswith(searchname)
- and self.objects[oname].objtype in objtypes
- ]
+ # ##
+ # search!
+ # ##
- matches = [(oname, self.objects[oname]) for oname in names]
+ def _search(needle: str) -> List[str]:
+ if (
+ needle
+ and needle in self.objects
+ and self.objects[needle].objtype in objtypes
+ ):
+ return [needle]
+ return []
+
+ if found := _search(name):
+ # Exact match!
+ pass
+ elif found := _search(implicit_fqn):
+ # Exact match using contextual information to fill in the gaps.
+ pass
+ else:
+ # No exact hits, perform applicable fuzzy searches.
+ searches = []
+
+ esc = tuple(re.escape(s) for s in parts)
+
+ # Try searching for ns:*.name or ns:name
+ if explicit[0] and not explicit[1]:
+ searches.append(f"^{esc[0]}:([^\\.]+\\.)?{esc[2]}$")
+ # Try searching for *:module.name or module.name
+ if explicit[1] and not explicit[0]:
+ searches.append(f"(^|:){esc[1]}\\.{esc[2]}$")
+ # Try searching for context-ns:*.name or context-ns:name
+ if parts[0] and not (explicit[0] or explicit[1]):
+ searches.append(f"^{esc[0]}:([^\\.]+\\.)?{esc[2]}$")
+ # Try searching for *:context-mod.name or context-mod.name
+ if parts[1] and not (explicit[0] or explicit[1]):
+ searches.append(f"(^|:){esc[1]}\\.{esc[2]}$")
+ # Try searching for *:name, *.name, or name
+ if not (explicit[0] or explicit[1]):
+ searches.append(f"(^|:|\\.){esc[2]}$")
+
+ for search in searches:
+ if found := [
+ oname
+ for oname in self.objects
+ if re.search(search, oname)
+ and self.objects[oname].objtype in objtypes
+ ]:
+ break
+
+ matches = [(oname, self.objects[oname]) for oname in found]
if len(matches) > 1:
matches = [m for m in matches if not m[1].aliased]
return matches
@@ -847,8 +959,9 @@ class QAPIDomain(Domain):
node: pending_xref,
contnode: Element,
) -> nodes.reference | None:
+ namespace = node.get("qapi:namespace")
modname = node.get("qapi:module")
- matches = self.find_obj(modname, target, typ)
+ matches = self.find_obj(namespace, modname, target, typ)
if not matches:
# Normally, we could pass warn_dangling=True to QAPIXRefRole(),
@@ -901,7 +1014,9 @@ class QAPIDomain(Domain):
contnode: Element,
) -> List[Tuple[str, nodes.reference]]:
results: List[Tuple[str, nodes.reference]] = []
- matches = self.find_obj(node.get("qapi:module"), target, None)
+ matches = self.find_obj(
+ node.get("qapi:namespace"), node.get("qapi:module"), target, None
+ )
for name, obj in matches:
rolename = self.role_for_objtype(obj.objtype)
assert rolename is not None
@@ -921,6 +1036,12 @@ def setup(app: Sphinx) -> Dict[str, Any]:
"env", # Setting impacts parsing phase
types=set,
)
+ app.add_config_value(
+ "qapi_namespaces",
+ set(),
+ "env",
+ types=set,
+ )
app.add_domain(QAPIDomain)
return {
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 432fef0..661b2c4 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -451,6 +451,12 @@ class Transmogrifier:
finally:
self._curr_ent = None
+ def set_namespace(self, namespace: str, source: str, lineno: int) -> None:
+ self.add_line_raw(
+ f".. qapi:namespace:: {namespace}", source, lineno + 1
+ )
+ self.ensure_blank_line()
+
class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
"""A QAPI schema visitor which adds Sphinx dependencies each module
@@ -496,6 +502,7 @@ class QAPIDocDirective(NestedDirective):
optional_arguments = 1
option_spec = {
"qapifile": directives.unchanged_required,
+ "namespace": directives.unchanged,
"transmogrify": directives.flag,
}
has_content = False
@@ -510,6 +517,11 @@ class QAPIDocDirective(NestedDirective):
vis = Transmogrifier()
modules = set()
+ if "namespace" in self.options:
+ vis.set_namespace(
+ self.options["namespace"], *self.get_source_info()
+ )
+
for doc in schema.docs:
module_source = doc.info.fname
if module_source not in modules:
diff --git a/qapi/block-core.json b/qapi/block-core.json
index ee6eccc..b193778 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -5913,35 +5913,31 @@
##
# @x-blockdev-change:
#
-# Dynamically reconfigure the block driver state graph. It can be
-# used to add, remove, insert or replace a graph node. Currently only
-# the Quorum driver implements this feature to add or remove its
-# child. This is useful to fix a broken quorum child.
+# Dynamically reconfigure the block driver state graph.
#
-# If @node is specified, it will be inserted under @parent. @child
-# may not be specified in this case. If both @parent and @child are
-# specified but @node is not, @child will be detached from @parent.
+# Currently only supports adding and deleting quorum children. A
+# child will be added at the end of the list of children. Its
+# contents *must* be consistent with the other childrens' contents.
+# Deleting a child that is not last in the list of children is
+# problematic, because it "renumbers" the children following it.
#
# @parent: the id or name of the parent node.
#
-# @child: the name of a child under the given parent node.
+# @child: the name of a child to be deleted. Mutually exclusive with
+# @node.
#
-# @node: the name of the node that will be added.
+# @node: the name of the node to be added. Mutually exclusive with
+# @child.
#
# Features:
#
-# @unstable: This command is experimental, and its API is not stable.
-# It does not support all kinds of operations, all kinds of
-# children, nor all block drivers.
+# @unstable: This command is experimental.
#
-# FIXME Removing children from a quorum node means introducing
+# TODO: Removing children from a quorum node means introducing
# gaps in the child indices. This cannot be represented in the
# 'children' list of BlockdevOptionsQuorum, as returned by
# .bdrv_refresh_filename().
#
-# Warning: The data in a new quorum child MUST be consistent with
-# that of the rest of the array.
-#
# Since: 2.7
#
# .. qmp-example::
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 4475e81..c41c01e 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -5,7 +5,7 @@
#
# This document describes all commands currently supported by QMP.
#
-# For locating a particular item, please see the `qapi-index`.
+# For locating a particular item, please see the `qapi-qmp-index`.
#
# Most of the time their usage is exactly the same as in the user
# Monitor, this means that any other document which also describe
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 995594a..35ec0e7 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -3,6 +3,9 @@
##
# = QEMU guest agent protocol commands and structs
+#
+# For a concise listing of all commands, events, and types in the QEMU
+# guest agent, please consult the `qapi-qga-index`.
##
{ 'pragma': { 'doc-required': true } }
diff --git a/storage-daemon/qapi/qapi-schema.json b/storage-daemon/qapi/qapi-schema.json
index f10c949..2a562ee 100644
--- a/storage-daemon/qapi/qapi-schema.json
+++ b/storage-daemon/qapi/qapi-schema.json
@@ -13,6 +13,14 @@
# the array type in the main schema, even if it is unused outside of the
# storage daemon.
+##
+# = QEMU storage daemon protocol commands and structs
+#
+# For a concise listing of all commands, events, and types in the QEMU
+# storage daemon, please consult the `qapi-qsd-index`.
+##
+
+
{ 'include': '../../qapi/pragma.json' }
# Documentation generated with qapi-gen.py is in source order, with