mirror of
https://github.com/python/cpython.git
synced 2025-12-23 09:19:18 +00:00
gh-139707: Add mechanism for distributors to supply error messages for missing stdlib modules (GH-140783)
Some checks are pending
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Some checks are pending
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / iOS (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
This commit is contained in:
parent
b708485d1a
commit
d4fa70706c
9 changed files with 149 additions and 2 deletions
|
|
@ -322,6 +322,30 @@ General Options
|
|||
|
||||
.. versionadded:: 3.11
|
||||
|
||||
.. option:: --with-missing-stdlib-config=FILE
|
||||
|
||||
Path to a `JSON <https://www.json.org/json-en.html>`_ configuration file
|
||||
containing custom error messages for missing :term:`standard library` modules.
|
||||
|
||||
This option is intended for Python distributors who wish to provide
|
||||
distribution-specific guidance when users encounter standard library
|
||||
modules that are missing or packaged separately.
|
||||
|
||||
The JSON file should map missing module names to custom error message strings.
|
||||
For example, if your distribution packages :mod:`tkinter` and
|
||||
:mod:`_tkinter` separately and excludes :mod:`!_gdbm` for legal reasons,
|
||||
the configuration could contain:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"_gdbm": "The '_gdbm' module is not available in this distribution"
|
||||
"tkinter": "Install the python-tk package to use tkinter",
|
||||
"_tkinter": "Install the python-tk package to use tkinter",
|
||||
}
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
.. option:: --enable-pystats
|
||||
|
||||
Turn on internal Python performance statistics gathering.
|
||||
|
|
|
|||
|
|
@ -1247,6 +1247,12 @@ Build changes
|
|||
set to ``no`` or with :option:`!--without-system-libmpdec`.
|
||||
(Contributed by Sergey B Kirpichev in :gh:`115119`.)
|
||||
|
||||
* The new configure option :option:`--with-missing-stdlib-config=FILE` allows
|
||||
distributors to pass a `JSON <https://www.json.org/json-en.html>`_
|
||||
configuration file containing custom error messages for :term:`standard library`
|
||||
modules that are missing or packaged separately.
|
||||
(Contributed by Stan Ulbrych and Petr Viktorin in :gh:`139707`.)
|
||||
|
||||
|
||||
Porting to Python 3.15
|
||||
======================
|
||||
|
|
|
|||
|
|
@ -5051,7 +5051,7 @@ class MiscTest(unittest.TestCase):
|
|||
b"or to enable your virtual environment?"), stderr
|
||||
)
|
||||
|
||||
def test_missing_stdlib_package(self):
|
||||
def test_missing_stdlib_module(self):
|
||||
code = """
|
||||
import sys
|
||||
sys.stdlib_module_names |= {'spam'}
|
||||
|
|
@ -5061,6 +5061,27 @@ class MiscTest(unittest.TestCase):
|
|||
|
||||
self.assertIn(b"Standard library module 'spam' was not found", stderr)
|
||||
|
||||
code = """
|
||||
import sys
|
||||
import traceback
|
||||
traceback._MISSING_STDLIB_MODULE_MESSAGES = {'spam': "Install 'spam4life' for 'spam'"}
|
||||
sys.stdlib_module_names |= {'spam'}
|
||||
import spam
|
||||
"""
|
||||
_, _, stderr = assert_python_failure('-S', '-c', code)
|
||||
|
||||
self.assertIn(b"Install 'spam4life' for 'spam'", stderr)
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "Non-Windows test")
|
||||
def test_windows_only_module_error(self):
|
||||
try:
|
||||
import msvcrt # noqa: F401
|
||||
except ModuleNotFoundError:
|
||||
formatted = traceback.format_exc()
|
||||
self.assertIn("Unsupported platform for Windows-only standard library module 'msvcrt'", formatted)
|
||||
else:
|
||||
self.fail("ModuleNotFoundError was not raised")
|
||||
|
||||
|
||||
class TestColorizedTraceback(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ import _colorize
|
|||
|
||||
from contextlib import suppress
|
||||
|
||||
try:
|
||||
from _missing_stdlib_info import _MISSING_STDLIB_MODULE_MESSAGES
|
||||
except ImportError:
|
||||
_MISSING_STDLIB_MODULE_MESSAGES = {}
|
||||
|
||||
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
|
||||
'format_exception_only', 'format_list', 'format_stack',
|
||||
'format_tb', 'print_exc', 'format_exc', 'print_exception',
|
||||
|
|
@ -1110,7 +1115,11 @@ class TracebackException:
|
|||
elif exc_type and issubclass(exc_type, ModuleNotFoundError):
|
||||
module_name = getattr(exc_value, "name", None)
|
||||
if module_name in sys.stdlib_module_names:
|
||||
self._str = f"Standard library module '{module_name}' was not found"
|
||||
message = _MISSING_STDLIB_MODULE_MESSAGES.get(
|
||||
module_name,
|
||||
f"Standard library module {module_name!r} was not found"
|
||||
)
|
||||
self._str = message
|
||||
elif sys.flags.no_site:
|
||||
self._str += (". Site initialization is disabled, did you forget to "
|
||||
+ "add the site-packages directory to sys.path "
|
||||
|
|
|
|||
|
|
@ -1604,6 +1604,11 @@ sharedmods: $(SHAREDMODS) pybuilddir.txt
|
|||
# dependency on BUILDPYTHON ensures that the target is run last
|
||||
.PHONY: checksharedmods
|
||||
checksharedmods: sharedmods $(PYTHON_FOR_BUILD_DEPS) $(BUILDPYTHON)
|
||||
@if [ -n "@MISSING_STDLIB_CONFIG@" ]; then \
|
||||
$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/check_extension_modules.py --generate-missing-stdlib-info --with-missing-stdlib-config="@MISSING_STDLIB_CONFIG@"; \
|
||||
else \
|
||||
$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/check_extension_modules.py --generate-missing-stdlib-info; \
|
||||
fi
|
||||
@$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/check_extension_modules.py
|
||||
|
||||
.PHONY: rundsymutil
|
||||
|
|
@ -2820,6 +2825,7 @@ libinstall: all $(srcdir)/Modules/xxmodule.c
|
|||
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py $(DESTDIR)$(LIBDEST); \
|
||||
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfig_vars_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).json $(DESTDIR)$(LIBDEST); \
|
||||
$(INSTALL_DATA) `cat pybuilddir.txt`/build-details.json $(DESTDIR)$(LIBDEST); \
|
||||
$(INSTALL_DATA) `cat pybuilddir.txt`/_missing_stdlib_info.py $(DESTDIR)$(LIBDEST); \
|
||||
$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
|
||||
@ # If app store compliance has been configured, apply the patch to the
|
||||
@ # installed library code. The patch has been previously validated against
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
Add configure option :option:`--with-missing-stdlib-config=FILE` allows
|
||||
which distributors to pass a `JSON <https://www.json.org/json-en.html>`_
|
||||
configuration file containing custom error messages for missing
|
||||
:term:`standard library` modules.
|
||||
|
|
@ -23,9 +23,11 @@ from __future__ import annotations
|
|||
import _imp
|
||||
import argparse
|
||||
import enum
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
import sysconfig
|
||||
|
|
@ -116,6 +118,18 @@ parser.add_argument(
|
|||
help="Print a list of module names to stdout and exit",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--generate-missing-stdlib-info",
|
||||
action="store_true",
|
||||
help="Generate file with stdlib module info",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--with-missing-stdlib-config",
|
||||
metavar="CONFIG_FILE",
|
||||
help="Path to JSON config file with custom missing module messages",
|
||||
)
|
||||
|
||||
|
||||
@enum.unique
|
||||
class ModuleState(enum.Enum):
|
||||
|
|
@ -281,6 +295,39 @@ class ModuleChecker:
|
|||
names.update(WINDOWS_MODULES)
|
||||
return names
|
||||
|
||||
def generate_missing_stdlib_info(self, config_path: str | None = None) -> None:
|
||||
config_messages = {}
|
||||
if config_path:
|
||||
try:
|
||||
with open(config_path, encoding='utf-8') as f:
|
||||
config_messages = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
raise RuntimeError(f"Failed to load missing stdlib config {config_path!r}") from e
|
||||
|
||||
messages = {}
|
||||
for name in WINDOWS_MODULES:
|
||||
messages[name] = f"Unsupported platform for Windows-only standard library module {name!r}"
|
||||
|
||||
for modinfo in self.modules:
|
||||
if modinfo.state in (ModuleState.DISABLED, ModuleState.DISABLED_SETUP):
|
||||
messages[modinfo.name] = f"Standard library module disabled during build {modinfo.name!r} was not found"
|
||||
elif modinfo.state == ModuleState.NA:
|
||||
messages[modinfo.name] = f"Unsupported platform for standard library module {modinfo.name!r}"
|
||||
|
||||
messages.update(config_messages)
|
||||
|
||||
content = f'''\
|
||||
# Standard library information used by the traceback module for more informative
|
||||
# ModuleNotFound error messages.
|
||||
# Generated by check_extension_modules.py
|
||||
|
||||
_MISSING_STDLIB_MODULE_MESSAGES = {pprint.pformat(messages)}
|
||||
'''
|
||||
|
||||
output_path = self.builddir / "_missing_stdlib_info.py"
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
def get_builddir(self) -> pathlib.Path:
|
||||
try:
|
||||
with open(self.pybuilddir_txt, encoding="utf-8") as f:
|
||||
|
|
@ -499,6 +546,9 @@ def main() -> None:
|
|||
names = checker.list_module_names(all=True)
|
||||
for name in sorted(names):
|
||||
print(name)
|
||||
elif args.generate_missing_stdlib_info:
|
||||
checker.check()
|
||||
checker.generate_missing_stdlib_info(args.with_missing_stdlib_config)
|
||||
else:
|
||||
checker.check()
|
||||
checker.summary(verbose=args.verbose)
|
||||
|
|
|
|||
18
configure
generated
vendored
18
configure
generated
vendored
|
|
@ -1012,6 +1012,7 @@ UNIVERSALSDK
|
|||
host_exec_prefix
|
||||
host_prefix
|
||||
MACHDEP
|
||||
MISSING_STDLIB_CONFIG
|
||||
PKG_CONFIG_LIBDIR
|
||||
PKG_CONFIG_PATH
|
||||
PKG_CONFIG
|
||||
|
|
@ -1083,6 +1084,7 @@ ac_user_opts='
|
|||
enable_option_checking
|
||||
with_build_python
|
||||
with_pkg_config
|
||||
with_missing_stdlib_config
|
||||
enable_universalsdk
|
||||
with_universal_archs
|
||||
with_framework_name
|
||||
|
|
@ -1862,6 +1864,9 @@ Optional Packages:
|
|||
--with-pkg-config=[yes|no|check]
|
||||
use pkg-config to detect build options (default is
|
||||
check)
|
||||
--with-missing-stdlib-config=FILE
|
||||
File with custom module error messages for missing
|
||||
stdlib modules
|
||||
--with-universal-archs=ARCH
|
||||
specify the kind of macOS universal binary that
|
||||
should be created. This option is only valid when
|
||||
|
|
@ -4095,6 +4100,19 @@ if test "$with_pkg_config" = yes -a -z "$PKG_CONFIG"; then
|
|||
as_fn_error $? "pkg-config is required" "$LINENO" 5]
|
||||
fi
|
||||
|
||||
|
||||
# Check whether --with-missing-stdlib-config was given.
|
||||
if test ${with_missing_stdlib_config+y}
|
||||
then :
|
||||
withval=$with_missing_stdlib_config; MISSING_STDLIB_CONFIG="$withval"
|
||||
else case e in #(
|
||||
e) MISSING_STDLIB_CONFIG=""
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Set name for machine-dependent library files
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MACHDEP" >&5
|
||||
|
|
|
|||
|
|
@ -307,6 +307,15 @@ if test "$with_pkg_config" = yes -a -z "$PKG_CONFIG"; then
|
|||
AC_MSG_ERROR([pkg-config is required])]
|
||||
fi
|
||||
|
||||
dnl Allow distributors to provide custom missing stdlib module error messages
|
||||
AC_ARG_WITH([missing-stdlib-config],
|
||||
[AS_HELP_STRING([--with-missing-stdlib-config=FILE],
|
||||
[File with custom module error messages for missing stdlib modules])],
|
||||
[MISSING_STDLIB_CONFIG="$withval"],
|
||||
[MISSING_STDLIB_CONFIG=""]
|
||||
)
|
||||
AC_SUBST([MISSING_STDLIB_CONFIG])
|
||||
|
||||
# Set name for machine-dependent library files
|
||||
AC_ARG_VAR([MACHDEP], [name for machine-dependent library files])
|
||||
AC_MSG_CHECKING([MACHDEP])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue