Skip to content

Commit 6b8750c

Browse files
committed
gh-133403: Check generate_stdlib_module_names and check_extension_modules with mypy
1 parent fb1cb00 commit 6b8750c

4 files changed

Lines changed: 63 additions & 41 deletions

File tree

.github/workflows/mypy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ on:
1313
- "Lib/test/libregrtest/**"
1414
- "Lib/tomllib/**"
1515
- "Misc/mypy/**"
16+
- "Tools/build/check_extension_modules.py"
1617
- "Tools/build/compute-changes.py"
1718
- "Tools/build/deepfreeze.py"
1819
- "Tools/build/generate_sbom.py"
20+
- "Tools/build/generate_stdlib_module_names.py"
1921
- "Tools/build/generate-build-details.py"
2022
- "Tools/build/verify_ensurepip_wheels.py"
2123
- "Tools/build/update_file.py"

Tools/build/check_extension_modules.py

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
1818
See --help for more information
1919
"""
20+
21+
from __future__ import annotations
22+
2023
import _imp
2124
import argparse
2225
import collections
@@ -29,13 +32,14 @@
2932
import sysconfig
3033
import warnings
3134
from collections.abc import Iterable
32-
from importlib._bootstrap import _load as bootstrap_load
35+
from importlib._bootstrap import _load as bootstrap_load # type: ignore[attr-defined]
3336
from importlib.machinery import (
3437
BuiltinImporter,
3538
ExtensionFileLoader,
3639
ModuleSpec,
3740
)
3841
from importlib.util import spec_from_file_location, spec_from_loader
42+
from typing import NamedTuple
3943

4044
SRC_DIR = pathlib.Path(__file__).parent.parent.parent
4145

@@ -112,6 +116,7 @@
112116
)
113117

114118

119+
@enum.unique
115120
class ModuleState(enum.Enum):
116121
# Makefile state "yes"
117122
BUILTIN = "builtin"
@@ -123,21 +128,23 @@ class ModuleState(enum.Enum):
123128
# disabled by Setup / makesetup rule
124129
DISABLED_SETUP = "disabled_setup"
125130

126-
def __bool__(self):
131+
def __bool__(self) -> bool:
127132
return self.value in {"builtin", "shared"}
128133

129134

130-
ModuleInfo = collections.namedtuple("ModuleInfo", "name state")
135+
class ModuleInfo(NamedTuple):
136+
name: str
137+
state: ModuleState
131138

132139

133140
class ModuleChecker:
134141
pybuilddir_txt = "pybuilddir.txt"
135142

136143
setup_files = (
137144
# see end of configure.ac
138-
"Modules/Setup.local",
139-
"Modules/Setup.stdlib",
140-
"Modules/Setup.bootstrap",
145+
pathlib.Path("Modules/Setup.local"),
146+
pathlib.Path("Modules/Setup.stdlib"),
147+
pathlib.Path("Modules/Setup.bootstrap"),
141148
SRC_DIR / "Modules/Setup",
142149
)
143150

@@ -149,15 +156,15 @@ def __init__(self, cross_compiling: bool = False, strict: bool = False):
149156
self.builddir = self.get_builddir()
150157
self.modules = self.get_modules()
151158

152-
self.builtin_ok = []
153-
self.shared_ok = []
154-
self.failed_on_import = []
155-
self.missing = []
156-
self.disabled_configure = []
157-
self.disabled_setup = []
158-
self.notavailable = []
159+
self.builtin_ok: list[ModuleInfo] = []
160+
self.shared_ok: list[ModuleInfo] = []
161+
self.failed_on_import: list[ModuleInfo] = []
162+
self.missing: list[ModuleInfo] = []
163+
self.disabled_configure: list[ModuleInfo] = []
164+
self.disabled_setup: list[ModuleInfo] = []
165+
self.notavailable: list[ModuleInfo] = []
159166

160-
def check(self):
167+
def check(self) -> None:
161168
if not hasattr(_imp, 'create_dynamic'):
162169
logger.warning(
163170
('Dynamic extensions not supported '
@@ -189,10 +196,10 @@ def check(self):
189196
assert modinfo.state == ModuleState.SHARED
190197
self.shared_ok.append(modinfo)
191198

192-
def summary(self, *, verbose: bool = False):
199+
def summary(self, *, verbose: bool = False) -> None:
193200
longest = max([len(e.name) for e in self.modules], default=0)
194201

195-
def print_three_column(modinfos: list[ModuleInfo]):
202+
def print_three_column(modinfos: list[ModuleInfo]) -> None:
196203
names = [modinfo.name for modinfo in modinfos]
197204
names.sort(key=str.lower)
198205
# guarantee zip() doesn't drop anything
@@ -262,12 +269,12 @@ def print_three_column(modinfos: list[ModuleInfo]):
262269
f"{len(self.failed_on_import)} failed on import)"
263270
)
264271

265-
def check_strict_build(self):
272+
def check_strict_build(self) -> None:
266273
"""Fail if modules are missing and it's a strict build"""
267274
if self.strict_extensions_build and (self.failed_on_import or self.missing):
268275
raise RuntimeError("Failed to build some stdlib modules")
269276

270-
def list_module_names(self, *, all: bool = False) -> set:
277+
def list_module_names(self, *, all: bool = False) -> set[str]:
271278
names = {modinfo.name for modinfo in self.modules}
272279
if all:
273280
names.update(WINDOWS_MODULES)
@@ -280,9 +287,9 @@ def get_builddir(self) -> pathlib.Path:
280287
except FileNotFoundError:
281288
logger.error("%s must be run from the top build directory", __file__)
282289
raise
283-
builddir = pathlib.Path(builddir)
284-
logger.debug("%s: %s", self.pybuilddir_txt, builddir)
285-
return builddir
290+
builddir_path = pathlib.Path(builddir)
291+
logger.debug("%s: %s", self.pybuilddir_txt, builddir_path)
292+
return builddir_path
286293

287294
def get_modules(self) -> list[ModuleInfo]:
288295
"""Get module info from sysconfig and Modules/Setup* files"""
@@ -367,7 +374,7 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
367374
case ["*disabled*"]:
368375
state = ModuleState.DISABLED
369376
case ["*noconfig*"]:
370-
state = None
377+
continue
371378
case [*items]:
372379
if state == ModuleState.DISABLED:
373380
# *disabled* can disable multiple modules per line
@@ -384,34 +391,41 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
384391
def get_spec(self, modinfo: ModuleInfo) -> ModuleSpec:
385392
"""Get ModuleSpec for builtin or extension module"""
386393
if modinfo.state == ModuleState.SHARED:
387-
location = os.fspath(self.get_location(modinfo))
394+
mod_location = self.get_location(modinfo)
395+
assert mod_location is not None
396+
location = os.fspath(mod_location)
388397
loader = ExtensionFileLoader(modinfo.name, location)
389-
return spec_from_file_location(modinfo.name, location, loader=loader)
398+
spec = spec_from_file_location(modinfo.name, location, loader=loader)
399+
assert spec is not None
400+
return spec
390401
elif modinfo.state == ModuleState.BUILTIN:
391-
return spec_from_loader(modinfo.name, loader=BuiltinImporter)
402+
spec = spec_from_loader(modinfo.name, loader=BuiltinImporter)
403+
assert spec is not None
404+
return spec
392405
else:
393406
raise ValueError(modinfo)
394407

395-
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path:
408+
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path | None:
396409
"""Get shared library location in build directory"""
397410
if modinfo.state == ModuleState.SHARED:
398411
return self.builddir / f"{modinfo.name}{self.ext_suffix}"
399412
else:
400413
return None
401414

402-
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec):
415+
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec) -> None:
403416
"""Check that the module file is present and not empty"""
404-
if spec.loader is BuiltinImporter:
417+
if spec.loader is BuiltinImporter: # type: ignore[comparison-overlap]
405418
return
406419
try:
420+
assert spec.origin is not None
407421
st = os.stat(spec.origin)
408422
except FileNotFoundError:
409423
logger.error("%s (%s) is missing", modinfo.name, spec.origin)
410424
raise
411425
if not st.st_size:
412426
raise ImportError(f"{spec.origin} is an empty file")
413427

414-
def check_module_import(self, modinfo: ModuleInfo):
428+
def check_module_import(self, modinfo: ModuleInfo) -> None:
415429
"""Attempt to import module and report errors"""
416430
spec = self.get_spec(modinfo)
417431
self._check_file(modinfo, spec)
@@ -430,7 +444,7 @@ def check_module_import(self, modinfo: ModuleInfo):
430444
logger.exception("Importing extension '%s' failed!", modinfo.name)
431445
raise
432446

433-
def check_module_cross(self, modinfo: ModuleInfo):
447+
def check_module_cross(self, modinfo: ModuleInfo) -> None:
434448
"""Sanity check for cross compiling"""
435449
spec = self.get_spec(modinfo)
436450
self._check_file(modinfo, spec)
@@ -443,6 +457,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
443457

444458
failed_name = f"{modinfo.name}_failed{self.ext_suffix}"
445459
builddir_path = self.get_location(modinfo)
460+
assert builddir_path is not None
446461
if builddir_path.is_symlink():
447462
symlink = builddir_path
448463
module_path = builddir_path.resolve().relative_to(os.getcwd())
@@ -466,7 +481,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
466481
logger.debug("Rename '%s' -> '%s'", module_path, failed_path)
467482

468483

469-
def main():
484+
def main() -> None:
470485
args = parser.parse_args()
471486
if args.debug:
472487
args.verbose = True

Tools/build/generate_stdlib_module_names.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# This script lists the names of standard library modules
22
# to update Python/stdlib_module_names.h
3+
from __future__ import annotations
4+
35
import _imp
46
import os.path
57
import sys
68
import sysconfig
9+
from typing import TextIO
710

811
from check_extension_modules import ModuleChecker
912

@@ -48,12 +51,12 @@
4851
}
4952

5053
# Built-in modules
51-
def list_builtin_modules(names):
54+
def list_builtin_modules(names: set[str]) -> None:
5255
names |= set(sys.builtin_module_names)
5356

5457

5558
# Pure Python modules (Lib/*.py)
56-
def list_python_modules(names):
59+
def list_python_modules(names: set[str]) -> None:
5760
for filename in os.listdir(STDLIB_PATH):
5861
if not filename.endswith(".py"):
5962
continue
@@ -62,7 +65,7 @@ def list_python_modules(names):
6265

6366

6467
# Packages in Lib/
65-
def list_packages(names):
68+
def list_packages(names: set[str]) -> None:
6669
for name in os.listdir(STDLIB_PATH):
6770
if name in IGNORE:
6871
continue
@@ -76,16 +79,16 @@ def list_packages(names):
7679

7780
# Built-in and extension modules built by Modules/Setup*
7881
# includes Windows and macOS extensions.
79-
def list_modules_setup_extensions(names):
82+
def list_modules_setup_extensions(names: set[str]) -> None:
8083
checker = ModuleChecker()
8184
names.update(checker.list_module_names(all=True))
8285

8386

8487
# List frozen modules of the PyImport_FrozenModules list (Python/frozen.c).
8588
# Use the "./Programs/_testembed list_frozen" command.
86-
def list_frozen(names):
89+
def list_frozen(names: set[str]) -> None:
8790
submodules = set()
88-
for name in _imp._frozen_module_names():
91+
for name in _imp._frozen_module_names(): # type: ignore[attr-defined]
8992
# To skip __hello__, __hello_alias__ and etc.
9093
if name.startswith('__'):
9194
continue
@@ -101,8 +104,8 @@ def list_frozen(names):
101104
raise Exception(f'unexpected frozen submodules: {sorted(submodules)}')
102105

103106

104-
def list_modules():
105-
names = set()
107+
def list_modules() -> set[str]:
108+
names: set[str] = set()
106109

107110
list_builtin_modules(names)
108111
list_modules_setup_extensions(names)
@@ -127,7 +130,7 @@ def list_modules():
127130
return names
128131

129132

130-
def write_modules(fp, names):
133+
def write_modules(fp: TextIO, names: set[str]) -> None:
131134
print(f"// Auto-generated by {SCRIPT_NAME}.",
132135
file=fp)
133136
print("// List used to create sys.stdlib_module_names.", file=fp)
@@ -138,7 +141,7 @@ def write_modules(fp, names):
138141
print("};", file=fp)
139142

140143

141-
def main():
144+
def main() -> None:
142145
if not sysconfig.is_python_build():
143146
print(f"ERROR: {sys.executable} is not a Python build",
144147
file=sys.stderr)

Tools/build/mypy.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
# Please, when adding new files here, also add them to:
44
# .github/workflows/mypy.yml
55
files =
6+
Tools/build/check_extension_modules.py,
67
Tools/build/compute-changes.py,
78
Tools/build/deepfreeze.py,
89
Tools/build/generate-build-details.py,
910
Tools/build/generate_sbom.py,
11+
Tools/build/generate_stdlib_module_names.py,
1012
Tools/build/verify_ensurepip_wheels.py,
1113
Tools/build/update_file.py,
1214
Tools/build/umarshal.py

0 commit comments

Comments
 (0)