From e48804c904f43d07498d4c6235a1e306b90cb215 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 17 Sep 2026 20:45:36 +0200 Subject: [PATCH 01/17] gh-157695: Check that C API macros start with "Py" Add "make check-capi-macros". Ignore existing macros which don't start with "Py", only prevent adding new names which don't respect this convention. Run "make check-capi-macros" in the GitHub Action "Check if generated files are up to date" job. --- .github/workflows/build.yml | 2 + Makefile.pre.in | 5 + ...-09-17-20-50-56.gh-issue-157695.06UH5M.rst | 3 + Tools/build/check_capi_macros.py | 75 +++ Tools/build/check_capi_macros_ignored.txt | 473 ++++++++++++++++++ 5 files changed, 558 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst create mode 100644 Tools/build/check_capi_macros.py create mode 100644 Tools/build/check_capi_macros_ignored.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7fdc81ae0ade1d2..720d2911f712c8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -140,6 +140,8 @@ jobs: - name: Check for unsupported C global variables if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME run: make check-c-globals + - name: Check C ABI macros + run: make check-capi-macros check-c-api-docs: name: C API Docs diff --git a/Makefile.pre.in b/Makefile.pre.in index 67e00f102403d56..19cc9ef2a9fa4e2 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -3367,6 +3367,11 @@ distclean: clobber docclean smelly: all $(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/smelly.py +# Check that macros defined by the Python C API start with "Py" +.PHONY: check-capi-macros +check-capi-macros: all + $(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/check_capi_macros.py + # Check if any unsupported C global variables have been added. .PHONY: check-c-globals check-c-globals: diff --git a/Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst b/Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst new file mode 100644 index 000000000000000..fc92b8af7789a8a --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst @@ -0,0 +1,3 @@ +Add ``make check-capi-macros`` which fails if a new macro is added to the C +API with a name which doesn't start with "Py". Existing macros which don't +respect this convention are ignored. Patch by Victor Stinner. diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py new file mode 100644 index 000000000000000..48c1d6b8a6f3d94 --- /dev/null +++ b/Tools/build/check_capi_macros.py @@ -0,0 +1,75 @@ +""" +Check that all macros defined by the Python C API have a name starting with +"Py". Ignore names listed by check_capi_macros_ignored.txt: macros with an +invalid name, added before this script was created. +""" + +import glob +import os.path +import re +import sys + +TOOLS_BUILD_DIR = os.path.abspath(os.path.dirname(__file__)) +SRC_DIR = os.path.dirname(os.path.dirname(TOOLS_BUILD_DIR)) + +DEFINE_REGEX = re.compile(r'^\s*# *define\s+(.*)') +PYTHON_PREFIX = re.compile(r'^(Py|PY|_Py|_PY)') +DEFINE_NAME_REGEX = re.compile(r'^([A-Za-z_][A-Za-z0-9_]*)\b') + + +def parse_file(filename, names, ignored): + with open(filename, encoding='utf8') as fp: + for line in fp: + match = DEFINE_REGEX.match(line) + if not match: + continue + macro = match.group(1) + + if PYTHON_PREFIX.match(macro): + continue + + match = DEFINE_NAME_REGEX.search(macro) + if not match: + print(repr(macro)) + print(f"{filename}: Unable to parse {line!r}") + sys.exit(1) + name = match.group(1) + if name in ignored: + continue + + names.append((name, filename)) + + +def main(): + filename = os.path.join(TOOLS_BUILD_DIR, 'check_capi_macros_ignored.txt') + ignored = set() + with open(filename, encoding='utf8') as fp: + for line in fp: + name = line.strip() + if name: + ignored.add(name) + + include_dir = os.path.join(SRC_DIR, 'Include') + files = glob.glob(os.path.join(include_dir, '*.h')) + files.extend(glob.glob(os.path.join(include_dir, 'cpython', '*.h'))) + names = [] + for filename in files: + parse_file(filename, names, ignored) + names.sort() + + if not names: + print("OK: the Python C API only defines macros with name " + f"starting with Py (ignoring {len(ignored)} macros)") + sys.exit(0) + + print('ERROR: the Python C API defines the following macros ' + 'with a name not starting with "Py":') + for name, filename in names: + print(f"- {name} defined by {filename}") + print() + print(f"Total: {len(names)} macros") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt new file mode 100644 index 000000000000000..c2965c9325e2389 --- /dev/null +++ b/Tools/build/check_capi_macros_ignored.txt @@ -0,0 +1,473 @@ +ALIGNOF_MAX_ALIGN_T +ALIGNOF_MAX_ALIGN_T +ALIGNOF_MAX_ALIGN_T +ALTSEP +ANNOTATIONS_PLACEHOLDER +BINARY_OP +BINARY_OP_ADD_FLOAT +BINARY_OP_ADD_INT +BINARY_OP_ADD_UNICODE +BINARY_OP_EXTEND +BINARY_OP_INPLACE_ADD_UNICODE +BINARY_OP_MULTIPLY_FLOAT +BINARY_OP_MULTIPLY_INT +BINARY_OP_SUBSCR_DICT +BINARY_OP_SUBSCR_GETITEM +BINARY_OP_SUBSCR_LIST_INT +BINARY_OP_SUBSCR_LIST_SLICE +BINARY_OP_SUBSCR_STR_INT +BINARY_OP_SUBSCR_TUPLE_INT +BINARY_OP_SUBSCR_USTR_INT +BINARY_OP_SUBTRACT_FLOAT +BINARY_OP_SUBTRACT_INT +BINARY_SLICE +BUILD_INTERPOLATION +BUILD_LIST +BUILD_MAP +BUILD_SET +BUILD_SLICE +BUILD_STRING +BUILD_TEMPLATE +BUILD_TUPLE +CACHE +CALL +CALL_ALLOC_AND_ENTER_INIT +CALL_BOUND_METHOD_EXACT_ARGS +CALL_BOUND_METHOD_GENERAL +CALL_BUILTIN_CLASS +CALL_BUILTIN_FAST +CALL_BUILTIN_FAST_WITH_KEYWORDS +CALL_BUILTIN_O +CALL_EX_NON_PY_GENERAL +CALL_EX_PY +CALL_FUNCTION_EX +CALL_INTRINSIC_1 +CALL_INTRINSIC_2 +CALL_ISINSTANCE +CALL_KW +CALL_KW_BOUND_METHOD +CALL_KW_NON_PY +CALL_KW_PY +CALL_LEN +CALL_LIST_APPEND +CALL_METHOD_DESCRIPTOR_FAST +CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS +CALL_METHOD_DESCRIPTOR_NOARGS +CALL_METHOD_DESCRIPTOR_O +CALL_NON_PY_GENERAL +CALL_PY_EXACT_ARGS +CALL_PY_GENERAL +CALL_STR_1 +CALL_TUPLE_1 +CALL_TYPE_1 +CHECK_EG_MATCH +CHECK_EXC_MATCH +CLEANUP_THROW +COMPARE_OP +COMPARE_OP_FLOAT +COMPARE_OP_INT +COMPARE_OP_STR +CONTAINS_OP +CONTAINS_OP_DICT +CONTAINS_OP_SET +CONVERT_VALUE +COPY +COPY_FREE_VARS +CO_ASYNC_GENERATOR +CO_COROUTINE +CO_FUTURE_ABSOLUTE_IMPORT +CO_FUTURE_ANNOTATIONS +CO_FUTURE_BARRY_AS_BDFL +CO_FUTURE_DIVISION +CO_FUTURE_GENERATOR_STOP +CO_FUTURE_PRINT_FUNCTION +CO_FUTURE_UNICODE_LITERALS +CO_FUTURE_WITH_STATEMENT +CO_GENERATOR +CO_HAS_DOCSTRING +CO_ITERABLE_COROUTINE +CO_MAXBLOCKS +CO_METHOD +CO_NESTED +CO_NEWLOCALS +CO_NO_MONITORING_EVENTS +CO_OPTIMIZED +CO_VARARGS +CO_VARKEYWORDS +CURSES_ERROR_FORMAT +CURSES_ERROR_MUST_CALL_FORMAT +CURSES_ERROR_VERBOSE_FORMAT +DELETE_DEREF +DELETE_FAST +DELETE_SUBSCR +DELIM +DELIM +DELIM +DICT_MERGE +DICT_UPDATE +DOUBLE_IS_BIG_ENDIAN_IEEE754 +DOUBLE_IS_LITTLE_ENDIAN_IEEE754 +DYNAMIC_ANNOTATIONS_ENABLED +END_ASYNC_FOR +END_FOR +END_SEND +ENTER_EXECUTOR +EVAL_CALL_API +EVAL_CALL_BUILD_CLASS +EVAL_CALL_FUNCTION_EX +EVAL_CALL_FUNCTION_VECTORCALL +EVAL_CALL_GENERATOR +EVAL_CALL_KINDS +EVAL_CALL_LEGACY +EVAL_CALL_METHOD +EVAL_CALL_SLOT +EVAL_CALL_TOTAL +EVAL_CALL_VECTOR +EXIT_INIT_CHECK +EXTENDED_ARG +E_BADSINGLE +E_COLUMNOVERFLOW +E_DECODE +E_DEDENT +E_DONE +E_EOF +E_EOFS +E_EOLS +E_ERROR +E_INTERACT_STOP +E_INTR +E_LINECONT +E_NOMEM +E_OK +E_OVERFLOW +E_SYNTAX +E_TABSPACE +E_TOKEN +E_TOODEEP +FORMAT_SIMPLE +FORMAT_WITH_SPEC +FOR_ITER +FOR_ITER_GEN +FOR_ITER_LIST +FOR_ITER_RANGE +FOR_ITER_TUPLE +FOR_ITER_VIRTUAL +FUTURE_ABSOLUTE_IMPORT +FUTURE_ANNOTATIONS +FUTURE_BARRY_AS_BDFL +FUTURE_DIVISION +FUTURE_GENERATORS +FUTURE_GENERATOR_STOP +FUTURE_NESTED_SCOPES +FUTURE_PRINT_FUNCTION +FUTURE_UNICODE_LITERALS +FUTURE_WITH_STATEMENT +FVC_ALT_REPR +FVC_ASCII +FVC_MASK +FVC_NONE +FVC_REPR +FVC_STR +FVS_HAVE_SPEC +FVS_MASK +GET_AITER +GET_ANEXT +GET_AWAITABLE +GET_ITER +GET_ITER_SELF +GET_ITER_VIRTUAL +GET_LEN +HAVE_ARGUMENT +HAVE_CURSES_IS_PAD +HAVE_DECLSPEC_DLL +HAVE_GCC_ASM_FOR_X64 +HAVE_GCC_ASM_FOR_X87 +HAVE_LARGEFILE_SUPPORT +HAVE_LONG_LONG +HAVE_THREAD_LOCAL +HAVE_WCHAR_H +IMPORT_FROM +IMPORT_NAME +INSTRUMENTED_CALL +INSTRUMENTED_CALL_FUNCTION_EX +INSTRUMENTED_CALL_KW +INSTRUMENTED_END_ASYNC_FOR +INSTRUMENTED_END_FOR +INSTRUMENTED_END_SEND +INSTRUMENTED_FOR_ITER +INSTRUMENTED_INSTRUCTION +INSTRUMENTED_JUMP_BACKWARD +INSTRUMENTED_JUMP_FORWARD +INSTRUMENTED_LINE +INSTRUMENTED_LOAD_SUPER_ATTR +INSTRUMENTED_NOT_TAKEN +INSTRUMENTED_POP_ITER +INSTRUMENTED_POP_JUMP_IF_FALSE +INSTRUMENTED_POP_JUMP_IF_NONE +INSTRUMENTED_POP_JUMP_IF_NOT_NONE +INSTRUMENTED_POP_JUMP_IF_TRUE +INSTRUMENTED_RESUME +INSTRUMENTED_RETURN_VALUE +INSTRUMENTED_YIELD_VALUE +INTERPRETER_EXIT +INT_MAX +IS_OP +JUMP +JUMP_BACKWARD +JUMP_BACKWARD_JIT +JUMP_BACKWARD_NO_INTERRUPT +JUMP_BACKWARD_NO_JIT +JUMP_FORWARD +JUMP_IF_FALSE +JUMP_IF_TRUE +JUMP_NO_INTERRUPT +LIST_APPEND +LIST_EXTEND +LOAD_ATTR +LOAD_ATTR_CLASS +LOAD_ATTR_CLASS_WITH_METACLASS_CHECK +LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN +LOAD_ATTR_INSTANCE_VALUE +LOAD_ATTR_METHOD_LAZY_DICT +LOAD_ATTR_METHOD_NO_DICT +LOAD_ATTR_METHOD_WITH_VALUES +LOAD_ATTR_MODULE +LOAD_ATTR_NONDESCRIPTOR_NO_DICT +LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES +LOAD_ATTR_PROPERTY +LOAD_ATTR_SLOT +LOAD_ATTR_WITH_HINT +LOAD_BUILD_CLASS +LOAD_CLOSURE +LOAD_COMMON_CONSTANT +LOAD_CONST +LOAD_DEREF +LOAD_FAST +LOAD_FAST_AND_CLEAR +LOAD_FAST_BORROW +LOAD_FAST_BORROW_LOAD_FAST_BORROW +LOAD_FAST_CHECK +LOAD_FAST_LOAD_FAST +LOAD_FROM_DICT_OR_DEREF +LOAD_FROM_DICT_OR_GLOBALS +LOAD_GLOBAL +LOAD_GLOBAL_BUILTIN +LOAD_GLOBAL_MODULE +LOAD_LOCALS +LOAD_NAME +LOAD_SMALL_INT +LOAD_SPECIAL +LOAD_SUPER_ATTR +LOAD_SUPER_ATTR_ATTR +LOAD_SUPER_ATTR_METHOD +LONG_BIT +LONG_MAX +LONG_MAX +LONG_MIN +MAKE_CELL +MAKE_FUNCTION +MAP_ADD +MATCH_CLASS +MATCH_KEYS +MATCH_MAPPING +MATCH_SEQUENCE +MAXPATHLEN +MAXPATHLEN +MAXPATHLEN +MAX_CO_EXTRA_USERS +METH_CLASS +METH_COEXIST +METH_FASTCALL +METH_KEYWORDS +METH_METHOD +METH_NOARGS +METH_O +METH_STACKLESS +METH_STACKLESS +METH_STATIC +METH_VARARGS +MIN_INSTRUMENTED_OPCODE +MIN_SPECIALIZED_OPCODE +MVWDELCH_IS_EXPRESSION +NATIVE_TSS_KEY_T +NATIVE_TSS_KEY_T +NATIVE_TSS_KEY_T +NB_ADD +NB_AND +NB_FLOOR_DIVIDE +NB_INPLACE_ADD +NB_INPLACE_AND +NB_INPLACE_FLOOR_DIVIDE +NB_INPLACE_LSHIFT +NB_INPLACE_MATRIX_MULTIPLY +NB_INPLACE_MULTIPLY +NB_INPLACE_OR +NB_INPLACE_POWER +NB_INPLACE_REMAINDER +NB_INPLACE_RSHIFT +NB_INPLACE_SUBTRACT +NB_INPLACE_TRUE_DIVIDE +NB_INPLACE_XOR +NB_LSHIFT +NB_MATRIX_MULTIPLY +NB_MULTIPLY +NB_OPARG_LAST +NB_OR +NB_POWER +NB_REMAINDER +NB_RSHIFT +NB_SUBSCR +NB_SUBTRACT +NB_TRUE_DIVIDE +NB_XOR +NCURSES_OPAQUE +NOP +NOT_TAKEN +NOWAIT_LOCK +PATH_MAX +PDC_NCMOUSE +POP_BLOCK +POP_EXCEPT +POP_ITER +POP_JUMP_IF_FALSE +POP_JUMP_IF_NONE +POP_JUMP_IF_NOT_NONE +POP_JUMP_IF_TRUE +POP_TOP +PTHREAD_KEYS_MAX +PUSH_EXC_INFO +PUSH_NULL +RAISE_VARARGS +READONLY +READ_RESTRICTED +RERAISE +RESERVED +RESTRICTED +RESUME +RESUME_CHECK +RESUME_CHECK_JIT +RETURN_GENERATOR +RETURN_VALUE +SEND +SEND_ASYNC_GEN +SEND_GEN +SEND_VIRTUAL +SEP +SEP +SETUP_ANNOTATIONS +SETUP_CLEANUP +SETUP_FINALLY +SETUP_WITH +SET_ADD +SET_FUNCTION_ATTRIBUTE +SET_UPDATE +SIZEOF_LONG +SIZEOF_LONG +SIZEOF_LONG_DOUBLE +SIZEOF_LONG_DOUBLE +SIZEOF_PTHREAD_T +SIZEOF_PTHREAD_T +SIZEOF_PTHREAD_T +SIZEOF_PTHREAD_T +SIZEOF_PY_HASH_T +SIZEOF_PY_UHASH_T +SIZEOF_SIZE_T +SIZEOF_SIZE_T +SIZEOF_TIME_T +SIZEOF_TIME_T +SIZEOF_UINTPTR_T +SIZEOF_UINTPTR_T +SIZEOF_VOID_P +SIZEOF_VOID_P +SIZEOF__BOOL +SIZEOF__BOOL +SIZEOF__BOOL +SIZEOF__BOOL +SPECIALIZATION_FAILURE_KINDS +SSTATE_INTERNED_IMMORTAL +SSTATE_INTERNED_IMMORTAL_STATIC +SSTATE_INTERNED_MORTAL +SSTATE_NOT_INTERNED +STORE_ATTR +STORE_ATTR_INSTANCE_VALUE +STORE_ATTR_SLOT +STORE_ATTR_WITH_HINT +STORE_DEREF +STORE_FAST +STORE_FAST_LOAD_FAST +STORE_FAST_MAYBE_NULL +STORE_FAST_STORE_FAST +STORE_GLOBAL +STORE_NAME +STORE_SLICE +STORE_SUBSCR +STORE_SUBSCR_DICT +STORE_SUBSCR_LIST_INT +SWAP +S_IFLNK +S_IFMT +S_ISCHR +S_ISDIR +S_ISLNK +S_ISREG +TO_BOOL +TO_BOOL_ALWAYS_TRUE +TO_BOOL_BOOL +TO_BOOL_INT +TO_BOOL_LIST +TO_BOOL_NONE +TO_BOOL_STR +TRACE_RECORD +T_BOOL +T_BYTE +T_CHAR +T_DOUBLE +T_FLOAT +T_INT +T_LONG +T_LONGLONG +T_NONE +T_OBJECT +T_OBJECT_EX +T_PYSSIZET +T_SHORT +T_STRING +T_STRING_INPLACE +T_UBYTE +T_UINT +T_ULONG +T_ULONGLONG +T_USHORT +UNARY_INVERT +UNARY_NEGATIVE +UNARY_NOT +UNPACK_EX +UNPACK_SEQUENCE +UNPACK_SEQUENCE_LIST +UNPACK_SEQUENCE_TUPLE +UNPACK_SEQUENCE_TWO_TUPLE +USE_STACKCHECK +VA_LIST_IS_ARRAY +WAIT_LOCK +WINDOW_HAS_FLAGS +WITH_EXCEPT_START +WITH_THREAD +WORDS_BIGENDIAN +YIELD_VALUE +_Atomic +_BOOL +_POSIX_THREADS +_SGI_MP_SOURCE +_WCHAR_T +__DYNAMIC_ANNOTATIONS_H__ +__NEED_pthread_attr_t +__NEED_pthread_cond_t +__NEED_pthread_condattr_t +__NEED_pthread_key_t +__NEED_pthread_mutex_t +__NEED_pthread_mutexattr_t +__NEED_pthread_t +__wchar_t +__wint_t +import_curses +static_assert From 0d6621d6d531891183a26a57d1251139c3bcdcfb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 17 Sep 2026 21:24:53 +0200 Subject: [PATCH 02/17] Add a comment to the ignore file --- Tools/build/check_capi_macros.py | 11 ++++++++++- Tools/build/check_capi_macros_ignored.txt | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index 48c1d6b8a6f3d94..4394c6cf28fe14d 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -40,14 +40,22 @@ def parse_file(filename, names, ignored): names.append((name, filename)) -def main(): +def get_ignored_names(): filename = os.path.join(TOOLS_BUILD_DIR, 'check_capi_macros_ignored.txt') ignored = set() with open(filename, encoding='utf8') as fp: for line in fp: name = line.strip() + if name.startswith('#'): + # Ignore comment + continue if name: ignored.add(name) + return ignored + + +def main(): + ignored = get_ignored_names() include_dir = os.path.join(SRC_DIR, 'Include') files = glob.glob(os.path.join(include_dir, '*.h')) @@ -64,6 +72,7 @@ def main(): print('ERROR: the Python C API defines the following macros ' 'with a name not starting with "Py":') + print() for name, filename in names: print(f"- {name} defined by {filename}") print() diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index c2965c9325e2389..8d86864541475a5 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -1,3 +1,10 @@ +# Old C API macros with a name which doesn't start with "Py". If a macro +# is removed from the C API, it should be removed from this list as well. +# +# Adding a new macro to this ignore list requires the approval of the C API +# Working Group. Open an issue at: +# https://github.com/capi-workgroup/decisions/issues/ + ALIGNOF_MAX_ALIGN_T ALIGNOF_MAX_ALIGN_T ALIGNOF_MAX_ALIGN_T From 714f1f576cf3b1ed4f477a6955d1225d9b34cad6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 17 Sep 2026 22:00:57 +0200 Subject: [PATCH 03/17] Remove duplicates --- Tools/build/check_capi_macros_ignored.txt | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index 8d86864541475a5..5ccf7fca3952b97 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -5,8 +5,6 @@ # Working Group. Open an issue at: # https://github.com/capi-workgroup/decisions/issues/ -ALIGNOF_MAX_ALIGN_T -ALIGNOF_MAX_ALIGN_T ALIGNOF_MAX_ALIGN_T ALTSEP ANNOTATIONS_PLACEHOLDER @@ -108,8 +106,6 @@ DELETE_DEREF DELETE_FAST DELETE_SUBSCR DELIM -DELIM -DELIM DICT_MERGE DICT_UPDATE DOUBLE_IS_BIG_ENDIAN_IEEE754 @@ -269,7 +265,6 @@ LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_METHOD LONG_BIT LONG_MAX -LONG_MAX LONG_MIN MAKE_CELL MAKE_FUNCTION @@ -279,8 +274,6 @@ MATCH_KEYS MATCH_MAPPING MATCH_SEQUENCE MAXPATHLEN -MAXPATHLEN -MAXPATHLEN MAX_CO_EXTRA_USERS METH_CLASS METH_COEXIST @@ -290,15 +283,12 @@ METH_METHOD METH_NOARGS METH_O METH_STACKLESS -METH_STACKLESS METH_STATIC METH_VARARGS MIN_INSTRUMENTED_OPCODE MIN_SPECIALIZED_OPCODE MVWDELCH_IS_EXPRESSION NATIVE_TSS_KEY_T -NATIVE_TSS_KEY_T -NATIVE_TSS_KEY_T NB_ADD NB_AND NB_FLOOR_DIVIDE @@ -360,7 +350,6 @@ SEND_ASYNC_GEN SEND_GEN SEND_VIRTUAL SEP -SEP SETUP_ANNOTATIONS SETUP_CLEANUP SETUP_FINALLY @@ -369,27 +358,15 @@ SET_ADD SET_FUNCTION_ATTRIBUTE SET_UPDATE SIZEOF_LONG -SIZEOF_LONG SIZEOF_LONG_DOUBLE -SIZEOF_LONG_DOUBLE -SIZEOF_PTHREAD_T -SIZEOF_PTHREAD_T -SIZEOF_PTHREAD_T SIZEOF_PTHREAD_T SIZEOF_PY_HASH_T SIZEOF_PY_UHASH_T SIZEOF_SIZE_T -SIZEOF_SIZE_T -SIZEOF_TIME_T SIZEOF_TIME_T SIZEOF_UINTPTR_T -SIZEOF_UINTPTR_T -SIZEOF_VOID_P SIZEOF_VOID_P SIZEOF__BOOL -SIZEOF__BOOL -SIZEOF__BOOL -SIZEOF__BOOL SPECIALIZATION_FAILURE_KINDS SSTATE_INTERNED_IMMORTAL SSTATE_INTERNED_IMMORTAL_STATIC From 44eb795823c04d43a29d4a7b9b63a1a99b9d9593 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 17 Sep 2026 22:06:17 +0200 Subject: [PATCH 04/17] Fix error message --- Tools/build/check_capi_macros.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index 4394c6cf28fe14d..e773a0cecb82437 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -30,8 +30,7 @@ def parse_file(filename, names, ignored): match = DEFINE_NAME_REGEX.search(macro) if not match: - print(repr(macro)) - print(f"{filename}: Unable to parse {line!r}") + print(f"ERROR: {filename}: Unable to parse {line!r}") sys.exit(1) name = match.group(1) if name in ignored: From 819558aaee38da57052ee1ca5d32b183b3c11b82 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 01:18:06 +0200 Subject: [PATCH 05/17] Remove NEWS entry --- .../next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst diff --git a/Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst b/Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst deleted file mode 100644 index fc92b8af7789a8a..000000000000000 --- a/Misc/NEWS.d/next/C_API/2026-09-17-20-50-56.gh-issue-157695.06UH5M.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add ``make check-capi-macros`` which fails if a new macro is added to the C -API with a name which doesn't start with "Py". Existing macros which don't -respect this convention are ignored. Patch by Victor Stinner. From c6af62b70df1c48673c400122880ea681ae33a5c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 02:05:08 +0200 Subject: [PATCH 06/17] Parse also pyconfig.h.in --- Tools/build/check_capi_macros.py | 23 + Tools/build/check_capi_macros_ignored.txt | 690 ++++++++++++++++++++++ 2 files changed, 713 insertions(+) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index e773a0cecb82437..dd4a8c933dc93ec 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -2,6 +2,12 @@ Check that all macros defined by the Python C API have a name starting with "Py". Ignore names listed by check_capi_macros_ignored.txt: macros with an invalid name, added before this script was created. + +Python C API: + +* Include/*.h +* Include/cpython/*.h +* pyconfig.h.in """ import glob @@ -15,6 +21,7 @@ DEFINE_REGEX = re.compile(r'^\s*# *define\s+(.*)') PYTHON_PREFIX = re.compile(r'^(Py|PY|_Py|_PY)') DEFINE_NAME_REGEX = re.compile(r'^([A-Za-z_][A-Za-z0-9_]*)\b') +UNDEF_REGEX = re.compile(r'#undef (.*)$') def parse_file(filename, names, ignored): @@ -39,6 +46,18 @@ def parse_file(filename, names, ignored): names.append((name, filename)) +def parse_pyconfig_in(filename, names, ignored): + with open(filename, encoding='utf8') as fp: + for line in fp: + match = UNDEF_REGEX.match(line) + if not match: + continue + name = match.group(1) + if name in ignored: + continue + names.append((name, filename)) + + def get_ignored_names(): filename = os.path.join(TOOLS_BUILD_DIR, 'check_capi_macros_ignored.txt') ignored = set() @@ -62,6 +81,10 @@ def main(): names = [] for filename in files: parse_file(filename, names, ignored) + + filename = os.path.join(SRC_DIR, 'pyconfig.h.in') + parse_file(filename, names, ignored) + parse_pyconfig_in(filename, names, ignored) names.sort() if not names: diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index 5ccf7fca3952b97..4fab3d8b45ab941 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -5,8 +5,15 @@ # Working Group. Open an issue at: # https://github.com/capi-workgroup/decisions/issues/ +AC_APPLE_UNIVERSAL_BUILD +AIX_BUILDDATE +AIX_GENUINE_CPLUSPLUS +ALIGNOF_LONG ALIGNOF_MAX_ALIGN_T +ALIGNOF_SIZE_T ALTSEP +ALT_SOABI +ANDROID_API_LEVEL ANNOTATIONS_PLACEHOLDER BINARY_OP BINARY_OP_ADD_FLOAT @@ -111,6 +118,7 @@ DICT_UPDATE DOUBLE_IS_BIG_ENDIAN_IEEE754 DOUBLE_IS_LITTLE_ENDIAN_IEEE754 DYNAMIC_ANNOTATIONS_ENABLED +ENABLE_IPV6 END_ASYNC_FOR END_FOR END_SEND @@ -173,6 +181,7 @@ FVC_REPR FVC_STR FVS_HAVE_SPEC FVS_MASK +GETPGRP_HAVE_ARG GET_AITER GET_ANEXT GET_AWAITABLE @@ -180,15 +189,596 @@ GET_ITER GET_ITER_SELF GET_ITER_VIRTUAL GET_LEN +HAVE_ACCEPT +HAVE_ACCEPT4 +HAVE_ACOSH +HAVE_ACOSPI +HAVE_ADDRINFO +HAVE_ALARM +HAVE_ALIGNED_REQUIRED +HAVE_ALLOCA_H +HAVE_ALTZONE HAVE_ARGUMENT +HAVE_ASINH +HAVE_ASINPI +HAVE_ASM_TYPES_H +HAVE_ATAN2PI +HAVE_ATANH +HAVE_ATANPI +HAVE_BACKTRACE +HAVE_BIND +HAVE_BIND_TEXTDOMAIN_CODESET +HAVE_BLUETOOTH_BLUETOOTH_H +HAVE_BLUETOOTH_H +HAVE_BROKEN_MBSTOWCS +HAVE_BROKEN_NICE +HAVE_BROKEN_PIPE_BUF +HAVE_BROKEN_POLL +HAVE_BROKEN_POSIX_SEMAPHORES +HAVE_BROKEN_PTHREAD_SIGMASK +HAVE_BROKEN_SEM_GETVALUE +HAVE_BROKEN_UNSETENV +HAVE_BUILTIN_ATOMIC +HAVE_BZLIB_H +HAVE_CHFLAGS +HAVE_CHMOD +HAVE_CHOWN +HAVE_CHROOT +HAVE_CLEARENV +HAVE_CLOCK +HAVE_CLOCK_GETRES +HAVE_CLOCK_GETTIME +HAVE_CLOCK_NANOSLEEP +HAVE_CLOCK_SETTIME +HAVE_CLOCK_T +HAVE_CLOSEFROM +HAVE_CLOSE_RANGE +HAVE_COMPUTED_GOTOS +HAVE_CONFSTR +HAVE_CONIO_H +HAVE_CONNECT +HAVE_COPY_FILE_RANGE +HAVE_COSPI +HAVE_CTERMID +HAVE_CTERMID_R +HAVE_CURSES_DEFINE_KEY +HAVE_CURSES_ESCDELAY +HAVE_CURSES_FILTER +HAVE_CURSES_GETMOUSE +HAVE_CURSES_H +HAVE_CURSES_HAS_KEY +HAVE_CURSES_HAS_MOUSE +HAVE_CURSES_IMMEDOK +HAVE_CURSES_IS_KEYPAD +HAVE_CURSES_IS_LEAVEOK HAVE_CURSES_IS_PAD +HAVE_CURSES_IS_TERM_RESIZED +HAVE_CURSES_KEYOK +HAVE_CURSES_KEY_DEFINED +HAVE_CURSES_NEW_PRESCR +HAVE_CURSES_NOFILTER +HAVE_CURSES_RESIZETERM +HAVE_CURSES_RESIZE_TERM +HAVE_CURSES_SCR_DUMP +HAVE_CURSES_SCR_SET +HAVE_CURSES_SET_ESCDELAY +HAVE_CURSES_SET_TABSIZE +HAVE_CURSES_SLK_ATTR_OFF +HAVE_CURSES_SLK_ATTR_ON +HAVE_CURSES_SLK_ATTR_SET +HAVE_CURSES_SLK_COLOR +HAVE_CURSES_SYNCOK +HAVE_CURSES_TABSIZE +HAVE_CURSES_TERM_ATTRS +HAVE_CURSES_TYPEAHEAD +HAVE_CURSES_USE_ENV +HAVE_CURSES_USE_SCREEN +HAVE_CURSES_USE_WINDOW +HAVE_CURSES_WATTR_GET +HAVE_CURSES_WATTR_OFF +HAVE_CURSES_WATTR_ON +HAVE_CURSES_WATTR_SET +HAVE_CURSES_WCHGAT +HAVE_CURSES_WCOLOR_SET +HAVE_DB_H HAVE_DECLSPEC_DLL +HAVE_DECL_PR_SET_VMA_ANON_NAME +HAVE_DECL_RTLD_DEEPBIND +HAVE_DECL_RTLD_GLOBAL +HAVE_DECL_RTLD_LAZY +HAVE_DECL_RTLD_LOCAL +HAVE_DECL_RTLD_MEMBER +HAVE_DECL_RTLD_NODELETE +HAVE_DECL_RTLD_NOLOAD +HAVE_DECL_RTLD_NOW +HAVE_DECL_TZNAME +HAVE_DECL_UT_NAMESIZE +HAVE_DEVICE_MACROS +HAVE_DEV_PTC +HAVE_DEV_PTMX +HAVE_DIRECT_H +HAVE_DIRENT_D_TYPE +HAVE_DIRENT_H +HAVE_DIRFD +HAVE_DLADDR +HAVE_DLADDR1 +HAVE_DLFCN_H +HAVE_DLOPEN +HAVE_DL_ITERATE_PHDR +HAVE_DUP +HAVE_DUP2 +HAVE_DUP3 +HAVE_DYLD_SHARED_CACHE_CONTAINS_PATH +HAVE_DYNAMIC_LOADING +HAVE_EDITLINE_READLINE_H +HAVE_ENDIAN_H +HAVE_EPOLL +HAVE_EPOLL_CREATE1 +HAVE_ERF +HAVE_ERFC +HAVE_ERRNO_H +HAVE_EVENTFD +HAVE_EXECINFO_H +HAVE_EXECV +HAVE_EXPLICIT_BZERO +HAVE_EXPLICIT_MEMSET +HAVE_EXPM1 +HAVE_FACCESSAT +HAVE_FCHDIR +HAVE_FCHMOD +HAVE_FCHMODAT +HAVE_FCHOWN +HAVE_FCHOWNAT +HAVE_FCNTL_H +HAVE_FDATASYNC +HAVE_FDOPENDIR +HAVE_FDWALK +HAVE_FEXECVE +HAVE_FFI_CLOSURE_ALLOC +HAVE_FFI_PREP_CIF_VAR +HAVE_FFI_PREP_CLOSURE_LOC +HAVE_FLOAT16 +HAVE_FLOCK +HAVE_FORK +HAVE_FORK1 +HAVE_FORKPTY +HAVE_FPATHCONF +HAVE_FSEEK64 +HAVE_FSEEKO +HAVE_FSTATAT +HAVE_FSTATVFS +HAVE_FSYNC +HAVE_FTELL64 +HAVE_FTELLO +HAVE_FTIME +HAVE_FTRUNCATE +HAVE_FUTIMENS +HAVE_FUTIMES +HAVE_FUTIMESAT +HAVE_GAI_STRERROR +HAVE_GCC_ASM_FOR_MC68881 HAVE_GCC_ASM_FOR_X64 HAVE_GCC_ASM_FOR_X87 +HAVE_GCC_UINT128_T +HAVE_GDBM_DASH_NDBM_H +HAVE_GDBM_H +HAVE_GDBM_NDBM_H +HAVE_GETADDRINFO +HAVE_GETC_UNLOCKED +HAVE_GETDENTS64 +HAVE_GETEGID +HAVE_GETENTROPY +HAVE_GETEUID +HAVE_GETGID +HAVE_GETGRENT +HAVE_GETGRGID +HAVE_GETGRGID_R +HAVE_GETGRNAM_R +HAVE_GETGROUPLIST +HAVE_GETGROUPS +HAVE_GETHOSTBYADDR +HAVE_GETHOSTBYNAME +HAVE_GETHOSTBYNAME_R +HAVE_GETHOSTBYNAME_R_3_ARG +HAVE_GETHOSTBYNAME_R_5_ARG +HAVE_GETHOSTBYNAME_R_6_ARG +HAVE_GETHOSTNAME +HAVE_GETITIMER +HAVE_GETLOADAVG +HAVE_GETLOGIN +HAVE_GETLOGIN_R +HAVE_GETNAMEINFO +HAVE_GETPAGESIZE +HAVE_GETPEERNAME +HAVE_GETPGID +HAVE_GETPGRP +HAVE_GETPID +HAVE_GETPPID +HAVE_GETPRIORITY +HAVE_GETPROTOBYNAME +HAVE_GETPWENT +HAVE_GETPWNAM_R +HAVE_GETPWUID +HAVE_GETPWUID_R +HAVE_GETRANDOM +HAVE_GETRANDOM_SYSCALL +HAVE_GETRESGID +HAVE_GETRESUID +HAVE_GETRUSAGE +HAVE_GETSERVBYNAME +HAVE_GETSERVBYPORT +HAVE_GETSID +HAVE_GETSOCKNAME +HAVE_GETSPENT +HAVE_GETSPNAM +HAVE_GETTID +HAVE_GETUID +HAVE_GETWD +HAVE_GLIBC_MEMMOVE_BUG +HAVE_GRANTPT +HAVE_GRP_H +HAVE_HSTRERROR +HAVE_HTOLE64 +HAVE_ICONV +HAVE_ICONV_H +HAVE_IF_INDEXTONAME +HAVE_IF_NAMEINDEX +HAVE_IF_NAMETOINDEX +HAVE_INET_ATON +HAVE_INET_NTOA +HAVE_INET_PTON +HAVE_INITGROUPS +HAVE_INTTYPES_H +HAVE_IO_H +HAVE_IPA_PURE_CONST_BUG +HAVE_KILL +HAVE_KILLPG +HAVE_KQUEUE +HAVE_LANGINFO_H HAVE_LARGEFILE_SUPPORT +HAVE_LCHFLAGS +HAVE_LCHMOD +HAVE_LCHOWN +HAVE_LIBDB +HAVE_LIBDL +HAVE_LIBDLD +HAVE_LIBIEEE +HAVE_LIBINTL_H +HAVE_LIBSENDFILE +HAVE_LIBSQLITE3 +HAVE_LIBUTIL_H +HAVE_LINK +HAVE_LINKAT +HAVE_LINK_H +HAVE_LINUX_AUXVEC_H +HAVE_LINUX_CAN_BCM_H +HAVE_LINUX_CAN_H +HAVE_LINUX_CAN_ISOTP_H +HAVE_LINUX_CAN_J1939_H +HAVE_LINUX_CAN_RAW_FD_FRAMES +HAVE_LINUX_CAN_RAW_H +HAVE_LINUX_CAN_RAW_JOIN_FILTERS +HAVE_LINUX_FS_H +HAVE_LINUX_LIMITS_H +HAVE_LINUX_MEMFD_H +HAVE_LINUX_NETFILTER_IPV4_H +HAVE_LINUX_NETLINK_H +HAVE_LINUX_QRTR_H +HAVE_LINUX_RANDOM_H +HAVE_LINUX_SCHED_H +HAVE_LINUX_SOUNDCARD_H +HAVE_LINUX_TIPC_H +HAVE_LINUX_VM_SOCKETS_H +HAVE_LINUX_WAIT_H +HAVE_LISTEN +HAVE_LOCKF +HAVE_LOG1P +HAVE_LOG2 +HAVE_LOGIN_TTY +HAVE_LONG_DOUBLE HAVE_LONG_LONG +HAVE_LSTAT +HAVE_LUTIMES +HAVE_LZMA_H +HAVE_MADVISE +HAVE_MAKEDEV +HAVE_MAXLOGNAME +HAVE_MBRTOWC +HAVE_MEMFD_CREATE +HAVE_MEMRCHR +HAVE_MINIX_CONFIG_H +HAVE_MKDIRAT +HAVE_MKFIFO +HAVE_MKFIFOAT +HAVE_MKNOD +HAVE_MKNODAT +HAVE_MKTIME +HAVE_MMAP +HAVE_MREMAP +HAVE_NANOSLEEP +HAVE_NCURSES +HAVE_NCURSESW +HAVE_NCURSESW_CURSES_H +HAVE_NCURSESW_NCURSES_H +HAVE_NCURSESW_PANEL_H +HAVE_NCURSES_CURSES_H +HAVE_NCURSES_H +HAVE_NCURSES_NCURSES_H +HAVE_NCURSES_PANEL_H +HAVE_NDBM_H +HAVE_NDIR_H +HAVE_NETCAN_CAN_H +HAVE_NETDB_H +HAVE_NETINET_IN_H +HAVE_NETLINK_NETLINK_H +HAVE_NETPACKET_PACKET_H +HAVE_NET_ETHERNET_H +HAVE_NET_IF_H +HAVE_NICE +HAVE_NON_UNICODE_WCHAR_T_REPRESENTATION +HAVE_OPENAT +HAVE_OPENDIR +HAVE_OPENPTY +HAVE_PANEL +HAVE_PANELW +HAVE_PANEL_H +HAVE_PATHCONF +HAVE_PAUSE +HAVE_PIDFD_GETFD +HAVE_PIDFD_OPEN +HAVE_PIDFD_SEND_SIGNAL +HAVE_PIPE +HAVE_PIPE2 +HAVE_PLOCK +HAVE_POLL +HAVE_POLL_H +HAVE_POSIX_FADVISE +HAVE_POSIX_FALLOCATE +HAVE_POSIX_OPENPT +HAVE_POSIX_SPAWN +HAVE_POSIX_SPAWNP +HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP +HAVE_PPOLL +HAVE_PREAD +HAVE_PREADV +HAVE_PREADV2 +HAVE_PRLIMIT +HAVE_PROCESS_H +HAVE_PROCESS_VM_READV +HAVE_PROTOTYPES +HAVE_PTHREAD_CONDATTR_SETCLOCK +HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP +HAVE_PTHREAD_DESTRUCTOR +HAVE_PTHREAD_GETATTR_NP +HAVE_PTHREAD_GETCPUCLOCKID +HAVE_PTHREAD_GETNAME_NP +HAVE_PTHREAD_GET_NAME_NP +HAVE_PTHREAD_H +HAVE_PTHREAD_INIT +HAVE_PTHREAD_KILL +HAVE_PTHREAD_SETNAME_NP +HAVE_PTHREAD_SET_NAME_NP +HAVE_PTHREAD_SIGMASK +HAVE_PTHREAD_STUBS +HAVE_PTSNAME +HAVE_PTSNAME_R +HAVE_PTY_H +HAVE_PWRITE +HAVE_PWRITEV +HAVE_PWRITEV2 +HAVE_READLINE_READLINE_H +HAVE_READLINK +HAVE_READLINKAT +HAVE_READV +HAVE_REALPATH +HAVE_RECVFROM +HAVE_RENAMEAT +HAVE_RL_APPEND_HISTORY +HAVE_RL_CATCH_SIGNAL +HAVE_RL_CHANGE_ENVIRONMENT +HAVE_RL_COMPDISP_FUNC_T +HAVE_RL_COMPLETION_APPEND_CHARACTER +HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK +HAVE_RL_COMPLETION_MATCHES +HAVE_RL_COMPLETION_SUPPRESS_APPEND +HAVE_RL_PRE_INPUT_HOOK +HAVE_RL_RESIZE_TERMINAL +HAVE_RTPSPAWN +HAVE_SCHED_GET_PRIORITY_MAX +HAVE_SCHED_H +HAVE_SCHED_RR_GET_INTERVAL +HAVE_SCHED_SETAFFINITY +HAVE_SCHED_SETPARAM +HAVE_SCHED_SETSCHEDULER +HAVE_SEM_CLOCKWAIT +HAVE_SEM_GETVALUE +HAVE_SEM_OPEN +HAVE_SEM_TIMEDWAIT +HAVE_SEM_UNLINK +HAVE_SENDFILE +HAVE_SENDTO +HAVE_SETEGID +HAVE_SETEUID +HAVE_SETGID +HAVE_SETGROUPS +HAVE_SETHOSTNAME +HAVE_SETITIMER +HAVE_SETJMP_H +HAVE_SETLOCALE +HAVE_SETNS +HAVE_SETPGID +HAVE_SETPGRP +HAVE_SETPRIORITY +HAVE_SETREGID +HAVE_SETRESGID +HAVE_SETRESUID +HAVE_SETREUID +HAVE_SETSID +HAVE_SETSOCKOPT +HAVE_SETUID +HAVE_SETVBUF +HAVE_SHADOW_H +HAVE_SHM_OPEN +HAVE_SHM_UNLINK +HAVE_SHUTDOWN +HAVE_SIGACTION +HAVE_SIGALTSTACK +HAVE_SIGFILLSET +HAVE_SIGINFO_T_SI_BAND +HAVE_SIGINTERRUPT +HAVE_SIGNAL_H +HAVE_SIGPENDING +HAVE_SIGRELSE +HAVE_SIGTIMEDWAIT +HAVE_SIGWAIT +HAVE_SIGWAITINFO +HAVE_SINPI +HAVE_SNPRINTF +HAVE_SOCKADDR_ALG +HAVE_SOCKADDR_SA_LEN +HAVE_SOCKADDR_STORAGE +HAVE_SOCKET +HAVE_SOCKETPAIR +HAVE_SOCKLEN_T +HAVE_SPAWN_H +HAVE_SPLICE +HAVE_SSIZE_T +HAVE_STATVFS +HAVE_STATX +HAVE_STAT_TV_NSEC +HAVE_STAT_TV_NSEC2 +HAVE_STDINT_H +HAVE_STDIO_H +HAVE_STDLIB_H +HAVE_STD_ATOMIC +HAVE_STRFTIME +HAVE_STRINGS_H +HAVE_STRING_H +HAVE_STRLCPY +HAVE_STROPTS_H +HAVE_STRSIGNAL +HAVE_STRUCT_PASSWD_PW_GECOS +HAVE_STRUCT_PASSWD_PW_PASSWD +HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT +HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MIN +HAVE_STRUCT_STATX_STX_DIO_MEM_ALIGN +HAVE_STRUCT_STATX_STX_DIO_READ_OFFSET_ALIGN +HAVE_STRUCT_STATX_STX_MNT_ID +HAVE_STRUCT_STATX_STX_SUBVOL +HAVE_STRUCT_STAT_ST_BIRTHTIME +HAVE_STRUCT_STAT_ST_BLKSIZE +HAVE_STRUCT_STAT_ST_BLOCKS +HAVE_STRUCT_STAT_ST_FLAGS +HAVE_STRUCT_STAT_ST_GEN +HAVE_STRUCT_STAT_ST_RDEV +HAVE_STRUCT_TM_TM_ZONE +HAVE_SYMLINK +HAVE_SYMLINKAT +HAVE_SYNC +HAVE_SYSCONF +HAVE_SYSCTLBYNAME +HAVE_SYSEXITS_H +HAVE_SYSLOG_H +HAVE_SYSTEM +HAVE_SYS_AUDIOIO_H +HAVE_SYS_AUXV_H +HAVE_SYS_BSDTTY_H +HAVE_SYS_DEVPOLL_H +HAVE_SYS_DIR_H +HAVE_SYS_ENDIAN_H +HAVE_SYS_EPOLL_H +HAVE_SYS_EVENTFD_H +HAVE_SYS_EVENT_H +HAVE_SYS_FILE_H +HAVE_SYS_IOCTL_H +HAVE_SYS_KERN_CONTROL_H +HAVE_SYS_LOADAVG_H +HAVE_SYS_LOCK_H +HAVE_SYS_MEMFD_H +HAVE_SYS_MKDEV_H +HAVE_SYS_MMAN_H +HAVE_SYS_MODEM_H +HAVE_SYS_NDIR_H +HAVE_SYS_PARAM_H +HAVE_SYS_PIDFD_H +HAVE_SYS_POLL_H +HAVE_SYS_RANDOM_H +HAVE_SYS_RESOURCE_H +HAVE_SYS_SELECT_H +HAVE_SYS_SENDFILE_H +HAVE_SYS_SOCKET_H +HAVE_SYS_SOUNDCARD_H +HAVE_SYS_STATVFS_H +HAVE_SYS_STAT_H +HAVE_SYS_SYSCALL_H +HAVE_SYS_SYSCTL_H +HAVE_SYS_SYSMACROS_H +HAVE_SYS_SYS_DOMAIN_H +HAVE_SYS_TERMIO_H +HAVE_SYS_TIMERFD_H +HAVE_SYS_TIMES_H +HAVE_SYS_TIME_H +HAVE_SYS_TYPES_H +HAVE_SYS_UIO_H +HAVE_SYS_UN_H +HAVE_SYS_UTSNAME_H +HAVE_SYS_WAIT_H +HAVE_SYS_XATTR_H +HAVE_TANPI +HAVE_TCGETPGRP +HAVE_TCSETPGRP +HAVE_TEMPNAM +HAVE_TERMIOS_H +HAVE_TERM_H HAVE_THREAD_LOCAL +HAVE_TIMEGM +HAVE_TIMERFD_CREATE +HAVE_TIMES +HAVE_TMPFILE +HAVE_TMPNAM +HAVE_TMPNAM_R +HAVE_TM_ZONE +HAVE_TRUNCATE +HAVE_TTYNAME_R +HAVE_TZNAME +HAVE_UMASK +HAVE_UNAME +HAVE_UNISTD_H +HAVE_UNLINKAT +HAVE_UNLOCKPT +HAVE_UNSHARE +HAVE_USABLE_WCHAR_T +HAVE_UTIL_H +HAVE_UTIMENSAT +HAVE_UTIMES +HAVE_UTIME_H +HAVE_UTMP_H +HAVE_UT_NAMESIZE +HAVE_UUID_CREATE +HAVE_UUID_ENC_BE +HAVE_UUID_GENERATE_TIME_SAFE +HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC +HAVE_UUID_H +HAVE_UUID_UUID_H +HAVE_VFORK +HAVE_WAIT +HAVE_WAIT3 +HAVE_WAIT4 +HAVE_WAITID +HAVE_WAITPID HAVE_WCHAR_H +HAVE_WCSCOLL +HAVE_WCSFTIME +HAVE_WCSXFRM +HAVE_WMEMCMP +HAVE_WORKING_TZSET +HAVE_WRITEV +HAVE_ZDICT_H +HAVE_ZLIB_COPY +HAVE_ZLIB_H +HAVE_ZSTD_H +HAVE__GETPTY +HAVE___UINT128_T IMPORT_FROM IMPORT_NAME INSTRUMENTED_CALL @@ -266,6 +856,8 @@ LOAD_SUPER_ATTR_METHOD LONG_BIT LONG_MAX LONG_MIN +MAJOR_IN_MKDEV +MAJOR_IN_SYSMACROS MAKE_CELL MAKE_FUNCTION MAP_ADD @@ -321,6 +913,12 @@ NCURSES_OPAQUE NOP NOT_TAKEN NOWAIT_LOCK +PACKAGE_BUGREPORT +PACKAGE_NAME +PACKAGE_STRING +PACKAGE_TARNAME +PACKAGE_URL +PACKAGE_VERSION PATH_MAX PDC_NCMOUSE POP_BLOCK @@ -331,9 +929,31 @@ POP_JUMP_IF_NONE POP_JUMP_IF_NOT_NONE POP_JUMP_IF_TRUE POP_TOP +POSIX_SEMAPHORES_NOT_ENABLED PTHREAD_KEYS_MAX +PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT +PTHREAD_SYSTEM_SCHED_SUPPORTED PUSH_EXC_INFO PUSH_NULL +PYLONG_BITS_IN_DIGIT +PYMALLOC_USE_HUGEPAGES +PY_BUILTIN_HASHLIB_HASHES +PY_COERCE_C_LOCALE +PY_HAVE_PERF_TRAMPOLINE +PY_SQLITE_ENABLE_LOAD_EXTENSION +PY_SQLITE_HAVE_SERIALIZE +PY_SSL_DEFAULT_CIPHERS +PY_SSL_DEFAULT_CIPHER_STRING +PY_SUPPORT_TIER +Py_DEBUG +Py_ENABLE_SHARED +Py_GIL_DISABLED +Py_HASH_ALGORITHM +Py_REMOTE_DEBUG +Py_RL_STARTUP_HOOK_TAKES_ARGS +Py_STATS +Py_SUNOS_VERSION +Py_TRACE_REFS RAISE_VARARGS READONLY READ_RESTRICTED @@ -343,6 +963,7 @@ RESTRICTED RESUME RESUME_CHECK RESUME_CHECK_JIT +RETSIGTYPE RETURN_GENERATOR RETURN_VALUE SEND @@ -350,6 +971,7 @@ SEND_ASYNC_GEN SEND_GEN SEND_VIRTUAL SEP +SETPGRP_HAVE_ARG SETUP_ANNOTATIONS SETUP_CLEANUP SETUP_FINALLY @@ -357,21 +979,34 @@ SETUP_WITH SET_ADD SET_FUNCTION_ATTRIBUTE SET_UPDATE +SIGNED_RIGHT_SHIFT_ZERO_FILLS +SIZEOF_DOUBLE +SIZEOF_FLOAT +SIZEOF_FPOS_T +SIZEOF_INT SIZEOF_LONG SIZEOF_LONG_DOUBLE +SIZEOF_LONG_LONG +SIZEOF_OFF_T +SIZEOF_PID_T +SIZEOF_PTHREAD_KEY_T SIZEOF_PTHREAD_T SIZEOF_PY_HASH_T SIZEOF_PY_UHASH_T +SIZEOF_SHORT SIZEOF_SIZE_T SIZEOF_TIME_T SIZEOF_UINTPTR_T SIZEOF_VOID_P +SIZEOF_WCHAR_T SIZEOF__BOOL +SOABI_PLATFORM SPECIALIZATION_FAILURE_KINDS SSTATE_INTERNED_IMMORTAL SSTATE_INTERNED_IMMORTAL_STATIC SSTATE_INTERNED_MORTAL SSTATE_NOT_INTERNED +STDC_HEADERS STORE_ATTR STORE_ATTR_INSTANCE_VALUE STORE_ATTR_SLOT @@ -387,13 +1022,18 @@ STORE_SLICE STORE_SUBSCR STORE_SUBSCR_DICT STORE_SUBSCR_LIST_INT +STRICT_SYSV_CURSES SWAP +SYS_SELECT_WITH_SYS_TIME S_IFLNK S_IFMT S_ISCHR S_ISDIR S_ISLNK S_ISREG +THREAD_STACK_SIZE +TIMEMODULE_LIB +TM_IN_SYS_TIME TO_BOOL TO_BOOL_ALWAYS_TRUE TO_BOOL_BOOL @@ -430,19 +1070,59 @@ UNPACK_SEQUENCE UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TWO_TUPLE +USE_COMPUTED_GOTOS USE_STACKCHECK VA_LIST_IS_ARRAY WAIT_LOCK WINDOW_HAS_FLAGS +WITH_DECIMAL_CONTEXTVAR +WITH_DOC_STRINGS +WITH_DTRACE +WITH_DYLD +WITH_EDITLINE WITH_EXCEPT_START +WITH_LIBINTL +WITH_MIMALLOC +WITH_NEXT_FRAMEWORK +WITH_PYMALLOC WITH_THREAD +WITH_VALGRIND WORDS_BIGENDIAN +X87_DOUBLE_ROUNDING YIELD_VALUE _Atomic _BOOL +_BSD_SOURCE +_DARWIN_C_SOURCE +_FILE_OFFSET_BITS +_INCLUDE__STDC_A1_SOURCE +_ISOC23_SOURCE +_LARGEFILE_SOURCE +_LARGE_FILES +_NETBSD_SOURCE +_POSIX_C_SOURCE _POSIX_THREADS +_PYTHONFRAMEWORK +_PYTHREAD_NAME_MAXLEN +_Py_FFI_SUPPORT_C_COMPLEX +_Py_HACL_CAN_COMPILE_VEC128 +_Py_HACL_CAN_COMPILE_VEC256 +_Py_HAVE_EFFICIENT_BUILTIN_SHUFFLEVECTOR +_Py_HAVE_LIBGCC_EH_FRAME_REGISTRATION +_Py_HAVE_PR_SET_VMA_ANON_NAME +_Py_LINKER_THREAD_STACK_SIZE +_Py_STACK_GROWS_DOWN +_Py_TAIL_CALL_INTERP +_Py_WITH_FRAME_POINTERS +_REENTRANT _SGI_MP_SOURCE +_WASI_EMULATED_GETPID +_WASI_EMULATED_PROCESS_CLOCKS +_WASI_EMULATED_SIGNAL _WCHAR_T +_XOPEN_SOURCE +_XOPEN_SOURCE_EXTENDED +__BSD_VISIBLE __DYNAMIC_ANNOTATIONS_H__ __NEED_pthread_attr_t __NEED_pthread_cond_t @@ -453,5 +1133,15 @@ __NEED_pthread_mutexattr_t __NEED_pthread_t __wchar_t __wint_t +clock_t +const +gid_t import_curses +mode_t +off_t +pid_t +signed +size_t +socklen_t static_assert +uid_t From 998dd34dbd39ceb163edeec7a4dfb9a515b9ab80 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 02:10:39 +0200 Subject: [PATCH 07/17] Update Tools/build/check_capi_macros.py Co-authored-by: Peter Bierma --- Tools/build/check_capi_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index dd4a8c933dc93ec..d12509d1b75a9ab 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -88,7 +88,7 @@ def main(): names.sort() if not names: - print("OK: the Python C API only defines macros with name " + print("OK: the Python C API only defines macros with names " f"starting with Py (ignoring {len(ignored)} macros)") sys.exit(0) From fa62aacc1d448846b0de8b5dc9e73cf82ceb8a01 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 02:11:00 +0200 Subject: [PATCH 08/17] Update .github/workflows/build.yml Co-authored-by: Peter Bierma --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 720d2911f712c8a..7598a2d65c242e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -140,7 +140,7 @@ jobs: - name: Check for unsupported C global variables if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME run: make check-c-globals - - name: Check C ABI macros + - name: Check C API macros run: make check-capi-macros check-c-api-docs: From 6188980c5a6668e78ccc3ca95d0e5d92270992df Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 14:23:51 +0200 Subject: [PATCH 09/17] Cleanup regexs --- Tools/build/check_capi_macros.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index d12509d1b75a9ab..03d37b34f3ab875 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -18,10 +18,10 @@ TOOLS_BUILD_DIR = os.path.abspath(os.path.dirname(__file__)) SRC_DIR = os.path.dirname(os.path.dirname(TOOLS_BUILD_DIR)) -DEFINE_REGEX = re.compile(r'^\s*# *define\s+(.*)') -PYTHON_PREFIX = re.compile(r'^(Py|PY|_Py|_PY)') -DEFINE_NAME_REGEX = re.compile(r'^([A-Za-z_][A-Za-z0-9_]*)\b') -UNDEF_REGEX = re.compile(r'#undef (.*)$') +DEFINE_REGEX = re.compile(r'\s*# *define\s+(.*)') +PYTHON_PREFIX = re.compile(r'(Py|PY|_Py|_PY)') +DEFINE_NAME_REGEX = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\b') +UNDEF_REGEX = re.compile(r'#undef (.*)') def parse_file(filename, names, ignored): @@ -35,7 +35,7 @@ def parse_file(filename, names, ignored): if PYTHON_PREFIX.match(macro): continue - match = DEFINE_NAME_REGEX.search(macro) + match = DEFINE_NAME_REGEX.match(macro) if not match: print(f"ERROR: {filename}: Unable to parse {line!r}") sys.exit(1) @@ -49,7 +49,7 @@ def parse_file(filename, names, ignored): def parse_pyconfig_in(filename, names, ignored): with open(filename, encoding='utf8') as fp: for line in fp: - match = UNDEF_REGEX.match(line) + match = UNDEF_REGEX.fullmatch(line) if not match: continue name = match.group(1) From 8c09a6c01ec22d40b80692f23128e717a8467c92 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 14:24:03 +0200 Subject: [PATCH 10/17] Move the check fro Makefile to pre-commit --- .github/workflows/build.yml | 2 -- .pre-commit-config.yaml | 9 +++++++++ Makefile.pre.in | 5 ----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7598a2d65c242e0..7fdc81ae0ade1d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -140,8 +140,6 @@ jobs: - name: Check for unsupported C global variables if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME run: make check-c-globals - - name: Check C API macros - run: make check-capi-macros check-c-api-docs: name: C API Docs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 615ba587c6a4005..45528d9f32d6c94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -143,6 +143,15 @@ repos: entry: Space found in path, move to Misc/NEWS.d/next/Core_and_Builtins/ files: Misc/NEWS.d/next/Core and Builtins/20.*.rst + - repo: local + hooks: + - id: check-capi-macros + name: Check C API macros start with Py + language: python + entry: python Tools/build/check_capi_macros.py + pass_filenames: false + files: ^(Include/(cpython/)?[^/]+\.h|pyconfig\.h\.in|Tools/build/check_capi_macros) + - repo: meta hooks: - id: check-hooks-apply diff --git a/Makefile.pre.in b/Makefile.pre.in index 19cc9ef2a9fa4e2..67e00f102403d56 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -3367,11 +3367,6 @@ distclean: clobber docclean smelly: all $(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/smelly.py -# Check that macros defined by the Python C API start with "Py" -.PHONY: check-capi-macros -check-capi-macros: all - $(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/check_capi_macros.py - # Check if any unsupported C global variables have been added. .PHONY: check-c-globals check-c-globals: From 7c8c77ddb56d9374b93d7f0515a9ceb689738a17 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 14:34:05 +0200 Subject: [PATCH 11/17] Check also if the ignored list is outdated Fix UNDEF_REGEX macro: replace fullmatch() with match() --- Tools/build/check_capi_macros.py | 62 ++++++++++++++--------- Tools/build/check_capi_macros_ignored.txt | 1 - 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index 03d37b34f3ab875..6023b397d7da4e7 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -17,6 +17,7 @@ TOOLS_BUILD_DIR = os.path.abspath(os.path.dirname(__file__)) SRC_DIR = os.path.dirname(os.path.dirname(TOOLS_BUILD_DIR)) +IGNORED_FILENAME = os.path.join(TOOLS_BUILD_DIR, 'check_capi_macros_ignored.txt') DEFINE_REGEX = re.compile(r'\s*# *define\s+(.*)') PYTHON_PREFIX = re.compile(r'(Py|PY|_Py|_PY)') @@ -24,7 +25,7 @@ UNDEF_REGEX = re.compile(r'#undef (.*)') -def parse_file(filename, names, ignored): +def parse_file(filename, names): with open(filename, encoding='utf8') as fp: for line in fp: match = DEFINE_REGEX.match(line) @@ -40,28 +41,22 @@ def parse_file(filename, names, ignored): print(f"ERROR: {filename}: Unable to parse {line!r}") sys.exit(1) name = match.group(1) - if name in ignored: - continue - names.append((name, filename)) -def parse_pyconfig_in(filename, names, ignored): +def parse_pyconfig_in(filename, names): with open(filename, encoding='utf8') as fp: for line in fp: - match = UNDEF_REGEX.fullmatch(line) + match = UNDEF_REGEX.match(line) if not match: continue name = match.group(1) - if name in ignored: - continue names.append((name, filename)) def get_ignored_names(): - filename = os.path.join(TOOLS_BUILD_DIR, 'check_capi_macros_ignored.txt') ignored = set() - with open(filename, encoding='utf8') as fp: + with open(IGNORED_FILENAME, encoding='utf8') as fp: for line in fp: name = line.strip() if name.startswith('#'): @@ -73,32 +68,53 @@ def get_ignored_names(): def main(): - ignored = get_ignored_names() + failure = False include_dir = os.path.join(SRC_DIR, 'Include') files = glob.glob(os.path.join(include_dir, '*.h')) files.extend(glob.glob(os.path.join(include_dir, 'cpython', '*.h'))) - names = [] + names = [] # list of (name: str, filename: str) for filename in files: - parse_file(filename, names, ignored) + parse_file(filename, names) filename = os.path.join(SRC_DIR, 'pyconfig.h.in') - parse_file(filename, names, ignored) - parse_pyconfig_in(filename, names, ignored) + parse_file(filename, names) + parse_pyconfig_in(filename, names) names.sort() - if not names: + names_set = {name for name, filename in names} + ignored = get_ignored_names() + outdated = ignored - names_set + if outdated: + print(f"ERROR: {IGNORED_FILENAME} is outdated, " + "the macros can be removed:") + print() + for name in sorted(outdated): + print(f" - {name}") + print() + print(f"Total: {len(outdated)} macros") + print() + failure = True + + new_macros = names_set - ignored + if new_macros: + print('ERROR: the Python C API defines the following macros ' + 'with a name not starting with "Py":') + print() + count = 0 + for name, filename in names: + if name in ignored: + continue + print(f"- {name} defined by {filename}") + count += 1 + print() + print(f"Total: {count} macros") + + if not failure: print("OK: the Python C API only defines macros with names " f"starting with Py (ignoring {len(ignored)} macros)") sys.exit(0) - print('ERROR: the Python C API defines the following macros ' - 'with a name not starting with "Py":') - print() - for name, filename in names: - print(f"- {name} defined by {filename}") - print() - print(f"Total: {len(names)} macros") sys.exit(1) diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index 4fab3d8b45ab941..e0bba6fd025d952 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -173,7 +173,6 @@ FUTURE_NESTED_SCOPES FUTURE_PRINT_FUNCTION FUTURE_UNICODE_LITERALS FUTURE_WITH_STATEMENT -FVC_ALT_REPR FVC_ASCII FVC_MASK FVC_NONE From 80a2e2f6009e7622c3f091a98b83d0e8fbce5f93 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 14:38:53 +0200 Subject: [PATCH 12/17] Parse also #undef in Include/ headers --- Tools/build/check_capi_macros.py | 70 +++++++++++------------ Tools/build/check_capi_macros_ignored.txt | 32 +---------- 2 files changed, 34 insertions(+), 68 deletions(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index 6023b397d7da4e7..3230904f4bcca9c 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -21,37 +21,34 @@ DEFINE_REGEX = re.compile(r'\s*# *define\s+(.*)') PYTHON_PREFIX = re.compile(r'(Py|PY|_Py|_PY)') -DEFINE_NAME_REGEX = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\b') +NAME_REGEX = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\b') UNDEF_REGEX = re.compile(r'#undef (.*)') def parse_file(filename, names): with open(filename, encoding='utf8') as fp: for line in fp: + # Check for '#define MACRO' match = DEFINE_REGEX.match(line) - if not match: - continue + if match: + undef = False + else: + # Check for '#undef MACRO' + match = UNDEF_REGEX.match(line) + if not match: + continue + undef = True macro = match.group(1) if PYTHON_PREFIX.match(macro): continue - match = DEFINE_NAME_REGEX.match(macro) + match = NAME_REGEX.match(macro) if not match: print(f"ERROR: {filename}: Unable to parse {line!r}") sys.exit(1) name = match.group(1) - names.append((name, filename)) - - -def parse_pyconfig_in(filename, names): - with open(filename, encoding='utf8') as fp: - for line in fp: - match = UNDEF_REGEX.match(line) - if not match: - continue - name = match.group(1) - names.append((name, filename)) + names.append((name, filename, undef)) def get_ignored_names(): @@ -73,46 +70,45 @@ def main(): include_dir = os.path.join(SRC_DIR, 'Include') files = glob.glob(os.path.join(include_dir, '*.h')) files.extend(glob.glob(os.path.join(include_dir, 'cpython', '*.h'))) - names = [] # list of (name: str, filename: str) + files.append(os.path.join(SRC_DIR, 'pyconfig.h.in')) + names = [] # list of (name: str, filename: str, undef: bool) for filename in files: parse_file(filename, names) - filename = os.path.join(SRC_DIR, 'pyconfig.h.in') - parse_file(filename, names) - parse_pyconfig_in(filename, names) - names.sort() - - names_set = {name for name, filename in names} + names_set = {name for name, filename, undef in names} ignored = get_ignored_names() - outdated = ignored - names_set - if outdated: - print(f"ERROR: {IGNORED_FILENAME} is outdated, " - "the macros can be removed:") - print() - for name in sorted(outdated): - print(f" - {name}") - print() - print(f"Total: {len(outdated)} macros") - print() - failure = True new_macros = names_set - ignored if new_macros: - print('ERROR: the Python C API defines the following macros ' - 'with a name not starting with "Py":') + print('ERROR: the Python C API defines the following new macros:') print() count = 0 - for name, filename in names: + for name, filename, undef in sorted(names): if name in ignored: continue - print(f"- {name} defined by {filename}") + define = "undefined" if undef else "defined" + print(f"- {name} {define} by {filename}") count += 1 print() print(f"Total: {count} macros") + failure = True + + outdated = ignored - names_set + if outdated: + print(f"ERROR: {IGNORED_FILENAME} is outdated, " + "the following macros can be removed:") + print() + for name in sorted(outdated): + print(f" - {name}") + print() + print(f"Total: {len(outdated)} macros") + print() + failure = True if not failure: print("OK: the Python C API only defines macros with names " f"starting with Py (ignoring {len(ignored)} macros)") + print("OK: the ignore list is up to date") sys.exit(0) sys.exit(1) diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index e0bba6fd025d952..2f2c5548ad70c86 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -934,25 +934,6 @@ PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT PTHREAD_SYSTEM_SCHED_SUPPORTED PUSH_EXC_INFO PUSH_NULL -PYLONG_BITS_IN_DIGIT -PYMALLOC_USE_HUGEPAGES -PY_BUILTIN_HASHLIB_HASHES -PY_COERCE_C_LOCALE -PY_HAVE_PERF_TRAMPOLINE -PY_SQLITE_ENABLE_LOAD_EXTENSION -PY_SQLITE_HAVE_SERIALIZE -PY_SSL_DEFAULT_CIPHERS -PY_SSL_DEFAULT_CIPHER_STRING -PY_SUPPORT_TIER -Py_DEBUG -Py_ENABLE_SHARED -Py_GIL_DISABLED -Py_HASH_ALGORITHM -Py_REMOTE_DEBUG -Py_RL_STARTUP_HOOK_TAKES_ARGS -Py_STATS -Py_SUNOS_VERSION -Py_TRACE_REFS RAISE_VARARGS READONLY READ_RESTRICTED @@ -1101,18 +1082,6 @@ _LARGE_FILES _NETBSD_SOURCE _POSIX_C_SOURCE _POSIX_THREADS -_PYTHONFRAMEWORK -_PYTHREAD_NAME_MAXLEN -_Py_FFI_SUPPORT_C_COMPLEX -_Py_HACL_CAN_COMPILE_VEC128 -_Py_HACL_CAN_COMPILE_VEC256 -_Py_HAVE_EFFICIENT_BUILTIN_SHUFFLEVECTOR -_Py_HAVE_LIBGCC_EH_FRAME_REGISTRATION -_Py_HAVE_PR_SET_VMA_ANON_NAME -_Py_LINKER_THREAD_STACK_SIZE -_Py_STACK_GROWS_DOWN -_Py_TAIL_CALL_INTERP -_Py_WITH_FRAME_POINTERS _REENTRANT _SGI_MP_SOURCE _WASI_EMULATED_GETPID @@ -1130,6 +1099,7 @@ __NEED_pthread_key_t __NEED_pthread_mutex_t __NEED_pthread_mutexattr_t __NEED_pthread_t +__bool__ __wchar_t __wint_t clock_t From d2347d953d8d5b721150e7b775fad5df087201a4 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 18 Sep 2026 14:51:19 +0200 Subject: [PATCH 13/17] Check that the ignore list is sorted --- Tools/build/check_capi_macros.py | 50 ++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index 3230904f4bcca9c..a6fdd7bef90fd1b 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -10,6 +10,7 @@ * pyconfig.h.in """ +import difflib import glob import os.path import re @@ -52,7 +53,7 @@ def parse_file(filename, names): def get_ignored_names(): - ignored = set() + ignored = [] with open(IGNORED_FILENAME, encoding='utf8') as fp: for line in fp: name = line.strip() @@ -60,13 +61,14 @@ def get_ignored_names(): # Ignore comment continue if name: - ignored.add(name) + ignored.append(name) return ignored def main(): failure = False + # Parse header files include_dir = os.path.join(SRC_DIR, 'Include') files = glob.glob(os.path.join(include_dir, '*.h')) files.extend(glob.glob(os.path.join(include_dir, 'cpython', '*.h'))) @@ -75,24 +77,26 @@ def main(): for filename in files: parse_file(filename, names) - names_set = {name for name, filename, undef in names} + # Parse ignore list ignored = get_ignored_names() - new_macros = names_set - ignored - if new_macros: - print('ERROR: the Python C API defines the following new macros:') + # Check if the sorted list is sorted + ignored_sorted = sorted(ignored) + if ignored_sorted != ignored: + print(f"ERROR: {IGNORED_FILENAME} list is not sorted") print() - count = 0 - for name, filename, undef in sorted(names): - if name in ignored: - continue - define = "undefined" if undef else "defined" - print(f"- {name} {define} by {filename}") - count += 1 + diff = difflib.unified_diff(ignored, ignored_sorted, + fromfile=IGNORED_FILENAME, + tofile=IGNORED_FILENAME, + lineterm='') + for line in diff: + print(line) print() - print(f"Total: {count} macros") failure = True + # Check for outdated ignore list + names_set = {name for name, filename, undef in names} + ignored = set(ignored) outdated = ignored - names_set if outdated: print(f"ERROR: {IGNORED_FILENAME} is outdated, " @@ -105,10 +109,26 @@ def main(): print() failure = True + # Check for new macros + new_macros = names_set - ignored + if new_macros: + print('ERROR: the Python C API defines the following new macros:') + print() + count = 0 + for name, filename, undef in sorted(names): + if name in ignored: + continue + define = "undefined" if undef else "defined" + print(f"- {name} {define} by {filename}") + count += 1 + print() + print(f"Total: {count} macros") + failure = True + if not failure: + print("OK: the ignore list is up to date and sorted") print("OK: the Python C API only defines macros with names " f"starting with Py (ignoring {len(ignored)} macros)") - print("OK: the ignore list is up to date") sys.exit(0) sys.exit(1) From a63e01867891db2e06454afab6e63ff6064fef3b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 19 Sep 2026 19:27:17 +0200 Subject: [PATCH 14/17] Update Tools/build/check_capi_macros.py Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tools/build/check_capi_macros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index a6fdd7bef90fd1b..3ae4aafda942a98 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -103,7 +103,7 @@ def main(): "the following macros can be removed:") print() for name in sorted(outdated): - print(f" - {name}") + print(f"- {name}") print() print(f"Total: {len(outdated)} macros") print() From 86514dc947799f70c5386d41e40db85af0a436ad Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 19 Sep 2026 19:37:49 +0200 Subject: [PATCH 15/17] Accept spaces in more places in regex --- Tools/build/check_capi_macros.py | 4 ++-- Tools/build/check_capi_macros_ignored.txt | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index 3ae4aafda942a98..5a23e1e829f8fda 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -20,10 +20,10 @@ SRC_DIR = os.path.dirname(os.path.dirname(TOOLS_BUILD_DIR)) IGNORED_FILENAME = os.path.join(TOOLS_BUILD_DIR, 'check_capi_macros_ignored.txt') -DEFINE_REGEX = re.compile(r'\s*# *define\s+(.*)') +DEFINE_REGEX = re.compile(r'\s*#\s*define\s+(.*)') PYTHON_PREFIX = re.compile(r'(Py|PY|_Py|_PY)') NAME_REGEX = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\b') -UNDEF_REGEX = re.compile(r'#undef (.*)') +UNDEF_REGEX = re.compile(r'\s*#\s*undef\s+(.*)') def parse_file(filename, names): diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index 2f2c5548ad70c86..ea015ef329220ed 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -75,6 +75,7 @@ CALL_TYPE_1 CHECK_EG_MATCH CHECK_EXC_MATCH CLEANUP_THROW +CODESET COMPARE_OP COMPARE_OP_FLOAT COMPARE_OP_INT @@ -1070,20 +1071,29 @@ WITH_VALGRIND WORDS_BIGENDIAN X87_DOUBLE_ROUNDING YIELD_VALUE +_ALL_SOURCE _Atomic _BOOL _BSD_SOURCE _DARWIN_C_SOURCE _FILE_OFFSET_BITS +_GNU_SOURCE +_HPUX_ALT_XOPEN_SOCKET_API _INCLUDE__STDC_A1_SOURCE _ISOC23_SOURCE _LARGEFILE_SOURCE _LARGE_FILES +_MINIX _NETBSD_SOURCE +_OPENBSD_SOURCE +_POSIX_1_SOURCE _POSIX_C_SOURCE +_POSIX_PTHREAD_SEMANTICS +_POSIX_SOURCE _POSIX_THREADS _REENTRANT _SGI_MP_SOURCE +_TANDEM_SOURCE _WASI_EMULATED_GETPID _WASI_EMULATED_PROCESS_CLOCKS _WASI_EMULATED_SIGNAL @@ -1092,6 +1102,7 @@ _XOPEN_SOURCE _XOPEN_SOURCE_EXTENDED __BSD_VISIBLE __DYNAMIC_ANNOTATIONS_H__ +__EXTENSIONS__ __NEED_pthread_attr_t __NEED_pthread_cond_t __NEED_pthread_condattr_t @@ -1099,6 +1110,14 @@ __NEED_pthread_key_t __NEED_pthread_mutex_t __NEED_pthread_mutexattr_t __NEED_pthread_t +__STDC_WANT_IEC_60559_ATTRIBS_EXT__ +__STDC_WANT_IEC_60559_BFP_EXT__ +__STDC_WANT_IEC_60559_DFP_EXT__ +__STDC_WANT_IEC_60559_EXT__ +__STDC_WANT_IEC_60559_FUNCS_EXT__ +__STDC_WANT_IEC_60559_TYPES_EXT__ +__STDC_WANT_LIB_EXT2__ +__STDC_WANT_MATH_SPEC_FUNCS__ __bool__ __wchar_t __wint_t From dec9c888af45d003fbf52507c977eef0fb1ceba8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 19 Sep 2026 19:41:26 +0200 Subject: [PATCH 16/17] Check for duplicated entires in the ignore list --- Tools/build/check_capi_macros.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tools/build/check_capi_macros.py b/Tools/build/check_capi_macros.py index 5a23e1e829f8fda..a937cfb02317809 100644 --- a/Tools/build/check_capi_macros.py +++ b/Tools/build/check_capi_macros.py @@ -80,6 +80,19 @@ def main(): # Parse ignore list ignored = get_ignored_names() + # Check if the sorted list has duplicated entries + if len(set(ignored)) != len(ignored): + print(f"ERROR: {IGNORED_FILENAME} list contains duplicated entries:") + print() + seen = set() + for name in ignored: + if name not in seen: + seen.add(name) + continue + print(f"- {name}") + print() + failure = True + # Check if the sorted list is sorted ignored_sorted = sorted(ignored) if ignored_sorted != ignored: From d7a9559a788e1e933545fbe42fc2b520ee2ac3f0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 19 Sep 2026 21:05:16 +0200 Subject: [PATCH 17/17] Remove HAVE_FLOAT16 macro from ignore list --- Tools/build/check_capi_macros_ignored.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index ea015ef329220ed..fdd533ed653dd2a 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -337,7 +337,6 @@ HAVE_FEXECVE HAVE_FFI_CLOSURE_ALLOC HAVE_FFI_PREP_CIF_VAR HAVE_FFI_PREP_CLOSURE_LOC -HAVE_FLOAT16 HAVE_FLOCK HAVE_FORK HAVE_FORK1