17
17
18
18
See --help for more information
19
19
"""
20
+
21
+ from __future__ import annotations
22
+
20
23
import _imp
21
24
import argparse
22
- import collections
23
25
import enum
24
26
import logging
25
27
import os
29
31
import sysconfig
30
32
import warnings
31
33
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
+ )
33
37
from importlib .machinery import (
34
38
BuiltinImporter ,
35
39
ExtensionFileLoader ,
36
40
ModuleSpec ,
37
41
)
38
42
from importlib .util import spec_from_file_location , spec_from_loader
43
+ from typing import NamedTuple
39
44
40
45
SRC_DIR = pathlib .Path (__file__ ).parent .parent .parent
41
46
112
117
)
113
118
114
119
120
+ @enum .unique
115
121
class ModuleState (enum .Enum ):
116
122
# Makefile state "yes"
117
123
BUILTIN = "builtin"
@@ -123,21 +129,23 @@ class ModuleState(enum.Enum):
123
129
# disabled by Setup / makesetup rule
124
130
DISABLED_SETUP = "disabled_setup"
125
131
126
- def __bool__ (self ):
132
+ def __bool__ (self ) -> bool :
127
133
return self .value in {"builtin" , "shared" }
128
134
129
135
130
- ModuleInfo = collections .namedtuple ("ModuleInfo" , "name state" )
136
+ class ModuleInfo (NamedTuple ):
137
+ name : str
138
+ state : ModuleState
131
139
132
140
133
141
class ModuleChecker :
134
142
pybuilddir_txt = "pybuilddir.txt"
135
143
136
144
setup_files = (
137
145
# 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" ) ,
141
149
SRC_DIR / "Modules/Setup" ,
142
150
)
143
151
@@ -149,15 +157,15 @@ def __init__(self, cross_compiling: bool = False, strict: bool = False):
149
157
self .builddir = self .get_builddir ()
150
158
self .modules = self .get_modules ()
151
159
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 ] = []
159
167
160
- def check (self ):
168
+ def check (self ) -> None :
161
169
if not hasattr (_imp , 'create_dynamic' ):
162
170
logger .warning (
163
171
('Dynamic extensions not supported '
@@ -189,10 +197,10 @@ def check(self):
189
197
assert modinfo .state == ModuleState .SHARED
190
198
self .shared_ok .append (modinfo )
191
199
192
- def summary (self , * , verbose : bool = False ):
200
+ def summary (self , * , verbose : bool = False ) -> None :
193
201
longest = max ([len (e .name ) for e in self .modules ], default = 0 )
194
202
195
- def print_three_column (modinfos : list [ModuleInfo ]):
203
+ def print_three_column (modinfos : list [ModuleInfo ]) -> None :
196
204
names = [modinfo .name for modinfo in modinfos ]
197
205
names .sort (key = str .lower )
198
206
# guarantee zip() doesn't drop anything
@@ -262,12 +270,12 @@ def print_three_column(modinfos: list[ModuleInfo]):
262
270
f"{ len (self .failed_on_import )} failed on import)"
263
271
)
264
272
265
- def check_strict_build (self ):
273
+ def check_strict_build (self ) -> None :
266
274
"""Fail if modules are missing and it's a strict build"""
267
275
if self .strict_extensions_build and (self .failed_on_import or self .missing ):
268
276
raise RuntimeError ("Failed to build some stdlib modules" )
269
277
270
- def list_module_names (self , * , all : bool = False ) -> set :
278
+ def list_module_names (self , * , all : bool = False ) -> set [ str ] :
271
279
names = {modinfo .name for modinfo in self .modules }
272
280
if all :
273
281
names .update (WINDOWS_MODULES )
@@ -280,9 +288,9 @@ def get_builddir(self) -> pathlib.Path:
280
288
except FileNotFoundError :
281
289
logger .error ("%s must be run from the top build directory" , __file__ )
282
290
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
286
294
287
295
def get_modules (self ) -> list [ModuleInfo ]:
288
296
"""Get module info from sysconfig and Modules/Setup* files"""
@@ -367,7 +375,7 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
367
375
case ["*disabled*" ]:
368
376
state = ModuleState .DISABLED
369
377
case ["*noconfig*" ]:
370
- state = None
378
+ continue
371
379
case [* items ]:
372
380
if state == ModuleState .DISABLED :
373
381
# *disabled* can disable multiple modules per line
@@ -384,34 +392,41 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
384
392
def get_spec (self , modinfo : ModuleInfo ) -> ModuleSpec :
385
393
"""Get ModuleSpec for builtin or extension module"""
386
394
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 )
388
398
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
390
402
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
392
406
else :
393
407
raise ValueError (modinfo )
394
408
395
- def get_location (self , modinfo : ModuleInfo ) -> pathlib .Path :
409
+ def get_location (self , modinfo : ModuleInfo ) -> pathlib .Path | None :
396
410
"""Get shared library location in build directory"""
397
411
if modinfo .state == ModuleState .SHARED :
398
412
return self .builddir / f"{ modinfo .name } { self .ext_suffix } "
399
413
else :
400
414
return None
401
415
402
- def _check_file (self , modinfo : ModuleInfo , spec : ModuleSpec ):
416
+ def _check_file (self , modinfo : ModuleInfo , spec : ModuleSpec ) -> None :
403
417
"""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]
405
419
return
406
420
try :
421
+ assert spec .origin is not None
407
422
st = os .stat (spec .origin )
408
423
except FileNotFoundError :
409
424
logger .error ("%s (%s) is missing" , modinfo .name , spec .origin )
410
425
raise
411
426
if not st .st_size :
412
427
raise ImportError (f"{ spec .origin } is an empty file" )
413
428
414
- def check_module_import (self , modinfo : ModuleInfo ):
429
+ def check_module_import (self , modinfo : ModuleInfo ) -> None :
415
430
"""Attempt to import module and report errors"""
416
431
spec = self .get_spec (modinfo )
417
432
self ._check_file (modinfo , spec )
@@ -430,7 +445,7 @@ def check_module_import(self, modinfo: ModuleInfo):
430
445
logger .exception ("Importing extension '%s' failed!" , modinfo .name )
431
446
raise
432
447
433
- def check_module_cross (self , modinfo : ModuleInfo ):
448
+ def check_module_cross (self , modinfo : ModuleInfo ) -> None :
434
449
"""Sanity check for cross compiling"""
435
450
spec = self .get_spec (modinfo )
436
451
self ._check_file (modinfo , spec )
@@ -443,6 +458,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
443
458
444
459
failed_name = f"{ modinfo .name } _failed{ self .ext_suffix } "
445
460
builddir_path = self .get_location (modinfo )
461
+ assert builddir_path is not None
446
462
if builddir_path .is_symlink ():
447
463
symlink = builddir_path
448
464
module_path = builddir_path .resolve ().relative_to (os .getcwd ())
@@ -466,7 +482,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
466
482
logger .debug ("Rename '%s' -> '%s'" , module_path , failed_path )
467
483
468
484
469
- def main ():
485
+ def main () -> None :
470
486
args = parser .parse_args ()
471
487
if args .debug :
472
488
args .verbose = True
0 commit comments