mirror of
https://github.com/python/cpython.git
synced 2025-11-01 06:01:29 +00:00
gh-133403: Check generate_stdlib_module_names and check_extension_modules with mypy (#137546)
This commit is contained in:
parent
715647a99a
commit
68a61b0f1d
4 changed files with 65 additions and 42 deletions
|
|
@ -17,9 +17,11 @@
|
|||
|
||||
See --help for more information
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import _imp
|
||||
import argparse
|
||||
import collections
|
||||
import enum
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -29,13 +31,16 @@
|
|||
import sysconfig
|
||||
import warnings
|
||||
from collections.abc import Iterable
|
||||
from importlib._bootstrap import _load as bootstrap_load
|
||||
from importlib._bootstrap import ( # type: ignore[attr-defined]
|
||||
_load as bootstrap_load,
|
||||
)
|
||||
from importlib.machinery import (
|
||||
BuiltinImporter,
|
||||
ExtensionFileLoader,
|
||||
ModuleSpec,
|
||||
)
|
||||
from importlib.util import spec_from_file_location, spec_from_loader
|
||||
from typing import NamedTuple
|
||||
|
||||
SRC_DIR = pathlib.Path(__file__).parent.parent.parent
|
||||
|
||||
|
|
@ -112,6 +117,7 @@
|
|||
)
|
||||
|
||||
|
||||
@enum.unique
|
||||
class ModuleState(enum.Enum):
|
||||
# Makefile state "yes"
|
||||
BUILTIN = "builtin"
|
||||
|
|
@ -123,11 +129,13 @@ class ModuleState(enum.Enum):
|
|||
# disabled by Setup / makesetup rule
|
||||
DISABLED_SETUP = "disabled_setup"
|
||||
|
||||
def __bool__(self):
|
||||
def __bool__(self) -> bool:
|
||||
return self.value in {"builtin", "shared"}
|
||||
|
||||
|
||||
ModuleInfo = collections.namedtuple("ModuleInfo", "name state")
|
||||
class ModuleInfo(NamedTuple):
|
||||
name: str
|
||||
state: ModuleState
|
||||
|
||||
|
||||
class ModuleChecker:
|
||||
|
|
@ -135,9 +143,9 @@ class ModuleChecker:
|
|||
|
||||
setup_files = (
|
||||
# see end of configure.ac
|
||||
"Modules/Setup.local",
|
||||
"Modules/Setup.stdlib",
|
||||
"Modules/Setup.bootstrap",
|
||||
pathlib.Path("Modules/Setup.local"),
|
||||
pathlib.Path("Modules/Setup.stdlib"),
|
||||
pathlib.Path("Modules/Setup.bootstrap"),
|
||||
SRC_DIR / "Modules/Setup",
|
||||
)
|
||||
|
||||
|
|
@ -149,15 +157,15 @@ def __init__(self, cross_compiling: bool = False, strict: bool = False):
|
|||
self.builddir = self.get_builddir()
|
||||
self.modules = self.get_modules()
|
||||
|
||||
self.builtin_ok = []
|
||||
self.shared_ok = []
|
||||
self.failed_on_import = []
|
||||
self.missing = []
|
||||
self.disabled_configure = []
|
||||
self.disabled_setup = []
|
||||
self.notavailable = []
|
||||
self.builtin_ok: list[ModuleInfo] = []
|
||||
self.shared_ok: list[ModuleInfo] = []
|
||||
self.failed_on_import: list[ModuleInfo] = []
|
||||
self.missing: list[ModuleInfo] = []
|
||||
self.disabled_configure: list[ModuleInfo] = []
|
||||
self.disabled_setup: list[ModuleInfo] = []
|
||||
self.notavailable: list[ModuleInfo] = []
|
||||
|
||||
def check(self):
|
||||
def check(self) -> None:
|
||||
if not hasattr(_imp, 'create_dynamic'):
|
||||
logger.warning(
|
||||
('Dynamic extensions not supported '
|
||||
|
|
@ -189,10 +197,10 @@ def check(self):
|
|||
assert modinfo.state == ModuleState.SHARED
|
||||
self.shared_ok.append(modinfo)
|
||||
|
||||
def summary(self, *, verbose: bool = False):
|
||||
def summary(self, *, verbose: bool = False) -> None:
|
||||
longest = max([len(e.name) for e in self.modules], default=0)
|
||||
|
||||
def print_three_column(modinfos: list[ModuleInfo]):
|
||||
def print_three_column(modinfos: list[ModuleInfo]) -> None:
|
||||
names = [modinfo.name for modinfo in modinfos]
|
||||
names.sort(key=str.lower)
|
||||
# guarantee zip() doesn't drop anything
|
||||
|
|
@ -262,12 +270,12 @@ def print_three_column(modinfos: list[ModuleInfo]):
|
|||
f"{len(self.failed_on_import)} failed on import)"
|
||||
)
|
||||
|
||||
def check_strict_build(self):
|
||||
def check_strict_build(self) -> None:
|
||||
"""Fail if modules are missing and it's a strict build"""
|
||||
if self.strict_extensions_build and (self.failed_on_import or self.missing):
|
||||
raise RuntimeError("Failed to build some stdlib modules")
|
||||
|
||||
def list_module_names(self, *, all: bool = False) -> set:
|
||||
def list_module_names(self, *, all: bool = False) -> set[str]:
|
||||
names = {modinfo.name for modinfo in self.modules}
|
||||
if all:
|
||||
names.update(WINDOWS_MODULES)
|
||||
|
|
@ -280,9 +288,9 @@ def get_builddir(self) -> pathlib.Path:
|
|||
except FileNotFoundError:
|
||||
logger.error("%s must be run from the top build directory", __file__)
|
||||
raise
|
||||
builddir = pathlib.Path(builddir)
|
||||
logger.debug("%s: %s", self.pybuilddir_txt, builddir)
|
||||
return builddir
|
||||
builddir_path = pathlib.Path(builddir)
|
||||
logger.debug("%s: %s", self.pybuilddir_txt, builddir_path)
|
||||
return builddir_path
|
||||
|
||||
def get_modules(self) -> list[ModuleInfo]:
|
||||
"""Get module info from sysconfig and Modules/Setup* files"""
|
||||
|
|
@ -367,7 +375,7 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
|
|||
case ["*disabled*"]:
|
||||
state = ModuleState.DISABLED
|
||||
case ["*noconfig*"]:
|
||||
state = None
|
||||
continue
|
||||
case [*items]:
|
||||
if state == ModuleState.DISABLED:
|
||||
# *disabled* can disable multiple modules per line
|
||||
|
|
@ -384,26 +392,33 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
|
|||
def get_spec(self, modinfo: ModuleInfo) -> ModuleSpec:
|
||||
"""Get ModuleSpec for builtin or extension module"""
|
||||
if modinfo.state == ModuleState.SHARED:
|
||||
location = os.fspath(self.get_location(modinfo))
|
||||
mod_location = self.get_location(modinfo)
|
||||
assert mod_location is not None
|
||||
location = os.fspath(mod_location)
|
||||
loader = ExtensionFileLoader(modinfo.name, location)
|
||||
return spec_from_file_location(modinfo.name, location, loader=loader)
|
||||
spec = spec_from_file_location(modinfo.name, location, loader=loader)
|
||||
assert spec is not None
|
||||
return spec
|
||||
elif modinfo.state == ModuleState.BUILTIN:
|
||||
return spec_from_loader(modinfo.name, loader=BuiltinImporter)
|
||||
spec = spec_from_loader(modinfo.name, loader=BuiltinImporter)
|
||||
assert spec is not None
|
||||
return spec
|
||||
else:
|
||||
raise ValueError(modinfo)
|
||||
|
||||
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path:
|
||||
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path | None:
|
||||
"""Get shared library location in build directory"""
|
||||
if modinfo.state == ModuleState.SHARED:
|
||||
return self.builddir / f"{modinfo.name}{self.ext_suffix}"
|
||||
else:
|
||||
return None
|
||||
|
||||
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec):
|
||||
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec) -> None:
|
||||
"""Check that the module file is present and not empty"""
|
||||
if spec.loader is BuiltinImporter:
|
||||
if spec.loader is BuiltinImporter: # type: ignore[comparison-overlap]
|
||||
return
|
||||
try:
|
||||
assert spec.origin is not None
|
||||
st = os.stat(spec.origin)
|
||||
except FileNotFoundError:
|
||||
logger.error("%s (%s) is missing", modinfo.name, spec.origin)
|
||||
|
|
@ -411,7 +426,7 @@ def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec):
|
|||
if not st.st_size:
|
||||
raise ImportError(f"{spec.origin} is an empty file")
|
||||
|
||||
def check_module_import(self, modinfo: ModuleInfo):
|
||||
def check_module_import(self, modinfo: ModuleInfo) -> None:
|
||||
"""Attempt to import module and report errors"""
|
||||
spec = self.get_spec(modinfo)
|
||||
self._check_file(modinfo, spec)
|
||||
|
|
@ -430,7 +445,7 @@ def check_module_import(self, modinfo: ModuleInfo):
|
|||
logger.exception("Importing extension '%s' failed!", modinfo.name)
|
||||
raise
|
||||
|
||||
def check_module_cross(self, modinfo: ModuleInfo):
|
||||
def check_module_cross(self, modinfo: ModuleInfo) -> None:
|
||||
"""Sanity check for cross compiling"""
|
||||
spec = self.get_spec(modinfo)
|
||||
self._check_file(modinfo, spec)
|
||||
|
|
@ -443,6 +458,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
|
|||
|
||||
failed_name = f"{modinfo.name}_failed{self.ext_suffix}"
|
||||
builddir_path = self.get_location(modinfo)
|
||||
assert builddir_path is not None
|
||||
if builddir_path.is_symlink():
|
||||
symlink = builddir_path
|
||||
module_path = builddir_path.resolve().relative_to(os.getcwd())
|
||||
|
|
@ -466,7 +482,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
|
|||
logger.debug("Rename '%s' -> '%s'", module_path, failed_path)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
args = parser.parse_args()
|
||||
if args.debug:
|
||||
args.verbose = True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue