Repository navigation
Releases: ballistics-lab/cibuildmp
Releases · ballistics-lab/cibuildmp
Release list
v0.7.5
Changed
- llvm-mingw
20260908->20260922(docker/windows.Dockerfile), the pin [0046]'s weekly checker reported behind. sha256 recomputed from the real tarball; still unpacks to a single top-level directory (tar tJf). - emsdk
6.0.9/f04ea239->6.0.11/f6264d4a(docker/webassembly.Dockerfile), the pin [0046]'s weekly checker reported behind. sha256 recomputed from the real tarball; layout re-checked withtar tJf(still a top-levelinstall/withbin/).
v0.7.4
Changed
- Refreshed every pinned PyPA manylinux/musllinux base image to the
2026.09.29-1upstream build. This updates all 34 entries in
pinned_pypa_images.toml, including the nine images Dependabot reported,
and synchronizes the 14 direct runtime mirrors in
pinned_docker_images.toml. The remaining image-group pins are immutable
cibuildmp GHCR digests and were deliberately left untouched.
v0.7.3
Changed
- The two toolchain pins [0046]'s weekly checker reported behind are moved:
llvm-mingw20260616->20260908(docker/windows.Dockerfile) and emsdk
6.0.8/9d70dbe8->6.0.9/f04ea239(docker/webassembly.Dockerfile).
Both sha256s recomputed from the real tarball, and each one's internal layout
re-checked withtar tJfrather than carried over from the previous version:
llvm-mingw still unpacks to a single top-level directory (what
--strip-components=1assumes) and still ships all four
<arch>-w64-mingw32-bin/ prefixesbuild_windows.pydrives; the emsdk
tarball still has theinstall/emscripten+install/binshape the image's
ownENV PATHpoints at.bin/update_toolchains.py --checknow reports
every pin current. Nothing about the emsdk bump touches
[usermod.webassembly], which deliberately records no emsdk version at all
([0092]) -- MicroPython's owntools/ci.shhas never pinned one. - Both bumped
RUNsteps stopped claiming to track a deleted file. Each
said it was "pinned toresources/usermod.toml's own[llvm-mingw]/[emsdk]
table -- keep the URL and sha256 in step with it", directly contradicting the
"This file is the pin of record" paragraph a few lines above it in the
same file: [0092] deletedresources/usermod.toml, andusermod/llvmmingw.py
/usermod/emsdk.pywent with the bare-host resolvers ([0050]/[0042]). The
webassembly one also citedopen-questions.md's "nothing checks whether a
pinned version is stale" note, which [0046] andpin-staleness.ymlmade false
-- that checker is precisely what found both of these.
v0.7.2
Added
samdgets a realbuild_samd()driver — the easiest of [0053]'s
remaining nine driverless usermod ports to pick up next (esp8266ruled
out for tags too old to be worth it), modeled onrp2's own plain-Make
shape rather than esp32/rp2's cmake one. No new provisioning step needed:
likerp2,orchestrate.build()already generalizessources. fetch_micropython()for it. Live-verified against real identifiers
spanningv1.20.0through the current tag;KNOWN_PORTSnow carries
seven wired drivers, not six.[0100].
Fixed
rp2andesp32both passedtag_cflags()'s candidateCFLAGS_EXTRA
straight into their build command with no probing, unlike every other
cross-compiling port here —-Wno-error=unterminated-string-initialization
(a gcc-15-only diagnostic, flagged for every tagv1.12-v1.25.0
regardless of which toolchain a given row actually resolves to) hard-failed
cc1: error: ... no option '-Wunterminated-string-initialization'on every
pre-v1.26.0row of either port, since both resolve to a sub-gcc-15
toolchain there. Live-reproduced onv1.24.0-rp2-ADAFRUIT_FEATHER_RP2040
(now producing a genuinefirmware.uf2) and against a real ESP-IDF
xtensa-esp-elf-gcc 14.2.0. Fixed the same wayunixalready was
([0082]): fetch/discover the real compiler first, then probe it with
build_common.probe_supported_cflags()before buildingCFLAGS_EXTRA.
esp32has no static<prefix>gccto point at, so its own fix discovers
the real cross-compiler dynamically (a$PATHglob for*-elf-gcc, run
after ESP-IDF's own install+export sequence) rather than guessing an
idf_target -> prefixtable. Neither bug had ever been exercised by CI --
no workflow in this repo has builtrp2/esp32belowv1.26.0.[0060].
v0.7.1
Added
no-user-c-modules = truebuilds a stock, unmodified upstream MicroPython
through the usermod path — noUSER_C_MODULES, nothing of a consumer's own in
the result. Mutually exclusive withuser-c-modules(a load-time error, not
a precedence rule); the emptyUSER_C_MODULES=this resolves to is a verified
no-op on both build systems this project drives, confirmed live against a real
v1.29.0checkout — a stockunixbinary correctly raisingImportErrorfor
a module never linked in, and the same binary succeeding once one is. Surfaced
and fixed a real bug along the way: every per-port mount helper built
Path(opts.user_c_modules)unconditionally, and an empty value isPath(".")
— a relative pathdocker run -vrejects outright, not merely an
unnecessary over-mount.[0056].examples/bare-firmware, a real CI-built demonstration with nonatmod/,
nousermod/, nosrc/— just acibuildmp.tomlnaming one Make-driven
target (unixx86_64) and one CMake-driven one (rp2RPI_PICO), the two
build-system shapesno-user-c-moduleshas to no-op on cleanly.
build-examples.yml's newbuild-bare-firmwarejob activates the flag
throughCIBMP_NO_USER_C_MODULESrather than the TOML key, doubling as a live
demonstration of the environment-variable form.README.mdgets its own
"Building stock upstream firmware" section for it — framed as a real
capability (a cross-toolchain/board regression check with no C module
needed), useful to this project's own consumers but arguably more useful to
MicroPython's own maintainers.
Changed
- "List built artifacts" CI steps use
tree -ainstead ofls -laR, across
build-examples.yml,test-upstream-natmod.yml,test-upstream-usermodule.yml
andverify-lv-binding-micropython.yml— more readable output for the same
recursive listing;treeis already an apt package on theubuntu-24.04
runner image bothubuntu-latestandubuntu-24.04-armuse.
Removed
CIBMP_SCRATCH_PATH. Every usermod port has built through its own
Container/overlay since0.6.x(record 0095), so the directory this
variable named was never mounted or read by anything — setting it created a
real host directory nothing wrote to. Record 0095's own addendum had flagged
this as a live no-op and left "delete, repurpose, or keep as a no-op knob" as
an open question; deleting it won.orchestrate._resolved_build_dir()now
returns a fixed, container-internal path with no host directory, no env
override and no cleanup to manage.
v0.7.0
Fixed
bin/refresh_toolchain_pins.py --checkhad been a silent no-op since[0087]landed —
it read a shared toolchain version straight out ofarm_embedded.Dockerfile's ownARG TOOLCHAIN_URL=line, which[0087]deleted the moment the version became a per-row fact
instead; every check since then exited0regardless of whatbuild-platforms.tomlactually
held, confirmed live with a deliberately-broken row that passed clean. Rewritten to read the
row's own committedgccfield directly; deliberately re-broke the same row to confirm
--checknow catches it (v1.20.0 usermod.mimxrt: pinned 13.3.1 >= ceiling 13) before
restoring it.[0090].mimxrt's elevenv1.20.0rows carried a toolchain version (14.2.1-1.1) that violates
their own>= 13ceiling (sdcard_cmd_set_bus_width(), fixed upstream inv1.21.0) —
[0087]'s general per-row mechanism never special-cased this one disjoint window, exactly the
gap[0088]had scoped and left unimplemented. Now12.3.1-1.2, the newest xpack release
genuinely below the ceiling, sha256-verified live against the publisher's own sidecar.
[0088].- Every MicroPython tag before
v1.20.0— the nine ABI 5 and ABI 6 identifiers
(v1.12throughv1.19.1) — had never produced an artifact, on any arch, for
reasons that outlived the gcc 15 diagnostic[0082]bisected. Four separate
pre-v1.20.0upstream facts this project had hardcoded the modern shape of:
mpy-crosslinks tompy-cross/mpy-cross, notmpy-cross/build/mpy-cross
(py/mkrules.mkmoved it inv1.20.0, andpy/dynruntime.mk's own
MPY_CROSS =with it);py/persistentcode.hhas noMPY_SUB_VERSION, so the
ABI is the bare"5"/"6";py/stackctrl.candmpy-cross/main.cneed
-Wno-error=dangling-pointerand-Wno-error=enum-int-mismatchon top of the
-Wno-error=unterminated-string-initializationthose tags already needed; and
the example natmod Makefile scoped its object cache by arch but not by tag, so
one--build 'mpy*-<arch>'linked every tag after the first against the first
tag's objects. Live-verified onarm_embedded:v1.12,v1.18andv1.19.1
build end to end,v1.29.0unchanged beside them.examples/wasm2mpy's own
Makefile carried the same arch-but-not-tag object scoping and is fixed with it,
and so did all threeBUILD = .obj/$(ARCH)snippets inREADME.md— which also
claimed a header check catches this (it catches the arch axis; the tag axis
produces a correct arch header and no error) and that MicroPython v1.29.0's own
BUILD ?= build-$(ARCH)default makes it impossible (that default is arch-scoped,
never tag-scoped).[0082],[0093]. mpy-cross(and, for every port butnatmod, the port's own build) failed on every
MicroPython tag beforev1.26.0, onrp2,stm32,samd,nrf,cc3200,renesas-ra,
mimxrt,esp32,webassembly,windows, andnatmod's ownarm_embedded/riscv_embedded
cross arches —container_mpy_cross()always buildsmpy-crosswith the image's own native
compiler, and every one of these images' native compiler isubuntu:26.04's own gcc 15, which
rejectspy/emitinlinethumb.c's pre-v1.26.0string initializers
(-Werror=unterminated-string-initialization, [0082]) the same way it already broke
natmod_host/windows's ownx64/x86builds.[0091]threads the tag-keyed relaxation
already fixed forunix([0082]) through every other port's owncontainer_mpy_cross()call
and make invocation. Live-verified:natmod'sarmv7emsp/rv32imc/rv64imc(CI, run
33697330722) and, locally,rp2's own full firmware build onv1.20.0(the one tag needing two
stacked relaxations at once).windows's ownwin_arm64build would have failed the moment[0091]'s fix reached it:
win_arm64's cross compiler is Clang (llvm-mingw), not GCC, and it rejects
-Wno-error=dangling-pointeras an unrecognized diagnostic name — a hard `error: unknown
v0.6.2
Fixed
unixbuilds onriscv64(any tag whoselib/libffipin actually
supports it,v1.24.0+) failed at the final link withld: cannot find .../lib/libffi/out/lib/libffi.a, the same class of bug the
RHEL../lib64fixup already covered —deplibs' own symlink fixup
only ever checked one hardcoded path
(out/lib64/libffi.a), butriscv64's owngcc -print-multi-os-directory
answers../lib64/lp64d(its own ABI-variant subdirectory), one level
deeper than the RHEL case. The fixup now queries the real value live
with the same compilerdeplibsitself just used, instead of a value
predicted in advance — live-verified against the real
manylinux_2_39_riscv64image end to end (a full build, real link,
real binary).unixbuilds onriscv64failed outright on every tag through
v1.23.0withconfigure: error: "libffi has not been ported to riscv64-unknown-linux-gnu.":lib/libffi's own submodule pin, on
those tags, points tohttps://github.com/atgreen/libffi, a
fork/mirror whoseconfigure.hosthas noriscv*case at all
(v1.24.0moves the pin to the canonicallibffi/libffi, which does
— filed upstream, but there is no fix to backport onto a tag that has
already shipped).MICROPY_PY_FFI=0for exactly this (tag,riscv64)
combination, anddeplibsis skipped entirely rather than attempting
a build that cannot succeed.unixbuilds failed ons390x/riscv64, on multiple tags, with a
real GCC diagnostic inports/unix/main.c:
error: variable 'path_remaining' might be clobbered by 'longjmp' or 'vfork' [-Werror=clobbered]. The same diagnostic class record [0044]
found narrower (mpy-cross's ownmain.c,s390x,v1.28.0only)
and descoped by identifier rather than suppressed — this sweep found
it in the port'smain.ctoo, on multiple tags, on both
architectures, which is what moved it from "worth descoping two
identifiers for" to "worth suppressing at the architecture level" via
-Wno-error=clobbered, the same escalationaarch64's own
-Wno-error=array-boundsentry already went through once.unix's new-Wno-error=<diagnostic>gcc probe still failed
manylinux_2_41_mipselbuilds on every pre-v1.26.0tag, the one
case the probe was written for: it always probed the image's bare
gcc, butmipsel's own image is a Bootlin cross-toolchain where the
real build compiler ismipsel-linux-gnu-gcc(gcc 14.3.0) — a
different, older binary than whatever baregccresolves to inside
that same image. The probe's own false-positive verdict (from the
native image gcc, disproving this project's own prior assumption that
every Bootlin/xpack toolchain here was already gcc >=15) let
-Wno-error=unterminated-string-initializationback into
CFLAGS_EXTRA, and the real cross-compiler rejected it exactly as any
other gcc-14 would. Fixed by probing the actual compiler each build
step uses: baregccfor the in-containermpy-crossbuild (a host
tool, never cross-compiled),{cross_compile}gccfor the main port
build — identical for every arch butmipsel.- **
unixbuilds failed outright on every clone-path tag that needs
v0.6.1
Fixed
natmod'sx86arch failed to link throughnatmod_host, on every tag
throughv1.28.0, since0.6.0's ownubuntu:26.04bump — whenever the
module needs anything from libgcc.docker/natmod_host.Dockerfilepinned
gcc-13-multilibby version; once the base image's ownbuild-essential
started resolving to gcc 15, upstream's own-m32path forx86(every
MicroPython tag throughv1.28.0—v1.29.0switched to a separate
i686-linux-gnu-cross compiler, unaffected) linked against gcc 15's
64-bit-onlylibgcc.aand failed outright (LinkError: incompatible arch)
the moment a module actually referenced a libgcc symbol (soft-float,
64-bit arithmetic). Fixed by computing the real, version-specific package
name at build time (gcc-$(gcc -dumpversion | cut -d. -f1)-multilib)
instead of typing one by hand — the unversionedgcc-multilibmetapackage
was tried first and does not build, since it conflicts with
gcc-i686-linux-gnu, the other package this image has always installed
alongside it. The image build itself now also compiles and links an
explicit 64-bit multiply through both ofx86's toolchains before the
layer finishes, so a future regression here failsdocker build, not
just a real link somewhere downstream.x64and every cross-compiled
arch were unaffected. See docs/records/0068's own third correction.qemu'sPOWERNV9board failed to link throughppc64le_linux, since
0.6.0's ownubuntu:26.04bump — whenever the firmware references a
long-doubleprintf-family call (ports/qemu's ownreadline.cdoes).
docker/ppc64le_linux.Dockerfileinstalled a Debian-packaged cross
toolchain (gcc-powerpc64le-linux-gnu+libc6-dev-ppc64el-cross), the
same "paired with whatever gcc the base resolves to" shapenatmod_host's
multilib pin turned out to be. The base bump left the two packages out of
step and a real link failed withundefined reference to '__snprintfieee128'— powerpc64le's own glibc long-double ABI transition.
Fixed by replacing the apt toolchain outright with a pinned Bootlin
tarball (powerpc64le-power8--glibc--stable-2025.08-1, gcc 14.3.0, glibc
2.41-70, URL + sha256 — the same modelmanylinux_2_41_mipselalready
uses), rather than chasing the Ubuntu packaging mismatch: Bootlin's own
gcc and glibc come from one matched Buildroot build, so there is no
separately-packaged "-cross" half left to drift from the compiler's own
version. Verified against the exact failing call shape
(snprintf/%Lfon along double), statically linked, both directly
through the extracted toolchain and inside a realdocker buildof the
changed image. See docs/records/0068's own fourth addendum.
v0.6.0
Warning
natmod's x86 arch is broken in this release for every MicroPython tag
through v1.28.0 (upstream's own -m32 toolchain path) — the ubuntu:26.04
bump below made natmod_host's pinned gcc-13-multilib mismatch the base
image's actual gcc (15), so any real x86 build referencing libgcc
(soft-float, 64-bit arithmetic) fails with LinkError: incompatible arch.
v1.29.0+ (cross-prefixed toolchain) and every other arch are unaffected.
Fixed in v0.6.1.
Added
- A pre-1.0/alpha warning at the top of
README.md. Nothing said the config
schema and CLI can still change between releases without a deprecation cycle;
this repo has no1.0.0yet and no stability guarantee to imply otherwise. - A
.gitignore(*), dropped intooutput-dirthe first time anything
writes into it, both platforms. A freshmpyhouse/cibuildmp itself
creates should not need a matching entry hand-added to the caller's own
top-level.gitignore— verified live against a reala7pbuild (an
existingoutput-dirthat had never had one gainedmpyhouse/.gitignore
on the next build). Checked by existence, not written unconditionally:
never overwrites one already there, in case a caller wants something else
(e.g. a real!keep-thisexception).
Changed
- BREAKING: the
mipselcell ismanylinux_2_41_mipsel, not
manylinux_2_39_mipsel. Every identifier naming it changes with it
(v1.29.0-manylinux_2_39_mipsel->v1.29.0-manylinux_2_41_mipsel), and a
caller still naming the old one getsmatches no known identifier— there is
no alias. The rename follows its toolchain:docker/manylinux_2_41_mipsel.Dockerfile
no longer installsgcc-mipsel-linux-gnu/libc6-dev-mipsel-crossat all,
because Debian 13 "Trixie" dropped the mipsel port and Ubuntu's archive lost
those packages with it. It pins a Bootlin tarball instead
(mips32el--glibc--stable-2025.08-1, gcc 14.3.0, glibc 2.41-70, URL +
sha256), the same version+URL+sha256 model the four embedded toolchain images
already use — so this image no longer depends on an apt archive for an
architecture upstream has abandoned.2_39was apt's cross-glibc version, so
keeping the name would have made a real PEP 600 tag claim a floor its image no
longer has (record 0031's principle, record 0068's decision).
Verified on real artifacts, not inferred: a fullexamples/usercmodulebuild
(including theMICROPY_STANDALONEstatic-libffideplibsstep) links, and
the result runs underqemu-mipselwith all three upstream modules importing
—ELF 32-bit LSB executable, MIPS, statically linked. One real difference
from the apt toolchain: the ELF is nowmips32(r1) rather thanmips32r2,
which widens the hardware it runs on rather than narrowing it.
The image is published and pinned under the new package name
(ghcr.io/ballistics-lab/manylinux_2_41_mipsel@sha256:eee14e84…), verified by
a real build that pulls it with noCIBMP_UNIX_MANYLINUX_2_41_MIPSEL_DOCKER_IMAGE
override at all. Itsresources/pinned_docker_images.tomlrow was empty
between the rename and that publish — a GHCR digest is per package name, so
the pre-rename digest was never a reference this cell could carry, and an
empty group is the statedockerrun.image_for()already defines as "exists,
nothing published yet". - Every pypa base digest re-pinned, and every image digest with it. The
manylinux_2_28_*bases andpypa-tracker.Dockerfile's nine mirrored
references move to pypa's2026.09.01-1release (Dependabot's own grouped PR),
pinned_pypa_images.toml's 34 pins move with them via
bin/update_docker.py --pypa, and all 15 images were republished and re-pinned
with--images.--checkreports zero drift for the first time in a while:
fourteenghcr.iopins had been one publish behind their own:latestsince a
docker/**push tomainrepublished everything with no repin PR behind it —
the trigger removed above.
v0.5.0
Added
windowsandqemusmoke steps intest-upstream-usermodule.yml, closing
two of record 0079's own "not done here" gaps.build-windowsinstallswine
and runs the collectedwin_amd64.exeagainstsmoke_test.pyunmodified;
build-qemuinstallsqemu-system-armandmpremote, starts qemu itself
against the collectedfirmware-<identifier>.elf(qemu's own build target
isfirmware.elf, notmicropython.elflike every other port here — the
first two real CI attempts pointed at the wrong filename by pattern-matching
the other three steps, and failed with a generic "device busy" message that
looked like a pty race and then a socket-startup race untilqemu.logitself
was actually read) with its serial port redirected to a local TCP socket, and
runsmpremote ... runagainst it oversocket://. Both paths (a passing
script and a deliberately broken one) were verified live against real
cross-compiled binaries before being wired into CI —rp2's own.uf2still
has no smoke test, for the same hardware/emulator reason record 0069 gives.
Record 0080.- A
webassemblysmoke step intest-upstream-usermodule.yml, running the
unmodifiedexamples/usercmodule/smoke_test.pyundernodefrommpyhouse/.
That job stopped atlsbefore, on the premise that a.wasmdoes not run on
this runner --nodeis preinstalled there, every consuming repo already runs
its own wasm tests through it, and this is the check that would have caught the
collection bug above on the day it landed. Record 0079. - Record 0078: handing the repo to an uncontexted reader is the docs test the
suite cannot be. Five rounds of it produced this session's findings, and the
movement between rounds is the useful part — false user-facing claims, then
stale docstrings plus two holes in the guards themselves, then source comments
once the docs were clean, then the guard's own dependence on human memory, then
(given a task instead of a survey) a silent config footgun no reader would
ever see.CONTRIBUTING.mdcarries the procedure and the two things that make
a round useful: give no hints, and switch from survey to task once surveys
saturate.CLAUDE.mdnow also says which docs are machine-checked and which
are generated, so the next reader does not rediscover that.
Fixed
- A collected usermod artifact was always exactly one file, and for two ports
that is not the whole build.webassemblycollectedmicropython.mjs
withoutmicropython.wasm-- the.mjsloads the blob by that literal name,
so the collected copy aborted withfailed to asynchronously prepare wasm: ENOENT ... micropython.wasm, having shipped 217,344 of the 680,703 bytes the
build actually produced.esp32collectedmicropython.bin, the application
image, never the combinedfirmware.binthat is the flashable one. Each port
now declares its own companions (unix_companions(),
webassembly_companions(),esp32_companions()), whichbuild_one()copies
beside the primary under their own names -- both are references by exact name
from inside the primary, so only the primary is safely renameable. Record 0079. unixartifacts shipped 2.0M of the port's own object files.
ports/unix/build-<identifier>/lib/is where the port builds its bundled
libraries (mbedtls/,berkeley-db-1.xx/,littlefs/,oofatfs/), and
repair_unix_binary()vendors its shared object into that same directory --
so record 0070's "copy thelib/beside the binary" copied 94.oand 94
.Pfiles along with the 40Klibffi.so.6that is the actual dependency.