Observed on a pristine v1.28.0 checkout (git describe = v1.28.0; py/mkrules.cmake, py/makeqstrdefs.py, mpy-cross/, and py/modstring.c all unmodified), esp32 port, ESP32_GENERIC_S3.
The mpy-cross bootstrap in py/mkrules.cmake already insulates the sub-make from one inherited command-line variable:
COMMAND ${MICROPY_MAKE_EXECUTABLE} -C ${MICROPY_DIR}/mpy-cross USER_C_MODULES=
but not from BUILD=. GNU make propagates command-line variables to sub-makes, so building the esp32 port with an out-of-tree build directory:
make BOARD=ESP32_GENERIC_S3 BUILD=/path/to/out-of-tree
causes mpy-cross to be built into /path/to/out-of-tree as well. mpy-cross compiles the same py/*.c sources under its own configuration — mpy-cross/mpconfigport.h:82 sets MICROPY_PY_TSTRINGS (1) explicitly (MICROPY_PY_FSTRINGS (1) on line 81) — so its qstr module-registration fragments land in the firmware build's shared genhdr/module/. The leaked fragments are recognizable by their relative naming: makeqstrdefs maps .. → @@ and / → __, so @@__py__modstring.c.module reads ../py/modstring.c — the path as mpy-cross sees it from its own directory — unlike the firmware's absolute-path fragment names.
Six fragments leak (modbuiltins, modmath, modmicropython, modstring, modstruct, runtime); only string breaks the link, because the others register modules that exist in both configurations, while string is gated behind MICROPY_PY_TSTRINGS, which is 0 at the esp32 port's EXTRA_FEATURES ROM level. makeqstrdefs.py cat collects the directory blindly, moduledefs.h then declares mp_module_string, the firmware compiles py/modstring.c empty, and the link fails:
undefined reference to `mp_module_string'
Overriding BUILD is an ordinary thing to attempt (the port's own BUILD ?= build-$(BOARD) invites it), and the failure appears far from its cause.
Reproduction (isolated, no firmware build needed) — a parent make invoking the sub-make exactly as mkrules.cmake does:
# Makefile
unfixed:
$(MAKE) -C $(MP)/mpy-cross USER_C_MODULES=
$ make unfixed MP=/path/to/micropython BUILD=$PWD/leaked
$ ls leaked/ # mpy-cross binary + genhdr/module/@@__py__*.module fragments
Fix, verified under the same parent-make reproduction (an explicit assignment on the sub-make command line takes precedence over the propagated one):
COMMAND ${MICROPY_MAKE_EXECUTABLE} -C ${MICROPY_DIR}/mpy-cross BUILD=${MICROPY_DIR}/mpy-cross/build USER_C_MODULES=
With that line, the same parent invocation leaves the parent's BUILD dir untouched and mpy-cross lands in mpy-cross/build/, matching MICROPY_MPYCROSS_DEPENDENCY, which is already defined as ${MICROPY_DIR}/mpy-cross/build/mpy-cross. Two notes: BUILD= (empty) would NOT work — mpy-cross's BUILD ?= build does not override a defined-but-empty variable, so the value must name the path — and the fix prevents recurrence but does not repair an already-polluted build directory, which must be deleted before rebuilding.
Observed on a pristine v1.28.0 checkout (
git describe= v1.28.0;py/mkrules.cmake,py/makeqstrdefs.py,mpy-cross/, andpy/modstring.call unmodified), esp32 port, ESP32_GENERIC_S3.The mpy-cross bootstrap in py/mkrules.cmake already insulates the sub-make from one inherited command-line variable:
but not from
BUILD=. GNU make propagates command-line variables to sub-makes, so building the esp32 port with an out-of-tree build directory:causes mpy-cross to be built into
/path/to/out-of-treeas well. mpy-cross compiles the samepy/*.csources under its own configuration —mpy-cross/mpconfigport.h:82setsMICROPY_PY_TSTRINGS (1)explicitly (MICROPY_PY_FSTRINGS (1)on line 81) — so its qstr module-registration fragments land in the firmware build's sharedgenhdr/module/. The leaked fragments are recognizable by their relative naming: makeqstrdefs maps..→@@and/→__, so@@__py__modstring.c.modulereads../py/modstring.c— the path as mpy-cross sees it from its own directory — unlike the firmware's absolute-path fragment names.Six fragments leak (modbuiltins, modmath, modmicropython, modstring, modstruct, runtime); only
stringbreaks the link, because the others register modules that exist in both configurations, whilestringis gated behindMICROPY_PY_TSTRINGS, which is 0 at the esp32 port's EXTRA_FEATURES ROM level.makeqstrdefs.py catcollects the directory blindly,moduledefs.hthen declaresmp_module_string, the firmware compilespy/modstring.cempty, and the link fails:Overriding
BUILDis an ordinary thing to attempt (the port's ownBUILD ?= build-$(BOARD)invites it), and the failure appears far from its cause.Reproduction (isolated, no firmware build needed) — a parent make invoking the sub-make exactly as mkrules.cmake does:
Fix, verified under the same parent-make reproduction (an explicit assignment on the sub-make command line takes precedence over the propagated one):
With that line, the same parent invocation leaves the parent's BUILD dir untouched and mpy-cross lands in
mpy-cross/build/, matchingMICROPY_MPYCROSS_DEPENDENCY, which is already defined as${MICROPY_DIR}/mpy-cross/build/mpy-cross. Two notes:BUILD=(empty) would NOT work — mpy-cross'sBUILD ?= builddoes not override a defined-but-empty variable, so the value must name the path — and the fix prevents recurrence but does not repair an already-polluted build directory, which must be deleted before rebuilding.