Skip to content

Commit 68a61b0

Browse files
authored
gh-133403: Check generate_stdlib_module_names and check_extension_modules with mypy (#137546)
1 parent 715647a commit 68a61b0

File tree

4 files changed

+65
-42
lines changed

4 files changed

+65
-42
lines changed

.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: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
1818
See --help for more information
1919
"""
20+
21+
from __future__ import annotations
22+
2023
import _imp
2124
import argparse
22-
import collections
2325
import enum
2426
import logging
2527
import os
@@ -29,13 +31,16 @@
2931
import sysconfig
3032
import warnings
3133
from collections.abc import Iterable
32-
from importlib._bootstrap import _load as bootstrap_load
34+
from importlib._bootstrap import ( # type: ignore[attr-defined]
35+
_load as bootstrap_load,
36+
)
3337
from importlib.machinery import (
3438
BuiltinImporter,
3539
ExtensionFileLoader,
3640
ModuleSpec,
3741
)
3842
from importlib.util import spec_from_file_location, spec_from_loader
43+
from typing import NamedTuple
3944

4045
SRC_DIR = pathlib.Path(__file__).parent.parent.parent
4146

@@ -112,6 +117,7 @@
112117
)
113118

114119

120+
@enum.unique
115121
class ModuleState(enum.Enum):
116122
# Makefile state "yes"
117123
BUILTIN = "builtin"
@@ -123,21 +129,23 @@ class ModuleState(enum.Enum):
123129
# disabled by Setup / makesetup rule
124130
DISABLED_SETUP = "disabled_setup"
125131

126-
def __bool__(self):
132+
def __bool__(self) -> bool:
127133
return self.value in {"builtin", "shared"}
128134

129135

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

132140

133141
class ModuleChecker:
134142
pybuilddir_txt = "pybuilddir.txt"
135143

136144
setup_files = (
137145
# see end of configure.ac
138-
"Modules/Setup.local",
139-
"Modules/Setup.stdlib",
140-
"Modules/Setup.bootstrap",
146+
pathlib.Path("Modules/Setup.local"),
147+
pathlib.Path("Modules/Setup.stdlib"),
148+
pathlib.Path("Modules/Setup.bootstrap"),
141149
SRC_DIR / "Modules/Setup",
142150
)
143151

@@ -149,15 +157,15 @@ def __init__(self, cross_compiling: bool = False, strict: bool = False):
149157
self.builddir = self.get_builddir()
150158
self.modules = self.get_modules()
151159

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 = []
160+
self.builtin_ok: list[ModuleInfo] = []
161+
self.shared_ok: list[ModuleInfo] = []
162+
self.failed_on_import: list[ModuleInfo] = []
163+
self.missing: list[ModuleInfo] = []
164+
self.disabled_configure: list[ModuleInfo] = []
165+
self.disabled_setup: list[ModuleInfo] = []
166+
self.notavailable: list[ModuleInfo] = []
159167

160-
def check(self):
168+
def check(self) -> None:
161169
if not hasattr(_imp, 'create_dynamic'):
162170
logger.warning(
163171
('Dynamic extensions not supported '
@@ -189,10 +197,10 @@ def check(self):
189197
assert modinfo.state == ModuleState.SHARED
190198
self.shared_ok.append(modinfo)
191199

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

195-
def print_three_column(modinfos: list[ModuleInfo]):
203+
def print_three_column(modinfos: list[ModuleInfo]) -> None:
196204
names = [modinfo.name for modinfo in modinfos]
197205
names.sort(key=str.lower)
198206
# guarantee zip() doesn't drop anything
@@ -262,12 +270,12 @@ def print_three_column(modinfos: list[ModuleInfo]):
262270
f"{len(self.failed_on_import)} failed on import)"
263271
)
264272

265-
def check_strict_build(self):
273+
def check_strict_build(self) -> None:
266274
"""Fail if modules are missing and it's a strict build"""
267275
if self.strict_extensions_build and (self.failed_on_import or self.missing):
268276
raise RuntimeError("Failed to build some stdlib modules")
269277

270-
def list_module_names(self, *, all: bool = False) -> set:
278+
def list_module_names(self, *, all: bool = False) -> set[str]:
271279
names = {modinfo.name for modinfo in self.modules}
272280
if all:
273281
names.update(WINDOWS_MODULES)
@@ -280,9 +288,9 @@ def get_builddir(self) -> pathlib.Path:
280288
except FileNotFoundError:
281289
logger.error("%s must be run from the top build directory", __file__)
282290
raise
283-
builddir = pathlib.Path(builddir)
284-
logger.debug("%s: %s", self.pybuilddir_txt, builddir)
285-
return builddir
291+
builddir_path = pathlib.Path(builddir)
292+
logger.debug("%s: %s", self.pybuilddir_txt, builddir_path)
293+
return builddir_path
286294

287295
def get_modules(self) -> list[ModuleInfo]:
288296
"""Get module info from sysconfig and Modules/Setup* files"""
@@ -367,7 +375,7 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
367375
case ["*disabled*"]:
368376
state = ModuleState.DISABLED
369377
case ["*noconfig*"]:
370-
state = None
378+
continue
371379
case [*items]:
372380
if state == ModuleState.DISABLED:
373381
# *disabled* can disable multiple modules per line
@@ -384,34 +392,41 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
384392
def get_spec(self, modinfo: ModuleInfo) -> ModuleSpec:
385393
"""Get ModuleSpec for builtin or extension module"""
386394
if modinfo.state == ModuleState.SHARED:
387-
location = os.fspath(self.get_location(modinfo))
395+
mod_location = self.get_location(modinfo)
396+
assert mod_location is not None
397+
location = os.fspath(mod_location)
388398
loader = ExtensionFileLoader(modinfo.name, location)
389-
return spec_from_file_location(modinfo.name, location, loader=loader)
399+
spec = spec_from_file_location(modinfo.name, location, loader=loader)
400+
assert spec is not None
401+
return spec
390402
elif modinfo.state == ModuleState.BUILTIN:
391-
return spec_from_loader(modinfo.name, loader=BuiltinImporter)
403+
spec = spec_from_loader(modinfo.name, loader=BuiltinImporter)
404+
assert spec is not None
405+
return spec
392406
else:
393407
raise ValueError(modinfo)
394408

395-
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path:
409+
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path | None:
396410
"""Get shared library location in build directory"""
397411
if modinfo.state == ModuleState.SHARED:
398412
return self.builddir / f"{modinfo.name}{self.ext_suffix}"
399413
else:
400414
return None
401415

402-
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec):
416+
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec) -> None:
403417
"""Check that the module file is present and not empty"""
404-
if spec.loader is BuiltinImporter:
418+
if spec.loader is BuiltinImporter: # type: ignore[comparison-overlap]
405419
return
406420
try:
421+
assert spec.origin is not None
407422
st = os.stat(spec.origin)
408423
except FileNotFoundError:
409424
logger.error("%s (%s) is missing", modinfo.name, spec.origin)
410425
raise
411426
if not st.st_size:
412427
raise ImportError(f"{spec.origin} is an empty file")
413428

414-
def check_module_import(self, modinfo: ModuleInfo):
429+
def check_module_import(self, modinfo: ModuleInfo) -> None:
415430
"""Attempt to import module and report errors"""
416431
spec = self.get_spec(modinfo)
417432
self._check_file(modinfo, spec)
@@ -430,7 +445,7 @@ def check_module_import(self, modinfo: ModuleInfo):
430445
logger.exception("Importing extension '%s' failed!", modinfo.name)
431446
raise
432447

433-
def check_module_cross(self, modinfo: ModuleInfo):
448+
def check_module_cross(self, modinfo: ModuleInfo) -> None:
434449
"""Sanity check for cross compiling"""
435450
spec = self.get_spec(modinfo)
436451
self._check_file(modinfo, spec)
@@ -443,6 +458,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
443458

444459
failed_name = f"{modinfo.name}_failed{self.ext_suffix}"
445460
builddir_path = self.get_location(modinfo)
461+
assert builddir_path is not None
446462
if builddir_path.is_symlink():
447463
symlink = builddir_path
448464
module_path = builddir_path.resolve().relative_to(os.getcwd())
@@ -466,7 +482,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
466482
logger.debug("Rename '%s' -> '%s'", module_path, failed_path)
467483

468484

469-
def main():
485+
def main() -> None:
470486
args = parser.parse_args()
471487
if args.debug:
472488
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)