[Python-checkins] bpo-42545: Check that all symbols in the limited ABI are exported (GH-23616)

pablogsal webhook-mailer at python.org
Fri Dec 4 17:06:08 EST 2020


https://github.com/python/cpython/commit/85f1dedb8d05774e0d3739be0a11cd970b98097f
commit: 85f1dedb8d05774e0d3739be0a11cd970b98097f
branch: master
author: Pablo Galindo <Pablogsal at gmail.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2020-12-04T22:05:58Z
summary:

bpo-42545: Check that all symbols in the limited ABI are exported (GH-23616)

files:
A Doc/data/stable_abi.dat
A Tools/scripts/stable_abi.py
M .github/workflows/build.yml
M .travis.yml
M Makefile.pre.in

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 12c591e41362e..71c307b6c62a3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -75,6 +75,8 @@ jobs:
           fi
       - name: Check exported libpython symbols
         run: make smelly
+      - name: Check limited ABI symbols
+        run: make check-limited-abi
 
   build_win32:
     name: 'Windows (x86)'
diff --git a/.travis.yml b/.travis.yml
index dfdf670bff5f9..547d919974957 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -192,6 +192,8 @@ script:
   - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./python Tools/scripts/patchcheck.py --travis $TRAVIS_PULL_REQUEST; fi
   # Check that all symbols exported by libpython start with "Py" or "_Py"
   - make smelly
+  # Check that all symbols in the limited abi are present
+  - make check-limited-abi
   # `-r -w` implicitly provided through `make buildbottest`.
   - |
     if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
new file mode 100644
index 0000000000000..28cb50b12301b
--- /dev/null
+++ b/Doc/data/stable_abi.dat
@@ -0,0 +1,779 @@
+# File generated by 'make regen-limited-abi'
+# This is NOT an authoritative list of stable ABI symbols
+PyArg_Parse
+PyArg_ParseTuple
+PyArg_ParseTupleAndKeywords
+PyArg_UnpackTuple
+PyArg_VaParse
+PyArg_VaParseTupleAndKeywords
+PyArg_ValidateKeywordArguments
+PyBaseObject_Type
+PyBool_FromLong
+PyBool_Type
+PyByteArrayIter_Type
+PyByteArray_AsString
+PyByteArray_Concat
+PyByteArray_FromObject
+PyByteArray_FromStringAndSize
+PyByteArray_Resize
+PyByteArray_Size
+PyByteArray_Type
+PyBytesIter_Type
+PyBytes_AsString
+PyBytes_AsStringAndSize
+PyBytes_Concat
+PyBytes_ConcatAndDel
+PyBytes_DecodeEscape
+PyBytes_FromFormat
+PyBytes_FromFormatV
+PyBytes_FromObject
+PyBytes_FromString
+PyBytes_FromStringAndSize
+PyBytes_Repr
+PyBytes_Size
+PyBytes_Type
+PyCFunction_Call
+PyCFunction_GetFlags
+PyCFunction_GetFunction
+PyCFunction_GetSelf
+PyCFunction_NewEx
+PyCFunction_Type
+PyCMethod_New
+PyCallIter_New
+PyCallIter_Type
+PyCallable_Check
+PyCapsule_GetContext
+PyCapsule_GetDestructor
+PyCapsule_GetName
+PyCapsule_GetPointer
+PyCapsule_Import
+PyCapsule_IsValid
+PyCapsule_New
+PyCapsule_SetContext
+PyCapsule_SetDestructor
+PyCapsule_SetName
+PyCapsule_SetPointer
+PyCapsule_Type
+PyClassMethodDescr_Type
+PyCodec_BackslashReplaceErrors
+PyCodec_Decode
+PyCodec_Decoder
+PyCodec_Encode
+PyCodec_Encoder
+PyCodec_IgnoreErrors
+PyCodec_IncrementalDecoder
+PyCodec_IncrementalEncoder
+PyCodec_KnownEncoding
+PyCodec_LookupError
+PyCodec_NameReplaceErrors
+PyCodec_Register
+PyCodec_RegisterError
+PyCodec_ReplaceErrors
+PyCodec_StreamReader
+PyCodec_StreamWriter
+PyCodec_StrictErrors
+PyCodec_Unregister
+PyCodec_XMLCharRefReplaceErrors
+PyComplex_FromDoubles
+PyComplex_ImagAsDouble
+PyComplex_RealAsDouble
+PyComplex_Type
+PyDescr_NewClassMethod
+PyDescr_NewGetSet
+PyDescr_NewMember
+PyDescr_NewMethod
+PyDictItems_Type
+PyDictIterItem_Type
+PyDictIterKey_Type
+PyDictIterValue_Type
+PyDictKeys_Type
+PyDictProxy_New
+PyDictProxy_Type
+PyDictRevIterItem_Type
+PyDictRevIterKey_Type
+PyDictRevIterValue_Type
+PyDictValues_Type
+PyDict_Clear
+PyDict_Contains
+PyDict_Copy
+PyDict_DelItem
+PyDict_DelItemString
+PyDict_GetItem
+PyDict_GetItemString
+PyDict_GetItemWithError
+PyDict_Items
+PyDict_Keys
+PyDict_Merge
+PyDict_MergeFromSeq2
+PyDict_New
+PyDict_Next
+PyDict_SetItem
+PyDict_SetItemString
+PyDict_Size
+PyDict_Type
+PyDict_Update
+PyDict_Values
+PyEllipsis_Type
+PyEnum_Type
+PyErr_BadArgument
+PyErr_BadInternalCall
+PyErr_CheckSignals
+PyErr_Clear
+PyErr_Display
+PyErr_ExceptionMatches
+PyErr_Fetch
+PyErr_Format
+PyErr_FormatV
+PyErr_GetExcInfo
+PyErr_GivenExceptionMatches
+PyErr_NewException
+PyErr_NewExceptionWithDoc
+PyErr_NoMemory
+PyErr_NormalizeException
+PyErr_Occurred
+PyErr_Print
+PyErr_PrintEx
+PyErr_ProgramText
+PyErr_ResourceWarning
+PyErr_Restore
+PyErr_SetExcInfo
+PyErr_SetFromErrno
+PyErr_SetFromErrnoWithFilename
+PyErr_SetFromErrnoWithFilenameObject
+PyErr_SetFromErrnoWithFilenameObjects
+PyErr_SetImportError
+PyErr_SetImportErrorSubclass
+PyErr_SetInterrupt
+PyErr_SetNone
+PyErr_SetObject
+PyErr_SetString
+PyErr_SyntaxLocation
+PyErr_SyntaxLocationEx
+PyErr_WarnEx
+PyErr_WarnExplicit
+PyErr_WarnFormat
+PyErr_WriteUnraisable
+PyEval_AcquireLock
+PyEval_AcquireThread
+PyEval_CallFunction
+PyEval_CallMethod
+PyEval_CallObjectWithKeywords
+PyEval_EvalCode
+PyEval_EvalCodeEx
+PyEval_EvalFrame
+PyEval_EvalFrameEx
+PyEval_GetBuiltins
+PyEval_GetFrame
+PyEval_GetFuncDesc
+PyEval_GetFuncName
+PyEval_GetGlobals
+PyEval_GetLocals
+PyEval_InitThreads
+PyEval_ReleaseLock
+PyEval_ReleaseThread
+PyEval_RestoreThread
+PyEval_SaveThread
+PyEval_ThreadsInitialized
+PyExc_ArithmeticError
+PyExc_AssertionError
+PyExc_AttributeError
+PyExc_BaseException
+PyExc_BlockingIOError
+PyExc_BrokenPipeError
+PyExc_BufferError
+PyExc_BytesWarning
+PyExc_ChildProcessError
+PyExc_ConnectionAbortedError
+PyExc_ConnectionError
+PyExc_ConnectionRefusedError
+PyExc_ConnectionResetError
+PyExc_DeprecationWarning
+PyExc_EOFError
+PyExc_EnvironmentError
+PyExc_Exception
+PyExc_FileExistsError
+PyExc_FileNotFoundError
+PyExc_FloatingPointError
+PyExc_FutureWarning
+PyExc_GeneratorExit
+PyExc_IOError
+PyExc_ImportError
+PyExc_ImportWarning
+PyExc_IndentationError
+PyExc_IndexError
+PyExc_InterruptedError
+PyExc_IsADirectoryError
+PyExc_KeyError
+PyExc_KeyboardInterrupt
+PyExc_LookupError
+PyExc_MemoryError
+PyExc_ModuleNotFoundError
+PyExc_NameError
+PyExc_NotADirectoryError
+PyExc_NotImplementedError
+PyExc_OSError
+PyExc_OverflowError
+PyExc_PendingDeprecationWarning
+PyExc_PermissionError
+PyExc_ProcessLookupError
+PyExc_RecursionError
+PyExc_ReferenceError
+PyExc_ResourceWarning
+PyExc_RuntimeError
+PyExc_RuntimeWarning
+PyExc_StopAsyncIteration
+PyExc_StopIteration
+PyExc_SyntaxError
+PyExc_SyntaxWarning
+PyExc_SystemError
+PyExc_SystemExit
+PyExc_TabError
+PyExc_TimeoutError
+PyExc_TypeError
+PyExc_UnboundLocalError
+PyExc_UnicodeDecodeError
+PyExc_UnicodeEncodeError
+PyExc_UnicodeError
+PyExc_UnicodeTranslateError
+PyExc_UnicodeWarning
+PyExc_UserWarning
+PyExc_ValueError
+PyExc_Warning
+PyExc_ZeroDivisionError
+PyExceptionClass_Name
+PyException_GetCause
+PyException_GetContext
+PyException_GetTraceback
+PyException_SetCause
+PyException_SetContext
+PyException_SetTraceback
+PyFile_FromFd
+PyFile_GetLine
+PyFile_WriteObject
+PyFile_WriteString
+PyFilter_Type
+PyFloat_AsDouble
+PyFloat_FromDouble
+PyFloat_FromString
+PyFloat_GetInfo
+PyFloat_GetMax
+PyFloat_GetMin
+PyFloat_Type
+PyFrame_GetCode
+PyFrame_GetLineNumber
+PyFrozenSet_New
+PyFrozenSet_Type
+PyGC_Collect
+PyGILState_Ensure
+PyGILState_GetThisThreadState
+PyGILState_Release
+PyGetSetDescr_Type
+PyImport_AddModule
+PyImport_AddModuleObject
+PyImport_AppendInittab
+PyImport_ExecCodeModule
+PyImport_ExecCodeModuleEx
+PyImport_ExecCodeModuleObject
+PyImport_ExecCodeModuleWithPathnames
+PyImport_GetImporter
+PyImport_GetMagicNumber
+PyImport_GetMagicTag
+PyImport_GetModule
+PyImport_GetModuleDict
+PyImport_Import
+PyImport_ImportFrozenModule
+PyImport_ImportFrozenModuleObject
+PyImport_ImportModule
+PyImport_ImportModuleLevel
+PyImport_ImportModuleLevelObject
+PyImport_ImportModuleNoBlock
+PyImport_ReloadModule
+PyIndex_Check
+PyInterpreterState_Clear
+PyInterpreterState_Delete
+PyInterpreterState_Get
+PyInterpreterState_GetDict
+PyInterpreterState_GetID
+PyInterpreterState_New
+PyIter_Check
+PyIter_Next
+PyIter_Send
+PyListIter_Type
+PyListRevIter_Type
+PyList_Append
+PyList_AsTuple
+PyList_GetItem
+PyList_GetSlice
+PyList_Insert
+PyList_New
+PyList_Reverse
+PyList_SetItem
+PyList_SetSlice
+PyList_Size
+PyList_Sort
+PyList_Type
+PyLongRangeIter_Type
+PyLong_AsDouble
+PyLong_AsLong
+PyLong_AsLongAndOverflow
+PyLong_AsLongLong
+PyLong_AsLongLongAndOverflow
+PyLong_AsSize_t
+PyLong_AsSsize_t
+PyLong_AsUnsignedLong
+PyLong_AsUnsignedLongLong
+PyLong_AsUnsignedLongLongMask
+PyLong_AsUnsignedLongMask
+PyLong_AsVoidPtr
+PyLong_FromDouble
+PyLong_FromLong
+PyLong_FromLongLong
+PyLong_FromSize_t
+PyLong_FromSsize_t
+PyLong_FromString
+PyLong_FromUnsignedLong
+PyLong_FromUnsignedLongLong
+PyLong_FromVoidPtr
+PyLong_GetInfo
+PyLong_Type
+PyMap_Type
+PyMapping_Check
+PyMapping_GetItemString
+PyMapping_HasKey
+PyMapping_HasKeyString
+PyMapping_Items
+PyMapping_Keys
+PyMapping_Length
+PyMapping_SetItemString
+PyMapping_Size
+PyMapping_Values
+PyMarshal_ReadObjectFromString
+PyMarshal_WriteLongToFile
+PyMarshal_WriteObjectToFile
+PyMarshal_WriteObjectToString
+PyMem_Free
+PyMem_Malloc
+PyMem_Realloc
+PyMemberDescr_Type
+PyMember_GetOne
+PyMember_SetOne
+PyMemoryView_FromMemory
+PyMemoryView_FromObject
+PyMemoryView_GetContiguous
+PyMemoryView_Type
+PyMethodDescr_Type
+PyModuleDef_Init
+PyModuleDef_Type
+PyModule_AddFunctions
+PyModule_AddIntConstant
+PyModule_AddObject
+PyModule_AddObjectRef
+PyModule_AddStringConstant
+PyModule_AddType
+PyModule_Create2
+PyModule_ExecDef
+PyModule_FromDefAndSpec2
+PyModule_GetDef
+PyModule_GetDict
+PyModule_GetFilename
+PyModule_GetFilenameObject
+PyModule_GetName
+PyModule_GetNameObject
+PyModule_GetState
+PyModule_New
+PyModule_NewObject
+PyModule_SetDocString
+PyModule_Type
+PyNumber_Absolute
+PyNumber_Add
+PyNumber_And
+PyNumber_AsSsize_t
+PyNumber_Check
+PyNumber_Divmod
+PyNumber_Float
+PyNumber_FloorDivide
+PyNumber_InPlaceAdd
+PyNumber_InPlaceAnd
+PyNumber_InPlaceFloorDivide
+PyNumber_InPlaceLshift
+PyNumber_InPlaceMatrixMultiply
+PyNumber_InPlaceMultiply
+PyNumber_InPlaceOr
+PyNumber_InPlacePower
+PyNumber_InPlaceRemainder
+PyNumber_InPlaceRshift
+PyNumber_InPlaceSubtract
+PyNumber_InPlaceTrueDivide
+PyNumber_InPlaceXor
+PyNumber_Index
+PyNumber_Invert
+PyNumber_Long
+PyNumber_Lshift
+PyNumber_MatrixMultiply
+PyNumber_Multiply
+PyNumber_Negative
+PyNumber_Or
+PyNumber_Positive
+PyNumber_Power
+PyNumber_Remainder
+PyNumber_Rshift
+PyNumber_Subtract
+PyNumber_ToBase
+PyNumber_TrueDivide
+PyNumber_Xor
+PyOS_AfterFork
+PyOS_AfterFork_Child
+PyOS_AfterFork_Parent
+PyOS_BeforeFork
+PyOS_FSPath
+PyOS_InterruptOccurred
+PyOS_double_to_string
+PyOS_getsig
+PyOS_mystricmp
+PyOS_mystrnicmp
+PyOS_setsig
+PyOS_snprintf
+PyOS_string_to_double
+PyOS_strtol
+PyOS_strtoul
+PyOS_vsnprintf
+PyObject_ASCII
+PyObject_AsFileDescriptor
+PyObject_Bytes
+PyObject_Call
+PyObject_CallFunction
+PyObject_CallFunctionObjArgs
+PyObject_CallMethod
+PyObject_CallMethodObjArgs
+PyObject_CallNoArgs
+PyObject_CallObject
+PyObject_Calloc
+PyObject_ClearWeakRefs
+PyObject_DelItem
+PyObject_DelItemString
+PyObject_Dir
+PyObject_Format
+PyObject_Free
+PyObject_GC_Del
+PyObject_GC_IsFinalized
+PyObject_GC_IsTracked
+PyObject_GC_Track
+PyObject_GC_UnTrack
+PyObject_GenericGetAttr
+PyObject_GenericGetDict
+PyObject_GenericSetAttr
+PyObject_GenericSetDict
+PyObject_GetAttr
+PyObject_GetAttrString
+PyObject_GetItem
+PyObject_GetIter
+PyObject_HasAttr
+PyObject_HasAttrString
+PyObject_Hash
+PyObject_HashNotImplemented
+PyObject_Init
+PyObject_InitVar
+PyObject_IsInstance
+PyObject_IsSubclass
+PyObject_IsTrue
+PyObject_Length
+PyObject_Malloc
+PyObject_Not
+PyObject_Realloc
+PyObject_Repr
+PyObject_RichCompare
+PyObject_RichCompareBool
+PyObject_SelfIter
+PyObject_SetAttr
+PyObject_SetAttrString
+PyObject_SetItem
+PyObject_Size
+PyObject_Str
+PyObject_Type
+PyProperty_Type
+PyRangeIter_Type
+PyRange_Type
+PyReversed_Type
+PySeqIter_New
+PySeqIter_Type
+PySequence_Check
+PySequence_Concat
+PySequence_Contains
+PySequence_Count
+PySequence_DelItem
+PySequence_DelSlice
+PySequence_Fast
+PySequence_GetItem
+PySequence_GetSlice
+PySequence_In
+PySequence_InPlaceConcat
+PySequence_InPlaceRepeat
+PySequence_Index
+PySequence_Length
+PySequence_List
+PySequence_Repeat
+PySequence_SetItem
+PySequence_SetSlice
+PySequence_Size
+PySequence_Tuple
+PySetIter_Type
+PySet_Add
+PySet_Clear
+PySet_Contains
+PySet_Discard
+PySet_New
+PySet_Pop
+PySet_Size
+PySet_Type
+PySlice_AdjustIndices
+PySlice_GetIndices
+PySlice_GetIndicesEx
+PySlice_New
+PySlice_Type
+PySlice_Unpack
+PyState_AddModule
+PyState_FindModule
+PyState_RemoveModule
+PyStructSequence_GetItem
+PyStructSequence_New
+PyStructSequence_NewType
+PyStructSequence_SetItem
+PySuper_Type
+PySys_AddWarnOption
+PySys_AddWarnOptionUnicode
+PySys_AddXOption
+PySys_FormatStderr
+PySys_FormatStdout
+PySys_GetObject
+PySys_GetXOptions
+PySys_HasWarnOptions
+PySys_ResetWarnOptions
+PySys_SetArgv
+PySys_SetArgvEx
+PySys_SetObject
+PySys_SetPath
+PySys_WriteStderr
+PySys_WriteStdout
+PyThreadState_Clear
+PyThreadState_Delete
+PyThreadState_Get
+PyThreadState_GetDict
+PyThreadState_GetFrame
+PyThreadState_GetID
+PyThreadState_GetInterpreter
+PyThreadState_New
+PyThreadState_SetAsyncExc
+PyThreadState_Swap
+PyThread_GetInfo
+PyThread_ReInitTLS
+PyThread_acquire_lock
+PyThread_acquire_lock_timed
+PyThread_allocate_lock
+PyThread_create_key
+PyThread_delete_key
+PyThread_delete_key_value
+PyThread_exit_thread
+PyThread_free_lock
+PyThread_get_key_value
+PyThread_get_stacksize
+PyThread_get_thread_ident
+PyThread_get_thread_native_id
+PyThread_init_thread
+PyThread_release_lock
+PyThread_set_key_value
+PyThread_set_stacksize
+PyThread_start_new_thread
+PyThread_tss_alloc
+PyThread_tss_create
+PyThread_tss_delete
+PyThread_tss_free
+PyThread_tss_get
+PyThread_tss_is_created
+PyThread_tss_set
+PyTraceBack_Here
+PyTraceBack_Print
+PyTraceBack_Type
+PyTupleIter_Type
+PyTuple_GetItem
+PyTuple_GetSlice
+PyTuple_New
+PyTuple_Pack
+PyTuple_SetItem
+PyTuple_Size
+PyTuple_Type
+PyType_ClearCache
+PyType_FromModuleAndSpec
+PyType_FromSpec
+PyType_FromSpecWithBases
+PyType_GenericAlloc
+PyType_GenericNew
+PyType_GetFlags
+PyType_GetModule
+PyType_GetModuleState
+PyType_GetSlot
+PyType_IsSubtype
+PyType_Modified
+PyType_Ready
+PyType_Type
+PyUnicodeDecodeError_Create
+PyUnicodeDecodeError_GetEncoding
+PyUnicodeDecodeError_GetEnd
+PyUnicodeDecodeError_GetObject
+PyUnicodeDecodeError_GetReason
+PyUnicodeDecodeError_GetStart
+PyUnicodeDecodeError_SetEnd
+PyUnicodeDecodeError_SetReason
+PyUnicodeDecodeError_SetStart
+PyUnicodeEncodeError_GetEncoding
+PyUnicodeEncodeError_GetEnd
+PyUnicodeEncodeError_GetObject
+PyUnicodeEncodeError_GetReason
+PyUnicodeEncodeError_GetStart
+PyUnicodeEncodeError_SetEnd
+PyUnicodeEncodeError_SetReason
+PyUnicodeEncodeError_SetStart
+PyUnicodeIter_Type
+PyUnicodeTranslateError_GetEnd
+PyUnicodeTranslateError_GetObject
+PyUnicodeTranslateError_GetReason
+PyUnicodeTranslateError_GetStart
+PyUnicodeTranslateError_SetEnd
+PyUnicodeTranslateError_SetReason
+PyUnicodeTranslateError_SetStart
+PyUnicode_Append
+PyUnicode_AppendAndDel
+PyUnicode_AsASCIIString
+PyUnicode_AsCharmapString
+PyUnicode_AsDecodedObject
+PyUnicode_AsDecodedUnicode
+PyUnicode_AsEncodedObject
+PyUnicode_AsEncodedString
+PyUnicode_AsEncodedUnicode
+PyUnicode_AsLatin1String
+PyUnicode_AsRawUnicodeEscapeString
+PyUnicode_AsUCS4
+PyUnicode_AsUCS4Copy
+PyUnicode_AsUTF16String
+PyUnicode_AsUTF32String
+PyUnicode_AsUTF8AndSize
+PyUnicode_AsUTF8String
+PyUnicode_AsUnicodeEscapeString
+PyUnicode_AsWideChar
+PyUnicode_AsWideCharString
+PyUnicode_BuildEncodingMap
+PyUnicode_Compare
+PyUnicode_CompareWithASCIIString
+PyUnicode_Concat
+PyUnicode_Contains
+PyUnicode_Count
+PyUnicode_Decode
+PyUnicode_DecodeASCII
+PyUnicode_DecodeCharmap
+PyUnicode_DecodeFSDefault
+PyUnicode_DecodeFSDefaultAndSize
+PyUnicode_DecodeLatin1
+PyUnicode_DecodeLocale
+PyUnicode_DecodeLocaleAndSize
+PyUnicode_DecodeRawUnicodeEscape
+PyUnicode_DecodeUTF16
+PyUnicode_DecodeUTF16Stateful
+PyUnicode_DecodeUTF32
+PyUnicode_DecodeUTF32Stateful
+PyUnicode_DecodeUTF7
+PyUnicode_DecodeUTF7Stateful
+PyUnicode_DecodeUTF8
+PyUnicode_DecodeUTF8Stateful
+PyUnicode_DecodeUnicodeEscape
+PyUnicode_EncodeFSDefault
+PyUnicode_EncodeLocale
+PyUnicode_FSConverter
+PyUnicode_FSDecoder
+PyUnicode_Find
+PyUnicode_FindChar
+PyUnicode_Format
+PyUnicode_FromEncodedObject
+PyUnicode_FromFormat
+PyUnicode_FromFormatV
+PyUnicode_FromObject
+PyUnicode_FromOrdinal
+PyUnicode_FromString
+PyUnicode_FromStringAndSize
+PyUnicode_FromWideChar
+PyUnicode_GetDefaultEncoding
+PyUnicode_GetLength
+PyUnicode_GetSize
+PyUnicode_InternFromString
+PyUnicode_InternImmortal
+PyUnicode_InternInPlace
+PyUnicode_IsIdentifier
+PyUnicode_Join
+PyUnicode_Partition
+PyUnicode_RPartition
+PyUnicode_RSplit
+PyUnicode_ReadChar
+PyUnicode_Replace
+PyUnicode_Resize
+PyUnicode_RichCompare
+PyUnicode_Split
+PyUnicode_Splitlines
+PyUnicode_Substring
+PyUnicode_Tailmatch
+PyUnicode_Translate
+PyUnicode_Type
+PyUnicode_WriteChar
+PyWeakref_GetObject
+PyWeakref_NewProxy
+PyWeakref_NewRef
+PyWrapperDescr_Type
+PyWrapper_New
+PyZip_Type
+Py_AddPendingCall
+Py_AtExit
+Py_BuildValue
+Py_BytesMain
+Py_CompileString
+Py_DecRef
+Py_DecodeLocale
+Py_EncodeLocale
+Py_EndInterpreter
+Py_EnterRecursiveCall
+Py_Exit
+Py_FatalError
+Py_FileSystemDefaultEncodeErrors
+Py_FileSystemDefaultEncoding
+Py_Finalize
+Py_FinalizeEx
+Py_GenericAlias
+Py_GenericAliasType
+Py_GetBuildInfo
+Py_GetCompiler
+Py_GetCopyright
+Py_GetExecPrefix
+Py_GetPath
+Py_GetPlatform
+Py_GetPrefix
+Py_GetProgramFullPath
+Py_GetProgramName
+Py_GetPythonHome
+Py_GetRecursionLimit
+Py_GetVersion
+Py_HasFileSystemDefaultEncoding
+Py_IncRef
+Py_Initialize
+Py_InitializeEx
+Py_IsInitialized
+Py_LeaveRecursiveCall
+Py_Main
+Py_MakePendingCalls
+Py_NewInterpreter
+Py_NewRef
+Py_ReprEnter
+Py_ReprLeave
+Py_SetPath
+Py_SetProgramName
+Py_SetPythonHome
+Py_SetRecursionLimit
+Py_SymtableString
+Py_UTF8Mode
+Py_VaBuildValue
+Py_XNewRef
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 082945f58a777..f52a0f3cdf0d6 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -744,6 +744,13 @@ regen-importlib: Programs/_freeze_importlib
 	$(UPDATE_FILE) $(srcdir)/Python/importlib_zipimport.h $(srcdir)/Python/importlib_zipimport.h.new
 
 
+regen-limited-abi: all
+	@$(MKDIR_P) $(srcdir)/Doc/data/
+	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/stable_abi.py generate $(srcdir)/Doc/data/stable_abi.dat.new
+	$(UPDATE_FILE) $(srcdir)/Doc/data/stable_abi.dat \
+	$(srcdir)/Doc/data/stable_abi.dat.new
+
+
 ############################################################################
 # Regenerate all generated files
 
@@ -1900,6 +1907,9 @@ funny:
 patchcheck: @DEF_MAKE_RULE@
 	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/patchcheck.py
 
+check-limited-abi: all
+	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/stable_abi.py check $(srcdir)/Doc/data/stable_abi.dat
+
 # Dependencies
 
 Python/thread.o: @THREADHEADERS@ $(srcdir)/Python/condvar.h
diff --git a/Tools/scripts/stable_abi.py b/Tools/scripts/stable_abi.py
new file mode 100755
index 0000000000000..aa953b2dfde87
--- /dev/null
+++ b/Tools/scripts/stable_abi.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+
+import argparse
+import glob
+import re
+import pathlib
+import subprocess
+import sys
+import sysconfig
+
+EXCLUDED_HEADERS = {
+    "bytes_methods.h",
+    "cellobject.h",
+    "classobject.h",
+    "code.h",
+    "compile.h",
+    "datetime.h",
+    "dtoa.h",
+    "frameobject.h",
+    "funcobject.h",
+    "genobject.h",
+    "longintrepr.h",
+    "parsetok.h",
+    "pyarena.h",
+    "pyatomic.h",
+    "pyctype.h",
+    "pydebug.h",
+    "pytime.h",
+    "symtable.h",
+    "token.h",
+    "ucnhash.h",
+}
+
+
+def get_exported_symbols(library, dynamic=False):
+    # Only look at dynamic symbols
+    args = ["nm", "--no-sort"]
+    if dynamic:
+        args.append("--dynamic")
+    args.append(library)
+    proc = subprocess.run(args, stdout=subprocess.PIPE, universal_newlines=True)
+    if proc.returncode:
+        sys.stdout.write(proc.stdout)
+        sys.exit(proc.returncode)
+
+    stdout = proc.stdout.rstrip()
+    if not stdout:
+        raise Exception("command output is empty")
+
+    for line in stdout.splitlines():
+        # Split line '0000000000001b80 D PyTextIOWrapper_Type'
+        if not line:
+            continue
+
+        parts = line.split(maxsplit=2)
+        if len(parts) < 3:
+            continue
+
+        symbol = parts[-1]
+        yield symbol
+
+
+def check_library(library, abi_funcs, dynamic=False):
+    available_symbols = set(get_exported_symbols(library, dynamic))
+    missing_symbols = abi_funcs - available_symbols
+    if missing_symbols:
+        print(
+            f"Some symbols from the stable ABI are missing: {', '.join(missing_symbols)}"
+        )
+        return 1
+    return 0
+
+
+def generate_limited_api_symbols(args):
+    if hasattr(sys, "gettotalrefcount"):
+        print(
+            "Stable ABI symbols cannot be generated from a debug build", file=sys.stderr
+        )
+        sys.exit(1)
+    library = sysconfig.get_config_var("LIBRARY")
+    ldlibrary = sysconfig.get_config_var("LDLIBRARY")
+    if ldlibrary != library:
+        raise Exception("Limited ABI symbols can only be generated from a static build")
+    available_symbols = {
+        symbol for symbol in get_exported_symbols(library) if symbol.startswith("Py")
+    }
+
+    headers = [
+        file
+        for file in pathlib.Path("Include").glob("*.h")
+        if file.name not in EXCLUDED_HEADERS
+    ]
+    stable_data, stable_exported_data, stable_functions = get_limited_api_definitions(
+        headers
+    )
+    macros = get_limited_api_macros(headers)
+
+    stable_symbols = {
+        symbol
+        for symbol in (stable_functions | stable_exported_data | stable_data | macros)
+        if symbol.startswith("Py") and symbol in available_symbols
+    }
+    with open(args.output_file, "w") as output_file:
+        output_file.write(f"# File generated by 'make regen-limited-abi'\n")
+        output_file.write(
+            f"# This is NOT an authoritative list of stable ABI symbols\n"
+        )
+        for symbol in sorted(stable_symbols):
+            output_file.write(f"{symbol}\n")
+    sys.exit(0)
+
+
+def get_limited_api_macros(headers):
+    """Run the preprocesor over all the header files in "Include" setting
+    "-DPy_LIMITED_API" to the correct value for the running version of the interpreter
+    and extracting all macro definitions (via adding -dM to the compiler arguments).
+    """
+
+    preprocesor_output_with_macros = subprocess.check_output(
+        sysconfig.get_config_var("CC").split()
+        + [
+            # Prevent the expansion of the exported macros so we can capture them later
+            "-DSIZEOF_WCHAR_T=4",  # The actual value is not important
+            f"-DPy_LIMITED_API={sys.version_info.major << 24 | sys.version_info.minor << 16}",
+            "-I.",
+            "-I./Include",
+            "-dM",
+            "-E",
+        ]
+        + [str(file) for file in headers],
+        text=True,
+        stderr=subprocess.DEVNULL,
+    )
+
+    return {
+        target
+        for _, target in re.findall(
+            r"#define (\w+)\s*(?:\(.*?\))?\s+(\w+)", preprocesor_output_with_macros
+        )
+    }
+
+
+def get_limited_api_definitions(headers):
+    """Run the preprocesor over all the header files in "Include" setting
+    "-DPy_LIMITED_API" to the correct value for the running version of the interpreter.
+
+    The limited API symbols will be extracted from the output of this command as it includes
+    the prototypes and definitions of all the exported symbols that are in the limited api.
+
+    This function does *NOT* extract the macros defined on the limited API
+    """
+    preprocesor_output = subprocess.check_output(
+        sysconfig.get_config_var("CC").split()
+        + [
+            # Prevent the expansion of the exported macros so we can capture them later
+            "-DPyAPI_FUNC=__PyAPI_FUNC",
+            "-DPyAPI_DATA=__PyAPI_DATA",
+            "-DEXPORT_DATA=__EXPORT_DATA",
+            "-D_Py_NO_RETURN=",
+            "-DSIZEOF_WCHAR_T=4",  # The actual value is not important
+            f"-DPy_LIMITED_API={sys.version_info.major << 24 | sys.version_info.minor << 16}",
+            "-I.",
+            "-I./Include",
+            "-E",
+        ]
+        + [str(file) for file in headers],
+        text=True,
+        stderr=subprocess.DEVNULL,
+    )
+    stable_functions = set(
+        re.findall(r"__PyAPI_FUNC\(.*?\)\s*(.*?)\s*\(", preprocesor_output)
+    )
+    stable_exported_data = set(
+        re.findall(r"__EXPORT_DATA\((.*?)\)", preprocesor_output)
+    )
+    stable_data = set(
+        re.findall(r"__PyAPI_DATA\(.*?\)\s*\(?(.*?)\)?\s*;", preprocesor_output)
+    )
+    return stable_data, stable_exported_data, stable_functions
+
+
+def check_symbols(parser_args):
+    with open(parser_args.stable_abi_file, "r") as filename:
+        abi_funcs = {
+            symbol
+            for symbol in filename.read().splitlines()
+            if symbol and not symbol.startswith("#")
+        }
+
+    ret = 0
+    # static library
+    LIBRARY = sysconfig.get_config_var("LIBRARY")
+    if not LIBRARY:
+        raise Exception("failed to get LIBRARY variable from sysconfig")
+    ret = check_library(LIBRARY, abi_funcs)
+
+    # dynamic library
+    LDLIBRARY = sysconfig.get_config_var("LDLIBRARY")
+    if not LDLIBRARY:
+        raise Exception("failed to get LDLIBRARY variable from sysconfig")
+    if LDLIBRARY != LIBRARY:
+        ret |= check_library(LDLIBRARY, abi_funcs, dynamic=True)
+
+    sys.exit(ret)
+
+
+def main():
+    parser = argparse.ArgumentParser(description="Process some integers.")
+    subparsers = parser.add_subparsers()
+    check_parser = subparsers.add_parser(
+        "check", help="Check the exported symbols against a given ABI file"
+    )
+    check_parser.add_argument(
+        "stable_abi_file", type=str, help="File with the stable abi functions"
+    )
+    check_parser.set_defaults(func=check_symbols)
+    generate_parser = subparsers.add_parser(
+        "generate",
+        help="Generate symbols from the header files and the exported symbols",
+    )
+    generate_parser.add_argument(
+        "output_file", type=str, help="File to dump the symbols to"
+    )
+    generate_parser.set_defaults(func=generate_limited_api_symbols)
+    args = parser.parse_args()
+    if "func" not in args:
+        parser.error("Either 'check' or 'generate' must be used")
+        sys.exit(1)
+
+    args.func(args)
+
+
+if __name__ == "__main__":
+    main()



More information about the Python-checkins mailing list