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",
|
"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 ----------------------------------------------
|
||||||
|
|
||||||
|
@ -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
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -8,3 +8,4 @@ QEMU QMP Reference Manual
|
|||||||
|
|
||||||
.. qapi-doc:: qapi/qapi-schema.json
|
.. qapi-doc:: qapi/qapi-schema.json
|
||||||
:transmogrify:
|
:transmogrify:
|
||||||
|
:namespace: QMP
|
||||||
|
@ -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
|
||||||
|
@ -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
|
def _search(needle: str) -> List[str]:
|
||||||
|
if (
|
||||||
|
needle
|
||||||
|
and needle in self.objects
|
||||||
|
and self.objects[needle].objtype in objtypes
|
||||||
):
|
):
|
||||||
names = [fullname]
|
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:
|
else:
|
||||||
# exact match wasn't found; e.g. we are searching for
|
# No exact hits, perform applicable fuzzy searches.
|
||||||
# `query-block` from a different (or no) module.
|
searches = []
|
||||||
searchname = "." + name
|
|
||||||
names = [
|
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
|
oname
|
||||||
for oname in self.objects
|
for oname in self.objects
|
||||||
if oname.endswith(searchname)
|
if re.search(search, oname)
|
||||||
and self.objects[oname].objtype in objtypes
|
and self.objects[oname].objtype in objtypes
|
||||||
]
|
]:
|
||||||
|
break
|
||||||
|
|
||||||
matches = [(oname, self.objects[oname]) for oname in names]
|
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 {
|
||||||
|
@ -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:
|
||||||
|
@ -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::
|
||||||
|
@ -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
|
||||||
|
@ -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 } }
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user