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:
commit
9beccc2df0
@ -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 ----------------------------------------------
|
||||
|
||||
|
@ -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
|
||||
-----------
|
||||
|
||||
|
@ -5,3 +5,5 @@ QEMU Guest Agent Protocol Reference
|
||||
:depth: 3
|
||||
|
||||
.. qapi-doc:: qga/qapi-schema.json
|
||||
:transmogrify:
|
||||
:namespace: QGA
|
||||
|
@ -8,3 +8,4 @@ QEMU QMP Reference Manual
|
||||
|
||||
.. qapi-doc:: qapi/qapi-schema.json
|
||||
:transmogrify:
|
||||
:namespace: QMP
|
||||
|
@ -5,3 +5,5 @@ QEMU Storage Daemon QMP Reference Manual
|
||||
:depth: 3
|
||||
|
||||
.. qapi-doc:: storage-daemon/qapi/qapi-schema.json
|
||||
:transmogrify:
|
||||
:namespace: QSD
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -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::
|
||||
|
@ -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
|
||||
|
@ -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 } }
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user