Files
firmware/pkgs/3rdparty/python-numpy/build.sh
T
Oleksandr ShyshatskyiandClaude Opus 5 28eb20f3da Package numpy, because klippy does not start without it
extras/stepper_resonance_tester.py opens with a bare `import numpy as np`;
klippy.py:122 walks EVERY config section through a load_object whose
importlib.import_module is not wrapped in except ImportError; printer.base.cfg
includes FlashForge's printer.vibration.cfg, which declares
[stepper_resonance_tester]. Since klippy moved to our own CPython 3.13 that has
not been a printer missing resonance testing. It has been a klippy that never
reaches ready.

So this is not a scientific extra. anvil-klipper depends on anvil-python-numpy
the way it depends on cffi, except that the others mean "klippy imports this"
and this one means "klippy starts". It also gives back SHAPER_CALIBRATE and
TEST_RESONANCES, dead since the same switch, and the VFA calibration behind
STEPPER_RESONANCE_FACTORY_CALIBRATE that docs/notes/44-vfa-calibration.md
traces end to end.

WHAT WAS NOT OBVIOUS, in the order it bit.

numpy 2.x builds with meson-python, and meson has never heard of
_PYTHON_SYSCONFIGDATA_NAME -- the trick every other python-* recipe here leans
on. pkg_mesoncross writes a real cross file instead, naming the $PKG_XW
wrappers rather than the raw toolchain: the wrappers carry -EL -mnan=2008, and
a cross file that reaches past them builds a tree that passes every gate which
only reads symbols and is refused at exec() on the printer. pkg_pywheel grew a
PKG_PY_PIP_ARGS branch to pass the file as --config-settings, which is the one
thing PEP 517 does standardise.

The cross file's `python =` is the BUILD python, and that is not a mistake:
meson introspects an interpreter by running it and asking sysconfig, and
pkg_pytarget has already made that sysconfig answer for mipsel. The old trick
does reach meson, just not by its own mechanism.

numpy's meson.build hard-errors below gcc 8.4 and ours is 7.2. The gate is the
SciPy Toolchain Roadmap's support statement, not a requirement of the code:
this toolchain compiles if constexpr, structured bindings, std::optional,
std::string_view, and pocketfft_hdronly.h -- 3635 lines of the heaviest
template C++ in the tree, and the half np.fft.rfft needs. Probed before the
floor was lowered, and lowered rather than removed, behind a guard that fails
loudly if upstream moves the check. 19 extension modules, every one at e_flags
0x70001405/0x70001407, 14MB shipped after pruning tests/, f2py/, stubs and
static libs.

meson-python is self-hosting, so pkg_buildpython runs meson and ninja just to
install the backend -- hence ninja-build in the image and PYPKG_HOST_LIST in
dependency order rather than alphabetical. And pip puts meson and cython in
work/.py-host/bin, which nothing had ever looked in: without that on PATH the
backend fails to install with `meson executable "meson" not found` while pip
list shows it right there.

THE GATE THAT WOULD HAVE CAUGHT ALL OF IT is qa/replica/test_klippy_extras_import.py:
walk the real /usr/data/config include graph, map every section to its extra,
import each on the printer's own interpreter. It needs no MCU, because config
load happens before klippy:mcu_identify. Nothing asked that question before,
which is how two comments in this repo came to assert the module guarded its
own import. test_numpy.py asks for numeric answers rather than a module object
-- linalg.lstsq specifically, because -Dallow-noblas selects the bundled
lapack_lite and _r2_sinusoidal_fit calls it every calibration round. And
test_install.py's include check read only ff-*.cfg, so FlashForge's four --
printer.vibration.cfg among them -- were never asked about at all.

ff-toolchange.cfg wraps the two search commands in _FF_SHAPER_PREP, as
SHAPER_CALIBRATE already was. STEPPER_RESONANCE_FACTORY_CALIBRATE is
deliberately NOT wrapped: it is a gcode_macro, not a registered command, and
same-named sections merge under RawConfigParser(strict=False) -- a wrapper
would not wrap it, it would BE it, with rename_existing pointing at itself. It
needs none, since its body is two calls to a command that is wrapped. No
SAVE_CONFIG in any macro either; it restarts klippy and stays the operator's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJr2x7aDaY889qwSxbdLt8
2026-08-31 13:42:57 +03:00

78 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# numpy -- built for the printer's CPython, into its site-packages.
#
# THE ONLY meson-python RECIPE IN THE FEED. Every other python-* package here
# builds with setuptools, where _PYTHON_SYSCONFIGDATA_NAME alone is enough to
# make the build answer for mipsel. Meson has never heard of that variable, so
# this one hands it a cross file instead -- see pkg_mesoncross in pkgs/lib.sh
# for what is in it and why each line is there.
set -euo pipefail
. ./bin/common.sh
. pkgs/lib.sh
pkg_begin python-numpy || exit 0
pkg_toolchain
pkg_deps
pkg_buildpython
pkg_pytarget
pkg_unpack "$(pypkg_tgz numpy)"
_np=$(pkg_pysrc numpy)
# numpy's meson.build refuses gcc below 8.4, and ours is 7.2. THE GATE IS
# CONSERVATIVE RATHER THAN LOAD-BEARING, and that was probed before it was
# lowered: numpy 2.1's requirement is a C++17 compiler, and this toolchain
# compiles the features it actually uses -- if constexpr, structured bindings,
# std::optional, std::string_view -- as well as pocketfft_hdronly.h, 3635 lines
# of the heaviest template C++ in the tree and the half np.fft.rfft needs.
# The upstream number comes from the SciPy Toolchain Roadmap, which is a
# statement about what the project supports, not about what the code requires.
#
# The floor is LOWERED, not removed: a genuinely ancient gcc should still be
# refused here rather than three screens into a compile log. If this recipe
# ever fails with a C++ error rather than a version error, that is the gate
# turning out to be real after all, and the fix is a newer cross toolchain --
# not a smaller number.
if ! grep -q "version_compare('>=8.4')" "$_np/meson.build"; then
pkg_die "python-numpy: the gcc version gate in meson.build is not where
this recipe patches it -- numpy $(pypkg_version numpy) has moved
it, and the comment above needs re-checking against the new one"
fi
sed -i "s/version_compare('>=8.4')/version_compare('>=7.2')/" "$_np/meson.build"
# -Dallow-noblas: no BLAS/LAPACK in this feed, so take the bundled lapack_lite.
# Without this numpy 2.x fails configure rather than falling back.
# -Ddisable-optimization: numpy's SIMD dispatch machinery generates and builds
# a matrix of per-feature objects. There is no MIPS path in it, so every one
# of them is a baseline build anyway -- this drops the generation step and,
# with it, the largest surface for a gcc 7.2 to refuse something.
PKG_PY_PIP_ARGS="--config-settings=setup-args=--cross-file=$(pkg_mesoncross)
--config-settings=setup-args=-Dallow-noblas=true
--config-settings=setup-args=-Ddisable-optimization=true"
pkg_pywheel numpy
# PRUNE BEFORE SHIP, and only things nothing on a printer can reach.
# tests/ ~8MB of pytest files; pytest is not on this machine.
# f2py/ a Fortran wrapper generator, and there is no Fortran here.
# *.pyi, py.typed type stubs, read by type checkers and never at runtime.
# _core/lib/*.a static libs for BUILDING against numpy's C API, which
# nothing on the printer does.
# The .so files are not touched here: pkg_ship runs the cross strip over
# everything it copies.
find "$PKG_PYSP/numpy" -type d -name tests -prune -exec rm -rf {} + 2>/dev/null || true
rm -rf "$PKG_PYSP/numpy/f2py"
find "$PKG_PYSP/numpy" -name '*.pyi' -delete 2>/dev/null || true
find "$PKG_PYSP/numpy" -name 'py.typed' -delete 2>/dev/null || true
find "$PKG_PYSP/numpy" -name '*.a' -delete 2>/dev/null || true
pkg_ship "lib/python$PY_MM/site-packages"
# A COUNT, AND A HIGH ONE ON PURPOSE. numpy has a pure-python fallback for
# nothing -- if the cross build quietly produced no extensions there would be
# no import at all, but a partial build that lost _multiarray_umath while
# keeping the small ones would import far enough to look installed. Eight is
# comfortably below what a whole numpy ships and far above any partial one.
pkg_pynative 8
pkg_end