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-----

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>
This commit is contained in:
Stefan Hajnoczi 2025-03-16 02:44:56 -04:00
commit 9beccc2df0
11 changed files with 315 additions and 117 deletions

View File

@ -161,6 +161,13 @@ qapi_allowed_fields = {
"see also", "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 ---------------------------------------------- # -- Options for HTML output ----------------------------------------------

View File

@ -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 ``[typename]``, to indicate an array of that type. The cross-reference
will apply only to the type name between the brackets. For example; will apply only to the type name between the brackets. For example;
``:qapi:type:`[Qcow2BitmapInfoFlags]``` renders to: ``: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 To indicate an optional argument/member in a field list, the type name
can be suffixed with ``?``. The cross-reference will be transformed to can be suffixed with ``?``. The cross-reference will be transformed to
"type, Optional" with the link applying only to the type name. For "type, Optional" with the link applying only to the type name. For
example; ``:qapi:type:`BitmapSyncMode?``` renders to: example; ``:qapi:type:`BitmapSyncMode?``` renders to:
:qapi:type:`BitmapSyncMode?` :qapi:type:`QMP:BitmapSyncMode?`
Namespaces Namespaces
@ -400,17 +400,38 @@ Namespaces
Mimicking the `Python domain target specification syntax Mimicking the `Python domain target specification syntax
<https://www.sphinx-doc.org/en/master/usage/domains/python.html#target-specification>`_, <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 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 type.
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.
* A namespace can be explicitly provided;
e.g. ``:qapi:type:`QMP:BitmapSyncMode``
* A module can be explicitly provided; * A module can be explicitly provided;
``:qapi:type:`block-core.BitmapSyncMode``` will render to: ``:qapi:type:`QMP:block-core.BitmapSyncMode``` will render to:
:qapi:type:`block-core.BitmapSyncMode` :qapi:type:`QMP:block-core.BitmapSyncMode`
* If you don't want to display the "fully qualified" name, it can be * If you don't want to display the "fully qualified" name, it can be
prefixed with a tilde; ``:qapi:type:`~block-core.BitmapSyncMode``` prefixed with a tilde; ``:qapi:type:`~QMP:block-core.BitmapSyncMode```
will render to: :qapi:type:`~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 Custom link text
@ -423,7 +444,7 @@ using the ``custom text <target>`` syntax.
For example, ``:qapi:cmd:`Merge dirty bitmaps For example, ``:qapi:cmd:`Merge dirty bitmaps
<block-dirty-bitmap-merge>``` will render as: :qapi:cmd:`Merge dirty <block-dirty-bitmap-merge>``` will render as: :qapi:cmd:`Merge dirty
bitmaps <block-dirty-bitmap-merge>` bitmaps <QMP:block-dirty-bitmap-merge>`
Directives Directives
@ -464,8 +485,11 @@ removed in a future version.
QAPI standard options 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 * ``:module: modname`` -- Borrowed from the Python domain, this option allows
you to override the module association of a given definition. you to override the module association of a given definition.
* ``:since: x.y`` -- Allows the documenting of "Since" information, which is * ``: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. 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 qapi:module
----------- -----------

View File

@ -5,3 +5,5 @@ QEMU Guest Agent Protocol Reference
:depth: 3 :depth: 3
.. qapi-doc:: qga/qapi-schema.json .. qapi-doc:: qga/qapi-schema.json
:transmogrify:
:namespace: QGA

View File

@ -8,3 +8,4 @@ QEMU QMP Reference Manual
.. qapi-doc:: qapi/qapi-schema.json .. qapi-doc:: qapi/qapi-schema.json
:transmogrify: :transmogrify:
:namespace: QMP

View File

@ -5,3 +5,5 @@ QEMU Storage Daemon QMP Reference Manual
:depth: 3 :depth: 3
.. qapi-doc:: storage-daemon/qapi/qapi-schema.json .. qapi-doc:: storage-daemon/qapi/qapi-schema.json
:transmogrify:
:namespace: QSD

View File

@ -7,17 +7,14 @@ QAPI domain extension.
from __future__ import annotations from __future__ import annotations
import re
import types
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
AbstractSet,
Any,
Dict,
Iterable,
List, List,
NamedTuple, NamedTuple,
Optional,
Tuple, Tuple,
Union, Type,
cast, cast,
) )
@ -34,7 +31,6 @@ from compat import (
SpaceNode, SpaceNode,
) )
from sphinx import addnodes from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription from sphinx.directives import ObjectDescription
from sphinx.domains import ( from sphinx.domains import (
Domain, Domain,
@ -45,17 +41,29 @@ from sphinx.domains import (
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_id, make_refnode from sphinx.util.nodes import make_id, make_refnode
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import (
AbstractSet,
Any,
Dict,
Iterable,
Optional,
Union,
)
from docutils.nodes import Element, Node from docutils.nodes import Element, Node
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
from sphinx.util.typing import OptionSpec from sphinx.util.typing import OptionSpec
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -89,6 +97,7 @@ class QAPIXRefRole(XRefRole):
title: str, title: str,
target: str, target: str,
) -> tuple[str, str]: ) -> tuple[str, str]:
refnode["qapi:namespace"] = env.ref_context.get("qapi:namespace")
refnode["qapi:module"] = env.ref_context.get("qapi:module") refnode["qapi:module"] = env.ref_context.get("qapi:module")
# Cross-references that begin with a tilde adjust the title to # 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. # NB: this is used for the global index, not the QAPI index.
return ("single", f"{name} (QMP {self.objtype})") 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( def add_target_and_index(
self, name: Signature, sig: str, signode: desc_signature self, name: Signature, sig: str, signode: desc_signature
) -> None: ) -> None:
@ -183,14 +216,8 @@ class QAPIDescription(ParserFix):
assert self.objtype assert self.objtype
# If we're documenting a module, don't include the module as if not (fullname := signode.get("fullname", "")):
# part of the FQN. fullname = self._get_fqn(name)
modname = ""
if self.objtype != "module":
modname = self.options.get(
"module", self.env.ref_context.get("qapi:module")
)
fullname = (modname + "." if modname else "") + name
node_id = make_id( node_id = make_id(
self.env, self.state.document, self.objtype, fullname self.env, self.state.document, self.objtype, fullname
@ -209,18 +236,26 @@ class QAPIDescription(ParserFix):
(arity, indextext, node_id, "", None) (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( def _object_hierarchy_parts(
self, sig_node: desc_signature self, sig_node: desc_signature
) -> Tuple[str, ...]: ) -> Tuple[str, ...]:
if "fullname" not in sig_node: if "fullname" not in sig_node:
return () return ()
modname = sig_node.get("module") return self.split_fqn(sig_node["fullname"])
fullname = sig_node["fullname"]
if modname:
return (modname, *fullname.split("."))
return tuple(fullname.split("."))
def _toc_entry_name(self, sig_node: desc_signature) -> str: def _toc_entry_name(self, sig_node: desc_signature) -> str:
# This controls the name in the TOC and on the sidebar. # This controls the name in the TOC and on the sidebar.
@ -231,13 +266,23 @@ class QAPIDescription(ParserFix):
return "" return ""
config = self.env.app.config config = self.env.app.config
*parents, name = toc_parts namespace, modname, name = toc_parts
if config.toc_object_entries_show_parents == "domain": 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": if config.toc_object_entries_show_parents == "hide":
return name return name
if config.toc_object_entries_show_parents == "all": if config.toc_object_entries_show_parents == "all":
return ".".join(parents + [name]) return sig_node.get("fullname", name)
return "" return ""
@ -254,8 +299,9 @@ class QAPIObject(QAPIDescription):
) )
option_spec.update( option_spec.update(
{ {
# Borrowed from the Python domain: # Context overrides:
"module": directives.unchanged, # Override contextual module name "namespace": directives.unchanged,
"module": directives.unchanged,
# These are QAPI originals: # These are QAPI originals:
"since": directives.unchanged, "since": directives.unchanged,
"ifcond": 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 As such, the only argument here is "sig", which is just the QAPI
definition name. definition name.
""" """
modname = self.options.get( # No module or domain info allowed in the signature!
"module", self.env.ref_context.get("qapi:module") 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 signode["module"] = modname
sig_prefix = self.get_signature_prefix() sig_prefix = self.get_signature_prefix()
if sig_prefix: if sig_prefix:
signode += addnodes.desc_annotation( signode += addnodes.desc_annotation(
@ -601,6 +650,17 @@ class QAPIModule(QAPIDescription):
return ret 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): class QAPIIndex(Index):
""" """
Index subclass to provide the QAPI definition index. Index subclass to provide the QAPI definition index.
@ -611,6 +671,7 @@ class QAPIIndex(Index):
name = "index" name = "index"
localname = _("QAPI Index") localname = _("QAPI Index")
shortname = _("QAPI Index") shortname = _("QAPI Index")
namespace = ""
def generate( def generate(
self, self,
@ -620,25 +681,20 @@ class QAPIIndex(Index):
content: Dict[str, List[IndexEntry]] = {} content: Dict[str, List[IndexEntry]] = {}
collapse = False collapse = False
# list of all object (name, ObjectEntry) pairs, sorted by name for objname, obj in self.domain.objects.items():
# (ignoring the module)
objects = sorted(
self.domain.objects.items(),
key=lambda x: x[0].split(".")[-1].lower(),
)
for objname, obj in objects:
if docnames and obj.docname not in docnames: if docnames and obj.docname not in docnames:
continue continue
# Strip the module name out: ns, _mod, name = QAPIDescription.split_fqn(objname)
objname = objname.split(".")[-1]
if self.namespace != ns:
continue
# Add an alphabetical entry: # Add an alphabetical entry:
entries = content.setdefault(objname[0].upper(), []) entries = content.setdefault(name[0].upper(), [])
entries.append( entries.append(
IndexEntry( 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" category = obj.objtype.title() + "s"
entries = content.setdefault(category, []) entries = content.setdefault(category, [])
entries.append( 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( sorted_content = sorted(
content.items(), content.items(),
key=lambda x: (len(x[0]) == 1, x[0]), key=lambda x: (len(x[0]) == 1, x[0]),
@ -682,6 +742,7 @@ class QAPIDomain(Domain):
# Each of these provides a rST directive, # Each of these provides a rST directive,
# e.g. .. qapi:module:: block-core # e.g. .. qapi:module:: block-core
directives = { directives = {
"namespace": QAPINamespace,
"module": QAPIModule, "module": QAPIModule,
"command": QAPICommand, "command": QAPICommand,
"event": QAPIEvent, "event": QAPIEvent,
@ -721,6 +782,21 @@ class QAPIDomain(Domain):
ret = self.data.setdefault("objects", {}) ret = self.data.setdefault("objects", {})
return ret # type: ignore[no-any-return] 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( def note_object(
self, self,
name: str, name: str,
@ -773,40 +849,44 @@ class QAPIDomain(Domain):
self.objects[fullname] = obj self.objects[fullname] = obj
def find_obj( def find_obj(
self, modname: str, name: str, typ: Optional[str] self, namespace: str, modname: str, name: str, typ: Optional[str]
) -> list[tuple[str, ObjectEntry]]: ) -> 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. Returns a list of (name, object entry) tuples.
:param modname: The current module context (if any!) :param namespace: The current namespace context (if any!) under
under which we are searching. which we are searching.
:param name: The name of the x-ref to resolve; :param modname: The current module context (if any!) under
may or may not include a leading module. which we are searching.
:param type: The role name of the x-ref we're resolving, if provided. :param name: The name of the x-ref to resolve; may or may not
(This is absent for "any" lookups.) 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: if not name:
return [] return []
names: list[str] = [] # ##
matches: list[tuple[str, ObjectEntry]] = [] # what to search for
# ##
fullname = name parts = list(QAPIDescription.split_fqn(name))
if "." in fullname: explicit = tuple(bool(x) for x in parts)
# We're searching for a fully qualified reference;
# ignore the contextual module. # Fill in the blanks where possible:
pass if namespace and not parts[0]:
elif modname: parts[0] = namespace
# We're searching for something from somewhere; if modname and not parts[1]:
# try searching the current module first. parts[1] = modname
# e.g. :qapi:cmd:`query-block` or `query-block` is being searched.
fullname = f"{modname}.{name}" implicit_fqn = ""
if all(parts):
implicit_fqn = f"{parts[0]}:{parts[1]}.{parts[2]}"
if typ is None: if typ is None:
# type isn't specified, this is a generic xref. # :any: lookup, search everything:
# search *all* qapi-specific object types.
objtypes: List[str] = list(self.object_types) objtypes: List[str] = list(self.object_types)
else: else:
# type is specified and will be a role (e.g. obj, mod, cmd) # 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. # using the QAPIDomain.object_types table.
objtypes = self.objtypes_for_role(typ, []) objtypes = self.objtypes_for_role(typ, [])
if name in self.objects and self.objects[name].objtype in objtypes: # ##
names = [name] # search!
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
]
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: if len(matches) > 1:
matches = [m for m in matches if not m[1].aliased] matches = [m for m in matches if not m[1].aliased]
return matches return matches
@ -847,8 +959,9 @@ class QAPIDomain(Domain):
node: pending_xref, node: pending_xref,
contnode: Element, contnode: Element,
) -> nodes.reference | None: ) -> nodes.reference | None:
namespace = node.get("qapi:namespace")
modname = node.get("qapi:module") modname = node.get("qapi:module")
matches = self.find_obj(modname, target, typ) matches = self.find_obj(namespace, modname, target, typ)
if not matches: if not matches:
# Normally, we could pass warn_dangling=True to QAPIXRefRole(), # Normally, we could pass warn_dangling=True to QAPIXRefRole(),
@ -901,7 +1014,9 @@ class QAPIDomain(Domain):
contnode: Element, contnode: Element,
) -> List[Tuple[str, nodes.reference]]: ) -> List[Tuple[str, nodes.reference]]:
results: 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: for name, obj in matches:
rolename = self.role_for_objtype(obj.objtype) rolename = self.role_for_objtype(obj.objtype)
assert rolename is not None assert rolename is not None
@ -921,6 +1036,12 @@ def setup(app: Sphinx) -> Dict[str, Any]:
"env", # Setting impacts parsing phase "env", # Setting impacts parsing phase
types=set, types=set,
) )
app.add_config_value(
"qapi_namespaces",
set(),
"env",
types=set,
)
app.add_domain(QAPIDomain) app.add_domain(QAPIDomain)
return { return {

View File

@ -451,6 +451,12 @@ class Transmogrifier:
finally: finally:
self._curr_ent = None 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): class QAPISchemaGenDepVisitor(QAPISchemaVisitor):
"""A QAPI schema visitor which adds Sphinx dependencies each module """A QAPI schema visitor which adds Sphinx dependencies each module
@ -496,6 +502,7 @@ class QAPIDocDirective(NestedDirective):
optional_arguments = 1 optional_arguments = 1
option_spec = { option_spec = {
"qapifile": directives.unchanged_required, "qapifile": directives.unchanged_required,
"namespace": directives.unchanged,
"transmogrify": directives.flag, "transmogrify": directives.flag,
} }
has_content = False has_content = False
@ -510,6 +517,11 @@ class QAPIDocDirective(NestedDirective):
vis = Transmogrifier() vis = Transmogrifier()
modules = set() modules = set()
if "namespace" in self.options:
vis.set_namespace(
self.options["namespace"], *self.get_source_info()
)
for doc in schema.docs: for doc in schema.docs:
module_source = doc.info.fname module_source = doc.info.fname
if module_source not in modules: if module_source not in modules:

View File

@ -5913,35 +5913,31 @@
## ##
# @x-blockdev-change: # @x-blockdev-change:
# #
# Dynamically reconfigure the block driver state graph. It can be # Dynamically reconfigure the block driver state graph.
# 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.
# #
# If @node is specified, it will be inserted under @parent. @child # Currently only supports adding and deleting quorum children. A
# may not be specified in this case. If both @parent and @child are # child will be added at the end of the list of children. Its
# specified but @node is not, @child will be detached from @parent. # 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. # @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: # Features:
# #
# @unstable: This command is experimental, and its API is not stable. # @unstable: This command is experimental.
# It does not support all kinds of operations, all kinds of
# children, nor all block drivers.
# #
# 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 # gaps in the child indices. This cannot be represented in the
# 'children' list of BlockdevOptionsQuorum, as returned by # 'children' list of BlockdevOptionsQuorum, as returned by
# .bdrv_refresh_filename(). # .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 # Since: 2.7
# #
# .. qmp-example:: # .. qmp-example::

View File

@ -5,7 +5,7 @@
# #
# This document describes all commands currently supported by QMP. # 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 # Most of the time their usage is exactly the same as in the user
# Monitor, this means that any other document which also describe # Monitor, this means that any other document which also describe

View File

@ -3,6 +3,9 @@
## ##
# = QEMU guest agent protocol commands and structs # = 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 } } { 'pragma': { 'doc-required': true } }

View File

@ -13,6 +13,14 @@
# the array type in the main schema, even if it is unused outside of the # the array type in the main schema, even if it is unused outside of the
# storage daemon. # 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' } { 'include': '../../qapi/pragma.json' }
# Documentation generated with qapi-gen.py is in source order, with # Documentation generated with qapi-gen.py is in source order, with