|
23 | 23 | import _imp |
24 | 24 | import argparse |
25 | 25 | import enum |
| 26 | +import json |
26 | 27 | import logging |
27 | 28 | import os |
28 | 29 | import pathlib |
| 30 | +import pprint |
29 | 31 | import re |
30 | 32 | import sys |
31 | 33 | import sysconfig |
|
116 | 118 | help="Print a list of module names to stdout and exit", |
117 | 119 | ) |
118 | 120 |
|
| 121 | +parser.add_argument( |
| 122 | + "--generate-missing-stdlib-info", |
| 123 | + action="store_true", |
| 124 | + help="Generate file with stdlib module info", |
| 125 | +) |
| 126 | + |
| 127 | +parser.add_argument( |
| 128 | + "--with-missing-stdlib-config", |
| 129 | + metavar="CONFIG_FILE", |
| 130 | + help="Path to JSON config file with custom missing module messages", |
| 131 | +) |
| 132 | + |
119 | 133 |
|
120 | 134 | @enum.unique |
121 | 135 | class ModuleState(enum.Enum): |
@@ -281,6 +295,39 @@ def list_module_names(self, *, all: bool = False) -> set[str]: |
281 | 295 | names.update(WINDOWS_MODULES) |
282 | 296 | return names |
283 | 297 |
|
| 298 | + def generate_missing_stdlib_info(self, config_path: str | None = None) -> None: |
| 299 | + config_messages = {} |
| 300 | + if config_path: |
| 301 | + try: |
| 302 | + with open(config_path, encoding='utf-8') as f: |
| 303 | + config_messages = json.load(f) |
| 304 | + except (FileNotFoundError, json.JSONDecodeError) as e: |
| 305 | + raise RuntimeError(f"Failed to load missing stdlib config {config_path!r}") from e |
| 306 | + |
| 307 | + messages = {} |
| 308 | + for name in WINDOWS_MODULES: |
| 309 | + messages[name] = f"Unsupported platform for Windows-only standard library module {name!r}" |
| 310 | + |
| 311 | + for modinfo in self.modules: |
| 312 | + if modinfo.state in (ModuleState.DISABLED, ModuleState.DISABLED_SETUP): |
| 313 | + messages[modinfo.name] = f"Standard library module disabled during build {modinfo.name!r} was not found" |
| 314 | + elif modinfo.state == ModuleState.NA: |
| 315 | + messages[modinfo.name] = f"Unsupported platform for standard library module {modinfo.name!r}" |
| 316 | + |
| 317 | + messages.update(config_messages) |
| 318 | + |
| 319 | + content = f'''\ |
| 320 | +# Standard library information used by the traceback module for more informative |
| 321 | +# ModuleNotFound error messages. |
| 322 | +# Generated by check_extension_modules.py |
| 323 | +
|
| 324 | +_MISSING_STDLIB_MODULE_MESSAGES = {pprint.pformat(messages)} |
| 325 | +''' |
| 326 | + |
| 327 | + output_path = self.builddir / "_missing_stdlib_info.py" |
| 328 | + with open(output_path, "w", encoding="utf-8") as f: |
| 329 | + f.write(content) |
| 330 | + |
284 | 331 | def get_builddir(self) -> pathlib.Path: |
285 | 332 | try: |
286 | 333 | with open(self.pybuilddir_txt, encoding="utf-8") as f: |
@@ -499,6 +546,9 @@ def main() -> None: |
499 | 546 | names = checker.list_module_names(all=True) |
500 | 547 | for name in sorted(names): |
501 | 548 | print(name) |
| 549 | + elif args.generate_missing_stdlib_info: |
| 550 | + checker.check() |
| 551 | + checker.generate_missing_stdlib_info(args.with_missing_stdlib_config) |
502 | 552 | else: |
503 | 553 | checker.check() |
504 | 554 | checker.summary(verbose=args.verbose) |
|
0 commit comments