Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,8 +1037,10 @@ def exec_module(self, module):
def is_package(self, fullname):
"""Return True if the extension module is a package."""
file_name = _path_split(self.path)[1]
return any(file_name == '__init__' + suffix
for suffix in EXTENSION_SUFFIXES)
tail_name = fullname.rpartition('.')[2]
return tail_name != '__init__' and any(
file_name == '__init__' + suffix
for suffix in EXTENSION_SUFFIXES)

def get_code(self, fullname):
"""Return None as an extension module cannot create a code object."""
Expand Down
15 changes: 10 additions & 5 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1525,7 +1525,10 @@ sharedmods: $(SHAREDMODS) pybuilddir.txt
$(MKDIR_P) $$target; \
for mod in X $(SHAREDMODS); do \
if test $$mod != X; then \
$(LN) -sf ../../$$mod $$target/`basename $$mod`; \
rel=`echo $$mod | sed 's,^Modules/,,'`; \
dest=$$target/$$rel; \
$(MKDIR_P) `dirname $$dest`; \
$(LN) -sf `pwd`/$$mod $$dest; \
fi; \
done

Expand Down Expand Up @@ -2394,11 +2397,13 @@ sharedinstall: all
done
@for i in X $(SHAREDMODS); do \
if test $$i != X; then \
echo $(INSTALL_SHARED) $$i $(DESTSHARED)/`basename $$i`; \
$(INSTALL_SHARED) $$i $(DESTDIR)$(DESTSHARED)/`basename $$i`; \
rel=`echo $$i | sed 's,^Modules/,,'`; \
$(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(DESTSHARED)/`dirname $$rel`; \
echo $(INSTALL_SHARED) $$i $(DESTSHARED)/$$rel; \
$(INSTALL_SHARED) $$i $(DESTDIR)$(DESTSHARED)/$$rel; \
if test -d "$$i.dSYM"; then \
echo $(DSYMUTIL_PATH) $(DESTDIR)$(DESTSHARED)/`basename $$i`; \
$(DSYMUTIL_PATH) $(DESTDIR)$(DESTSHARED)/`basename $$i`; \
echo $(DSYMUTIL_PATH) $(DESTDIR)$(DESTSHARED)/$$rel; \
$(DSYMUTIL_PATH) $(DESTDIR)$(DESTSHARED)/$$rel; \
fi; \
fi; \
done
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The :mod:`math` module is now built as an extension *package*:
``math/__init__`` and ``math/integer``. :mod:`math.integer` is therefore a
real extension submodule instead of the ``_math_integer`` extension module
disguised as one. :file:`Modules/makesetup` now accepts dotted module names
(a package initializer is written with an explicit ``__init__`` leaf, e.g.
``math.__init__``) and maps each to a sub-directory path and the matching
``PyInit_`` symbol. This works both for the usual shared build and for
builds where ``math`` is compiled into the interpreter as a builtin module
(Windows and WebAssembly).
4 changes: 2 additions & 2 deletions Modules/Setup
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ PYTHONPATH=$(COREPYTHONPATH)
#array arraymodule.c
#binascii binascii.c
#cmath cmathmodule.c
#math mathmodule.c
#_math_integer mathintegermodule.c
#math.__init__ mathmodule.c
#math.integer mathintegermodule.c
#mmap mmapmodule.c
#select selectmodule.c
#_sysconfig _sysconfig.c
Expand Down
4 changes: 2 additions & 2 deletions Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@MODULE__HEAPQ_TRUE@_heapq _heapqmodule.c
@MODULE__JSON_TRUE@_json _json.c
@MODULE__LSPROF_TRUE@_lsprof _lsprof.c rotatingtree.c
@MODULE__MATH_INTEGER_TRUE@_math_integer mathintegermodule.c
@MODULE_MATH_INTEGER_TRUE@math.integer mathintegermodule.c
@MODULE__PICKLE_TRUE@_pickle _pickle.c
@MODULE__QUEUE_TRUE@_queue _queuemodule.c
@MODULE__RANDOM_TRUE@_random _randommodule.c
Expand All @@ -52,7 +52,7 @@
@MODULE__ZONEINFO_TRUE@_zoneinfo _zoneinfo.c

# needs libm
@MODULE_MATH_TRUE@math mathmodule.c
@MODULE_MATH_TRUE@math.__init__ mathmodule.c
@MODULE_CMATH_TRUE@cmath cmathmodule.c
@MODULE__STATISTICS_TRUE@_statistics _statisticsmodule.c

Expand Down
73 changes: 59 additions & 14 deletions Modules/makesetup
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,32 @@ esac
NL='\
'

# Map a module name as written in Setup to its three distinct attributes.
# Submodules use dotted names (math.integer); a package's initializer is
# written with an explicit __init__ leaf (math.__init__).
# mod_regname: import/registration name math.integer -> math.integer
# math.__init__ -> math
# mod_path: path under srcdir (no ext) math.integer -> math/integer
# math.__init__ -> math/__init__
# mod_sym: static PyInit_<sym> symbol math.integer -> math_integer
# math.__init__ -> math
# mod_sym (the static inittab symbol) uses the full dotted name so it stays
# unique across packages; shared submodules instead export
# PyInit_<last component> (see Py_BUILD_CORE_BUILTIN).
mod_regname() {
case $1 in
*.__init__) echo "${1%.__init__}";;
*) echo "$1";;
esac
}
mod_path() {
echo "$1" | tr '.' '/'
}
mod_sym() {
reg=`mod_regname "$1"`
echo "$reg" | tr '.' '_'
}

# Main loop
for i in ${*-Setup}
do
Expand Down Expand Up @@ -183,21 +209,35 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' |
\$\(*_RPATH\)) libs="$libs $arg";;
\$*) libs="$libs $arg"
cpps="$cpps $arg";;
*.*) echo 1>&2 "bad word $arg in $line"
exit 1;;
-u) skip=libs; libs="$libs -u";;
[a-zA-Z_]*)
mods="$mods $arg"
mods_upper=$(echo $mods | tr '[a-z]' '[A-Z]');;
# Module name, possibly dotted (math.integer) or a
# package initializer (math.__init__). Only identifier
# characters and dots are allowed.
case $arg in
*[!a-zA-Z0-9_.]*)
echo 1>&2 "bad word $arg in $line"
exit 1;;
esac
mods="$mods $arg";;
*) echo 1>&2 "bad word $arg in $line"
exit 1;;
esac
done
regmods=
for mod in $mods
do
regmods="$regmods `mod_regname $mod`"
done
# MODULE_<NAME>_* variables are keyed by the registration name with
# dots replaced by underscores (math.integer -> MODULE_MATH_INTEGER,
# math.__init__ -> MODULE_MATH).
mods_upper=`echo $regmods | tr 'a-z' 'A-Z' | tr '.' '_'`
if test -z "$cpps" -a -z "$libs"; then
cpps="\$(MODULE_${mods_upper}_CFLAGS)"
libs="\$(MODULE_${mods_upper}_LDFLAGS)"
fi
for mod in $mods
for mod in $regmods
do
case $CONFIGURED in
*,${mod},*)
Expand All @@ -211,14 +251,14 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' |
case $doconfig in
yes)
LIBS="$LIBS $libs"
MODS="$MODS $mods"
BUILT="$BUILT $mods"
MODS="$MODS $regmods"
BUILT="$BUILT $regmods"
;;
no)
BUILT="$BUILT $mods"
BUILT="$BUILT $regmods"
;;
disabled)
DISABLED="$DISABLED $mods"
DISABLED="$DISABLED $regmods"
continue
;;
esac
Expand Down Expand Up @@ -269,15 +309,17 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' |
esac
for mod in $mods
do
file="$srcdir/$mod\$(EXT_SUFFIX)"
file="$srcdir/`mod_path $mod`\$(EXT_SUFFIX)"
case $doconfig in
no)
SHAREDMODS="$SHAREDMODS $file"
BUILT_SHARED="$BUILT_SHARED $mod"
BUILT_SHARED="$BUILT_SHARED `mod_regname $mod`"
;;
esac
rule="$file: $objs \$(MODULE_${mods_upper}_LDEPS)"
rule="$rule; \$(BLDSHARED) $objs $libs \$(LIBPYTHON) -o $file"
# Submodules/packages land in a sub-directory (math/integer,
# math/__init__); create it before linking.
rule="$rule; \$(MKDIR_P) \$(@D); \$(BLDSHARED) $objs $libs \$(LIBPYTHON) -o $file"
echo "$rule" >>$rulesf
done
done
Expand All @@ -299,8 +341,11 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' |
INITBITS=
for mod in $MODS
do
EXTDECLS="${EXTDECLS}extern PyObject* PyInit_$mod(void);$NL"
INITBITS="${INITBITS} {\"$mod\", PyInit_$mod},$NL"
# $mod is the registration name (e.g. math.integer); the C init
# symbol is named after its last component only (PyInit_integer).
modsym=`mod_sym $mod`
EXTDECLS="${EXTDECLS}extern PyObject* PyInit_$modsym(void);$NL"
INITBITS="${INITBITS} {\"$mod\", PyInit_$modsym},$NL"
done


Expand Down
11 changes: 10 additions & 1 deletion Modules/mathintegermodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1287,8 +1287,17 @@ static struct PyModuleDef math_integer_module = {
.m_slots = math_integer_slots,
};

/* Shared, the dynamic loader looks up PyInit_integer (the last component of
* the name). Built into the interpreter (Py_BUILD_CORE_BUILTIN), the inittab
* uses PyInit_math_integer instead, so the static symbol stays unique. */
#ifdef Py_BUILD_CORE_BUILTIN
# define MATH_INTEGER_PYINIT PyInit_math_integer
#else
# define MATH_INTEGER_PYINIT PyInit_integer
#endif

PyMODINIT_FUNC
PyInit__math_integer(void)
MATH_INTEGER_PYINIT(void)
{
return PyModuleDef_Init(&math_integer_module);
}
31 changes: 23 additions & 8 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3188,7 +3188,26 @@ math_exec(PyObject *module)
return -1;
}

PyObject *intmath = PyImport_ImportModule("_math_integer");
/* math is a package. A shared build gets __path__ from the math/
* directory; a builtin build (e.g. Windows, WebAssembly) does not, so set
* an empty one to mark math as a package and find the builtin submodule. */
PyObject *path;
if (PyObject_GetOptionalAttrString(module, "__path__", &path) < 0) {
return -1;
}
if (path == NULL) {
path = PyList_New(0);
if (path == NULL || PyModule_Add(module, "__path__", path) < 0) {
return -1;
}
}
else {
Py_DECREF(path);
}

/* Importing the math.integer submodule sets sys.modules['math.integer']
* and the parent's 'integer' attribute automatically. */
PyObject *intmath = PyImport_ImportModule("math.integer");
if (!intmath) {
return -1;
}
Expand All @@ -3206,13 +3225,9 @@ math_exec(PyObject *module)
IMPORT_FROM_INTMATH(isqrt);
IMPORT_FROM_INTMATH(lcm);
IMPORT_FROM_INTMATH(perm);
if (_PyImport_SetModuleString("math.integer", intmath) < 0) {
Py_DECREF(intmath);
return -1;
}
if (PyModule_Add(module, "integer", intmath) < 0) {
return -1;
}
/* sys.modules['math.integer'] and the parent 'integer' attribute were
* set by the import machinery above. */
Py_DECREF(intmath);
return 0;
}

Expand Down
4 changes: 2 additions & 2 deletions PC/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extern PyObject* PyInit_errno(void);
extern PyObject* PyInit_faulthandler(void);
extern PyObject* PyInit__tracemalloc(void);
extern PyObject* PyInit_gc(void);
extern PyObject* PyInit__math_integer(void);
extern PyObject* PyInit_math_integer(void);
extern PyObject* PyInit_math(void);
extern PyObject* PyInit_nt(void);
extern PyObject* PyInit__operator(void);
Expand Down Expand Up @@ -101,8 +101,8 @@ struct _inittab _PyImport_Inittab[] = {
{"errno", PyInit_errno},
{"faulthandler", PyInit_faulthandler},
{"gc", PyInit_gc},
{"_math_integer", PyInit__math_integer},
{"math", PyInit_math},
{"math.integer", PyInit_math_integer},
{"nt", PyInit_nt}, /* Use the NT os functions, not posix */
{"_operator", PyInit__operator},
{"_signal", PyInit__signal},
Expand Down
1 change: 0 additions & 1 deletion Python/stdlib_module_names.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 33 additions & 3 deletions Tools/build/check_extension_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ def __bool__(self) -> bool:
return self.value in {"builtin", "shared"}


def registration_name(setup_token: str) -> str:
"""Map a Setup module token to its import/registration name.

Submodules use dotted names (math.integer); an extension package's
initializer is written with an explicit __init__ leaf (math.__init__),
whose registration name is the package itself (math).
"""
if setup_token.endswith(".__init__"):
return setup_token[: -len(".__init__")]
return setup_token


class ModuleInfo(NamedTuple):
name: str
state: ModuleState
Expand Down Expand Up @@ -380,13 +392,22 @@ def get_sysconfig_modules(self) -> Iterable[ModuleInfo]:
else:
modbuiltin = set(sys.builtin_module_names)

# MODULE_<NAME>_STATE keys flatten dotted registration names to
# underscores (math.integer -> MODULE_MATH_INTEGER_STATE). Recover
# the real, possibly dotted, name from the MOD*_NAMES lists.
realnames = {}
for var in ("MODBUILT_NAMES", "MODSHARED_NAMES", "MODDISABLED_NAMES"):
for name in (sysconfig.get_config_var(var) or "").split():
realnames[name.replace(".", "_")] = name

for key, value in sysconfig.get_config_vars().items():
if not key.startswith("MODULE_") or not key.endswith("_STATE"):
continue
if value not in {"yes", "disabled", "missing", "n/a"}:
raise ValueError(f"Unsupported value '{value}' for {key}")

modname = key[7:-6].lower()
modname = realnames.get(modname, modname)
if modname in moddisabled:
# Setup "*disabled*" rule
state = ModuleState.DISABLED_SETUP
Expand Down Expand Up @@ -427,12 +448,12 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
if state == ModuleState.DISABLED:
# *disabled* can disable multiple modules per line
for item in items:
modinfo = ModuleInfo(item, state)
modinfo = ModuleInfo(registration_name(item), state)
logger.debug("Found %s in %s", modinfo, setup_file)
yield modinfo
elif state in {ModuleState.SHARED, ModuleState.BUILTIN}:
# *shared* and *static*, first item is the name of the module.
modinfo = ModuleInfo(items[0], state)
modinfo = ModuleInfo(registration_name(items[0]), state)
logger.debug("Found %s in %s", modinfo, setup_file)
yield modinfo

Expand All @@ -456,7 +477,16 @@ def get_spec(self, modinfo: ModuleInfo) -> ModuleSpec:
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}"
# Dotted submodule names map onto sub-directories
# (math.integer -> math/integer.so). An extension *package* is
# registered under its bare name but lives in <name>/__init__.so.
stem = modinfo.name.replace(".", "/")
location = self.builddir / f"{stem}{self.ext_suffix}"
if not location.exists():
pkg_init = self.builddir / stem / f"__init__{self.ext_suffix}"
if pkg_init.exists():
return pkg_init
return location
else:
return None

Expand Down
8 changes: 8 additions & 0 deletions Tools/build/generate_stdlib_module_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ def list_modules() -> set[str]:
if package_name in IGNORE:
names.discard(name)

# Extension submodules (e.g. math.integer) are not listed individually,
# but their parent package must be present.
for name in list(names):
if "." in name:
if name.partition(".")[0] not in names:
raise Exception(f"submodule without a known parent: {name}")
names.discard(name)

# Sanity checks
for name in names:
if "." in name:
Expand Down
Loading
Loading