mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	gh-91324: Convert the stable ABI manifest to TOML (GH-92026)
This commit is contained in:
		
							parent
							
								
									89c6b2b8f6
								
							
						
					
					
						commit
						83bce8ef14
					
				
					 5 changed files with 2412 additions and 2415 deletions
				
			
		
							
								
								
									
										17
									
								
								Lib/test/test_stable_abi_ctypes.py
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								Lib/test/test_stable_abi_ctypes.py
									
										
									
										generated
									
									
									
								
							|  | @ -20,7 +20,8 @@ class TestStableABIAvailability(unittest.TestCase): | ||||||
|                 ctypes_test.pythonapi[symbol_name] |                 ctypes_test.pythonapi[symbol_name] | ||||||
| 
 | 
 | ||||||
|     def test_feature_macros(self): |     def test_feature_macros(self): | ||||||
|         self.assertEqual(set(get_feature_macros()), EXPECTED_IFDEFS) |         self.assertEqual( | ||||||
|  |             set(get_feature_macros()), EXPECTED_FEATURE_MACROS) | ||||||
| 
 | 
 | ||||||
|     # The feature macros for Windows are used in creating the DLL |     # The feature macros for Windows are used in creating the DLL | ||||||
|     # definition, so they must be known on all platforms. |     # definition, so they must be known on all platforms. | ||||||
|  | @ -28,7 +29,7 @@ class TestStableABIAvailability(unittest.TestCase): | ||||||
|     # the reality. |     # the reality. | ||||||
|     @unittest.skipIf(sys.platform != "win32", "Windows specific test") |     @unittest.skipIf(sys.platform != "win32", "Windows specific test") | ||||||
|     def test_windows_feature_macros(self): |     def test_windows_feature_macros(self): | ||||||
|         for name, value in WINDOWS_IFDEFS.items(): |         for name, value in WINDOWS_FEATURE_MACROS.items(): | ||||||
|             if value != 'maybe': |             if value != 'maybe': | ||||||
|                 with self.subTest(name): |                 with self.subTest(name): | ||||||
|                     self.assertEqual(feature_macros[name], value) |                     self.assertEqual(feature_macros[name], value) | ||||||
|  | @ -909,5 +910,13 @@ if feature_macros['Py_REF_DEBUG']: | ||||||
|         '_Py_RefTotal', |         '_Py_RefTotal', | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| EXPECTED_IFDEFS = set(['HAVE_FORK', 'MS_WINDOWS', 'PY_HAVE_THREAD_NATIVE_ID', 'Py_REF_DEBUG', 'USE_STACKCHECK']) | EXPECTED_FEATURE_MACROS = set(['HAVE_FORK', | ||||||
| WINDOWS_IFDEFS = {'MS_WINDOWS': True, 'HAVE_FORK': False, 'USE_STACKCHECK': 'maybe', 'PY_HAVE_THREAD_NATIVE_ID': True, 'Py_REF_DEBUG': 'maybe'} |  'MS_WINDOWS', | ||||||
|  |  'PY_HAVE_THREAD_NATIVE_ID', | ||||||
|  |  'Py_REF_DEBUG', | ||||||
|  |  'USE_STACKCHECK']) | ||||||
|  | WINDOWS_FEATURE_MACROS = {'HAVE_FORK': False, | ||||||
|  |  'MS_WINDOWS': True, | ||||||
|  |  'PY_HAVE_THREAD_NATIVE_ID': True, | ||||||
|  |  'Py_REF_DEBUG': 'maybe', | ||||||
|  |  'USE_STACKCHECK': 'maybe'} | ||||||
|  |  | ||||||
|  | @ -1199,7 +1199,7 @@ regen-global-objects: $(srcdir)/Tools/scripts/generate_global_objects.py | ||||||
| # ABI | # ABI | ||||||
| 
 | 
 | ||||||
| regen-limited-abi: all | regen-limited-abi: all | ||||||
| 	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/stable_abi.py --generate-all $(srcdir)/Misc/stable_abi.txt | 	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/stable_abi.py --generate-all $(srcdir)/Misc/stable_abi.toml | ||||||
| 
 | 
 | ||||||
| ############################################################################ | ############################################################################ | ||||||
| # Regenerate all generated files | # Regenerate all generated files | ||||||
|  | @ -2476,7 +2476,7 @@ patchcheck: @DEF_MAKE_RULE@ | ||||||
| 	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/patchcheck.py | 	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/patchcheck.py | ||||||
| 
 | 
 | ||||||
| check-limited-abi: all | check-limited-abi: all | ||||||
| 	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/stable_abi.py --all $(srcdir)/Misc/stable_abi.txt | 	$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/scripts/stable_abi.py --all $(srcdir)/Misc/stable_abi.toml | ||||||
| 
 | 
 | ||||||
| .PHONY: update-config | .PHONY: update-config | ||||||
| update-config: | update-config: | ||||||
|  |  | ||||||
							
								
								
									
										2275
									
								
								Misc/stable_abi.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2275
									
								
								Misc/stable_abi.toml
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										2284
									
								
								Misc/stable_abi.txt
									
										
									
									
									
								
							
							
						
						
									
										2284
									
								
								Misc/stable_abi.txt
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -14,8 +14,10 @@ | ||||||
| import sysconfig | import sysconfig | ||||||
| import argparse | import argparse | ||||||
| import textwrap | import textwrap | ||||||
|  | import tomllib | ||||||
| import difflib | import difflib | ||||||
| import shutil | import shutil | ||||||
|  | import pprint | ||||||
| import sys | import sys | ||||||
| import os | import os | ||||||
| import os.path | import os.path | ||||||
|  | @ -46,17 +48,15 @@ | ||||||
| UNIXY = MACOS or (sys.platform == "linux")  # XXX should this be "not Windows"? | UNIXY = MACOS or (sys.platform == "linux")  # XXX should this be "not Windows"? | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # The stable ABI manifest (Misc/stable_abi.txt) exists only to fill the | # The stable ABI manifest (Misc/stable_abi.toml) exists only to fill the | ||||||
| # following dataclasses. | # following dataclasses. | ||||||
| # Feel free to change its syntax (and the `parse_manifest` function) | # Feel free to change its syntax (and the `parse_manifest` function) | ||||||
| # to better serve that purpose (while keeping it human-readable). | # to better serve that purpose (while keeping it human-readable). | ||||||
| 
 | 
 | ||||||
| @dataclasses.dataclass |  | ||||||
| class Manifest: | class Manifest: | ||||||
|     """Collection of `ABIItem`s forming the stable ABI/limited API.""" |     """Collection of `ABIItem`s forming the stable ABI/limited API.""" | ||||||
| 
 |     def __init__(self): | ||||||
|     kind = 'manifest' |         self.contents = dict() | ||||||
|     contents: dict = dataclasses.field(default_factory=dict) |  | ||||||
| 
 | 
 | ||||||
|     def add(self, item): |     def add(self, item): | ||||||
|         if item.name in self.contents: |         if item.name in self.contents: | ||||||
|  | @ -65,14 +65,6 @@ def add(self, item): | ||||||
|             raise ValueError(f'duplicate ABI item {item.name}') |             raise ValueError(f'duplicate ABI item {item.name}') | ||||||
|         self.contents[item.name] = item |         self.contents[item.name] = item | ||||||
| 
 | 
 | ||||||
|     @property |  | ||||||
|     def feature_defines(self): |  | ||||||
|         """Return all feature defines which affect what's available |  | ||||||
| 
 |  | ||||||
|         These are e.g. HAVE_FORK and MS_WINDOWS. |  | ||||||
|         """ |  | ||||||
|         return set(item.ifdef for item in self.contents.values()) - {None} |  | ||||||
| 
 |  | ||||||
|     def select(self, kinds, *, include_abi_only=True, ifdef=None): |     def select(self, kinds, *, include_abi_only=True, ifdef=None): | ||||||
|         """Yield selected items of the manifest |         """Yield selected items of the manifest | ||||||
| 
 | 
 | ||||||
|  | @ -81,7 +73,7 @@ def select(self, kinds, *, include_abi_only=True, ifdef=None): | ||||||
|             stable ABI. |             stable ABI. | ||||||
|             If False, include only items from the limited API |             If False, include only items from the limited API | ||||||
|             (i.e. items people should use today) |             (i.e. items people should use today) | ||||||
|         ifdef: set of feature defines (e.g. {'HAVE_FORK', 'MS_WINDOWS'}). |         ifdef: set of feature macros (e.g. {'HAVE_FORK', 'MS_WINDOWS'}). | ||||||
|             If None (default), items are not filtered by this. (This is |             If None (default), items are not filtered by this. (This is | ||||||
|             different from the empty set, which filters out all such |             different from the empty set, which filters out all such | ||||||
|             conditional items.) |             conditional items.) | ||||||
|  | @ -99,109 +91,74 @@ def select(self, kinds, *, include_abi_only=True, ifdef=None): | ||||||
| 
 | 
 | ||||||
|     def dump(self): |     def dump(self): | ||||||
|         """Yield lines to recreate the manifest file (sans comments/newlines)""" |         """Yield lines to recreate the manifest file (sans comments/newlines)""" | ||||||
|         # Recursive in preparation for struct member & function argument nodes |  | ||||||
|         for item in self.contents.values(): |         for item in self.contents.values(): | ||||||
|             yield from item.dump(indent=0) |             fields = dataclasses.fields(item) | ||||||
|  |             yield f"[{item.kind}.{item.name}]" | ||||||
|  |             for field in fields: | ||||||
|  |                 if field.name in {'name', 'value', 'kind'}: | ||||||
|  |                     continue | ||||||
|  |                 value = getattr(item, field.name) | ||||||
|  |                 if value == field.default: | ||||||
|  |                     pass | ||||||
|  |                 elif value is True: | ||||||
|  |                     yield f"    {field.name} = true" | ||||||
|  |                 elif value: | ||||||
|  |                     yield f"    {field.name} = {value!r}" | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | itemclasses = {} | ||||||
|  | def itemclass(kind): | ||||||
|  |     """Register the decorated class in `itemclasses`""" | ||||||
|  |     def decorator(cls): | ||||||
|  |         itemclasses[kind] = cls | ||||||
|  |         return cls | ||||||
|  |     return decorator | ||||||
|  | 
 | ||||||
|  | @itemclass('function') | ||||||
|  | @itemclass('macro') | ||||||
|  | @itemclass('data') | ||||||
|  | @itemclass('const') | ||||||
|  | @itemclass('typedef') | ||||||
| @dataclasses.dataclass | @dataclasses.dataclass | ||||||
| class ABIItem: | class ABIItem: | ||||||
|     """Information on one item (function, macro, struct, etc.)""" |     """Information on one item (function, macro, struct, etc.)""" | ||||||
| 
 | 
 | ||||||
|     kind: str |  | ||||||
|     name: str |     name: str | ||||||
|  |     kind: str | ||||||
|     added: str = None |     added: str = None | ||||||
|     contents: list = dataclasses.field(default_factory=list) |  | ||||||
|     abi_only: bool = False |     abi_only: bool = False | ||||||
|     ifdef: str = None |     ifdef: str = None | ||||||
|     struct_abi_kind: str = None | 
 | ||||||
|     members: list = None | @itemclass('feature_macro') | ||||||
|     doc: str = None | @dataclasses.dataclass(kw_only=True) | ||||||
|  | class FeatureMacro(ABIItem): | ||||||
|  |     name: str | ||||||
|  |     doc: str | ||||||
|     windows: bool = False |     windows: bool = False | ||||||
|  |     abi_only: bool = True | ||||||
| 
 | 
 | ||||||
|     KINDS = frozenset({ | @itemclass('struct') | ||||||
|         'struct', 'function', 'macro', 'data', 'const', 'typedef', 'ifdef', | @dataclasses.dataclass(kw_only=True) | ||||||
|     }) | class Struct(ABIItem): | ||||||
|  |     struct_abi_kind: str | ||||||
|  |     members: list = None | ||||||
| 
 | 
 | ||||||
|     def dump(self, indent=0): |  | ||||||
|         yield f"{'    ' * indent}{self.kind} {self.name}" |  | ||||||
|         if self.added: |  | ||||||
|             yield f"{'    ' * (indent+1)}added {self.added}" |  | ||||||
|         if self.ifdef: |  | ||||||
|             yield f"{'    ' * (indent+1)}ifdef {self.ifdef}" |  | ||||||
|         if self.abi_only: |  | ||||||
|             yield f"{'    ' * (indent+1)}abi_only" |  | ||||||
| 
 | 
 | ||||||
| def parse_manifest(file): | def parse_manifest(file): | ||||||
|     """Parse the given file (iterable of lines) to a Manifest""" |     """Parse the given file (iterable of lines) to a Manifest""" | ||||||
| 
 | 
 | ||||||
|     LINE_RE = re.compile('(?P<indent>[ ]*)(?P<kind>[^ ]+)[ ]*(?P<content>.*)') |  | ||||||
|     manifest = Manifest() |     manifest = Manifest() | ||||||
| 
 | 
 | ||||||
|     # parents of currently processed line, each with its indentation level |     data = tomllib.load(file) | ||||||
|     levels = [(manifest, -1)] |  | ||||||
| 
 | 
 | ||||||
|     def raise_error(msg): |     for kind, itemclass in itemclasses.items(): | ||||||
|         raise SyntaxError(f'line {lineno}: {msg}') |         for name, item_data in data[kind].items(): | ||||||
| 
 |             try: | ||||||
|     for lineno, line in enumerate(file, start=1): |                 item = itemclass(name=name, kind=kind, **item_data) | ||||||
|         line, sep, comment = line.partition('#') |                 manifest.add(item) | ||||||
|         line = line.rstrip() |             except BaseException as exc: | ||||||
|         if not line: |                 exc.add_note(f'in {kind} {name}') | ||||||
|             continue |                 raise | ||||||
|         match = LINE_RE.fullmatch(line) |  | ||||||
|         if not match: |  | ||||||
|             raise_error(f'invalid syntax: {line}') |  | ||||||
|         level = len(match['indent']) |  | ||||||
|         kind = match['kind'] |  | ||||||
|         content = match['content'] |  | ||||||
|         while level <= levels[-1][1]: |  | ||||||
|             levels.pop() |  | ||||||
|         parent = levels[-1][0] |  | ||||||
|         entry = None |  | ||||||
|         if parent.kind == 'manifest': |  | ||||||
|             if kind not in kind in ABIItem.KINDS: |  | ||||||
|                 raise_error(f'{kind} cannot go in {parent.kind}') |  | ||||||
|             entry = ABIItem(kind, content) |  | ||||||
|             parent.add(entry) |  | ||||||
|         elif kind in {'added', 'ifdef'}: |  | ||||||
|             if parent.kind not in ABIItem.KINDS: |  | ||||||
|                 raise_error(f'{kind} cannot go in {parent.kind}') |  | ||||||
|             setattr(parent, kind, content) |  | ||||||
|         elif kind in {'abi_only'}: |  | ||||||
|             if parent.kind not in {'function', 'data'}: |  | ||||||
|                 raise_error(f'{kind} cannot go in {parent.kind}') |  | ||||||
|             parent.abi_only = True |  | ||||||
|         elif kind in {'members', 'full-abi', 'opaque'}: |  | ||||||
|             if parent.kind not in {'struct'}: |  | ||||||
|                 raise_error(f'{kind} cannot go in {parent.kind}') |  | ||||||
|             if prev := getattr(parent, 'struct_abi_kind', None): |  | ||||||
|                 raise_error( |  | ||||||
|                     f'{parent.name} already has {prev}, cannot add {kind}') |  | ||||||
|             parent.struct_abi_kind = kind |  | ||||||
|             if kind == 'members': |  | ||||||
|                 parent.members = content.split() |  | ||||||
|         elif kind in {'doc'}: |  | ||||||
|             if parent.kind not in {'ifdef'}: |  | ||||||
|                 raise_error(f'{kind} cannot go in {parent.kind}') |  | ||||||
|             parent.doc = content |  | ||||||
|         elif kind in {'windows'}: |  | ||||||
|             if parent.kind not in {'ifdef'}: |  | ||||||
|                 raise_error(f'{kind} cannot go in {parent.kind}') |  | ||||||
|             if not content: |  | ||||||
|                 parent.windows = True |  | ||||||
|             elif content == 'maybe': |  | ||||||
|                 parent.windows = content |  | ||||||
|             else: |  | ||||||
|                 raise_error(f'Unexpected: {content}') |  | ||||||
|         else: |  | ||||||
|             raise_error(f"unknown kind {kind!r}") |  | ||||||
|             # When adding more, update the comment in stable_abi.txt. |  | ||||||
|         levels.append((entry, level)) |  | ||||||
| 
 |  | ||||||
|     ifdef_names = {i.name for i in manifest.select({'ifdef'})} |  | ||||||
|     for item in manifest.contents.values(): |  | ||||||
|         if item.ifdef and item.ifdef not in ifdef_names: |  | ||||||
|             raise ValueError(f'{item.name} uses undeclared ifdef {item.ifdef}') |  | ||||||
| 
 | 
 | ||||||
|     return manifest |     return manifest | ||||||
| 
 | 
 | ||||||
|  | @ -246,12 +203,14 @@ def gen_python3dll(manifest, args, outfile): | ||||||
|     def sort_key(item): |     def sort_key(item): | ||||||
|         return item.name.lower() |         return item.name.lower() | ||||||
| 
 | 
 | ||||||
|     windows_ifdefs = { |     windows_feature_macros = { | ||||||
|         item.name for item in manifest.select({'ifdef'}) if item.windows |         item.name for item in manifest.select({'feature_macro'}) if item.windows | ||||||
|     } |     } | ||||||
|     for item in sorted( |     for item in sorted( | ||||||
|             manifest.select( |             manifest.select( | ||||||
|                 {'function'}, include_abi_only=True, ifdef=windows_ifdefs), |                 {'function'}, | ||||||
|  |                 include_abi_only=True, | ||||||
|  |                 ifdef=windows_feature_macros), | ||||||
|             key=sort_key): |             key=sort_key): | ||||||
|         write(f'EXPORT_FUNC({item.name})') |         write(f'EXPORT_FUNC({item.name})') | ||||||
| 
 | 
 | ||||||
|  | @ -259,7 +218,9 @@ def sort_key(item): | ||||||
| 
 | 
 | ||||||
|     for item in sorted( |     for item in sorted( | ||||||
|             manifest.select( |             manifest.select( | ||||||
|                 {'data'}, include_abi_only=True, ifdef=windows_ifdefs), |                 {'data'}, | ||||||
|  |                 include_abi_only=True, | ||||||
|  |                 ifdef=windows_feature_macros), | ||||||
|             key=sort_key): |             key=sort_key): | ||||||
|         write(f'EXPORT_DATA({item.name})') |         write(f'EXPORT_DATA({item.name})') | ||||||
| 
 | 
 | ||||||
|  | @ -285,17 +246,20 @@ def gen_doc_annotations(manifest, args, outfile): | ||||||
|             ifdef_note = manifest.contents[item.ifdef].doc |             ifdef_note = manifest.contents[item.ifdef].doc | ||||||
|         else: |         else: | ||||||
|             ifdef_note = None |             ifdef_note = None | ||||||
|         writer.writerow({ |         row = { | ||||||
|             'role': REST_ROLES[item.kind], |             'role': REST_ROLES[item.kind], | ||||||
|             'name': item.name, |             'name': item.name, | ||||||
|             'added': item.added, |             'added': item.added, | ||||||
|             'ifdef_note': ifdef_note, |             'ifdef_note': ifdef_note} | ||||||
|             'struct_abi_kind': item.struct_abi_kind}) |         rows = [row] | ||||||
|         for member_name in item.members or (): |         if item.kind == 'struct': | ||||||
|             writer.writerow({ |             row['struct_abi_kind'] = item.struct_abi_kind | ||||||
|                 'role': 'member', |             for member_name in item.members or (): | ||||||
|                 'name': f'{item.name}.{member_name}', |                 rows.append({ | ||||||
|                 'added': item.added}) |                     'role': 'member', | ||||||
|  |                     'name': f'{item.name}.{member_name}', | ||||||
|  |                     'added': item.added}) | ||||||
|  |         writer.writerows(rows) | ||||||
| 
 | 
 | ||||||
| @generator("ctypes_test", 'Lib/test/test_stable_abi_ctypes.py') | @generator("ctypes_test", 'Lib/test/test_stable_abi_ctypes.py') | ||||||
| def gen_ctypes_test(manifest, args, outfile): | def gen_ctypes_test(manifest, args, outfile): | ||||||
|  | @ -323,7 +287,8 @@ def test_available_symbols(self): | ||||||
|                         ctypes_test.pythonapi[symbol_name] |                         ctypes_test.pythonapi[symbol_name] | ||||||
| 
 | 
 | ||||||
|             def test_feature_macros(self): |             def test_feature_macros(self): | ||||||
|                 self.assertEqual(set(get_feature_macros()), EXPECTED_IFDEFS) |                 self.assertEqual( | ||||||
|  |                     set(get_feature_macros()), EXPECTED_FEATURE_MACROS) | ||||||
| 
 | 
 | ||||||
|             # The feature macros for Windows are used in creating the DLL |             # The feature macros for Windows are used in creating the DLL | ||||||
|             # definition, so they must be known on all platforms. |             # definition, so they must be known on all platforms. | ||||||
|  | @ -331,7 +296,7 @@ def test_feature_macros(self): | ||||||
|             # the reality. |             # the reality. | ||||||
|             @unittest.skipIf(sys.platform != "win32", "Windows specific test") |             @unittest.skipIf(sys.platform != "win32", "Windows specific test") | ||||||
|             def test_windows_feature_macros(self): |             def test_windows_feature_macros(self): | ||||||
|                 for name, value in WINDOWS_IFDEFS.items(): |                 for name, value in WINDOWS_FEATURE_MACROS.items(): | ||||||
|                     if value != 'maybe': |                     if value != 'maybe': | ||||||
|                         with self.subTest(name): |                         with self.subTest(name): | ||||||
|                             self.assertEqual(feature_macros[name], value) |                             self.assertEqual(feature_macros[name], value) | ||||||
|  | @ -342,7 +307,7 @@ def test_windows_feature_macros(self): | ||||||
|         {'function', 'data'}, |         {'function', 'data'}, | ||||||
|         include_abi_only=True, |         include_abi_only=True, | ||||||
|     ) |     ) | ||||||
|     ifdef_items = {} |     optional_items = {} | ||||||
|     for item in items: |     for item in items: | ||||||
|         if item.name in ( |         if item.name in ( | ||||||
|                 # Some symbols aren't exported on all platforms. |                 # Some symbols aren't exported on all platforms. | ||||||
|  | @ -351,23 +316,23 @@ def test_windows_feature_macros(self): | ||||||
|             ): |             ): | ||||||
|             continue |             continue | ||||||
|         if item.ifdef: |         if item.ifdef: | ||||||
|             ifdef_items.setdefault(item.ifdef, []).append(item.name) |             optional_items.setdefault(item.ifdef, []).append(item.name) | ||||||
|         else: |         else: | ||||||
|             write(f'    "{item.name}",') |             write(f'    "{item.name}",') | ||||||
|     write(")") |     write(")") | ||||||
|     for ifdef, names in ifdef_items.items(): |     for ifdef, names in optional_items.items(): | ||||||
|         write(f"if feature_macros[{ifdef!r}]:") |         write(f"if feature_macros[{ifdef!r}]:") | ||||||
|         write(f"    SYMBOL_NAMES += (") |         write(f"    SYMBOL_NAMES += (") | ||||||
|         for name in names: |         for name in names: | ||||||
|             write(f"        {name!r},") |             write(f"        {name!r},") | ||||||
|         write("    )") |         write("    )") | ||||||
|     write("") |     write("") | ||||||
|     write(f"EXPECTED_IFDEFS = set({sorted(ifdef_items)})") |     feature_macros = list(manifest.select({'feature_macro'})) | ||||||
|  |     feature_names = sorted(m.name for m in feature_macros) | ||||||
|  |     write(f"EXPECTED_FEATURE_MACROS = set({pprint.pformat(feature_names)})") | ||||||
| 
 | 
 | ||||||
|     windows_ifdef_values = { |     windows_feature_macros = {m.name: m.windows for m in feature_macros} | ||||||
|         name: manifest.contents[name].windows for name in ifdef_items |     write(f"WINDOWS_FEATURE_MACROS = {pprint.pformat(windows_feature_macros)}") | ||||||
|     } |  | ||||||
|     write(f"WINDOWS_IFDEFS = {windows_ifdef_values}") |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @generator("testcapi_feature_macros", 'Modules/_testcapi_feature_macros.inc') | @generator("testcapi_feature_macros", 'Modules/_testcapi_feature_macros.inc') | ||||||
|  | @ -378,7 +343,7 @@ def gen_testcapi_feature_macros(manifest, args, outfile): | ||||||
|     write() |     write() | ||||||
|     write('// Add an entry in dict `result` for each Stable ABI feature macro.') |     write('// Add an entry in dict `result` for each Stable ABI feature macro.') | ||||||
|     write() |     write() | ||||||
|     for macro in manifest.select({'ifdef'}): |     for macro in manifest.select({'feature_macro'}): | ||||||
|         name = macro.name |         name = macro.name | ||||||
|         write(f'#ifdef {name}') |         write(f'#ifdef {name}') | ||||||
|         write(f'    res = PyDict_SetItemString(result, "{name}", Py_True);') |         write(f'    res = PyDict_SetItemString(result, "{name}", Py_True);') | ||||||
|  | @ -425,7 +390,8 @@ def do_unixy_check(manifest, args): | ||||||
|     # Get all macros first: we'll need feature macros like HAVE_FORK and |     # Get all macros first: we'll need feature macros like HAVE_FORK and | ||||||
|     # MS_WINDOWS for everything else |     # MS_WINDOWS for everything else | ||||||
|     present_macros = gcc_get_limited_api_macros(['Include/Python.h']) |     present_macros = gcc_get_limited_api_macros(['Include/Python.h']) | ||||||
|     feature_defines = manifest.feature_defines & present_macros |     feature_macros = set(m.name for m in manifest.select({'feature_macro'})) | ||||||
|  |     feature_macros &= present_macros | ||||||
| 
 | 
 | ||||||
|     # Check that we have all needed macros |     # Check that we have all needed macros | ||||||
|     expected_macros = set( |     expected_macros = set( | ||||||
|  | @ -438,7 +404,7 @@ def do_unixy_check(manifest, args): | ||||||
|         + 'with Py_LIMITED_API:') |         + 'with Py_LIMITED_API:') | ||||||
| 
 | 
 | ||||||
|     expected_symbols = set(item.name for item in manifest.select( |     expected_symbols = set(item.name for item in manifest.select( | ||||||
|         {'function', 'data'}, include_abi_only=True, ifdef=feature_defines, |         {'function', 'data'}, include_abi_only=True, ifdef=feature_macros, | ||||||
|     )) |     )) | ||||||
| 
 | 
 | ||||||
|     # Check the static library (*.a) |     # Check the static library (*.a) | ||||||
|  | @ -458,7 +424,7 @@ def do_unixy_check(manifest, args): | ||||||
| 
 | 
 | ||||||
|     # Check definitions in the header files |     # Check definitions in the header files | ||||||
|     expected_defs = set(item.name for item in manifest.select( |     expected_defs = set(item.name for item in manifest.select( | ||||||
|         {'function', 'data'}, include_abi_only=False, ifdef=feature_defines, |         {'function', 'data'}, include_abi_only=False, ifdef=feature_macros, | ||||||
|     )) |     )) | ||||||
|     found_defs = gcc_get_limited_api_definitions(['Include/Python.h']) |     found_defs = gcc_get_limited_api_definitions(['Include/Python.h']) | ||||||
|     missing_defs = expected_defs - found_defs |     missing_defs = expected_defs - found_defs | ||||||
|  | @ -635,6 +601,28 @@ def check_private_names(manifest): | ||||||
|                 f'`{name}` is private (underscore-prefixed) and should be ' |                 f'`{name}` is private (underscore-prefixed) and should be ' | ||||||
|                 + 'removed from the stable ABI list or or marked `abi_only`') |                 + 'removed from the stable ABI list or or marked `abi_only`') | ||||||
| 
 | 
 | ||||||
|  | def check_dump(manifest, filename): | ||||||
|  |     """Check that manifest.dump() corresponds to the data. | ||||||
|  | 
 | ||||||
|  |     Mainly useful when debugging this script. | ||||||
|  |     """ | ||||||
|  |     dumped = tomllib.loads('\n'.join(manifest.dump())) | ||||||
|  |     with filename.open('rb') as file: | ||||||
|  |         from_file = tomllib.load(file) | ||||||
|  |     if dumped != from_file: | ||||||
|  |         print(f'Dump differs from loaded data!', file=sys.stderr) | ||||||
|  |         diff = difflib.unified_diff( | ||||||
|  |             pprint.pformat(dumped).splitlines(), | ||||||
|  |             pprint.pformat(from_file).splitlines(), | ||||||
|  |             '<dumped>', str(filename), | ||||||
|  |             lineterm='', | ||||||
|  |         ) | ||||||
|  |         for line in diff: | ||||||
|  |             print(line, file=sys.stderr) | ||||||
|  |         return False | ||||||
|  |     else: | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
| def main(): | def main(): | ||||||
|     parser = argparse.ArgumentParser( |     parser = argparse.ArgumentParser( | ||||||
|         description=__doc__, |         description=__doc__, | ||||||
|  | @ -696,7 +684,16 @@ def main(): | ||||||
|         run_all_generators = True |         run_all_generators = True | ||||||
|         args.unixy_check = True |         args.unixy_check = True | ||||||
| 
 | 
 | ||||||
|     with args.file.open() as file: |     try: | ||||||
|  |         file = args.file.open('rb') | ||||||
|  |     except FileNotFoundError as err: | ||||||
|  |         if args.file.suffix == '.txt': | ||||||
|  |             # Provide a better error message | ||||||
|  |             suggestion = args.file.with_suffix('.toml') | ||||||
|  |             raise FileNotFoundError( | ||||||
|  |                 f'{args.file} not found. Did you mean {suggestion} ?') from err | ||||||
|  |         raise | ||||||
|  |     with file: | ||||||
|         manifest = parse_manifest(file) |         manifest = parse_manifest(file) | ||||||
| 
 | 
 | ||||||
|     check_private_names(manifest) |     check_private_names(manifest) | ||||||
|  | @ -709,7 +706,7 @@ def main(): | ||||||
|     if args.dump: |     if args.dump: | ||||||
|         for line in manifest.dump(): |         for line in manifest.dump(): | ||||||
|             print(line) |             print(line) | ||||||
|         results['dump'] = True |         results['dump'] = check_dump(manifest, args.file) | ||||||
| 
 | 
 | ||||||
|     for gen in generators: |     for gen in generators: | ||||||
|         filename = getattr(args, gen.var_name) |         filename = getattr(args, gen.var_name) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Petr Viktorin
						Petr Viktorin