gh-132390: Apply Ruff linting to Tools/build (#132391)

---------

Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
This commit is contained in:
Bénédikt Tran 2025-04-20 11:21:41 +02:00 committed by GitHub
parent 246ed23456
commit 5d8e432d9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 109 additions and 99 deletions

View file

@ -11,9 +11,9 @@ repos:
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
files: ^Lib/test/ files: ^Lib/test/
- id: ruff - id: ruff
name: Run Ruff (lint) on Tools/build/check_warnings.py name: Run Ruff (lint) on Tools/build/
args: [--exit-non-zero-on-fix, --config=Tools/build/.ruff.toml] args: [--exit-non-zero-on-fix, --config=Tools/build/.ruff.toml]
files: ^Tools/build/check_warnings.py files: ^Tools/build/
- id: ruff - id: ruff
name: Run Ruff (lint) on Argument Clinic name: Run Ruff (lint) on Argument Clinic
args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml] args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml]

View file

@ -18,3 +18,22 @@ select = [
"W", # pycodestyle "W", # pycodestyle
"YTT", # flake8-2020 "YTT", # flake8-2020
] ]
ignore = [
"E501", # Line too long
"F541", # f-string without any placeholders
"PYI024", # Use `typing.NamedTuple` instead of `collections.namedtuple`
"PYI025", # Use `from collections.abc import Set as AbstractSet`
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
]
[per-file-target-version]
"deepfreeze.py" = "py310"
"stable_abi.py" = "py311" # requires 'tomllib'
[lint.per-file-ignores]
"{check_extension_modules,freeze_modules}.py" = [
"UP031", # Use format specifiers instead of percent format
]
"generate_{re_casefix,sre_constants,token}.py" = [
"UP031", # Use format specifiers instead of percent format
]

View file

@ -17,6 +17,7 @@ Module information is parsed from several sources:
See --help for more information See --help for more information
""" """
import _imp
import argparse import argparse
import collections import collections
import enum import enum
@ -27,12 +28,14 @@ import re
import sys import sys
import sysconfig import sysconfig
import warnings import warnings
import _imp from collections.abc import Iterable
from importlib._bootstrap import _load as bootstrap_load from importlib._bootstrap import _load as bootstrap_load
from importlib.machinery import BuiltinImporter, ExtensionFileLoader, ModuleSpec from importlib.machinery import (
BuiltinImporter,
ExtensionFileLoader,
ModuleSpec,
)
from importlib.util import spec_from_file_location, spec_from_loader from importlib.util import spec_from_file_location, spec_from_loader
from typing import Iterable
SRC_DIR = pathlib.Path(__file__).parent.parent.parent SRC_DIR = pathlib.Path(__file__).parent.parent.parent
@ -195,7 +198,7 @@ class ModuleChecker:
# guarantee zip() doesn't drop anything # guarantee zip() doesn't drop anything
while len(names) % 3: while len(names) % 3:
names.append("") names.append("")
for l, m, r in zip(names[::3], names[1::3], names[2::3]): for l, m, r in zip(names[::3], names[1::3], names[2::3]): # noqa: E741
print("%-*s %-*s %-*s" % (longest, l, longest, m, longest, r)) print("%-*s %-*s %-*s" % (longest, l, longest, m, longest, r))
if verbose and self.builtin_ok: if verbose and self.builtin_ok:
@ -420,7 +423,7 @@ class ModuleChecker:
except ImportError as e: except ImportError as e:
logger.error("%s failed to import: %s", modinfo.name, e) logger.error("%s failed to import: %s", modinfo.name, e)
raise raise
except Exception as e: except Exception:
if not hasattr(_imp, 'create_dynamic'): if not hasattr(_imp, 'create_dynamic'):
logger.warning("Dynamic extension '%s' ignored", modinfo.name) logger.warning("Dynamic extension '%s' ignored", modinfo.name)
return return

View file

@ -13,7 +13,7 @@ import os
import re import re
import time import time
import types import types
from typing import Dict, FrozenSet, TextIO, Tuple from typing import TextIO
import umarshal import umarshal
@ -57,8 +57,8 @@ def get_localsplus(code: types.CodeType):
def get_localsplus_counts(code: types.CodeType, def get_localsplus_counts(code: types.CodeType,
names: Tuple[str, ...], names: tuple[str, ...],
kinds: bytes) -> Tuple[int, int, int, int]: kinds: bytes) -> tuple[int, int, int]:
nlocals = 0 nlocals = 0
ncellvars = 0 ncellvars = 0
nfreevars = 0 nfreevars = 0
@ -84,7 +84,7 @@ PyUnicode_2BYTE_KIND = 2
PyUnicode_4BYTE_KIND = 4 PyUnicode_4BYTE_KIND = 4
def analyze_character_width(s: str) -> Tuple[int, bool]: def analyze_character_width(s: str) -> tuple[int, bool]:
maxchar = ' ' maxchar = ' '
for c in s: for c in s:
maxchar = max(maxchar, c) maxchar = max(maxchar, c)
@ -109,7 +109,7 @@ class Printer:
def __init__(self, file: TextIO) -> None: def __init__(self, file: TextIO) -> None:
self.level = 0 self.level = 0
self.file = file self.file = file
self.cache: Dict[tuple[type, object, str], str] = {} self.cache: dict[tuple[type, object, str], str] = {}
self.hits, self.misses = 0, 0 self.hits, self.misses = 0, 0
self.finis: list[str] = [] self.finis: list[str] = []
self.inits: list[str] = [] self.inits: list[str] = []
@ -305,7 +305,7 @@ class Printer:
self.inits.append(f"_PyStaticCode_Init({name_as_code})") self.inits.append(f"_PyStaticCode_Init({name_as_code})")
return f"& {name}.ob_base.ob_base" return f"& {name}.ob_base.ob_base"
def generate_tuple(self, name: str, t: Tuple[object, ...]) -> str: def generate_tuple(self, name: str, t: tuple[object, ...]) -> str:
if len(t) == 0: if len(t) == 0:
return f"(PyObject *)& _Py_SINGLETON(tuple_empty)" return f"(PyObject *)& _Py_SINGLETON(tuple_empty)"
items = [self.generate(f"{name}_{i}", it) for i, it in enumerate(t)] items = [self.generate(f"{name}_{i}", it) for i, it in enumerate(t)]
@ -379,7 +379,7 @@ class Printer:
self.write(f".cval = {{ {z.real}, {z.imag} }},") self.write(f".cval = {{ {z.real}, {z.imag} }},")
return f"&{name}.ob_base" return f"&{name}.ob_base"
def generate_frozenset(self, name: str, fs: FrozenSet[object]) -> str: def generate_frozenset(self, name: str, fs: frozenset[object]) -> str:
try: try:
fs = sorted(fs) fs = sorted(fs)
except TypeError: except TypeError:
@ -465,7 +465,7 @@ def generate(args: list[str], output: TextIO) -> None:
printer = Printer(output) printer = Printer(output)
for arg in args: for arg in args:
file, modname = arg.rsplit(':', 1) file, modname = arg.rsplit(':', 1)
with open(file, "r", encoding="utf8") as fd: with open(file, encoding="utf8") as fd:
source = fd.read() source = fd.read()
if is_frozen_header(source): if is_frozen_header(source):
code = decode_frozen_data(source) code = decode_frozen_data(source)
@ -513,7 +513,7 @@ def main() -> None:
if args.file: if args.file:
if verbose: if verbose:
print(f"Reading targets from {args.file}") print(f"Reading targets from {args.file}")
with open(args.file, "rt", encoding="utf-8-sig") as fin: with open(args.file, encoding="utf-8-sig") as fin:
rules = [x.strip() for x in fin] rules = [x.strip() for x in fin]
else: else:
rules = args.args rules = args.args

View file

@ -3,15 +3,14 @@
See the notes at the top of Python/frozen.c for more info. See the notes at the top of Python/frozen.c for more info.
""" """
from collections import namedtuple
import hashlib import hashlib
import ntpath import ntpath
import os import os
import posixpath import posixpath
from collections import namedtuple
from update_file import updating_file_with_tmpfile from update_file import updating_file_with_tmpfile
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
ROOT_DIR = os.path.abspath(ROOT_DIR) ROOT_DIR = os.path.abspath(ROOT_DIR)
FROZEN_ONLY = os.path.join(ROOT_DIR, 'Tools', 'freeze', 'flag.py') FROZEN_ONLY = os.path.join(ROOT_DIR, 'Tools', 'freeze', 'flag.py')
@ -482,7 +481,6 @@ def regen_frozen(modules):
header = relpath_for_posix_display(src.frozenfile, parentdir) header = relpath_for_posix_display(src.frozenfile, parentdir)
headerlines.append(f'#include "{header}"') headerlines.append(f'#include "{header}"')
externlines = UniqueList()
bootstraplines = [] bootstraplines = []
stdliblines = [] stdliblines = []
testlines = [] testlines = []
@ -625,7 +623,6 @@ def regen_makefile(modules):
def regen_pcbuild(modules): def regen_pcbuild(modules):
projlines = [] projlines = []
filterlines = [] filterlines = []
corelines = []
for src in _iter_sources(modules): for src in _iter_sources(modules):
pyfile = relpath_for_windows_display(src.pyfile, ROOT_DIR) pyfile = relpath_for_windows_display(src.pyfile, ROOT_DIR)
header = relpath_for_windows_display(src.frozenfile, ROOT_DIR) header = relpath_for_windows_display(src.frozenfile, ROOT_DIR)

View file

@ -286,7 +286,8 @@ def generate_runtime_init(identifiers, strings):
break break
else: else:
raise NotImplementedError raise NotImplementedError
assert nsmallposints and nsmallnegints assert nsmallposints
assert nsmallnegints
# Then target the runtime initializer. # Then target the runtime initializer.
filename = os.path.join(INTERNAL, 'pycore_runtime_init_generated.h') filename = os.path.join(INTERNAL, 'pycore_runtime_init_generated.h')
@ -434,7 +435,7 @@ def get_identifiers_and_strings() -> 'tuple[set[str], dict[str, str]]':
# To cover tricky cases (like "\n") we also generate C asserts. # To cover tricky cases (like "\n") we also generate C asserts.
raise ValueError( raise ValueError(
'do not use &_Py_ID or &_Py_STR for one-character latin-1 ' 'do not use &_Py_ID or &_Py_STR for one-character latin-1 '
+ f'strings, use _Py_LATIN1_CHR instead: {string!r}') f'strings, use _Py_LATIN1_CHR instead: {string!r}')
if string not in strings: if string not in strings:
strings[string] = name strings[string] = name
elif name != strings[string]: elif name != strings[string]:

View file

@ -1,12 +1,11 @@
"""Generate 10,000 unique examples for the Levenshtein short-circuit tests.""" """Generate 10,000 unique examples for the Levenshtein short-circuit tests."""
import argparse import argparse
from functools import lru_cache
import json import json
import os.path import os.path
from functools import lru_cache
from random import choices, randrange from random import choices, randrange
# This should be in sync with Lib/traceback.py. It's not importing those values # This should be in sync with Lib/traceback.py. It's not importing those values
# because this script is being executed by PYTHON_FOR_REGEN and not by the in-tree # because this script is being executed by PYTHON_FOR_REGEN and not by the in-tree
# build of Python. # build of Python.

View file

@ -9,7 +9,7 @@ SCRIPT_NAME = 'Tools/build/generate_re_casefix.py'
def update_file(file, content): def update_file(file, content):
try: try:
with open(file, 'r', encoding='utf-8') as fobj: with open(file, encoding='utf-8') as fobj:
if fobj.read() == content: if fobj.read() == content:
return False return False
except (OSError, ValueError): except (OSError, ValueError):
@ -50,7 +50,7 @@ def main(outfile='Lib/re/_casefix.py'):
# List of codes of lowercased characters which have the same uppercase. # List of codes of lowercased characters which have the same uppercase.
equivalent_lower_codes = [sorted(t) equivalent_lower_codes = [sorted(t)
for s in equivalent_chars for s in equivalent_chars
for t in [set(ord(c.lower()) for c in s)] for t in [{ord(c.lower()) for c in s}]
if len(t) > 1] if len(t) > 1]
bad_codes = [] bad_codes = []

View file

@ -1,14 +1,15 @@
"""Tool for generating Software Bill of Materials (SBOM) for Python's dependencies""" """Tool for generating Software Bill of Materials (SBOM) for Python's dependencies"""
import os
import re import glob
import hashlib import hashlib
import json import json
import glob import os
from pathlib import Path, PurePosixPath, PureWindowsPath import re
import subprocess import subprocess
import sys import sys
import urllib.request
import typing import typing
import urllib.request
from pathlib import Path, PurePosixPath, PureWindowsPath
CPYTHON_ROOT_DIR = Path(__file__).parent.parent.parent CPYTHON_ROOT_DIR = Path(__file__).parent.parent.parent
@ -250,7 +251,7 @@ def check_sbom_packages(sbom_data: dict[str, typing.Any]) -> None:
license_concluded = package["licenseConcluded"] license_concluded = package["licenseConcluded"]
error_if( error_if(
license_concluded != "NOASSERTION", license_concluded != "NOASSERTION",
f"License identifier must be 'NOASSERTION'" "License identifier must be 'NOASSERTION'"
) )

View file

@ -6,7 +6,7 @@ SCRIPT_NAME = 'Tools/build/generate_sre_constants.py'
def update_file(file, content): def update_file(file, content):
try: try:
with open(file, 'r') as fobj: with open(file) as fobj:
if fobj.read() == content: if fobj.read() == content:
return False return False
except (OSError, ValueError): except (OSError, ValueError):

View file

@ -7,7 +7,6 @@ import sysconfig
from check_extension_modules import ModuleChecker from check_extension_modules import ModuleChecker
SCRIPT_NAME = 'Tools/build/generate_stdlib_module_names.py' SCRIPT_NAME = 'Tools/build/generate_stdlib_module_names.py'
SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))

View file

@ -13,7 +13,6 @@
import re import re
SCRIPT_NAME = 'Tools/build/generate_token.py' SCRIPT_NAME = 'Tools/build/generate_token.py'
AUTO_GENERATED_BY_SCRIPT = f'Auto-generated by {SCRIPT_NAME}' AUTO_GENERATED_BY_SCRIPT = f'Auto-generated by {SCRIPT_NAME}'
NT_OFFSET = 256 NT_OFFSET = 256
@ -46,7 +45,7 @@ def load_tokens(path):
def update_file(file, content): def update_file(file, content):
try: try:
with open(file, 'r') as fobj: with open(file) as fobj:
if fobj.read() == content: if fobj.read() == content:
return False return False
except (OSError, ValueError): except (OSError, ValueError):

View file

@ -12,11 +12,11 @@ The page now contains the following note:
Written by Ezio Melotti and Iuliia Proskurnia. Written by Ezio Melotti and Iuliia Proskurnia.
""" """
import json
import os import os
import sys import sys
import json
from urllib.request import urlopen
from html.entities import html5 from html.entities import html5
from urllib.request import urlopen
SCRIPT_NAME = 'Tools/build/parse_html5_entities.py' SCRIPT_NAME = 'Tools/build/parse_html5_entities.py'
PAGE_URL = 'https://html.spec.whatwg.org/multipage/named-characters.html' PAGE_URL = 'https://html.spec.whatwg.org/multipage/named-characters.html'
@ -40,20 +40,20 @@ def compare_dicts(old, new):
"""Compare the old and new dicts and print the differences.""" """Compare the old and new dicts and print the differences."""
added = new.keys() - old.keys() added = new.keys() - old.keys()
if added: if added:
print('{} entitie(s) have been added:'.format(len(added))) print(f'{len(added)} entitie(s) have been added:')
for name in sorted(added): for name in sorted(added):
print(' {!r}: {!r}'.format(name, new[name])) print(f' {name!r}: {new[name]!r}')
removed = old.keys() - new.keys() removed = old.keys() - new.keys()
if removed: if removed:
print('{} entitie(s) have been removed:'.format(len(removed))) print(f'{len(removed)} entitie(s) have been removed:')
for name in sorted(removed): for name in sorted(removed):
print(' {!r}: {!r}'.format(name, old[name])) print(f' {name!r}: {old[name]!r}')
changed = set() changed = set()
for name in (old.keys() & new.keys()): for name in (old.keys() & new.keys()):
if old[name] != new[name]: if old[name] != new[name]:
changed.add((name, old[name], new[name])) changed.add((name, old[name], new[name]))
if changed: if changed:
print('{} entitie(s) have been modified:'.format(len(changed))) print(f'{len(changed)} entitie(s) have been modified:')
for item in sorted(changed): for item in sorted(changed):
print(' {!r}: {!r} -> {!r}'.format(*item)) print(' {!r}: {!r} -> {!r}'.format(*item))
@ -111,5 +111,5 @@ if __name__ == '__main__':
print('The current dictionary is updated.') print('The current dictionary is updated.')
else: else:
compare_dicts(html5, new_html5) compare_dicts(html5, new_html5)
print('Run "./python {0} --patch" to update Lib/html/entities.html ' print(f'Run "./python {__file__} --patch" to update Lib/html/entities.html '
'or "./python {0} --create" to see the generated ' 'dictionary.'.format(__file__)) f'or "./python {__file__} --create" to see the generated dictionary.')

View file

@ -6,7 +6,6 @@ import subprocess
import sys import sys
import sysconfig import sysconfig
ALLOWED_PREFIXES = ('Py', '_Py') ALLOWED_PREFIXES = ('Py', '_Py')
if sys.platform == 'darwin': if sys.platform == 'darwin':
ALLOWED_PREFIXES += ('__Py',) ALLOWED_PREFIXES += ('__Py',)
@ -52,8 +51,8 @@ def get_exported_symbols(library, dynamic=False):
if dynamic: if dynamic:
args.append('--dynamic') args.append('--dynamic')
args.append(library) args.append(library)
print("+ %s" % ' '.join(args)) print(f"+ {' '.join(args)}")
proc = subprocess.run(args, stdout=subprocess.PIPE, universal_newlines=True) proc = subprocess.run(args, stdout=subprocess.PIPE, encoding='utf-8')
if proc.returncode: if proc.returncode:
sys.stdout.write(proc.stdout) sys.stdout.write(proc.stdout)
sys.exit(proc.returncode) sys.exit(proc.returncode)
@ -80,7 +79,7 @@ def get_smelly_symbols(stdout, dynamic=False):
symtype = parts[1].strip() symtype = parts[1].strip()
symbol = parts[-1] symbol = parts[-1]
result = '%s (type: %s)' % (symbol, symtype) result = f'{symbol} (type: {symtype})'
if (symbol.startswith(ALLOWED_PREFIXES) or if (symbol.startswith(ALLOWED_PREFIXES) or
symbol in EXCEPTIONS or symbol in EXCEPTIONS or
@ -111,10 +110,10 @@ def check_library(library, dynamic=False):
print() print()
smelly_symbols.sort() smelly_symbols.sort()
for symbol in smelly_symbols: for symbol in smelly_symbols:
print("Smelly symbol: %s" % symbol) print(f"Smelly symbol: {symbol}")
print() print()
print("ERROR: Found %s smelly symbols!" % len(smelly_symbols)) print(f"ERROR: Found {len(smelly_symbols)} smelly symbols!")
return len(smelly_symbols) return len(smelly_symbols)

View file

@ -7,22 +7,22 @@ For actions that take a FILENAME, the filename can be left out to use a default
(relative to the manifest file, as they appear in the CPython codebase). (relative to the manifest file, as they appear in the CPython codebase).
""" """
from functools import partial
from pathlib import Path
import dataclasses
import subprocess
import sysconfig
import argparse import argparse
import textwrap import csv
import tomllib import dataclasses
import difflib import difflib
import pprint import io
import sys
import os import os
import os.path import os.path
import io import pprint
import re import re
import csv import subprocess
import sys
import sysconfig
import textwrap
import tomllib
from functools import partial
from pathlib import Path
SCRIPT_NAME = 'Tools/build/stable_abi.py' SCRIPT_NAME = 'Tools/build/stable_abi.py'
DEFAULT_MANIFEST_PATH = ( DEFAULT_MANIFEST_PATH = (
@ -57,7 +57,7 @@ UNIXY = MACOS or (sys.platform == "linux") # XXX should this be "not Windows"?
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): def __init__(self):
self.contents = dict() self.contents = {}
def add(self, item): def add(self, item):
if item.name in self.contents: if item.name in self.contents:
@ -404,22 +404,20 @@ 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_macros = set(m.name for m in manifest.select({'feature_macro'})) feature_macros = {m.name for m in manifest.select({'feature_macro'})}
feature_macros &= present_macros feature_macros &= present_macros
# Check that we have all needed macros # Check that we have all needed macros
expected_macros = set( expected_macros = {item.name for item in manifest.select({'macro'})}
item.name for item in manifest.select({'macro'})
)
missing_macros = expected_macros - present_macros missing_macros = expected_macros - present_macros
okay &= _report_unexpected_items( okay &= _report_unexpected_items(
missing_macros, missing_macros,
'Some macros from are not defined from "Include/Python.h"' 'Some macros from are not defined from "Include/Python.h" '
+ 'with Py_LIMITED_API:') 'with Py_LIMITED_API:')
expected_symbols = set(item.name for item in manifest.select( expected_symbols = {item.name for item in manifest.select(
{'function', 'data'}, include_abi_only=True, ifdef=feature_macros, {'function', 'data'}, include_abi_only=True, ifdef=feature_macros,
)) )}
# Check the static library (*.a) # Check the static library (*.a)
LIBRARY = sysconfig.get_config_var("LIBRARY") LIBRARY = sysconfig.get_config_var("LIBRARY")
@ -437,15 +435,15 @@ def do_unixy_check(manifest, args):
manifest, LDLIBRARY, expected_symbols, dynamic=False) manifest, LDLIBRARY, expected_symbols, dynamic=False)
# Check definitions in the header files # Check definitions in the header files
expected_defs = set(item.name for item in manifest.select( expected_defs = {item.name for item in manifest.select(
{'function', 'data'}, include_abi_only=False, ifdef=feature_macros, {'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
okay &= _report_unexpected_items( okay &= _report_unexpected_items(
missing_defs, missing_defs,
'Some expected declarations were not declared in ' 'Some expected declarations were not declared in '
+ '"Include/Python.h" with Py_LIMITED_API:') '"Include/Python.h" with Py_LIMITED_API:')
# Some Limited API macros are defined in terms of private symbols. # Some Limited API macros are defined in terms of private symbols.
# These are not part of Limited API (even though they're defined with # These are not part of Limited API (even though they're defined with
@ -455,7 +453,7 @@ def do_unixy_check(manifest, args):
okay &= _report_unexpected_items( okay &= _report_unexpected_items(
extra_defs, extra_defs,
'Some extra declarations were found in "Include/Python.h" ' 'Some extra declarations were found in "Include/Python.h" '
+ 'with Py_LIMITED_API:') 'with Py_LIMITED_API:')
return okay return okay
@ -477,7 +475,7 @@ def binutils_get_exported_symbols(library, dynamic=False):
if dynamic: if dynamic:
args.append("--dynamic") args.append("--dynamic")
args.append(library) args.append(library)
proc = subprocess.run(args, stdout=subprocess.PIPE, universal_newlines=True) proc = subprocess.run(args, stdout=subprocess.PIPE, encoding='utf-8')
if proc.returncode: if proc.returncode:
sys.stdout.write(proc.stdout) sys.stdout.write(proc.stdout)
sys.exit(proc.returncode) sys.exit(proc.returncode)
@ -547,15 +545,10 @@ def gcc_get_limited_api_macros(headers):
"-E", "-E",
] ]
+ [str(file) for file in headers], + [str(file) for file in headers],
text=True, encoding='utf-8',
) )
return { return set(re.findall(r"#define (\w+)", preprocessor_output_with_macros))
target
for target in re.findall(
r"#define (\w+)", preprocessor_output_with_macros
)
}
def gcc_get_limited_api_definitions(headers): def gcc_get_limited_api_definitions(headers):
@ -590,7 +583,7 @@ def gcc_get_limited_api_definitions(headers):
"-E", "-E",
] ]
+ [str(file) for file in headers], + [str(file) for file in headers],
text=True, encoding='utf-8',
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
) )
stable_functions = set( stable_functions = set(
@ -613,7 +606,7 @@ def check_private_names(manifest):
if name.startswith('_') and not item.abi_only: if name.startswith('_') and not item.abi_only:
raise ValueError( raise ValueError(
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 marked `abi_only`') 'removed from the stable ABI list or marked `abi_only`')
def check_dump(manifest, filename): def check_dump(manifest, filename):
"""Check that manifest.dump() corresponds to the data. """Check that manifest.dump() corresponds to the data.
@ -624,7 +617,7 @@ def check_dump(manifest, filename):
with filename.open('rb') as file: with filename.open('rb') as file:
from_file = tomllib.load(file) from_file = tomllib.load(file)
if dumped != from_file: if dumped != from_file:
print(f'Dump differs from loaded data!', file=sys.stderr) print('Dump differs from loaded data!', file=sys.stderr)
diff = difflib.unified_diff( diff = difflib.unified_diff(
pprint.pformat(dumped).splitlines(), pprint.pformat(dumped).splitlines(),
pprint.pformat(from_file).splitlines(), pprint.pformat(from_file).splitlines(),
@ -654,7 +647,7 @@ def main():
parser.add_argument( parser.add_argument(
"--generate-all", action='store_true', "--generate-all", action='store_true',
help="as --generate, but generate all file(s) using default filenames." help="as --generate, but generate all file(s) using default filenames."
+ " (unlike --all, does not run any extra checks)", " (unlike --all, does not run any extra checks)",
) )
parser.add_argument( parser.add_argument(
"-a", "--all", action='store_true', "-a", "--all", action='store_true',
@ -739,9 +732,9 @@ def main():
if not results: if not results:
if args.generate: if args.generate:
parser.error('No file specified. Use --generate-all to regenerate ' parser.error('No file specified. Use --generate-all to regenerate '
+ 'all files, or --help for usage.') 'all files, or --help for usage.')
parser.error('No check specified. Use --all to check all files, ' parser.error('No check specified. Use --all to check all files, '
+ 'or --help for usage.') 'or --help for usage.')
failed_results = [name for name, result in results.items() if not result] failed_results = [name for name, result in results.items() if not result]

View file

@ -1,8 +1,7 @@
# Implementation of marshal.loads() in pure Python # Implementation of marshal.loads() in pure Python
import ast import ast
from typing import Any
from typing import Any, Tuple
class Type: class Type:
@ -55,10 +54,10 @@ class Code:
def __repr__(self) -> str: def __repr__(self) -> str:
return f"Code(**{self.__dict__})" return f"Code(**{self.__dict__})"
co_localsplusnames: Tuple[str] co_localsplusnames: tuple[str, ...]
co_localspluskinds: Tuple[int] co_localspluskinds: tuple[int, ...]
def get_localsplus_names(self, select_kind: int) -> Tuple[str, ...]: def get_localsplus_names(self, select_kind: int) -> tuple[str, ...]:
varnames: list[str] = [] varnames: list[str] = []
for name, kind in zip(self.co_localsplusnames, for name, kind in zip(self.co_localsplusnames,
self.co_localspluskinds): self.co_localspluskinds):
@ -67,15 +66,15 @@ class Code:
return tuple(varnames) return tuple(varnames)
@property @property
def co_varnames(self) -> Tuple[str, ...]: def co_varnames(self) -> tuple[str, ...]:
return self.get_localsplus_names(CO_FAST_LOCAL) return self.get_localsplus_names(CO_FAST_LOCAL)
@property @property
def co_cellvars(self) -> Tuple[str, ...]: def co_cellvars(self) -> tuple[str, ...]:
return self.get_localsplus_names(CO_FAST_CELL) return self.get_localsplus_names(CO_FAST_CELL)
@property @property
def co_freevars(self) -> Tuple[str, ...]: def co_freevars(self) -> tuple[str, ...]:
return self.get_localsplus_names(CO_FAST_FREE) return self.get_localsplus_names(CO_FAST_FREE)
@property @property
@ -309,7 +308,8 @@ def loads(data: bytes) -> Any:
def main(): def main():
# Test # Test
import marshal, pprint import marshal
import pprint
sample = {'foo': {(42, "bar", 3.14)}} sample = {'foo': {(42, "bar", 3.14)}}
data = marshal.dumps(sample) data = marshal.dumps(sample)
retval = loads(data) retval = loads(data)