Found by review while fixing the same bug in jpegio (fb4fd6a). Inert today, and it arms itself the day someone adds a CMakeLists.txt to MicroPython's samd port.
The general rule, which is worth stating once: any MP_QSTR_ name referenced only under a define a usermod supplies through target_compile_definitions(... INTERFACE ...) is invisible to the CMake QSTR pass. py/mkrules.cmake builds the extraction flags from the port target's own COMPILE_DEFINITIONS, so the usermod's INTERFACE defines never reach makeqstrdefs.py. The Makefile ports are unaffected: CFLAGS_USERMOD is folded into CFLAGS by py/py.mk, and their QSTR pass reads CFLAGS. So the same source compiles on unix and fails on esp32 with error: 'MP_QSTR_<name>' undeclared.
This instance: src/ports/samd/mod_i80bus.c puts its whole body inside #if defined(MCU_SAMD51) || defined(__SAMD51__), and src/ports/samd/micropython.cmake supplies __SAMD51__=1 as an INTERFACE define. The 18 names under that guard: I80Bus, i80bus, data_pins, chip_select, command, data0, frequency, send, read, write, reset, deinit, __del__, __name__ and the rest.
It cannot break in MicroPython v1.28.0 because samd is Makefile-only there; only esp32 and rp2 have a CMakeLists.txt. It breaks the day samd gains one, and whoever adds that file will read the failure as their own mistake.
The fix pattern is the one jpegio now uses: every name a module exposes is referenced on every build, whatever the feature flags say, and the flag decides behaviour rather than existence. For I80Bus that means the type and its methods exist unconditionally, with the non-SAMD51 build raising a clear error rather than the symbol vanishing. src/jpegio/README.md documents the trap and the reasoning under "Why these two are always present".
Not urgent. Filed so the trap is written down where the next person will look.
Found by review while fixing the same bug in jpegio (fb4fd6a). Inert today, and it arms itself the day someone adds a
CMakeLists.txtto MicroPython's samd port.The general rule, which is worth stating once: any
MP_QSTR_name referenced only under a define a usermod supplies throughtarget_compile_definitions(... INTERFACE ...)is invisible to the CMake QSTR pass.py/mkrules.cmakebuilds the extraction flags from the port target's ownCOMPILE_DEFINITIONS, so the usermod's INTERFACE defines never reachmakeqstrdefs.py. The Makefile ports are unaffected:CFLAGS_USERMODis folded intoCFLAGSbypy/py.mk, and their QSTR pass readsCFLAGS. So the same source compiles on unix and fails on esp32 witherror: 'MP_QSTR_<name>' undeclared.This instance:
src/ports/samd/mod_i80bus.cputs its whole body inside#if defined(MCU_SAMD51) || defined(__SAMD51__), andsrc/ports/samd/micropython.cmakesupplies__SAMD51__=1as an INTERFACE define. The 18 names under that guard:I80Bus,i80bus,data_pins,chip_select,command,data0,frequency,send,read,write,reset,deinit,__del__,__name__and the rest.It cannot break in MicroPython v1.28.0 because samd is Makefile-only there; only esp32 and rp2 have a
CMakeLists.txt. It breaks the day samd gains one, and whoever adds that file will read the failure as their own mistake.The fix pattern is the one jpegio now uses: every name a module exposes is referenced on every build, whatever the feature flags say, and the flag decides behaviour rather than existence. For
I80Busthat means the type and its methods exist unconditionally, with the non-SAMD51 build raising a clear error rather than the symbol vanishing.src/jpegio/README.mddocuments the trap and the reasoning under "Why these two are always present".Not urgent. Filed so the trap is written down where the next person will look.