1717
1818See --help for more information
1919"""
20+
21+ from __future__ import annotations
22+
2023import _imp
2124import argparse
22- import collections
2325import enum
2426import logging
2527import os
2931import sysconfig
3032import warnings
3133from 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+ )
3337from importlib .machinery import (
3438 BuiltinImporter ,
3539 ExtensionFileLoader ,
3640 ModuleSpec ,
3741)
3842from importlib .util import spec_from_file_location , spec_from_loader
43+ from typing import NamedTuple
3944
4045SRC_DIR = pathlib .Path (__file__ ).parent .parent .parent
4146
112117)
113118
114119
120+ @enum .unique
115121class 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
133141class 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
0 commit comments