Skip to content

Releases: ballistics-lab/cibuildmp

v0.7.5

Choose a tag to compare

@github-actions github-actions released this 05 Oct 13:53
eb7cf38

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 with tar tJf (still a top-level install/ with bin/).

v0.7.4

Choose a tag to compare

@github-actions github-actions released this 05 Oct 13:51
503ffe1

Changed

  • Refreshed every pinned PyPA manylinux/musllinux base image to the
    2026.09.29-1 upstream 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

Choose a tag to compare

@github-actions github-actions released this 10 Sep 12:40
b26f174

Changed

  • The two toolchain pins [0046]'s weekly checker reported behind are moved:
    llvm-mingw 20260616 -> 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 with tar tJf rather than carried over from the previous version:
    llvm-mingw still unpacks to a single top-level directory (what
    --strip-components=1 assumes) and still ships all four
    <arch>-w64-mingw32- bin/ prefixes build_windows.py drives; the emsdk
    tarball still has the install/emscripten + install/bin shape the image's
    own ENV PATH points at. bin/update_toolchains.py --check now reports
    every pin current. Nothing about the emsdk bump touches
    [usermod.webassembly], which deliberately records no emsdk version at all
    ([0092]) -- MicroPython's own tools/ci.sh has never pinned one.
  • Both bumped RUN steps stopped claiming to track a deleted file. Each
    said it was "pinned to resources/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] deleted resources/usermod.toml, and usermod/llvmmingw.py
    / usermod/emsdk.py went with the bare-host resolvers ([0050]/[0042]). The
    webassembly one also cited open-questions.md's "nothing checks whether a
    pinned version is stale" note, which [0046] and pin-staleness.yml made false
    -- that checker is precisely what found both of these.

v0.7.2

Choose a tag to compare

@github-actions github-actions released this 06 Sep 00:21
afa36d9

Added

  • samd gets a real build_samd() driver — the easiest of [0053]'s
    remaining nine driverless usermod ports to pick up next (esp8266 ruled
    out for tags too old to be worth it), modeled on rp2's own plain-Make
    shape rather than esp32/rp2's cmake one. No new provisioning step needed:
    like rp2, orchestrate.build() already generalizes sources. fetch_micropython() for it. Live-verified against real identifiers
    spanning v1.20.0 through the current tag; KNOWN_PORTS now carries
    seven wired drivers, not six. [0100].

Fixed

  • rp2 and esp32 both passed tag_cflags()'s candidate CFLAGS_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 tag v1.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.0 row of either port, since both resolve to a sub-gcc-15
    toolchain there. Live-reproduced on v1.24.0-rp2-ADAFRUIT_FEATHER_RP2040
    (now producing a genuine firmware.uf2) and against a real ESP-IDF
    xtensa-esp-elf-gcc 14.2.0. Fixed the same way unix already was
    ([0082]): fetch/discover the real compiler first, then probe it with
    build_common.probe_supported_cflags() before building CFLAGS_EXTRA.
    esp32 has no static <prefix>gcc to point at, so its own fix discovers
    the real cross-compiler dynamically (a $PATH glob for *-elf-gcc, run
    after ESP-IDF's own install+export sequence) rather than guessing an
    idf_target -> prefix table. Neither bug had ever been exercised by CI --
    no workflow in this repo has built rp2/esp32 below v1.26.0. [0060].

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 05 Sep 15:44
e9cdf5d

Added

  • no-user-c-modules = true builds a stock, unmodified upstream MicroPython
    through the usermod path — no USER_C_MODULES, nothing of a consumer's own in
    the result.
    Mutually exclusive with user-c-modules (a load-time error, not
    a precedence rule); the empty USER_C_MODULES= this resolves to is a verified
    no-op on both build systems this project drives, confirmed live against a real
    v1.29.0 checkout — a stock unix binary correctly raising ImportError for
    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 is Path(".")
    — a relative path docker run -v rejects outright, not merely an
    unnecessary over-mount. [0056].
  • examples/bare-firmware, a real CI-built demonstration with no natmod/,
    no usermod/, no src/ — just a cibuildmp.toml naming one Make-driven
    target (unix x86_64) and one CMake-driven one (rp2 RPI_PICO), the two
    build-system shapes no-user-c-modules has to no-op on cleanly.
    build-examples.yml's new build-bare-firmware job activates the flag
    through CIBMP_NO_USER_C_MODULES rather than the TOML key, doubling as a live
    demonstration of the environment-variable form. README.md gets 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 -a instead of ls -laR, across
    build-examples.yml, test-upstream-natmod.yml, test-upstream-usermodule.yml
    and verify-lv-binding-micropython.yml — more readable output for the same
    recursive listing; tree is already an apt package on the ubuntu-24.04
    runner image both ubuntu-latest and ubuntu-24.04-arm use.

Removed

  • CIBMP_SCRATCH_PATH. Every usermod port has built through its own
    Container/overlay since 0.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

Choose a tag to compare

@github-actions github-actions released this 04 Sep 19:53
4d509a7

Fixed

  • bin/refresh_toolchain_pins.py --check had been a silent no-op since [0087] landed —
    it read a shared toolchain version straight out of arm_embedded.Dockerfile's own ARG TOOLCHAIN_URL= line, which [0087] deleted the moment the version became a per-row fact
    instead; every check since then exited 0 regardless of what build-platforms.toml actually
    held, confirmed live with a deliberately-broken row that passed clean. Rewritten to read the
    row's own committed gcc field directly; deliberately re-broke the same row to confirm
    --check now catches it (v1.20.0 usermod.mimxrt: pinned 13.3.1 >= ceiling 13) before
    restoring it. [0090].
  • mimxrt's eleven v1.20.0 rows carried a toolchain version (14.2.1-1.1) that violates
    their own >= 13 ceiling
    (sdcard_cmd_set_bus_width(), fixed upstream in v1.21.0) —
    [0087]'s general per-row mechanism never special-cased this one disjoint window, exactly the
    gap [0088] had scoped and left unimplemented. Now 12.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.12 through v1.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.0 upstream facts this project had hardcoded the modern shape of:
    mpy-cross links to mpy-cross/mpy-cross, not mpy-cross/build/mpy-cross
    (py/mkrules.mk moved it in v1.20.0, and py/dynruntime.mk's own
    MPY_CROSS = with it); py/persistentcode.h has no MPY_SUB_VERSION, so the
    ABI is the bare "5"/"6"; py/stackctrl.c and mpy-cross/main.c need
    -Wno-error=dangling-pointer and -Wno-error=enum-int-mismatch on top of the
    -Wno-error=unterminated-string-initialization those 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 on arm_embedded: v1.12, v1.18 and v1.19.1
    build end to end, v1.29.0 unchanged 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 three BUILD = .obj/$(ARCH) snippets in README.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 but natmod, the port's own build) failed on every
    MicroPython tag before v1.26.0, on rp2, stm32, samd, nrf, cc3200, renesas-ra,
    mimxrt, esp32, webassembly, windows, and natmod's own arm_embedded/riscv_embedded
    cross arches
    — container_mpy_cross() always builds mpy-cross with the image's own native
    compiler, and every one of these images' native compiler is ubuntu:26.04's own gcc 15, which
    rejects py/emitinlinethumb.c's pre-v1.26.0 string initializers
    (-Werror=unterminated-string-initialization, [0082]) the same way it already broke
    natmod_host/windows's own x64/x86 builds. [0091] threads the tag-keyed relaxation
    already fixed for unix ([0082]) through every other port's own container_mpy_cross() call
    and make invocation. Live-verified: natmod's armv7emsp/rv32imc/rv64imc (CI, run
    33697330722) and, locally, rp2's own full firmware build on v1.20.0 (the one tag needing two
    stacked relaxations at once).
  • windows's own win_arm64 build 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-pointer as an unrecognized diagnostic name — a hard `error: unknown

v0.6.2

Choose a tag to compare

@github-actions github-actions released this 02 Sep 23:27
7ab472c

Fixed

  • unix builds on riscv64 (any tag whose lib/libffi pin actually
    supports it, v1.24.0+) failed at the final link with ld: cannot find .../lib/libffi/out/lib/libffi.a
    , the same class of bug the
    RHEL ../lib64 fixup already covered — deplibs' own symlink fixup
    only ever checked one hardcoded path
    (out/lib64/libffi.a), but riscv64's own gcc -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 compiler deplibs itself just used, instead of a value
    predicted in advance — live-verified against the real
    manylinux_2_39_riscv64 image end to end (a full build, real link,
    real binary).
  • unix builds on riscv64 failed outright on every tag through
    v1.23.0
    with configure: error: "libffi has not been ported to riscv64-unknown-linux-gnu.": lib/libffi's own submodule pin, on
    those tags, points to https://github.com/atgreen/libffi, a
    fork/mirror whose configure.host has no riscv* case at all
    (v1.24.0 moves the pin to the canonical libffi/libffi, which does
    — filed upstream, but there is no fix to backport onto a tag that has
    already shipped). MICROPY_PY_FFI=0 for exactly this (tag, riscv64)
    combination, and deplibs is skipped entirely rather than attempting
    a build that cannot succeed.
  • unix builds failed on s390x/riscv64, on multiple tags, with a
    real GCC diagnostic in ports/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 own main.c, s390x, v1.28.0 only)
    and descoped by identifier rather than suppressed — this sweep found
    it in the port's main.c too, 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 escalation aarch64's own
    -Wno-error=array-bounds entry already went through once.
  • unix's new -Wno-error=<diagnostic> gcc probe still failed
    manylinux_2_41_mipsel builds on every pre-v1.26.0 tag
    , the one
    case the probe was written for: it always probed the image's bare
    gcc, but mipsel's own image is a Bootlin cross-toolchain where the
    real build compiler is mipsel-linux-gnu-gcc (gcc 14.3.0) — a
    different, older binary than whatever bare gcc resolves 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-initialization back 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: bare gcc for the in-container mpy-cross build (a host
    tool, never cross-compiled), {cross_compile}gcc for the main port
    build — identical for every arch but mipsel.
  • **unix builds failed outright on every clone-path tag that needs

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 01 Sep 19:52
86f5fff

Fixed

  • natmod's x86 arch failed to link through natmod_host, on every tag
    through v1.28.0, since 0.6.0's own ubuntu:26.04 bump — whenever the
    module needs anything from libgcc.
    docker/natmod_host.Dockerfile pinned
    gcc-13-multilib by version; once the base image's own build-essential
    started resolving to gcc 15, upstream's own -m32 path for x86 (every
    MicroPython tag through v1.28.0 — v1.29.0 switched to a separate
    i686-linux-gnu- cross compiler, unaffected) linked against gcc 15's
    64-bit-only libgcc.a and 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 unversioned gcc-multilib metapackage
    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 of x86's toolchains before the
    layer finishes, so a future regression here fails docker build, not
    just a real link somewhere downstream. x64 and every cross-compiled
    arch were unaffected. See docs/records/0068's own third correction.
  • qemu's POWERNV9 board failed to link through ppc64le_linux, since
    0.6.0's own ubuntu:26.04 bump — whenever the firmware references a
    long-double printf-family call (ports/qemu's own readline.c does).

    docker/ppc64le_linux.Dockerfile installed a Debian-packaged cross
    toolchain (gcc-powerpc64le-linux-gnu + libc6-dev-ppc64el-cross), the
    same "paired with whatever gcc the base resolves to" shape natmod_host's
    multilib pin turned out to be. The base bump left the two packages out of
    step and a real link failed with undefined 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 model manylinux_2_41_mipsel already
    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/%Lf on a long double), statically linked, both directly
    through the extracted toolchain and inside a real docker build of the
    changed image. See docs/records/0068's own fourth addendum.

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 14:05
a9ade6f

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 no 1.0.0 yet and no stability guarantee to imply otherwise.
  • A .gitignore (*), dropped into output-dir the first time anything
    writes into it, both platforms.
    A fresh mpyhouse/ cibuildmp itself
    creates should not need a matching entry hand-added to the caller's own
    top-level .gitignore — verified live against a real a7p build (an
    existing output-dir that had never had one gained mpyhouse/.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-this exception).

Changed

  • BREAKING: the mipsel cell is manylinux_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 gets matches no known identifier — there is
    no alias. The rename follows its toolchain: docker/manylinux_2_41_mipsel.Dockerfile
    no longer installs gcc-mipsel-linux-gnu/libc6-dev-mipsel-cross at 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_39 was 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 full examples/usercmodule build
    (including the MICROPY_STANDALONE static-libffi deplibs step) links, and
    the result runs under qemu-mipsel with all three upstream modules importing
    — ELF 32-bit LSB executable, MIPS, statically linked. One real difference
    from the apt toolchain: the ELF is now mips32 (r1) rather than mips32r2,
    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 no CIBMP_UNIX_MANYLINUX_2_41_MIPSEL_DOCKER_IMAGE
    override at all. Its resources/pinned_docker_images.toml row 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 state dockerrun.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 and pypa-tracker.Dockerfile's nine mirrored
    references move to pypa's 2026.09.01-1 release (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. --check reports zero drift for the first time in a while:
    fourteen ghcr.io pins had been one publish behind their own :latest since a
    docker/** push to main republished everything with no repin PR behind it —
    the trigger removed above.

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 21:32
432da41

Added

  • windows and qemu smoke steps in test-upstream-usermodule.yml, closing
    two of record 0079's own "not done here" gaps. build-windows installs wine
    and runs the collected win_amd64 .exe against smoke_test.py unmodified;
    build-qemu installs qemu-system-arm and mpremote, starts qemu itself
    against the collected firmware-<identifier>.elf (qemu's own build target
    is firmware.elf, not micropython.elf like 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 until qemu.log itself
    was actually read) with its serial port redirected to a local TCP socket, and
    runs mpremote ... run against it over socket://. 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 .uf2 still
    has no smoke test, for the same hardware/emulator reason record 0069 gives.
    Record 0080.
  • A webassembly smoke step in test-upstream-usermodule.yml, running the
    unmodified examples/usercmodule/smoke_test.py under node from mpyhouse/.
    That job stopped at ls before, on the premise that a .wasm does not run on
    this runner -- node is 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.md carries the procedure and the two things that make
    a round useful: give no hints, and switch from survey to task once surveys
    saturate. CLAUDE.md now 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.
    webassembly collected micropython.mjs
    without micropython.wasm -- the .mjs loads the blob by that literal name,
    so the collected copy aborted with failed to asynchronously prepare wasm: ENOENT ... micropython.wasm, having shipped 217,344 of the 680,703 bytes the
    build actually produced. esp32 collected micropython.bin, the application
    image, never the combined firmware.bin that is the flashable one. Each port
    now declares its own companions (unix_companions(),
    webassembly_companions(), esp32_companions()), which build_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.
  • unix artifacts 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 the lib/ beside the binary" copied 94 .o and 94
    .P files along with the 40K libffi.so.6 that is the actual dependency.