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
This commit is contained in:
Oleksandr ShyshatskyiandClaude Opus 5 committed 2026-08-31 13:42:57 +03:00
1 parent a10930aa26
commit 28eb20f3da
14 files changed
+880 -86

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
#!/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
+36
View File
@@ -0,0 +1,36 @@
# anvil-python-numpy -- numpy for the printer's CPython, into its
# site-packages.
#
# THIS IS NOT AN OPTIONAL SCIENTIFIC EXTRA. It is the package klippy cannot
# start without. extras/stepper_resonance_tester.py opens with a bare
# `import numpy as np`; klippy.py:122 walks EVERY config section through a
# load_object that does not catch ImportError; and printer.base.cfg includes
# FlashForge's printer.vibration.cfg, which declares [stepper_resonance_tester].
# So on a numpy-less install klippy never reaches ready -- it does not merely
# lose a feature. anvil-klipper names this package in its Depends for that
# reason. docs/notes/44-vfa-calibration.md has the whole chain.
#
# WHAT IT BUYS BACK, beyond the boot: the VFA (motor torque-ripple) calibration
# behind STEPPER_RESONANCE_FACTORY_CALIBRATE, and SHAPER_CALIBRATE /
# TEST_RESONANCES, which have been dead for exactly the same reason since
# klippy moved to our own CPython 3.13.
PKG_NAME=anvil-python-numpy
# ARCHITECTURE IS THE DEFAULT, mipsel_xburst2, and it is not a formality here
# any more than it is for anvil-klipper: this package is mostly compiled code.
# An `all` would let it install on a machine whose kernel refuses every .so in
# it, and the failure would arrive as an ImportError halfway through a boot.
PKG_VERSION="$(pypkg_version numpy)"
PKG_RELEASE=1
PKG_SECTION=python
PKG_EXCLUDE=".version"
PKG_DEPENDS="anvil-python"
# python ALONE, and no BLAS: -Dallow-noblas=true below picks numpy's bundled
# lapack_lite instead. That is not a corner cut for size -- there is no BLAS in
# this feed to link against, and lapack_lite is enough for the one linalg call
# the calibration makes (np.linalg.lstsq, in _r2_sinusoidal_fit).
PKG_BUILD_DEPENDS="python"
PKG_DESCRIPTION="numpy for klippy -- the FFT, least-squares and polynomial fitting that extras/stepper_resonance_tester.py and shaper_calibrate.py are written against. Built with meson-python against a cross file, without BLAS."
+13 -9
View File
@@ -66,19 +66,23 @@ export LD_LIBRARY_PATH
# klippy -- which reaches c_helper.so through _cffi_backend, the reason this
# interpreter had to be a glibc build.
#
# klippy's numpy gap (extras/stepper_resonance_tester.py) is this switch's
# problem and it is NOT a smaller item. This comment used to say the module
# guards its own import, so the printer runs without it and loses resonance
# testing. Both halves are wrong: line 1 of that file is a bare
# THE NUMPY GAP WAS THIS SWITCH'S, and it was not the small item this comment
# used to call it. The old text said extras/stepper_resonance_tester.py guards
# its own import, so the printer runs without numpy and merely loses resonance
# testing. Both halves were wrong: line 1 of that file is a bare
# `import numpy as np`, and klippy.py:122 loads EVERY config section through a
# load_object that does not catch ImportError. printer.base.cfg includes
# FlashForge's printer.vibration.cfg, which declares
# [stepper_resonance_tester] -- so on this interpreter klippy does not come up
# at all, rather than coming up without one feature.
# [stepper_resonance_tester] -- so moving klippy to an interpreter without
# numpy did not cost a feature, it stopped klippy reaching ready at all.
#
# See docs/notes/44-vfa-calibration.md for the chain and the options. Nothing
# here is fixed by editing this line; it takes either a packaged numpy or a
# guarded import in the fork.
# CLOSED BY pkgs/3rdparty/python-numpy, which anvil-klipper now names in its
# Depends. FlashForge's 3.8.2 had numpy in its rootfs and a 3.8 .so is not
# importable from 3.13, so the interpreter switch had to bring its own -- and
# it is the reason ninja-build is in docker/Dockerfile.build and
# pkgs/lib.sh grew pkg_mesoncross. docs/notes/44-vfa-calibration.md has the
# chain, and qa/replica/test_klippy_extras_import.py is the gate that would
# have caught it.
#
# Note what is NOT on PATH below: $MODDIR/bin is prepended and the interpreter
# is in there, but it is called python3.13 and only that. bin/payload.sh
@@ -209,3 +209,50 @@ rename_existing: _FF_MEASURE_AXES_NOISE_BASE
gcode:
_FF_SHAPER_PREP
_FF_MEASURE_AXES_NOISE_BASE {rawparams}
# VFA (vertical fine artifacts) -- the motor torque-ripple compensation, which
# is a DIFFERENT mechanism from the input shaping above and shares only the
# accelerometer. The commands come from klippy/extras/stepper_resonance_tester.py;
# these wrappers are ours, and exist for the reason the three above do: both
# sweep the carriage and read [lis2dw], so an empty carriage measures the wrong
# moving mass.
#
# STEPPER_RESONANCE_FACTORY_CALIBRATE IS DELIBERATELY NOT WRAPPED, and the
# reason is worth stating because the obvious edit is wrong. It is not a
# Python-registered command like the two below -- it is a [gcode_macro] in
# FlashForge's printer.vibration.cfg. Klipper parses with
# RawConfigParser(strict=False), so a second section of the same name MERGES
# with the first rather than sitting beside it: a
# `[gcode_macro STEPPER_RESONANCE_FACTORY_CALIBRATE]` here would not wrap that
# macro, it would BE that macro, with a rename_existing pointing at a command
# that does not exist yet.
#
# It needs no wrapper anyway. Its whole body is two
# STEPPER_RESONANCE_SEARCH_CALIBRATE calls, and those are wrapped here -- so
# the prep happens either way, once, from whichever command is reached first
# (_FF_SHAPER_PREP homes only if unhomed and grabs a tool only if none is
# mounted, so the second call is a no-op).
#
# NO SAVE_CONFIG HERE EITHER. The search applies its result live with
# MCLIB_SET_RESONANCE_DAMP and stages it with configfile.set, which only
# SAVE_CONFIG writes -- so without one the calibration is lost on the next
# restart. The stock app had exactly this split: the macro's own SAVE_CONFIG is
# commented out, and the app issued one separately at the end of the whole
# calibration run. It stays the operator's step because SAVE_CONFIG restarts
# klippy, which is not a thing a macro should do to somebody mid-session:
#
# STEPPER_RESONANCE_FACTORY_CALIBRATE
# SAVE_CONFIG
#
# See docs/notes/44-vfa-calibration.md.
[gcode_macro STEPPER_RESONANCE_SEARCH_CALIBRATE]
rename_existing: _FF_STEPPER_RESONANCE_SEARCH_CALIBRATE_BASE
gcode:
_FF_SHAPER_PREP
_FF_STEPPER_RESONANCE_SEARCH_CALIBRATE_BASE {rawparams}
[gcode_macro STEPPER_RESONANCE_REFINE_CALIBRATE]
rename_existing: _FF_STEPPER_RESONANCE_REFINE_CALIBRATE_BASE
gcode:
_FF_SHAPER_PREP
_FF_STEPPER_RESONANCE_REFINE_CALIBRATE_BASE {rawparams}
+10 -9
View File
@@ -24,14 +24,15 @@ PKG_EXCLUDE=".version"
# HERE. python-can is not packaged (no CAN transport on this machine) and it
# is import-guarded, so its absence costs nothing.
#
# NUMPY IS NOT IN THAT CLASS and this comment used to claim it was.
# extras/stepper_resonance_tester.py opens with a bare `import numpy as np`,
# and klippy.py:122 walks every config section through a load_object that does
# not catch ImportError -- so with printer.base.cfg including FlashForge's
# printer.vibration.cfg ([stepper_resonance_tester]), a numpy-less install is
# a klippy that never reaches ready. It is a Depends this package cannot yet
# declare because no anvil-python-numpy exists to name;
# docs/notes/44-vfa-calibration.md has the chain and what packaging it takes.
# NUMPY IS NOT IN THAT CLASS, and it is the one Depends here whose absence is
# not a missing feature but a dead printer. extras/stepper_resonance_tester.py
# opens with a bare `import numpy as np`, and klippy.py:122 walks every config
# section through a load_object that does not catch ImportError -- so with
# printer.base.cfg including FlashForge's printer.vibration.cfg
# ([stepper_resonance_tester]), a numpy-less install is a klippy that never
# reaches ready. Read the rest of this list as "klippy imports it"; read this
# one as "klippy does not start". docs/notes/44-vfa-calibration.md has the
# chain.
#
# markupsafe and setuptools are not named
# because anvil-python-jinja2 declares the first and nothing needs the second.
@@ -46,7 +47,7 @@ PKG_EXCLUDE=".version"
# printer.cfg includes and every other config hangs off it.
PKG_DEPENDS="anvil-klipper-config, anvil-python, anvil-python-cffi,
anvil-python-greenlet, anvil-python-pyserial,
anvil-python-jinja2"
anvil-python-jinja2, anvil-python-numpy"
PKG_BUILD_DEPENDS=""
# NO PKG_WHEN. The fork is the only Klipper, and nothing here is optional --
+105 -2
View File
@@ -197,7 +197,7 @@ pkg_begin() {
# its project. See pkg_build.
PKG_CONFIGURE='./configure'; PKG_CONFIGURE_AUTO=1
PKG_MAKE_TARGET=''; PKG_INSTALL_TARGET='install'; PKG_MAKE_ARGS=''
PKG_PY_SETUP_ARGS=''; PKG_CC_SHARED=''
PKG_PY_SETUP_ARGS=''; PKG_PY_PIP_ARGS=''; PKG_CC_SHARED=''
return 0
}
@@ -285,6 +285,25 @@ pkg_buildpython() {
HOSTPY="$PKG_HOSTPY_ROOT/bin/python$PY_MM"
export HOSTPY
# THE BUILD-PYTHON'S SCRIPT DIRECTORY GOES ON PATH, because two of the
# backends installed below are programs another backend EXECS rather than
# modules it imports. meson-python looks for a `meson` executable and
# numpy's meson.build does find_program('cython'); pip puts both in
# $PKG_HOSTPY_ROOT/bin, which nothing has ever had a reason to look in. The
# symptom without this is meson-python failing to install with
# `meson executable "meson" not found` while `pip list` shows meson right
# there -- installed, importable and unreachable.
#
# It also puts this python on PATH as python3, which is why it is done HERE
# and not in bin/common.sh: build-packages.sh runs each recipe as a child
# process, so an export in a recipe cannot reach the one bare `python3` in
# the build lane (build-packages.sh's opkg-index call) and cannot change
# what that resolves to.
case ":$PATH:" in
*":$PKG_HOSTPY_ROOT/bin:"*) ;;
*) PATH="$PKG_HOSTPY_ROOT/bin:$PATH"; export PATH ;;
esac
_hpstamp="$PY_VERSION"
for _p in setuptools $PYPKG_HOST_LIST; do
_hpstamp="$_hpstamp $(pypkg_var "$_p" FILE)"
@@ -602,6 +621,74 @@ PYEOF
pkg_say "$PKG_ID: sysconfig answers for mipsel, headers at $PKG_PYINC"
}
# pkg_mesoncross -- write a meson cross file for this target and echo its path.
# After pkg_toolchain and pkg_pytarget. For meson-python packages only; a
# setuptools package needs none of this.
#
# WHY IT EXISTS AT ALL: _PYTHON_SYSCONFIGDATA_NAME is a distutils/setuptools
# mechanism. Meson has never heard of it and will not cross-compile without
# being told, in this file, what the host machine is.
#
# THE COMPILERS ARE THE WRAPPERS, NOT THE TOOLCHAIN'S OWN. $PKG_XW/bin holds
# three-line shims that add $PKG_CC_FLAGS -- `-EL -mnan=2008` -- and the raw
# mips-linux-gnu-gcc beside them does not. A cross file naming the raw binary
# produces a tree that builds perfectly, passes every test that only reads
# symbols, and is refused at exec() on the printer, which is the failure
# pkg_toolchain's own e_flags gate exists to prevent. Reaching past the wrapper
# here would walk straight around that gate.
#
# python = $HOSTPY IS NOT A MISTAKE. Meson's python module introspects an
# interpreter by RUNNING it and asking sysconfig, and pkg_pytarget has already
# made $HOSTPY's sysconfig answer for mipsel. So the host interpreter reports
# the target's EXT_SUFFIX and INCLUDEPY, which is exactly what meson needs and
# what pkg_pytarget's closing gate has already proved before we get here.
#
# longdouble_format IS REQUIRED AND HAS NO DEFAULT. numpy determines it by
# compiling and RUNNING a probe, which a cross build cannot do; without this
# property the configure step dies with "Cannot determine long double format".
# o32 MIPS has a 64-bit long double -- the same representation as double --
# which is IEEE_DOUBLE_LE on a little-endian target.
pkg_mesoncross() {
[ -n "${PKG_XW:-}" ] || pkg_die \
"$PKG_ID: pkg_mesoncross needs the toolchain -- call pkg_toolchain first"
[ -n "${PKG_PYSP:-}" ] || pkg_die \
"$PKG_ID: pkg_mesoncross needs the target environment -- call pkg_pytarget first"
_xf="$PWD/$PKG_WORK/meson-cross.ini"
cat > "$_xf" <<EOF
# Generated by pkgs/lib.sh: pkg_mesoncross. Build-time only; never ships.
[binaries]
c = '$PKG_XW/bin/$PKG_HOST-gcc'
cpp = '$PKG_XW/bin/$PKG_HOST-g++'
ar = '$PKG_XW/bin/$PKG_HOST-ar'
strip = '$PKG_XW/bin/$PKG_HOST-strip'
pkg-config = 'pkg-config'
python = '$HOSTPY'
[host_machine]
system = 'linux'
cpu_family = 'mips'
cpu = 'mips32r2'
endian = 'little'
[properties]
longdouble_format = 'IEEE_DOUBLE_LE'
EOF
# Gated here rather than at the far end of a ten-minute compile: a wrapper
# that is not executable, or a $PKG_HOST that does not match the toolchain,
# is a meson "compiler not found" three screens into a configure log.
for _b in gcc g++; do
[ -x "$PKG_XW/bin/$PKG_HOST-$_b" ] || pkg_die \
"$PKG_ID: pkg_mesoncross names $PKG_XW/bin/$PKG_HOST-$_b, which is not executable"
done
# >&2 BECAUSE THIS FUNCTION'S STDOUT IS ITS RETURN VALUE. pkg_say prints to
# stdout like every other reporter here, and a recipe reads this as
# $(pkg_mesoncross) -- so an unredirected line would be captured into the
# path and handed to meson as part of a filename.
pkg_say "$PKG_ID: meson cross file at $_xf" >&2
printf '%s' "$_xf"
}
# pkg_pysrc <list-entry> -- where this package's sdist unpacked to: the file
# name with .tar.gz removed, under $PKG_WORK/src.
pkg_pysrc() {
@@ -615,6 +702,15 @@ pkg_pysrc() {
# PKG_PY_SETUP_ARGS drive setup.py build_ext with these and then
# bdist_wheel, instead of pip. PEP 517 offers no way to
# pass build_ext options, and pillow's --disable-* are.
# PKG_PY_PIP_ARGS extra arguments for the pip path, unquoted as a list.
# For --config-settings, which is the ONE thing PEP 517
# does standardise -- and the only way to hand
# meson-python a --cross-file. numpy's are.
#
# THE TWO OPTIONS ARE NOT ALTERNATIVES TO EACH OTHER. PKG_PY_SETUP_ARGS leaves
# the pip path entirely; PKG_PY_PIP_ARGS stays on it. Setting both is a recipe
# asking for setuptools and configuring meson, so it is refused below rather
# than silently resolved in favour of whichever branch is written first.
#
# NOTHING HERE TALKS TO A NETWORK: pip runs --no-index against a tree whose
# sha256 bin/fetch-assets.sh checked, and --no-binary :all: guards against
@@ -630,6 +726,9 @@ pkg_pywheel() {
"$PKG_ID: $(pypkg_var "$_pw" FILE) did not unpack to $(basename "$_wdir")"
[ -n "${PKG_PYSP:-}" ] || pkg_die \
"$PKG_ID: pkg_pywheel needs the target environment -- call pkg_pytarget first"
[ -z "${PKG_PY_SETUP_ARGS:-}" ] || [ -z "${PKG_PY_PIP_ARGS:-}" ] || pkg_die \
"$PKG_ID: PKG_PY_SETUP_ARGS and PKG_PY_PIP_ARGS are both set, and they
drive different build paths -- setup.py or pip, not both"
_wheels="$PWD/$PKG_WORK/wheels"
rm -rf "$_wheels"; mkdir -p "$_wheels"
_wlog="$PKG_LOG/wheel-$_pw.log"
@@ -648,8 +747,12 @@ pkg_pywheel() {
cp -a "$_wdir"/dist/*.whl "$_wheels/" \
|| pkg_die "$PKG_ID: $_pw's setup.py produced no wheel -- see $_wlog"
else
# Deliberately unquoted: PKG_PY_PIP_ARGS is a list of options, empty
# for every recipe that does not set it.
# shellcheck disable=SC2086
env "$@" "$HOSTPY" -m pip wheel --no-deps --no-build-isolation \
--no-index --no-binary :all: --no-cache-dir -w "$_wheels" "$_wdir" \
--no-index --no-binary :all: --no-cache-dir \
${PKG_PY_PIP_ARGS:-} -w "$_wheels" "$_wdir" \
> "$_wlog" 2>&1 || {
printf ' !! %s: %s failed to build -- see %s\n' "$PKG_ID" "$_pw" "$_wlog" >&2
tail -25 "$_wlog" | sed 's/^/ /' >&2