17db96d56Sopenharmony_ci"""Check the stable ABI manifest or generate files from it
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciBy default, the tool only checks existing files/libraries.
47db96d56Sopenharmony_ciPass --generate to recreate auto-generated files instead.
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciFor actions that take a FILENAME, the filename can be left out to use a default
77db96d56Sopenharmony_ci(relative to the manifest file, as they appear in the CPython codebase).
87db96d56Sopenharmony_ci"""
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_cifrom functools import partial
117db96d56Sopenharmony_cifrom pathlib import Path
127db96d56Sopenharmony_ciimport dataclasses
137db96d56Sopenharmony_ciimport subprocess
147db96d56Sopenharmony_ciimport sysconfig
157db96d56Sopenharmony_ciimport argparse
167db96d56Sopenharmony_ciimport textwrap
177db96d56Sopenharmony_ciimport tomllib
187db96d56Sopenharmony_ciimport difflib
197db96d56Sopenharmony_ciimport shutil
207db96d56Sopenharmony_ciimport pprint
217db96d56Sopenharmony_ciimport sys
227db96d56Sopenharmony_ciimport os
237db96d56Sopenharmony_ciimport os.path
247db96d56Sopenharmony_ciimport io
257db96d56Sopenharmony_ciimport re
267db96d56Sopenharmony_ciimport csv
277db96d56Sopenharmony_ci
287db96d56Sopenharmony_ciMISSING = object()
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_ciEXCLUDED_HEADERS = {
317db96d56Sopenharmony_ci    "bytes_methods.h",
327db96d56Sopenharmony_ci    "cellobject.h",
337db96d56Sopenharmony_ci    "classobject.h",
347db96d56Sopenharmony_ci    "code.h",
357db96d56Sopenharmony_ci    "compile.h",
367db96d56Sopenharmony_ci    "datetime.h",
377db96d56Sopenharmony_ci    "dtoa.h",
387db96d56Sopenharmony_ci    "frameobject.h",
397db96d56Sopenharmony_ci    "genobject.h",
407db96d56Sopenharmony_ci    "longintrepr.h",
417db96d56Sopenharmony_ci    "parsetok.h",
427db96d56Sopenharmony_ci    "pyatomic.h",
437db96d56Sopenharmony_ci    "pytime.h",
447db96d56Sopenharmony_ci    "token.h",
457db96d56Sopenharmony_ci    "ucnhash.h",
467db96d56Sopenharmony_ci}
477db96d56Sopenharmony_ciMACOS = (sys.platform == "darwin")
487db96d56Sopenharmony_ciUNIXY = MACOS or (sys.platform == "linux")  # XXX should this be "not Windows"?
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci
517db96d56Sopenharmony_ci# The stable ABI manifest (Misc/stable_abi.toml) exists only to fill the
527db96d56Sopenharmony_ci# following dataclasses.
537db96d56Sopenharmony_ci# Feel free to change its syntax (and the `parse_manifest` function)
547db96d56Sopenharmony_ci# to better serve that purpose (while keeping it human-readable).
557db96d56Sopenharmony_ci
567db96d56Sopenharmony_ciclass Manifest:
577db96d56Sopenharmony_ci    """Collection of `ABIItem`s forming the stable ABI/limited API."""
587db96d56Sopenharmony_ci    def __init__(self):
597db96d56Sopenharmony_ci        self.contents = dict()
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci    def add(self, item):
627db96d56Sopenharmony_ci        if item.name in self.contents:
637db96d56Sopenharmony_ci            # We assume that stable ABI items do not share names,
647db96d56Sopenharmony_ci            # even if they're different kinds (e.g. function vs. macro).
657db96d56Sopenharmony_ci            raise ValueError(f'duplicate ABI item {item.name}')
667db96d56Sopenharmony_ci        self.contents[item.name] = item
677db96d56Sopenharmony_ci
687db96d56Sopenharmony_ci    def select(self, kinds, *, include_abi_only=True, ifdef=None):
697db96d56Sopenharmony_ci        """Yield selected items of the manifest
707db96d56Sopenharmony_ci
717db96d56Sopenharmony_ci        kinds: set of requested kinds, e.g. {'function', 'macro'}
727db96d56Sopenharmony_ci        include_abi_only: if True (default), include all items of the
737db96d56Sopenharmony_ci            stable ABI.
747db96d56Sopenharmony_ci            If False, include only items from the limited API
757db96d56Sopenharmony_ci            (i.e. items people should use today)
767db96d56Sopenharmony_ci        ifdef: set of feature macros (e.g. {'HAVE_FORK', 'MS_WINDOWS'}).
777db96d56Sopenharmony_ci            If None (default), items are not filtered by this. (This is
787db96d56Sopenharmony_ci            different from the empty set, which filters out all such
797db96d56Sopenharmony_ci            conditional items.)
807db96d56Sopenharmony_ci        """
817db96d56Sopenharmony_ci        for name, item in sorted(self.contents.items()):
827db96d56Sopenharmony_ci            if item.kind not in kinds:
837db96d56Sopenharmony_ci                continue
847db96d56Sopenharmony_ci            if item.abi_only and not include_abi_only:
857db96d56Sopenharmony_ci                continue
867db96d56Sopenharmony_ci            if (ifdef is not None
877db96d56Sopenharmony_ci                    and item.ifdef is not None
887db96d56Sopenharmony_ci                    and item.ifdef not in ifdef):
897db96d56Sopenharmony_ci                continue
907db96d56Sopenharmony_ci            yield item
917db96d56Sopenharmony_ci
927db96d56Sopenharmony_ci    def dump(self):
937db96d56Sopenharmony_ci        """Yield lines to recreate the manifest file (sans comments/newlines)"""
947db96d56Sopenharmony_ci        for item in self.contents.values():
957db96d56Sopenharmony_ci            fields = dataclasses.fields(item)
967db96d56Sopenharmony_ci            yield f"[{item.kind}.{item.name}]"
977db96d56Sopenharmony_ci            for field in fields:
987db96d56Sopenharmony_ci                if field.name in {'name', 'value', 'kind'}:
997db96d56Sopenharmony_ci                    continue
1007db96d56Sopenharmony_ci                value = getattr(item, field.name)
1017db96d56Sopenharmony_ci                if value == field.default:
1027db96d56Sopenharmony_ci                    pass
1037db96d56Sopenharmony_ci                elif value is True:
1047db96d56Sopenharmony_ci                    yield f"    {field.name} = true"
1057db96d56Sopenharmony_ci                elif value:
1067db96d56Sopenharmony_ci                    yield f"    {field.name} = {value!r}"
1077db96d56Sopenharmony_ci
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ciitemclasses = {}
1107db96d56Sopenharmony_cidef itemclass(kind):
1117db96d56Sopenharmony_ci    """Register the decorated class in `itemclasses`"""
1127db96d56Sopenharmony_ci    def decorator(cls):
1137db96d56Sopenharmony_ci        itemclasses[kind] = cls
1147db96d56Sopenharmony_ci        return cls
1157db96d56Sopenharmony_ci    return decorator
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ci@itemclass('function')
1187db96d56Sopenharmony_ci@itemclass('macro')
1197db96d56Sopenharmony_ci@itemclass('data')
1207db96d56Sopenharmony_ci@itemclass('const')
1217db96d56Sopenharmony_ci@itemclass('typedef')
1227db96d56Sopenharmony_ci@dataclasses.dataclass
1237db96d56Sopenharmony_ciclass ABIItem:
1247db96d56Sopenharmony_ci    """Information on one item (function, macro, struct, etc.)"""
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci    name: str
1277db96d56Sopenharmony_ci    kind: str
1287db96d56Sopenharmony_ci    added: str = None
1297db96d56Sopenharmony_ci    abi_only: bool = False
1307db96d56Sopenharmony_ci    ifdef: str = None
1317db96d56Sopenharmony_ci
1327db96d56Sopenharmony_ci@itemclass('feature_macro')
1337db96d56Sopenharmony_ci@dataclasses.dataclass(kw_only=True)
1347db96d56Sopenharmony_ciclass FeatureMacro(ABIItem):
1357db96d56Sopenharmony_ci    name: str
1367db96d56Sopenharmony_ci    doc: str
1377db96d56Sopenharmony_ci    windows: bool = False
1387db96d56Sopenharmony_ci    abi_only: bool = True
1397db96d56Sopenharmony_ci
1407db96d56Sopenharmony_ci@itemclass('struct')
1417db96d56Sopenharmony_ci@dataclasses.dataclass(kw_only=True)
1427db96d56Sopenharmony_ciclass Struct(ABIItem):
1437db96d56Sopenharmony_ci    struct_abi_kind: str
1447db96d56Sopenharmony_ci    members: list = None
1457db96d56Sopenharmony_ci
1467db96d56Sopenharmony_ci
1477db96d56Sopenharmony_cidef parse_manifest(file):
1487db96d56Sopenharmony_ci    """Parse the given file (iterable of lines) to a Manifest"""
1497db96d56Sopenharmony_ci
1507db96d56Sopenharmony_ci    manifest = Manifest()
1517db96d56Sopenharmony_ci
1527db96d56Sopenharmony_ci    data = tomllib.load(file)
1537db96d56Sopenharmony_ci
1547db96d56Sopenharmony_ci    for kind, itemclass in itemclasses.items():
1557db96d56Sopenharmony_ci        for name, item_data in data[kind].items():
1567db96d56Sopenharmony_ci            try:
1577db96d56Sopenharmony_ci                item = itemclass(name=name, kind=kind, **item_data)
1587db96d56Sopenharmony_ci                manifest.add(item)
1597db96d56Sopenharmony_ci            except BaseException as exc:
1607db96d56Sopenharmony_ci                exc.add_note(f'in {kind} {name}')
1617db96d56Sopenharmony_ci                raise
1627db96d56Sopenharmony_ci
1637db96d56Sopenharmony_ci    return manifest
1647db96d56Sopenharmony_ci
1657db96d56Sopenharmony_ci# The tool can run individual "actions".
1667db96d56Sopenharmony_ci# Most actions are "generators", which generate a single file from the
1677db96d56Sopenharmony_ci# manifest. (Checking works by generating a temp file & comparing.)
1687db96d56Sopenharmony_ci# Other actions, like "--unixy-check", don't work on a single file.
1697db96d56Sopenharmony_ci
1707db96d56Sopenharmony_cigenerators = []
1717db96d56Sopenharmony_cidef generator(var_name, default_path):
1727db96d56Sopenharmony_ci    """Decorates a file generator: function that writes to a file"""
1737db96d56Sopenharmony_ci    def _decorator(func):
1747db96d56Sopenharmony_ci        func.var_name = var_name
1757db96d56Sopenharmony_ci        func.arg_name = '--' + var_name.replace('_', '-')
1767db96d56Sopenharmony_ci        func.default_path = default_path
1777db96d56Sopenharmony_ci        generators.append(func)
1787db96d56Sopenharmony_ci        return func
1797db96d56Sopenharmony_ci    return _decorator
1807db96d56Sopenharmony_ci
1817db96d56Sopenharmony_ci
1827db96d56Sopenharmony_ci@generator("python3dll", 'PC/python3dll.c')
1837db96d56Sopenharmony_cidef gen_python3dll(manifest, args, outfile):
1847db96d56Sopenharmony_ci    """Generate/check the source for the Windows stable ABI library"""
1857db96d56Sopenharmony_ci    write = partial(print, file=outfile)
1867db96d56Sopenharmony_ci    write(textwrap.dedent(r"""
1877db96d56Sopenharmony_ci        /* Re-export stable Python ABI */
1887db96d56Sopenharmony_ci
1897db96d56Sopenharmony_ci        /* Generated by Tools/scripts/stable_abi.py */
1907db96d56Sopenharmony_ci
1917db96d56Sopenharmony_ci        #ifdef _M_IX86
1927db96d56Sopenharmony_ci        #define DECORATE "_"
1937db96d56Sopenharmony_ci        #else
1947db96d56Sopenharmony_ci        #define DECORATE
1957db96d56Sopenharmony_ci        #endif
1967db96d56Sopenharmony_ci
1977db96d56Sopenharmony_ci        #define EXPORT_FUNC(name) \
1987db96d56Sopenharmony_ci            __pragma(comment(linker, "/EXPORT:" DECORATE #name "=" PYTHON_DLL_NAME "." #name))
1997db96d56Sopenharmony_ci        #define EXPORT_DATA(name) \
2007db96d56Sopenharmony_ci            __pragma(comment(linker, "/EXPORT:" DECORATE #name "=" PYTHON_DLL_NAME "." #name ",DATA"))
2017db96d56Sopenharmony_ci    """))
2027db96d56Sopenharmony_ci
2037db96d56Sopenharmony_ci    def sort_key(item):
2047db96d56Sopenharmony_ci        return item.name.lower()
2057db96d56Sopenharmony_ci
2067db96d56Sopenharmony_ci    windows_feature_macros = {
2077db96d56Sopenharmony_ci        item.name for item in manifest.select({'feature_macro'}) if item.windows
2087db96d56Sopenharmony_ci    }
2097db96d56Sopenharmony_ci    for item in sorted(
2107db96d56Sopenharmony_ci            manifest.select(
2117db96d56Sopenharmony_ci                {'function'},
2127db96d56Sopenharmony_ci                include_abi_only=True,
2137db96d56Sopenharmony_ci                ifdef=windows_feature_macros),
2147db96d56Sopenharmony_ci            key=sort_key):
2157db96d56Sopenharmony_ci        write(f'EXPORT_FUNC({item.name})')
2167db96d56Sopenharmony_ci
2177db96d56Sopenharmony_ci    write()
2187db96d56Sopenharmony_ci
2197db96d56Sopenharmony_ci    for item in sorted(
2207db96d56Sopenharmony_ci            manifest.select(
2217db96d56Sopenharmony_ci                {'data'},
2227db96d56Sopenharmony_ci                include_abi_only=True,
2237db96d56Sopenharmony_ci                ifdef=windows_feature_macros),
2247db96d56Sopenharmony_ci            key=sort_key):
2257db96d56Sopenharmony_ci        write(f'EXPORT_DATA({item.name})')
2267db96d56Sopenharmony_ci
2277db96d56Sopenharmony_ciREST_ROLES = {
2287db96d56Sopenharmony_ci    'function': 'function',
2297db96d56Sopenharmony_ci    'data': 'var',
2307db96d56Sopenharmony_ci    'struct': 'type',
2317db96d56Sopenharmony_ci    'macro': 'macro',
2327db96d56Sopenharmony_ci    # 'const': 'const',  # all undocumented
2337db96d56Sopenharmony_ci    'typedef': 'type',
2347db96d56Sopenharmony_ci}
2357db96d56Sopenharmony_ci
2367db96d56Sopenharmony_ci@generator("doc_list", 'Doc/data/stable_abi.dat')
2377db96d56Sopenharmony_cidef gen_doc_annotations(manifest, args, outfile):
2387db96d56Sopenharmony_ci    """Generate/check the stable ABI list for documentation annotations"""
2397db96d56Sopenharmony_ci    writer = csv.DictWriter(
2407db96d56Sopenharmony_ci        outfile,
2417db96d56Sopenharmony_ci        ['role', 'name', 'added', 'ifdef_note', 'struct_abi_kind'],
2427db96d56Sopenharmony_ci        lineterminator='\n')
2437db96d56Sopenharmony_ci    writer.writeheader()
2447db96d56Sopenharmony_ci    for item in manifest.select(REST_ROLES.keys(), include_abi_only=False):
2457db96d56Sopenharmony_ci        if item.ifdef:
2467db96d56Sopenharmony_ci            ifdef_note = manifest.contents[item.ifdef].doc
2477db96d56Sopenharmony_ci        else:
2487db96d56Sopenharmony_ci            ifdef_note = None
2497db96d56Sopenharmony_ci        row = {
2507db96d56Sopenharmony_ci            'role': REST_ROLES[item.kind],
2517db96d56Sopenharmony_ci            'name': item.name,
2527db96d56Sopenharmony_ci            'added': item.added,
2537db96d56Sopenharmony_ci            'ifdef_note': ifdef_note}
2547db96d56Sopenharmony_ci        rows = [row]
2557db96d56Sopenharmony_ci        if item.kind == 'struct':
2567db96d56Sopenharmony_ci            row['struct_abi_kind'] = item.struct_abi_kind
2577db96d56Sopenharmony_ci            for member_name in item.members or ():
2587db96d56Sopenharmony_ci                rows.append({
2597db96d56Sopenharmony_ci                    'role': 'member',
2607db96d56Sopenharmony_ci                    'name': f'{item.name}.{member_name}',
2617db96d56Sopenharmony_ci                    'added': item.added})
2627db96d56Sopenharmony_ci        writer.writerows(rows)
2637db96d56Sopenharmony_ci
2647db96d56Sopenharmony_ci@generator("ctypes_test", 'Lib/test/test_stable_abi_ctypes.py')
2657db96d56Sopenharmony_cidef gen_ctypes_test(manifest, args, outfile):
2667db96d56Sopenharmony_ci    """Generate/check the ctypes-based test for exported symbols"""
2677db96d56Sopenharmony_ci    write = partial(print, file=outfile)
2687db96d56Sopenharmony_ci    write(textwrap.dedent('''
2697db96d56Sopenharmony_ci        # Generated by Tools/scripts/stable_abi.py
2707db96d56Sopenharmony_ci
2717db96d56Sopenharmony_ci        """Test that all symbols of the Stable ABI are accessible using ctypes
2727db96d56Sopenharmony_ci        """
2737db96d56Sopenharmony_ci
2747db96d56Sopenharmony_ci        import sys
2757db96d56Sopenharmony_ci        import unittest
2767db96d56Sopenharmony_ci        from test.support.import_helper import import_module
2777db96d56Sopenharmony_ci        from _testcapi import get_feature_macros
2787db96d56Sopenharmony_ci
2797db96d56Sopenharmony_ci        feature_macros = get_feature_macros()
2807db96d56Sopenharmony_ci        ctypes_test = import_module('ctypes')
2817db96d56Sopenharmony_ci
2827db96d56Sopenharmony_ci        class TestStableABIAvailability(unittest.TestCase):
2837db96d56Sopenharmony_ci            def test_available_symbols(self):
2847db96d56Sopenharmony_ci
2857db96d56Sopenharmony_ci                for symbol_name in SYMBOL_NAMES:
2867db96d56Sopenharmony_ci                    with self.subTest(symbol_name):
2877db96d56Sopenharmony_ci                        ctypes_test.pythonapi[symbol_name]
2887db96d56Sopenharmony_ci
2897db96d56Sopenharmony_ci            def test_feature_macros(self):
2907db96d56Sopenharmony_ci                self.assertEqual(
2917db96d56Sopenharmony_ci                    set(get_feature_macros()), EXPECTED_FEATURE_MACROS)
2927db96d56Sopenharmony_ci
2937db96d56Sopenharmony_ci            # The feature macros for Windows are used in creating the DLL
2947db96d56Sopenharmony_ci            # definition, so they must be known on all platforms.
2957db96d56Sopenharmony_ci            # If we are on Windows, we check that the hardcoded data matches
2967db96d56Sopenharmony_ci            # the reality.
2977db96d56Sopenharmony_ci            @unittest.skipIf(sys.platform != "win32", "Windows specific test")
2987db96d56Sopenharmony_ci            def test_windows_feature_macros(self):
2997db96d56Sopenharmony_ci                for name, value in WINDOWS_FEATURE_MACROS.items():
3007db96d56Sopenharmony_ci                    if value != 'maybe':
3017db96d56Sopenharmony_ci                        with self.subTest(name):
3027db96d56Sopenharmony_ci                            self.assertEqual(feature_macros[name], value)
3037db96d56Sopenharmony_ci
3047db96d56Sopenharmony_ci        SYMBOL_NAMES = (
3057db96d56Sopenharmony_ci    '''))
3067db96d56Sopenharmony_ci    items = manifest.select(
3077db96d56Sopenharmony_ci        {'function', 'data'},
3087db96d56Sopenharmony_ci        include_abi_only=True,
3097db96d56Sopenharmony_ci    )
3107db96d56Sopenharmony_ci    optional_items = {}
3117db96d56Sopenharmony_ci    for item in items:
3127db96d56Sopenharmony_ci        if item.name in (
3137db96d56Sopenharmony_ci                # Some symbols aren't exported on all platforms.
3147db96d56Sopenharmony_ci                # This is a bug: https://bugs.python.org/issue44133
3157db96d56Sopenharmony_ci                'PyModule_Create2', 'PyModule_FromDefAndSpec2',
3167db96d56Sopenharmony_ci            ):
3177db96d56Sopenharmony_ci            continue
3187db96d56Sopenharmony_ci        if item.ifdef:
3197db96d56Sopenharmony_ci            optional_items.setdefault(item.ifdef, []).append(item.name)
3207db96d56Sopenharmony_ci        else:
3217db96d56Sopenharmony_ci            write(f'    "{item.name}",')
3227db96d56Sopenharmony_ci    write(")")
3237db96d56Sopenharmony_ci    for ifdef, names in optional_items.items():
3247db96d56Sopenharmony_ci        write(f"if feature_macros[{ifdef!r}]:")
3257db96d56Sopenharmony_ci        write(f"    SYMBOL_NAMES += (")
3267db96d56Sopenharmony_ci        for name in names:
3277db96d56Sopenharmony_ci            write(f"        {name!r},")
3287db96d56Sopenharmony_ci        write("    )")
3297db96d56Sopenharmony_ci    write("")
3307db96d56Sopenharmony_ci    feature_macros = list(manifest.select({'feature_macro'}))
3317db96d56Sopenharmony_ci    feature_names = sorted(m.name for m in feature_macros)
3327db96d56Sopenharmony_ci    write(f"EXPECTED_FEATURE_MACROS = set({pprint.pformat(feature_names)})")
3337db96d56Sopenharmony_ci
3347db96d56Sopenharmony_ci    windows_feature_macros = {m.name: m.windows for m in feature_macros}
3357db96d56Sopenharmony_ci    write(f"WINDOWS_FEATURE_MACROS = {pprint.pformat(windows_feature_macros)}")
3367db96d56Sopenharmony_ci
3377db96d56Sopenharmony_ci
3387db96d56Sopenharmony_ci@generator("testcapi_feature_macros", 'Modules/_testcapi_feature_macros.inc')
3397db96d56Sopenharmony_cidef gen_testcapi_feature_macros(manifest, args, outfile):
3407db96d56Sopenharmony_ci    """Generate/check the stable ABI list for documentation annotations"""
3417db96d56Sopenharmony_ci    write = partial(print, file=outfile)
3427db96d56Sopenharmony_ci    write('// Generated by Tools/scripts/stable_abi.py')
3437db96d56Sopenharmony_ci    write()
3447db96d56Sopenharmony_ci    write('// Add an entry in dict `result` for each Stable ABI feature macro.')
3457db96d56Sopenharmony_ci    write()
3467db96d56Sopenharmony_ci    for macro in manifest.select({'feature_macro'}):
3477db96d56Sopenharmony_ci        name = macro.name
3487db96d56Sopenharmony_ci        write(f'#ifdef {name}')
3497db96d56Sopenharmony_ci        write(f'    res = PyDict_SetItemString(result, "{name}", Py_True);')
3507db96d56Sopenharmony_ci        write('#else')
3517db96d56Sopenharmony_ci        write(f'    res = PyDict_SetItemString(result, "{name}", Py_False);')
3527db96d56Sopenharmony_ci        write('#endif')
3537db96d56Sopenharmony_ci        write('if (res) {')
3547db96d56Sopenharmony_ci        write('    Py_DECREF(result); return NULL;')
3557db96d56Sopenharmony_ci        write('}')
3567db96d56Sopenharmony_ci        write()
3577db96d56Sopenharmony_ci
3587db96d56Sopenharmony_ci
3597db96d56Sopenharmony_cidef generate_or_check(manifest, args, path, func):
3607db96d56Sopenharmony_ci    """Generate/check a file with a single generator
3617db96d56Sopenharmony_ci
3627db96d56Sopenharmony_ci    Return True if successful; False if a comparison failed.
3637db96d56Sopenharmony_ci    """
3647db96d56Sopenharmony_ci
3657db96d56Sopenharmony_ci    outfile = io.StringIO()
3667db96d56Sopenharmony_ci    func(manifest, args, outfile)
3677db96d56Sopenharmony_ci    generated = outfile.getvalue()
3687db96d56Sopenharmony_ci    existing = path.read_text()
3697db96d56Sopenharmony_ci
3707db96d56Sopenharmony_ci    if generated != existing:
3717db96d56Sopenharmony_ci        if args.generate:
3727db96d56Sopenharmony_ci            path.write_text(generated)
3737db96d56Sopenharmony_ci        else:
3747db96d56Sopenharmony_ci            print(f'File {path} differs from expected!')
3757db96d56Sopenharmony_ci            diff = difflib.unified_diff(
3767db96d56Sopenharmony_ci                generated.splitlines(), existing.splitlines(),
3777db96d56Sopenharmony_ci                str(path), '<expected>',
3787db96d56Sopenharmony_ci                lineterm='',
3797db96d56Sopenharmony_ci            )
3807db96d56Sopenharmony_ci            for line in diff:
3817db96d56Sopenharmony_ci                print(line)
3827db96d56Sopenharmony_ci            return False
3837db96d56Sopenharmony_ci    return True
3847db96d56Sopenharmony_ci
3857db96d56Sopenharmony_ci
3867db96d56Sopenharmony_cidef do_unixy_check(manifest, args):
3877db96d56Sopenharmony_ci    """Check headers & library using "Unixy" tools (GCC/clang, binutils)"""
3887db96d56Sopenharmony_ci    okay = True
3897db96d56Sopenharmony_ci
3907db96d56Sopenharmony_ci    # Get all macros first: we'll need feature macros like HAVE_FORK and
3917db96d56Sopenharmony_ci    # MS_WINDOWS for everything else
3927db96d56Sopenharmony_ci    present_macros = gcc_get_limited_api_macros(['Include/Python.h'])
3937db96d56Sopenharmony_ci    feature_macros = set(m.name for m in manifest.select({'feature_macro'}))
3947db96d56Sopenharmony_ci    feature_macros &= present_macros
3957db96d56Sopenharmony_ci
3967db96d56Sopenharmony_ci    # Check that we have all needed macros
3977db96d56Sopenharmony_ci    expected_macros = set(
3987db96d56Sopenharmony_ci        item.name for item in manifest.select({'macro'})
3997db96d56Sopenharmony_ci    )
4007db96d56Sopenharmony_ci    missing_macros = expected_macros - present_macros
4017db96d56Sopenharmony_ci    okay &= _report_unexpected_items(
4027db96d56Sopenharmony_ci        missing_macros,
4037db96d56Sopenharmony_ci        'Some macros from are not defined from "Include/Python.h"'
4047db96d56Sopenharmony_ci        + 'with Py_LIMITED_API:')
4057db96d56Sopenharmony_ci
4067db96d56Sopenharmony_ci    expected_symbols = set(item.name for item in manifest.select(
4077db96d56Sopenharmony_ci        {'function', 'data'}, include_abi_only=True, ifdef=feature_macros,
4087db96d56Sopenharmony_ci    ))
4097db96d56Sopenharmony_ci
4107db96d56Sopenharmony_ci    # Check the static library (*.a)
4117db96d56Sopenharmony_ci    LIBRARY = sysconfig.get_config_var("LIBRARY")
4127db96d56Sopenharmony_ci    if not LIBRARY:
4137db96d56Sopenharmony_ci        raise Exception("failed to get LIBRARY variable from sysconfig")
4147db96d56Sopenharmony_ci    if os.path.exists(LIBRARY):
4157db96d56Sopenharmony_ci        okay &= binutils_check_library(
4167db96d56Sopenharmony_ci            manifest, LIBRARY, expected_symbols, dynamic=False)
4177db96d56Sopenharmony_ci
4187db96d56Sopenharmony_ci    # Check the dynamic library (*.so)
4197db96d56Sopenharmony_ci    LDLIBRARY = sysconfig.get_config_var("LDLIBRARY")
4207db96d56Sopenharmony_ci    if not LDLIBRARY:
4217db96d56Sopenharmony_ci        raise Exception("failed to get LDLIBRARY variable from sysconfig")
4227db96d56Sopenharmony_ci    okay &= binutils_check_library(
4237db96d56Sopenharmony_ci            manifest, LDLIBRARY, expected_symbols, dynamic=False)
4247db96d56Sopenharmony_ci
4257db96d56Sopenharmony_ci    # Check definitions in the header files
4267db96d56Sopenharmony_ci    expected_defs = set(item.name for item in manifest.select(
4277db96d56Sopenharmony_ci        {'function', 'data'}, include_abi_only=False, ifdef=feature_macros,
4287db96d56Sopenharmony_ci    ))
4297db96d56Sopenharmony_ci    found_defs = gcc_get_limited_api_definitions(['Include/Python.h'])
4307db96d56Sopenharmony_ci    missing_defs = expected_defs - found_defs
4317db96d56Sopenharmony_ci    okay &= _report_unexpected_items(
4327db96d56Sopenharmony_ci        missing_defs,
4337db96d56Sopenharmony_ci        'Some expected declarations were not declared in '
4347db96d56Sopenharmony_ci        + '"Include/Python.h" with Py_LIMITED_API:')
4357db96d56Sopenharmony_ci
4367db96d56Sopenharmony_ci    # Some Limited API macros are defined in terms of private symbols.
4377db96d56Sopenharmony_ci    # These are not part of Limited API (even though they're defined with
4387db96d56Sopenharmony_ci    # Py_LIMITED_API). They must be part of the Stable ABI, though.
4397db96d56Sopenharmony_ci    private_symbols = {n for n in expected_symbols if n.startswith('_')}
4407db96d56Sopenharmony_ci    extra_defs = found_defs - expected_defs - private_symbols
4417db96d56Sopenharmony_ci    okay &= _report_unexpected_items(
4427db96d56Sopenharmony_ci        extra_defs,
4437db96d56Sopenharmony_ci        'Some extra declarations were found in "Include/Python.h" '
4447db96d56Sopenharmony_ci        + 'with Py_LIMITED_API:')
4457db96d56Sopenharmony_ci
4467db96d56Sopenharmony_ci    return okay
4477db96d56Sopenharmony_ci
4487db96d56Sopenharmony_ci
4497db96d56Sopenharmony_cidef _report_unexpected_items(items, msg):
4507db96d56Sopenharmony_ci    """If there are any `items`, report them using "msg" and return false"""
4517db96d56Sopenharmony_ci    if items:
4527db96d56Sopenharmony_ci        print(msg, file=sys.stderr)
4537db96d56Sopenharmony_ci        for item in sorted(items):
4547db96d56Sopenharmony_ci            print(' -', item, file=sys.stderr)
4557db96d56Sopenharmony_ci        return False
4567db96d56Sopenharmony_ci    return True
4577db96d56Sopenharmony_ci
4587db96d56Sopenharmony_ci
4597db96d56Sopenharmony_cidef binutils_get_exported_symbols(library, dynamic=False):
4607db96d56Sopenharmony_ci    """Retrieve exported symbols using the nm(1) tool from binutils"""
4617db96d56Sopenharmony_ci    # Only look at dynamic symbols
4627db96d56Sopenharmony_ci    args = ["nm", "--no-sort"]
4637db96d56Sopenharmony_ci    if dynamic:
4647db96d56Sopenharmony_ci        args.append("--dynamic")
4657db96d56Sopenharmony_ci    args.append(library)
4667db96d56Sopenharmony_ci    proc = subprocess.run(args, stdout=subprocess.PIPE, universal_newlines=True)
4677db96d56Sopenharmony_ci    if proc.returncode:
4687db96d56Sopenharmony_ci        sys.stdout.write(proc.stdout)
4697db96d56Sopenharmony_ci        sys.exit(proc.returncode)
4707db96d56Sopenharmony_ci
4717db96d56Sopenharmony_ci    stdout = proc.stdout.rstrip()
4727db96d56Sopenharmony_ci    if not stdout:
4737db96d56Sopenharmony_ci        raise Exception("command output is empty")
4747db96d56Sopenharmony_ci
4757db96d56Sopenharmony_ci    for line in stdout.splitlines():
4767db96d56Sopenharmony_ci        # Split line '0000000000001b80 D PyTextIOWrapper_Type'
4777db96d56Sopenharmony_ci        if not line:
4787db96d56Sopenharmony_ci            continue
4797db96d56Sopenharmony_ci
4807db96d56Sopenharmony_ci        parts = line.split(maxsplit=2)
4817db96d56Sopenharmony_ci        if len(parts) < 3:
4827db96d56Sopenharmony_ci            continue
4837db96d56Sopenharmony_ci
4847db96d56Sopenharmony_ci        symbol = parts[-1]
4857db96d56Sopenharmony_ci        if MACOS and symbol.startswith("_"):
4867db96d56Sopenharmony_ci            yield symbol[1:]
4877db96d56Sopenharmony_ci        else:
4887db96d56Sopenharmony_ci            yield symbol
4897db96d56Sopenharmony_ci
4907db96d56Sopenharmony_ci
4917db96d56Sopenharmony_cidef binutils_check_library(manifest, library, expected_symbols, dynamic):
4927db96d56Sopenharmony_ci    """Check that library exports all expected_symbols"""
4937db96d56Sopenharmony_ci    available_symbols = set(binutils_get_exported_symbols(library, dynamic))
4947db96d56Sopenharmony_ci    missing_symbols = expected_symbols - available_symbols
4957db96d56Sopenharmony_ci    if missing_symbols:
4967db96d56Sopenharmony_ci        print(textwrap.dedent(f"""\
4977db96d56Sopenharmony_ci            Some symbols from the limited API are missing from {library}:
4987db96d56Sopenharmony_ci                {', '.join(missing_symbols)}
4997db96d56Sopenharmony_ci
5007db96d56Sopenharmony_ci            This error means that there are some missing symbols among the
5017db96d56Sopenharmony_ci            ones exported in the library.
5027db96d56Sopenharmony_ci            This normally means that some symbol, function implementation or
5037db96d56Sopenharmony_ci            a prototype belonging to a symbol in the limited API has been
5047db96d56Sopenharmony_ci            deleted or is missing.
5057db96d56Sopenharmony_ci        """), file=sys.stderr)
5067db96d56Sopenharmony_ci        return False
5077db96d56Sopenharmony_ci    return True
5087db96d56Sopenharmony_ci
5097db96d56Sopenharmony_ci
5107db96d56Sopenharmony_cidef gcc_get_limited_api_macros(headers):
5117db96d56Sopenharmony_ci    """Get all limited API macros from headers.
5127db96d56Sopenharmony_ci
5137db96d56Sopenharmony_ci    Runs the preprocessor over all the header files in "Include" setting
5147db96d56Sopenharmony_ci    "-DPy_LIMITED_API" to the correct value for the running version of the
5157db96d56Sopenharmony_ci    interpreter and extracting all macro definitions (via adding -dM to the
5167db96d56Sopenharmony_ci    compiler arguments).
5177db96d56Sopenharmony_ci
5187db96d56Sopenharmony_ci    Requires Python built with a GCC-compatible compiler. (clang might work)
5197db96d56Sopenharmony_ci    """
5207db96d56Sopenharmony_ci
5217db96d56Sopenharmony_ci    api_hexversion = sys.version_info.major << 24 | sys.version_info.minor << 16
5227db96d56Sopenharmony_ci
5237db96d56Sopenharmony_ci    preprocesor_output_with_macros = subprocess.check_output(
5247db96d56Sopenharmony_ci        sysconfig.get_config_var("CC").split()
5257db96d56Sopenharmony_ci        + [
5267db96d56Sopenharmony_ci            # Prevent the expansion of the exported macros so we can
5277db96d56Sopenharmony_ci            # capture them later
5287db96d56Sopenharmony_ci            "-DSIZEOF_WCHAR_T=4",  # The actual value is not important
5297db96d56Sopenharmony_ci            f"-DPy_LIMITED_API={api_hexversion}",
5307db96d56Sopenharmony_ci            "-I.",
5317db96d56Sopenharmony_ci            "-I./Include",
5327db96d56Sopenharmony_ci            "-dM",
5337db96d56Sopenharmony_ci            "-E",
5347db96d56Sopenharmony_ci        ]
5357db96d56Sopenharmony_ci        + [str(file) for file in headers],
5367db96d56Sopenharmony_ci        text=True,
5377db96d56Sopenharmony_ci    )
5387db96d56Sopenharmony_ci
5397db96d56Sopenharmony_ci    return {
5407db96d56Sopenharmony_ci        target
5417db96d56Sopenharmony_ci        for target in re.findall(
5427db96d56Sopenharmony_ci            r"#define (\w+)", preprocesor_output_with_macros
5437db96d56Sopenharmony_ci        )
5447db96d56Sopenharmony_ci    }
5457db96d56Sopenharmony_ci
5467db96d56Sopenharmony_ci
5477db96d56Sopenharmony_cidef gcc_get_limited_api_definitions(headers):
5487db96d56Sopenharmony_ci    """Get all limited API definitions from headers.
5497db96d56Sopenharmony_ci
5507db96d56Sopenharmony_ci    Run the preprocessor over all the header files in "Include" setting
5517db96d56Sopenharmony_ci    "-DPy_LIMITED_API" to the correct value for the running version of the
5527db96d56Sopenharmony_ci    interpreter.
5537db96d56Sopenharmony_ci
5547db96d56Sopenharmony_ci    The limited API symbols will be extracted from the output of this command
5557db96d56Sopenharmony_ci    as it includes the prototypes and definitions of all the exported symbols
5567db96d56Sopenharmony_ci    that are in the limited api.
5577db96d56Sopenharmony_ci
5587db96d56Sopenharmony_ci    This function does *NOT* extract the macros defined on the limited API
5597db96d56Sopenharmony_ci
5607db96d56Sopenharmony_ci    Requires Python built with a GCC-compatible compiler. (clang might work)
5617db96d56Sopenharmony_ci    """
5627db96d56Sopenharmony_ci    api_hexversion = sys.version_info.major << 24 | sys.version_info.minor << 16
5637db96d56Sopenharmony_ci    preprocesor_output = subprocess.check_output(
5647db96d56Sopenharmony_ci        sysconfig.get_config_var("CC").split()
5657db96d56Sopenharmony_ci        + [
5667db96d56Sopenharmony_ci            # Prevent the expansion of the exported macros so we can capture
5677db96d56Sopenharmony_ci            # them later
5687db96d56Sopenharmony_ci            "-DPyAPI_FUNC=__PyAPI_FUNC",
5697db96d56Sopenharmony_ci            "-DPyAPI_DATA=__PyAPI_DATA",
5707db96d56Sopenharmony_ci            "-DEXPORT_DATA=__EXPORT_DATA",
5717db96d56Sopenharmony_ci            "-D_Py_NO_RETURN=",
5727db96d56Sopenharmony_ci            "-DSIZEOF_WCHAR_T=4",  # The actual value is not important
5737db96d56Sopenharmony_ci            f"-DPy_LIMITED_API={api_hexversion}",
5747db96d56Sopenharmony_ci            "-I.",
5757db96d56Sopenharmony_ci            "-I./Include",
5767db96d56Sopenharmony_ci            "-E",
5777db96d56Sopenharmony_ci        ]
5787db96d56Sopenharmony_ci        + [str(file) for file in headers],
5797db96d56Sopenharmony_ci        text=True,
5807db96d56Sopenharmony_ci        stderr=subprocess.DEVNULL,
5817db96d56Sopenharmony_ci    )
5827db96d56Sopenharmony_ci    stable_functions = set(
5837db96d56Sopenharmony_ci        re.findall(r"__PyAPI_FUNC\(.*?\)\s*(.*?)\s*\(", preprocesor_output)
5847db96d56Sopenharmony_ci    )
5857db96d56Sopenharmony_ci    stable_exported_data = set(
5867db96d56Sopenharmony_ci        re.findall(r"__EXPORT_DATA\((.*?)\)", preprocesor_output)
5877db96d56Sopenharmony_ci    )
5887db96d56Sopenharmony_ci    stable_data = set(
5897db96d56Sopenharmony_ci        re.findall(r"__PyAPI_DATA\(.*?\)[\s\*\(]*([^);]*)\)?.*;", preprocesor_output)
5907db96d56Sopenharmony_ci    )
5917db96d56Sopenharmony_ci    return stable_data | stable_exported_data | stable_functions
5927db96d56Sopenharmony_ci
5937db96d56Sopenharmony_cidef check_private_names(manifest):
5947db96d56Sopenharmony_ci    """Ensure limited API doesn't contain private names
5957db96d56Sopenharmony_ci
5967db96d56Sopenharmony_ci    Names prefixed by an underscore are private by definition.
5977db96d56Sopenharmony_ci    """
5987db96d56Sopenharmony_ci    for name, item in manifest.contents.items():
5997db96d56Sopenharmony_ci        if name.startswith('_') and not item.abi_only:
6007db96d56Sopenharmony_ci            raise ValueError(
6017db96d56Sopenharmony_ci                f'`{name}` is private (underscore-prefixed) and should be '
6027db96d56Sopenharmony_ci                + 'removed from the stable ABI list or or marked `abi_only`')
6037db96d56Sopenharmony_ci
6047db96d56Sopenharmony_cidef check_dump(manifest, filename):
6057db96d56Sopenharmony_ci    """Check that manifest.dump() corresponds to the data.
6067db96d56Sopenharmony_ci
6077db96d56Sopenharmony_ci    Mainly useful when debugging this script.
6087db96d56Sopenharmony_ci    """
6097db96d56Sopenharmony_ci    dumped = tomllib.loads('\n'.join(manifest.dump()))
6107db96d56Sopenharmony_ci    with filename.open('rb') as file:
6117db96d56Sopenharmony_ci        from_file = tomllib.load(file)
6127db96d56Sopenharmony_ci    if dumped != from_file:
6137db96d56Sopenharmony_ci        print(f'Dump differs from loaded data!', file=sys.stderr)
6147db96d56Sopenharmony_ci        diff = difflib.unified_diff(
6157db96d56Sopenharmony_ci            pprint.pformat(dumped).splitlines(),
6167db96d56Sopenharmony_ci            pprint.pformat(from_file).splitlines(),
6177db96d56Sopenharmony_ci            '<dumped>', str(filename),
6187db96d56Sopenharmony_ci            lineterm='',
6197db96d56Sopenharmony_ci        )
6207db96d56Sopenharmony_ci        for line in diff:
6217db96d56Sopenharmony_ci            print(line, file=sys.stderr)
6227db96d56Sopenharmony_ci        return False
6237db96d56Sopenharmony_ci    else:
6247db96d56Sopenharmony_ci        return True
6257db96d56Sopenharmony_ci
6267db96d56Sopenharmony_cidef main():
6277db96d56Sopenharmony_ci    parser = argparse.ArgumentParser(
6287db96d56Sopenharmony_ci        description=__doc__,
6297db96d56Sopenharmony_ci        formatter_class=argparse.RawDescriptionHelpFormatter,
6307db96d56Sopenharmony_ci    )
6317db96d56Sopenharmony_ci    parser.add_argument(
6327db96d56Sopenharmony_ci        "file", type=Path, metavar='FILE',
6337db96d56Sopenharmony_ci        help="file with the stable abi manifest",
6347db96d56Sopenharmony_ci    )
6357db96d56Sopenharmony_ci    parser.add_argument(
6367db96d56Sopenharmony_ci        "--generate", action='store_true',
6377db96d56Sopenharmony_ci        help="generate file(s), rather than just checking them",
6387db96d56Sopenharmony_ci    )
6397db96d56Sopenharmony_ci    parser.add_argument(
6407db96d56Sopenharmony_ci        "--generate-all", action='store_true',
6417db96d56Sopenharmony_ci        help="as --generate, but generate all file(s) using default filenames."
6427db96d56Sopenharmony_ci            + " (unlike --all, does not run any extra checks)",
6437db96d56Sopenharmony_ci    )
6447db96d56Sopenharmony_ci    parser.add_argument(
6457db96d56Sopenharmony_ci        "-a", "--all", action='store_true',
6467db96d56Sopenharmony_ci        help="run all available checks using default filenames",
6477db96d56Sopenharmony_ci    )
6487db96d56Sopenharmony_ci    parser.add_argument(
6497db96d56Sopenharmony_ci        "-l", "--list", action='store_true',
6507db96d56Sopenharmony_ci        help="list available generators and their default filenames; then exit",
6517db96d56Sopenharmony_ci    )
6527db96d56Sopenharmony_ci    parser.add_argument(
6537db96d56Sopenharmony_ci        "--dump", action='store_true',
6547db96d56Sopenharmony_ci        help="dump the manifest contents (used for debugging the parser)",
6557db96d56Sopenharmony_ci    )
6567db96d56Sopenharmony_ci
6577db96d56Sopenharmony_ci    actions_group = parser.add_argument_group('actions')
6587db96d56Sopenharmony_ci    for gen in generators:
6597db96d56Sopenharmony_ci        actions_group.add_argument(
6607db96d56Sopenharmony_ci            gen.arg_name, dest=gen.var_name,
6617db96d56Sopenharmony_ci            type=str, nargs="?", default=MISSING,
6627db96d56Sopenharmony_ci            metavar='FILENAME',
6637db96d56Sopenharmony_ci            help=gen.__doc__,
6647db96d56Sopenharmony_ci        )
6657db96d56Sopenharmony_ci    actions_group.add_argument(
6667db96d56Sopenharmony_ci        '--unixy-check', action='store_true',
6677db96d56Sopenharmony_ci        help=do_unixy_check.__doc__,
6687db96d56Sopenharmony_ci    )
6697db96d56Sopenharmony_ci    args = parser.parse_args()
6707db96d56Sopenharmony_ci
6717db96d56Sopenharmony_ci    base_path = args.file.parent.parent
6727db96d56Sopenharmony_ci
6737db96d56Sopenharmony_ci    if args.list:
6747db96d56Sopenharmony_ci        for gen in generators:
6757db96d56Sopenharmony_ci            print(f'{gen.arg_name}: {base_path / gen.default_path}')
6767db96d56Sopenharmony_ci        sys.exit(0)
6777db96d56Sopenharmony_ci
6787db96d56Sopenharmony_ci    run_all_generators = args.generate_all
6797db96d56Sopenharmony_ci
6807db96d56Sopenharmony_ci    if args.generate_all:
6817db96d56Sopenharmony_ci        args.generate = True
6827db96d56Sopenharmony_ci
6837db96d56Sopenharmony_ci    if args.all:
6847db96d56Sopenharmony_ci        run_all_generators = True
6857db96d56Sopenharmony_ci        args.unixy_check = True
6867db96d56Sopenharmony_ci
6877db96d56Sopenharmony_ci    try:
6887db96d56Sopenharmony_ci        file = args.file.open('rb')
6897db96d56Sopenharmony_ci    except FileNotFoundError as err:
6907db96d56Sopenharmony_ci        if args.file.suffix == '.txt':
6917db96d56Sopenharmony_ci            # Provide a better error message
6927db96d56Sopenharmony_ci            suggestion = args.file.with_suffix('.toml')
6937db96d56Sopenharmony_ci            raise FileNotFoundError(
6947db96d56Sopenharmony_ci                f'{args.file} not found. Did you mean {suggestion} ?') from err
6957db96d56Sopenharmony_ci        raise
6967db96d56Sopenharmony_ci    with file:
6977db96d56Sopenharmony_ci        manifest = parse_manifest(file)
6987db96d56Sopenharmony_ci
6997db96d56Sopenharmony_ci    check_private_names(manifest)
7007db96d56Sopenharmony_ci
7017db96d56Sopenharmony_ci    # Remember results of all actions (as booleans).
7027db96d56Sopenharmony_ci    # At the end we'll check that at least one action was run,
7037db96d56Sopenharmony_ci    # and also fail if any are false.
7047db96d56Sopenharmony_ci    results = {}
7057db96d56Sopenharmony_ci
7067db96d56Sopenharmony_ci    if args.dump:
7077db96d56Sopenharmony_ci        for line in manifest.dump():
7087db96d56Sopenharmony_ci            print(line)
7097db96d56Sopenharmony_ci        results['dump'] = check_dump(manifest, args.file)
7107db96d56Sopenharmony_ci
7117db96d56Sopenharmony_ci    for gen in generators:
7127db96d56Sopenharmony_ci        filename = getattr(args, gen.var_name)
7137db96d56Sopenharmony_ci        if filename is None or (run_all_generators and filename is MISSING):
7147db96d56Sopenharmony_ci            filename = base_path / gen.default_path
7157db96d56Sopenharmony_ci        elif filename is MISSING:
7167db96d56Sopenharmony_ci            continue
7177db96d56Sopenharmony_ci
7187db96d56Sopenharmony_ci        results[gen.var_name] = generate_or_check(manifest, args, filename, gen)
7197db96d56Sopenharmony_ci
7207db96d56Sopenharmony_ci    if args.unixy_check:
7217db96d56Sopenharmony_ci        results['unixy_check'] = do_unixy_check(manifest, args)
7227db96d56Sopenharmony_ci
7237db96d56Sopenharmony_ci    if not results:
7247db96d56Sopenharmony_ci        if args.generate:
7257db96d56Sopenharmony_ci            parser.error('No file specified. Use --help for usage.')
7267db96d56Sopenharmony_ci        parser.error('No check specified. Use --help for usage.')
7277db96d56Sopenharmony_ci
7287db96d56Sopenharmony_ci    failed_results = [name for name, result in results.items() if not result]
7297db96d56Sopenharmony_ci
7307db96d56Sopenharmony_ci    if failed_results:
7317db96d56Sopenharmony_ci        raise Exception(f"""
7327db96d56Sopenharmony_ci        These checks related to the stable ABI did not succeed:
7337db96d56Sopenharmony_ci            {', '.join(failed_results)}
7347db96d56Sopenharmony_ci
7357db96d56Sopenharmony_ci        If you see diffs in the output, files derived from the stable
7367db96d56Sopenharmony_ci        ABI manifest the were not regenerated.
7377db96d56Sopenharmony_ci        Run `make regen-limited-abi` to fix this.
7387db96d56Sopenharmony_ci
7397db96d56Sopenharmony_ci        Otherwise, see the error(s) above.
7407db96d56Sopenharmony_ci
7417db96d56Sopenharmony_ci        The stable ABI manifest is at: {args.file}
7427db96d56Sopenharmony_ci        Note that there is a process to follow when modifying it.
7437db96d56Sopenharmony_ci
7447db96d56Sopenharmony_ci        You can read more about the limited API and its contracts at:
7457db96d56Sopenharmony_ci
7467db96d56Sopenharmony_ci        https://docs.python.org/3/c-api/stable.html
7477db96d56Sopenharmony_ci
7487db96d56Sopenharmony_ci        And in PEP 384:
7497db96d56Sopenharmony_ci
7507db96d56Sopenharmony_ci        https://peps.python.org/pep-0384/
7517db96d56Sopenharmony_ci        """)
7527db96d56Sopenharmony_ci
7537db96d56Sopenharmony_ci
7547db96d56Sopenharmony_ciif __name__ == "__main__":
7557db96d56Sopenharmony_ci    main()
756