gh-91271: Document which parts of structs are in limited API/stable ABI (GH-32196) (GH-95711)

Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@innova.no>
This commit is contained in:
Petr Viktorin 2022-08-05 17:30:51 +02:00 committed by GitHub
parent 57446f9e33
commit b66b6e1cc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 982 additions and 871 deletions

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,7 @@
'type': 'type', 'type': 'type',
'macro': 'macro', 'macro': 'macro',
'type': 'type', 'type': 'type',
'member': 'member',
} }
@ -100,6 +101,12 @@ def add_annotations(self, app, doctree):
# Stable ABI annotation. These have two forms: # Stable ABI annotation. These have two forms:
# Part of the [Stable ABI](link). # Part of the [Stable ABI](link).
# Part of the [Stable ABI](link) since version X.Y. # Part of the [Stable ABI](link) since version X.Y.
# For structs, there's some more info in the message:
# Part of the [Limited API](link) (as an opaque struct).
# Part of the [Stable ABI](link) (including all members).
# Part of the [Limited API](link) (Only some members are part
# of the stable ABI.).
# ... all of which can have "since version X.Y" appended.
record = self.stable_abi_data.get(name) record = self.stable_abi_data.get(name)
if record: if record:
if record['role'] != objtype: if record['role'] != objtype:
@ -113,15 +120,27 @@ def add_annotations(self, app, doctree):
ref_node = addnodes.pending_xref( ref_node = addnodes.pending_xref(
'Stable ABI', refdomain="std", reftarget='stable', 'Stable ABI', refdomain="std", reftarget='stable',
reftype='ref', refexplicit="False") reftype='ref', refexplicit="False")
struct_abi_kind = record['struct_abi_kind']
if struct_abi_kind in {'opaque', 'members'}:
ref_node += nodes.Text('Limited API')
else:
ref_node += nodes.Text('Stable ABI') ref_node += nodes.Text('Stable ABI')
emph_node += ref_node emph_node += ref_node
if struct_abi_kind == 'opaque':
emph_node += nodes.Text(' (as an opaque struct)')
elif struct_abi_kind == 'full-abi':
emph_node += nodes.Text(' (including all members)')
if record['ifdef_note']: if record['ifdef_note']:
emph_node += nodes.Text(' ' + record['ifdef_note']) emph_node += nodes.Text(' ' + record['ifdef_note'])
if stable_added == '3.2': if stable_added == '3.2':
# Stable ABI was introduced in 3.2. # Stable ABI was introduced in 3.2.
emph_node += nodes.Text('.') pass
else: else:
emph_node += nodes.Text(f' since version {stable_added}.') emph_node += nodes.Text(f' since version {stable_added}')
emph_node += nodes.Text('.')
if struct_abi_kind == 'members':
emph_node += nodes.Text(
' (Only some members are part of the stable ABI.)')
node.insert(0, emph_node) node.insert(0, emph_node)
# Return value annotation # Return value annotation

View file

@ -0,0 +1,2 @@
The documentation now lists which members of C structs are part of the
:ref:`Limited API/Stable ABI <stable>`.

View file

@ -10,44 +10,111 @@
# and PC/pythonXYstub.def # and PC/pythonXYstub.def
# The current format is a simple line-based one with significant indentation.
# Anything after a hash is a comment.
# There are these kinds of top-level "items":
# - struct: A C struct. Currently this file does not distinguish between:
# - opaque structs, which the Limited API only handles via pointers
# (so these can change at any time)
# - structs where only certain members are part of the stable ABI (e.g.
# PyObject)
# - structs which must not be changed at all (e.g. PyType_Slot, which is
# fully defined and used in arrays)
# - function: A function that must be kept available (and exported, i.e. not
# converted to a macro).
# - const: A simple value, defined with `#define`.
# - macro: A preprocessor macro more complex than a simple `const` value.
# - data: An exported object, which must continue to be available but its exact
# value may change.
# - typedef: A C typedef which is used in other definitions in the limited API.
# Its size/layout/signature must not change.
# Each top-level item can have details defined below it:
# - added: The version in which the item was added to the stable ABI.
# - ifdef: A feature macro: the item is only available if this macro is defined
# - abi_only: If present, the item is not part of the Limited API, but it *is*
# part of the stable ABI. The item will not show up in user-facing docs.
# Typically used for:
# - private functions called by public macros, e.g. _Py_BuildValue_SizeT
# - items that were part of the limited API in the past, and must remain part
# of the stable ABI.
# - a combination of the above (functions that were called by macros that
# were public in the past)
# For structs, one of the following must be set:
# - opaque: The struct name is available in the Limited API, but its members
# are not. Users must manipulate it via pointers.
# - members: Space-separated list of members which are part of the
# Limited API and Stable ABI.
# Members that aren't listed are not accessible to applications.
# - full-abi: The entire struct -- all its members and its size -- is part of
# the Stable ABI, and must not change.
# Removing items from this file is generally not allowed, and additions should
# be considered with that in mind. See the devguide for exact rules:
# https://devguide.python.org/c-api/#limited-api
# User-facing docs are at:
# https://docs.python.org/3/c-api/stable.html#stable
# Mentioned in PEP 384: # Mentioned in PEP 384:
struct PyObject struct PyObject
added 3.2 added 3.2
members ob_refcnt ob_type
struct PyVarObject struct PyVarObject
added 3.2 added 3.2
members ob_base ob_size
struct PyMethodDef struct PyMethodDef
added 3.2 added 3.2
full-abi
struct PyMemberDef struct PyMemberDef
added 3.2 added 3.2
full-abi
struct PyGetSetDef struct PyGetSetDef
added 3.2 added 3.2
full-abi
struct PyModuleDef_Base struct PyModuleDef_Base
added 3.2 added 3.2
full-abi
struct PyModuleDef struct PyModuleDef
added 3.2 added 3.2
full-abi
struct PyStructSequence_Field struct PyStructSequence_Field
added 3.2 added 3.2
full-abi
struct PyStructSequence_Desc struct PyStructSequence_Desc
added 3.2 added 3.2
full-abi
struct PyType_Slot struct PyType_Slot
added 3.2 added 3.2
full-abi
struct PyType_Spec struct PyType_Spec
added 3.2 added 3.2
full-abi
struct PyThreadState struct PyThreadState
added 3.2 added 3.2
opaque
struct PyInterpreterState struct PyInterpreterState
added 3.2 added 3.2
opaque
struct PyFrameObject struct PyFrameObject
added 3.2 added 3.2
opaque
struct symtable struct symtable
added 3.2 added 3.2
opaque
struct PyWeakReference struct PyWeakReference
added 3.2 added 3.2
opaque
struct PyLongObject struct PyLongObject
added 3.2 added 3.2
opaque
struct PyTypeObject struct PyTypeObject
added 3.2 added 3.2
opaque
function PyType_FromSpec function PyType_FromSpec
added 3.2 added 3.2
@ -259,11 +326,11 @@ typedef newfunc
added 3.2 added 3.2
typedef allocfunc typedef allocfunc
added 3.2 added 3.2
struct PyCFunction typedef PyCFunction
added 3.2 added 3.2
struct PyCFunctionWithKeywords typedef PyCFunctionWithKeywords
added 3.2 added 3.2
struct PyCapsule_Destructor typedef PyCapsule_Destructor
added 3.2 added 3.2
typedef getter typedef getter
added 3.2 added 3.2

View file

@ -119,6 +119,8 @@ class ABIItem:
contents: list = dataclasses.field(default_factory=list) contents: list = dataclasses.field(default_factory=list)
abi_only: bool = False abi_only: bool = False
ifdef: str = None ifdef: str = None
struct_abi_kind: str = None
members: list = None
KINDS = frozenset({ KINDS = frozenset({
'struct', 'function', 'macro', 'data', 'const', 'typedef', 'struct', 'function', 'macro', 'data', 'const', 'typedef',
@ -173,6 +175,15 @@ def raise_error(msg):
if parent.kind not in {'function', 'data'}: if parent.kind not in {'function', 'data'}:
raise_error(f'{kind} cannot go in {parent.kind}') raise_error(f'{kind} cannot go in {parent.kind}')
parent.abi_only = True parent.abi_only = True
elif kind in {'members', 'full-abi', 'opaque'}:
if parent.kind not in {'struct'}:
raise_error(f'{kind} cannot go in {parent.kind}')
if prev := getattr(parent, 'struct_abi_kind', None):
raise_error(
f'{parent.name} already has {prev}, cannot add {kind}')
parent.struct_abi_kind = kind
if kind == 'members':
parent.members = content.split()
else: else:
raise_error(f"unknown kind {kind!r}") raise_error(f"unknown kind {kind!r}")
levels.append((entry, level)) levels.append((entry, level))
@ -246,7 +257,9 @@ def sort_key(item):
def gen_doc_annotations(manifest, args, outfile): def gen_doc_annotations(manifest, args, outfile):
"""Generate/check the stable ABI list for documentation annotations""" """Generate/check the stable ABI list for documentation annotations"""
writer = csv.DictWriter( writer = csv.DictWriter(
outfile, ['role', 'name', 'added', 'ifdef_note'], lineterminator='\n') outfile,
['role', 'name', 'added', 'ifdef_note', 'struct_abi_kind'],
lineterminator='\n')
writer.writeheader() writer.writeheader()
for item in manifest.select(REST_ROLES.keys(), include_abi_only=False): for item in manifest.select(REST_ROLES.keys(), include_abi_only=False):
if item.ifdef: if item.ifdef:
@ -257,7 +270,13 @@ def gen_doc_annotations(manifest, args, outfile):
'role': REST_ROLES[item.kind], 'role': REST_ROLES[item.kind],
'name': item.name, 'name': item.name,
'added': item.added, 'added': item.added,
'ifdef_note': ifdef_note}) 'ifdef_note': ifdef_note,
'struct_abi_kind': item.struct_abi_kind})
for member_name in item.members or ():
writer.writerow({
'role': 'member',
'name': f'{item.name}.{member_name}',
'added': item.added})
def generate_or_check(manifest, args, path, func): def generate_or_check(manifest, args, path, func):
"""Generate/check a file with a single generator """Generate/check a file with a single generator