klippy runs from $MODDIR, so patch.sh stops staging a second copy

The klipper s6-rc service already execs klippy under $FF_PYTHON -- our own
CPython 3.13 -- but it read the tree from /usr/prog/klipper/klippy: a copy
bin/patch.sh staged into the software component out of the very anvil-klipper
package that also installed it under $MODDIR. Two copies of one tree, and the
one that ran was the one a stock flash could overwrite.

One line moves it: KLIPPY=$MODDIR/klipper/klippy/klippy.py.

That deletes sections 1 and 2 of bin/patch.sh -- the cp -a of the klippy tree,
the chelper.tar wrapped around a .so the package already contained, the rm -rf
of the stock extras/ and kinematics/, and the ff_*.py copy. The software
component now carries firmwareExe, start.sh and run.sh and nothing else of
ours, which is the phase 7 end state in docs/notes/80-s6-migration.md.

It also lets pkgs/klipper declare the dependencies it has had since the
interpreter moved. cffi and greenlet reached the printer only through a
hardcoded list in patch.sh, so every build had them and no `opkg install
anvil-klipper` did; pyserial and jinja2 came only as anvil-moonraker's
Depends, so a BUILD_MOONRAKER=0 build shipped a klippy that dies the first
time it opens an MCU. Both are real Depends now and MOD_ROOTS drops to ten.

Also repointed: run-append.sh's __pycache__ sweep, which was clearing the tree
nothing imports while klippy writes bytecode into the one it does -- and those
runtime-created directories are in no .install-manifest, so the manifest
delete walks past them.

verify.sh reads the klippy tree and c_helper.so out of the payload (extracting
./klipper alone), and gains a check that the component does NOT carry a klippy
tree. New replica tests assert the service execs the payload tree under
$FF_PYTHON and that cffi/greenlet/serial/jinja2 import on it.

Comments in anvil-env.sh, verify.sh and pkgs/klipper/pkg.conf that still said
klippy runs on FlashForge's 3.8.2 are corrected; that stopped being true when
FF_PYTHON moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La5MaDKqTW675k5fsXsP6S
This commit is contained in:
Oleksandr ShyshatskyiandClaude Opus 5 committed 2026-08-29 23:59:58 +03:00
1 parent c185ef3285
commit 53292b4d78
11 files changed
+243 -158

No files matched your search

+45 -90
View File
@@ -56,14 +56,20 @@ say "payload: installing the feed with the printer's own opkg"
# flag restated in this file. A missing DEPENDENCY is still an error, and opkg
# raises it.
#
# The four loose python packages are Recommends, which opkg has no field for:
# pillow and preprocess-cancellation are Moonraker's thumbnail path
# (pkgs/moonraker argues them out of Depends), greenlet and cffi are klippy's,
# which pkgs/klipper cannot declare while klippy still runs under
# FlashForge's interpreter.
# The two loose python packages are Recommends, which opkg has no field for:
# pillow and preprocess-cancellation are Moonraker's thumbnail path, and
# pkgs/moonraker argues them out of Depends because a Moonraker without them
# serves every other endpoint.
#
# GREENLET AND CFFI WERE HERE TOO, for klippy, with a comment saying
# pkgs/klipper could not declare them "while klippy still runs under
# FlashForge's interpreter". It does not: the klipper service execs
# $FF_PYTHON, so they are ordinary Depends of anvil-klipper now and this list
# does not name them. That is the difference that matters -- listed here they
# were installed by every build and by no `opkg install anvil-klipper`, which
# is the one command that has to work on a printer.
MOD_ROOTS="anvil-core anvil-opkg anvil-s6-rc anvil-klipper
anvil-moonraker anvil-python-pillow anvil-python-preprocess-cancellation
anvil-python-greenlet anvil-python-cffi
anvil-mainsail anvil-helixscreen anvil-busybox"
# Named, not versioned: opkg reads the index and picks. The one version that
@@ -91,95 +97,44 @@ done
say "payload: $(grep -c '^Package:' "$PAYLOAD_DIR/var/lib/opkg/status") packages installed (both chamber configs; the printer picks)"
# ---------------------------------------------------------------- 1. Klipper
# THE BUILD LIVES IN pkgs/klipper, and this section copies what it produced --
# the arrangement sections 3, 4, 5 and 5d have had since each became a recipe,
# and for the same reason: the .ipk and the tarball have to contain the same
# klippy tree and the same c_helper.so, or they are two different Klippers
# wearing one version number and no test can tell which a printer got.
# NOTHING IS STAGED HERE ANY MORE. anvil-klipper installs the whole klippy
# tree at $MODDIR/klipper/klippy and the klipper s6-rc service execs it there,
# on our own $FF_PYTHON -- so the package is not merely the source of the
# printer's Klipper, it IS the printer's Klipper.
#
# THERE IS NO STOCK PATH. BUILD_KLIPPER=stock kept FlashForge's v0.12 tree and
# it is gone -- everything else the mod ships is written against the fork. The
# flag's own history is why it went rather than gaining another guard: a
# "fork" build silently falling back to stock is exactly what shipped as
# v20260824-nova-kakhovka, where the 0.12-era overlay half-overwrote the v0.13
# tree and klippy died at connect with "expects 4 arguments, got 3" -- a v0.12
# extruder.py calling the v0.13 chelper cdef.
# WHAT THIS SECTION USED TO BE, because the shape is worth not rebuilding: a
# `cp -a` of the payload's klippy tree into the software component, a
# chelper.tar wrapped around a c_helper.so the package already contained, an
# `rm -rf` of the stock extras/ and kinematics/ the fork replaces, and a copy
# of the ff_*.py toolchanger extras beside them. All four existed for one
# reason -- klippy was started from /usr/prog/klipper/klippy by FlashForge's
# klipperDaemon, so the tree had to be on the firmware partition, which meant
# shipping a second copy of it in the software component and hoping the two
# agreed. They are one copy now. See etc/s6-rc/source/klipper/run.
#
# THE TREE COMES OUT OF THE PAYLOAD, which is to say out of the installed
# anvil-klipper package, not out of the recipe's build directory. That was the
# last place this script reached into work/pkg, and removing it is what makes
# "the tarball and the .ipk contain the same klippy" true by construction
# rather than by two copies of one cp -a agreeing.
_kl="$PAYLOAD_DIR/klipper"
[ -d "$_kl/klippy" ] || pkg_die \
# WHAT IS LEFT AT /usr/prog/klipper: FlashForge's own stock klippy, untouched
# and unread. A stock flash restores it, which also replaces firmwareExe and
# takes the mod down whatever else survived -- so it was never the fallback it
# looked like. klipper_pri.sh and klipperDaemon are still read there: the
# first is FlashForge's SCHED_FIFO helper, the second is our shim, and the
# service greps KLIPPER_NICENESS out of it.
#
# THE ASSERTION STAYS. It is the whole of what this section still does, and it
# is cheap: an anvil-klipper that did not install is a printer with no Klipper
# at all now, with no component copy to mask it.
[ -d "$PAYLOAD_DIR/klipper/klippy" ] || pkg_die \
"the payload has no $MODDIR/klipper/klippy -- anvil-klipper did not install"
# THE FORK GOES TO THE SOFTWARE COMPONENT, not to $MODDIR, and that is why
# this is a copy rather than a line in the payload loop below. klippy is
# started by the stock /usr/prog/klipper/klipperDaemon, so it has to be
# where that binary looks. The package installs the same tree under
# $MODDIR/klipper, which is inert until phase 7 of
# docs/notes/80-s6-migration.md moves Klipper there and this mod starts it
# itself; see pkgs/klipper/pkg.conf.
#
# Stock ships only a handful of klippy files as an overlay; the fork is a
# different Klipper generation, so the WHOLE tree replaces it.
rm -rf "$SOFTWARE_DIR/klipper/klippy"
mkdir -p "$SOFTWARE_DIR/klipper"
cp -a "$_kl/klippy" "$SOFTWARE_DIR/klipper/klippy"
# chelper.tar IS THE STOCK INSTALLER'S VEHICLE, not a second build. The
# software component's run.sh ends with
# `tar -xf $WORK_DIR/klipper/chelper.tar -C /usr/prog/klipper/klippy/`
# -- read out of a real stock package, so a package without it
# extracts nothing over the .so already inside klippy/ -- harmless today,
# and a missing file the moment FlashForge's installer stops copying
# klippy/ wholesale. bin/verify.sh fails a fork package that lacks it.
# Packing it here rather than in the recipe keeps it out of the .ipk,
# where a tarball of a file the package already contains would be 30KB of
# the same object under a second name.
rm -rf work/.chelper
mkdir -p work/.chelper/chelper
cp -f "$_kl/klippy/chelper/c_helper.so" work/.chelper/chelper/c_helper.so
tar -cf "$SOFTWARE_DIR/klipper/chelper.tar" -C work/.chelper chelper
rm -rf work/.chelper
# klippy/ now contains the fork's own extras+kinematics, so the stock
# overlay dirs would only re-inject 0.12-era files on top. Drop them.
# extras/ is recreated empty because section 2 puts ff_*.py in it.
rm -rf "$SOFTWARE_DIR/klipper/extras" "$SOFTWARE_DIR/klipper/kinematics"
mkdir -p "$SOFTWARE_DIR/klipper/extras"
say "Klipper: fork tree from pinned commit ${KLIPPER_VERSION:0:8}"
[ -f "$PAYLOAD_DIR/klipper/klippy/chelper/c_helper.so" ] || pkg_die \
"the payload has no $MODDIR/klipper/klippy/chelper/c_helper.so -- klippy cannot connect to an MCU without it"
say "Klipper: fork tree from pinned commit ${KLIPPER_VERSION:0:8} (payload only)"
# ----------------------------------------------------------- 2. Toolchanger
# The configs are three packages, installed by section 0: anvil-klipper-config
# for the ff-*.cfg and one of the two model packages for printer.chamber.cfg.
# What is left here is the residue that cannot be a package -- klippy's extras
# and printer.base.cfg go to /usr/prog, beside FlashForge's own Klipper tree,
# and every path in a package of ours lands under $MODDIR.
if [ "${BUILD_TOOLCHANGE:-1}" = "1" ]; then
say "Toolchange: ff_*.py + configs"
# OUT OF THE PAYLOAD, like section 1's klippy tree and for the same
# reason: anvil-klipper carries these now, so copying them from the
# recipe directory would be a second source for a file a package holds.
_ffx="$PAYLOAD_DIR/klipper/klippy/extras"
mkdir -p "$SOFTWARE_DIR/klipper/extras"
# Named by the recipe, not globbed out of the payload: the fork ships an
# ff_eddy.py of its own and `ff_*.py` cannot tell it from ours. It rides
# the klippy tree section 1 copied either way, so sweeping it up here
# would put the same file in the component twice.
for _f in pkgs/klipper/payload/klipper/klippy/extras/ff_*.py; do
cp -f "$_ffx/$(basename "$_f")" "$SOFTWARE_DIR/klipper/extras/"
done
# NO .cfg IS WRITTEN HERE: the software component's klipper/config is left
# exactly as FlashForge shipped it. anvil-klipper-config carries every
# config the mod owns and anvil-link-prog.sh symlinks them into
# /usr/data/config, which is where the stock run.sh puts that directory
# anyway. The ff_*.py above are what is left that cannot be a package.
else
skip "Toolchange"
fi
# GONE, with section 1. The ff_*.py extras ride the klippy tree anvil-klipper
# installs, and the ff-*.cfg are anvil-klipper-config's -- anvil-link-prog.sh
# symlinks them into /usr/data/config, which is where printer.cfg looks. There
# is no longer any part of the toolchanger that cannot be a package, so
# BUILD_TOOLCHANGE is answered entirely by pkgs/klipper-config's PKG_WHEN and
# this script does not restate it.
# ------------------------------------------------------- 3. the user's seams
# What is in the payload and in NO package, because being a package member
+42 -25
View File
@@ -82,22 +82,37 @@ if [ -f "$WORK/sw/app_startup.sh" ]; then
fi
# 7 ------------------------------------------------- MIPS ABI of any binaries
# THE KLIPPY TREE IS IN THE PAYLOAD, NOT IN THE SOFTWARE COMPONENT. It used to
# be checked at $WORK/sw/klipper -- a copy bin/patch.sh staged for FlashForge's
# run.sh to place on the firmware partition, back when klipperDaemon started
# klippy from there. The klipper s6-rc service execs $MODDIR/klipper/klippy on
# $FF_PYTHON now, so the component copy and the chelper.tar that carried its
# .so are both gone and this is where the one remaining tree lives.
#
# A package that ships no fork is a broken package, not an optional check: the
# v20260824 release passed here precisely because everything below was
# conditional on files that the broken build simply did not contain. It is
# unconditional now for a second reason as well -- BUILD_KLIPPER is gone, so
# there is no configuration in which a package legitimately has no klippy.
if [ -f "$WORK/sw/klipper/klippy/chelper/__init__.py" ]; then
ok "package carries the fork klippy tree"
else
bad "no klippy tree in the package -- this is the stock-overlay build that shipped as v20260824"
# ./klipper alone, not the whole payload: the rest is Mainsail, HelixScreen
# and a 30MB interpreter, and section 8 answers everything it needs from a
# listing. The paths carry a leading ./ because bin/pack.sh tars `.` from
# inside $PAYLOAD_DIR.
PAYLOAD_TAR=$(ls -1 "$WORK"/anvil.tar.xz 2>/dev/null | head -n1)
if [ -n "$PAYLOAD_TAR" ]; then
mkdir -p "$WORK/pl"
xz -dc "$PAYLOAD_TAR" 2>/dev/null | tar -xf - -C "$WORK/pl" ./klipper 2>/dev/null \
|| tar -xf "$PAYLOAD_TAR" -C "$WORK/pl" ./klipper 2>/dev/null
fi
[ -f "$WORK/sw/klipper/chelper.tar" ] \
|| bad "no chelper.tar in the package"
if [ -f "$WORK/sw/klipper/chelper.tar" ]; then
mkdir -p "$WORK/ch" && tar -xf "$WORK/sw/klipper/chelper.tar" -C "$WORK/ch"
CHELPER=$(find "$WORK/ch" -name 'c_helper.so' | head -n1)
if [ -n "$CHELPER" ] && ! head -c 4 "$CHELPER" | grep -q ELF; then
if [ -f "$WORK/pl/klipper/klippy/chelper/__init__.py" ]; then
ok "payload carries the fork klippy tree"
else
bad "no klippy tree in the payload -- anvil-klipper did not install; this is the stock-overlay build that shipped as v20260824"
fi
CHELPER="$WORK/pl/klipper/klippy/chelper/c_helper.so"
[ -f "$CHELPER" ] || bad "no c_helper.so in the payload klippy tree -- klippy cannot talk to an MCU"
if [ -f "$CHELPER" ]; then
if ! head -c 4 "$CHELPER" | grep -q ELF; then
warn "c_helper.so is not an ELF (synthetic fixture) -- ABI check skipped"
CHELPER=""
fi
@@ -110,15 +125,19 @@ if [ -f "$WORK/sw/klipper/chelper.tar" ]; then
# The failure mode that reached a printer: a .so from older sources
# than the klippy tree beside it. cffi resolves lazily, so only a
# symbol-table check catches it before the machine does.
if [ -f "$WORK/sw/klipper/klippy/chelper/__init__.py" ]; then
if python3 "$ROOT/test/test-chelper.py" "$WORK/sw/klipper" >/dev/null 2>&1; then
ok "c_helper.so exports everything the shipped klippy declares"
else
bad "c_helper.so does not match the shipped klippy tree (stale build)"
fi
if python3 "$ROOT/test/test-chelper.py" "$WORK/pl/klipper" >/dev/null 2>&1; then
ok "c_helper.so exports everything the shipped klippy declares"
else
bad "c_helper.so does not match the shipped klippy tree (stale build)"
fi
fi
fi
# THE COMPONENT MUST NOT CARRY ONE. Two klippy trees under one version number
# is the arrangement this release removed, and a stray one would be the copy a
# stock flash leaves running.
[ -e "$WORK/sw/klipper/klippy" ] \
&& bad "the software component still carries a klippy tree -- bin/patch.sh section 1 is staging one again" \
|| ok "no klippy tree in the software component (the payload is the only one)"
# 8 ------------------------------------------------------------ mod payload
if [ -f "$WORK/anvil.tar.xz" ]; then
@@ -188,11 +207,11 @@ if [ -f "$WORK/anvil.tar.xz" ]; then
|| bad "no s6 in the payload -- bin/patch.sh did not stage the cross-build"
grep -q 'libexec/s6-ftrigrd' <<<"$LIST" && ok "s6-ftrigrd present in libexec (the waiting verbs can spawn it)" \
|| bad "no libexec/s6-ftrigrd -- s6-svwait and s6-svc -w will fail on the printer"
# CPython 3.13. anvil-env.sh points FF_PYTHON here -- Moonraker,
# CPython 3.13. anvil-env.sh points FF_PYTHON here -- Moonraker, klippy,
# ff-startup.py, ffscreen.py and ff_mcu_bringup.py all run on it -- so a
# package missing it breaks the boot, not just a future release. klippy is
# not among FF_PYTHON's callers: it stays on FlashForge's 3.8.2, started by
# the klipper s6-rc service independently.
# package missing it breaks the boot, not just a future release. klippy IS
# among FF_PYTHON's callers: the klipper s6-rc service execs $FF_PYTHON
# against $MODDIR/klipper/klippy, and anvil-klipper declares it.
#
# Two checks, not one, and the second is the one worth having. The
# interpreter can be present and perfectly runnable while _sqlite3 is
@@ -213,11 +232,9 @@ if [ -f "$WORK/anvil.tar.xz" ]; then
# symlink CPython installs beside it. $MODDIR/bin is prepended to PATH by
# anvil-env.sh (s6 needs that), so shipping one would silently put our
# interpreter ahead of FlashForge's for every process that says `python3`
# -- an accidental switch, on a printer whose klippy still needs 3.8's
# site-packages (it is started separately, by FlashForge's own
# start.sh, and never goes through FF_PYTHON). Reads the way round it
# does for the same reason the dropbear check below does: its presence is
# the bug.
# -- an accidental switch, and the mod's own callers do not need it: every
# one of them names $FF_PYTHON. Reads the way round it does for the same
# reason the dropbear check below does: its presence is the bug.
grep -qE '(^|/)bin/python3$' <<<"$LIST" \
&& bad "payload ships bin/python3 -- it would shadow FlashForge's interpreter on PATH" \
|| ok "no bin/python3 symlink (nothing shadows FlashForge's python3 on PATH)"
+2 -1
View File
@@ -321,7 +321,8 @@ test/ 28 host-side unit tests over our own Python, and the two
ffcfg.py Klipper config parsing, as klippy's own parser sees it
integration/ test_startup.py, test_tool_transform.py, test_ffscreen.py,
test_chamber.py, test_gcode.py -- see docs/testing.md
test-chelper.py in neither suite: bin/patch.sh and bin/verify.sh call it
test-chelper.py in neither suite: pkgs/klipper/build.sh and
bin/verify.sh call it
tools/replica/ THE REPLICA, which is a build tool as much as a test one --
bin/patch.sh assembles the payload inside it
printer/ the machine: binfmt, the mount layout, its two Dockerfiles
+7 -2
View File
@@ -61,8 +61,13 @@ cdef and the caller both live in the tree; a stale .so shows up as a missing
symbol (`AttributeError`) instead, since cffi resolves symbols lazily.
There is now no fork build with no fork tree to refuse: `pkgs/klipper` has one
source and `pkg_unpack` fails on a missing tarball. `verify.sh` still fails a
fork package that lacks the klippy tree or `chelper.tar`, and the compile-
from-shipped-sources rule makes a stale .so unrepresentable in a release.
fork package that lacks the klippy tree or its `c_helper.so`, and the
compile-from-shipped-sources rule makes a stale .so unrepresentable in a
release. Both are read out of the PAYLOAD now: the klippy tree moved to
`$MODDIR/klipper/klippy` — the `klipper` s6-rc service execs it there, on our
own CPython 3.13 — so the software-component copy and the `chelper.tar` that
carried its .so onto the firmware partition are both gone. One tree, in one
package.
## Custom commands (by area)
+26 -7
View File
@@ -318,14 +318,33 @@ has to catch that class of failure.
## Phase 7 -- own Klipper
**Now reachable: phase 6 is done.** The interpreter it needs is `pkgs/3rdparty/python`,
built with the Ingenic glibc toolchain -- the requirement the paragraph above
calls fatal if got wrong, since klippy `dlopen`s a glibc `c_helper.so`.
**DONE.** The interpreter it needed is `pkgs/3rdparty/python`, built with the
Ingenic glibc toolchain -- the requirement the paragraph above calls fatal if
got wrong, since klippy `dlopen`s a glibc `c_helper.so`.
Ship the klippy tree into `$MODDIR` instead of
handing it to FlashForge's `run.sh`; replace `klipperDaemon` with an s6 `run`
script (the command line is quoted above); turn `S70klipper`'s retry loop into
an s6 restart policy plus readiness. `checkEboard`, `libmcu-bare.bin` and
It landed in two halves and the gap between them is the part worth recording.
The `klipperDaemon` replacement, the s6 `run` script and the move onto
`$FF_PYTHON` went in together; the klippy TREE stayed at
`/usr/prog/klipper/klippy` for a while afterwards, staged there by
`bin/patch.sh` out of the very `anvil-klipper` package that also installed it
under `$MODDIR`. So the printer ran our Klipper, on our Python, out of a copy
on the firmware partition that a stock flash could overwrite -- and because
nothing declared it, `anvil-klipper` carried no dependency on the interpreter
it had already moved to. `KLIPPY=$MODDIR/klipper/klippy/klippy.py` closed
both: it deleted sections 1 and 2 of `bin/patch.sh` (~85 lines, the software
component's whole `klipper/` directory and the `chelper.tar` that fed stock
`run.sh`) and let `pkgs/klipper` declare `anvil-python`, `-cffi`, `-greenlet`,
`-pyserial` and `-jinja2` for real. Until then `cffi` and `greenlet` reached
the printer only via a hardcoded list in `patch.sh`, and `pyserial` and
`jinja2` only as `anvil-moonraker`'s Depends -- so a `BUILD_MOONRAKER=0` build
shipped a klippy that dies the first time it opens an MCU.
**The lesson, stated once:** moving what a program RUNS ON without moving what
it RUNS leaves the dependency graph lying, and nothing fails until a
configuration nobody builds gets built.
Still open: `S70klipper`'s retry loop is not yet an s6 restart policy plus
readiness. `checkEboard`, `libmcu-bare.bin` and
`cmd_mcu` stay where they are -- they are version-matched to the firmware and
reading them from the firmware partition is correct.
+1 -1
View File
@@ -67,7 +67,7 @@ whose subject had become the fake it was built on, or whose failure the printer
reports loudly the first time. What is kept is the mistakes that RUN
PERFECTLY and are wrong.
`test/test-chelper.py` sits outside both suites: `bin/patch.sh` and
`test/test-chelper.py` sits outside both suites: `pkgs/klipper/build.sh` and
`bin/verify.sh` call it directly at build time.
## The replica is a tool, not a test
+11
View File
@@ -345,6 +345,17 @@ sync
# Stale bytecode from the previous Klipper generation is a silent import-time
# landmine; the stock run.sh only clears four of the __pycache__ dirs.
#
# $MODDIR, NOT /usr/prog: the klipper s6-rc service execs
# $MODDIR/klipper/klippy, so that is the tree whose .pyc files get imported.
# It also needs the sweep MORE than /usr/prog ever did -- klippy WRITES those
# directories at runtime on a writable /usr/data, and a path created after
# install is in no .install-manifest, so the manifest delete above walks past
# it and leaves last release's bytecode beside this release's source.
find $MODDIR/klipper/klippy -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null
# The stock tree is swept too, and only because it costs one line: nothing
# imports it any more, but a machine that has been through several releases
# has bytecode there from when something did.
find /usr/prog/klipper/klippy -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null
sync
+16 -10
View File
@@ -87,18 +87,24 @@ export LD_LIBRARY_PATH
#
# WHO ELSE THIS MOVES. grep FF_PYTHON payload/ bin/ before touching this line
# again -- the answer changes as callers are added. Today it is Moonraker,
# ff-startup.py, ffscreen.py and ff_mcu_bringup.py (the MCU bootloader
# handshake start.sh runs before Klipper). The last three are stdlib-only
# KLIPPY, ff-startup.py, ffscreen.py and ff_mcu_bringup.py (the MCU bootloader
# handshake that runs before Klipper). Three of those five are stdlib-only
# (os/sys/termios/time/argparse/json/subprocess/urllib -- checked against
# CPython's own removed-in-3.13 list), so the only one with any C-extension
# surface is Moonraker, and that is the one measured above.
# CPython's own removed-in-3.13 list); the two with C-extension surface are
# Moonraker, measured above, and klippy.
#
# WHO THIS DOES NOT MOVE. Klipper is not on this list. It is started by
# FlashForge's own /usr/prog/klipper/start.sh, hardcoded to
# /usr/prog/Python-3.8.2/bin/python3, independently of FF_PYTHON -- see
# init.d/S70klipper's own header. klippy's numpy gap
# (extras/stepper_resonance_tester.py) is therefore not this switch's problem;
# it stays open as a separate, smaller item.
# KLIPPY IS ON THIS LIST NOW. This block used to say it was not -- that
# FlashForge's own start.sh started it, hardcoded to
# /usr/prog/Python-3.8.2/bin/python3. Both halves are gone:
# etc/s6-rc/source/klipper/run execs $FF_PYTHON against $MODDIR/klipper/klippy,
# and prog/start.sh is a `s6-rc -u change klipper` and nothing else. klippy
# reaches c_helper.so through _cffi_backend, which is the reason this
# interpreter had to be a glibc build; anvil-klipper declares cffi, greenlet,
# pyserial and jinja2 against it.
#
# klippy's numpy gap (extras/stepper_resonance_tester.py) IS this switch's
# problem now, and stays open as a separate, smaller item: the module guards
# its own import, so the printer runs without it and loses resonance testing.
#
# FlashForge's tree is not touched either way: nothing here writes to
# /usr/prog, and everything of ours lives under /usr/data/anvil like every
@@ -11,18 +11,32 @@
# here as well would hand the same serial ports to two processes at once.
MODDIR=/usr/data/anvil
# klippy is FlashForge's Python 3.8.2 on the firmware partition, linked against
# libraries in four directories nothing on the base rootfs knows about.
# s6-supervise hands this script the scanner's environment, which is already
# correct on the boot path -- but a service must not work only because the
# shell that started the tree happened to be set up right.
# anvil-env.sh names the interpreter ($FF_PYTHON) and puts the mod's four
# library directories on LD_LIBRARY_PATH. s6-supervise hands this script the
# scanner's environment, which is already correct on the boot path -- but a
# service must not work only because the shell that started the tree happened
# to be set up right.
if [ -f $MODDIR/anvil-env.sh ]; then
. $MODDIR/anvil-env.sh
else
echo "klipper/run: !! no $MODDIR/anvil-env.sh -- klippy will not find its libraries"
fi
KLIPPY=/usr/prog/klipper/klippy/klippy.py
# BOTH HALVES ARE OURS AND BOTH ARE UNDER $MODDIR: the klippy tree anvil-klipper
# installs, on the CPython 3.13 anvil-python installs. There is no /usr/prog in
# this command line any more.
#
# The tree used to be read from /usr/prog/klipper/klippy -- a copy bin/patch.sh
# staged into the software component out of this very package, so that
# FlashForge's run.sh would place it on the firmware partition. Two copies of
# one tree, and the one that ran was the one a stock flash could overwrite.
# anvil-klipper is the only source now, which is also what lets this package
# declare the interpreter it actually needs: see pkgs/klipper/pkg.conf.
#
# THE INTERPRETER IS NOT FLASHFORGE'S 3.8.2 and has not been since $FF_PYTHON
# moved to 3.13. klippy reaches c_helper.so through _cffi_backend, which is why
# pkgs/3rdparty/python is a glibc build and not a musl one.
KLIPPY=$MODDIR/klipper/klippy/klippy.py
PRINTER_CFG=/usr/data/config/printer.cfg
PRINTER_LOG=/usr/data/logs/printer.log
UDS=/tmp/uds
+29 -13
View File
@@ -23,20 +23,36 @@ PKG_RELEASE=1
PKG_SECTION=base
PKG_EXCLUDE=".version"
# NO DEPENDS ON THE INTERPRETER, and the reason is where klippy runs rather
# than what it imports. The stock /usr/prog/klipper/klipperDaemon starts
# klippy under FlashForge's own interpreter -- bin/patch.sh copies this tree
# into the SOFTWARE component, not into $MODDIR -- so naming anvil-python here
# would declare a dependency the running system does not have. It becomes a
# real Depends when phase 7 of docs/notes/80-s6-migration.md moves Klipper
# under $MODDIR and this mod starts klippy itself.
# THE INTERPRETER IS A DEPENDS NOW. This block used to say the opposite --
# "the reason is where klippy runs rather than what it imports", the stock
# klipperDaemon starting klippy under FlashForge's 3.8.2 while bin/patch.sh
# copied the tree into the software component -- and it named phase 7 of
# docs/notes/80-s6-migration.md as the change that would make it false. That
# change has happened: etc/s6-rc/source/klipper/run execs $FF_PYTHON against
# $MODDIR/klipper/klippy, so klippy is on our CPython 3.13 and the tree it
# runs is the one THIS package installs.
#
# anvil-klipper-config is not optional in that way: printer.base.cfg is the
# file printer.cfg includes and every other config hangs off it, so a klippy
# tree without it is a Klipper that cannot start. Naming it in bin/patch.sh's
# MOD_ROOTS instead would answer the dependency in the one place a printer's
# own `opkg install anvil-klipper` never reads.
PKG_DEPENDS="anvil-klipper-config"
# THE LIST IS scripts/klippy-requirements.txt MINUS WHAT IS NOT REACHABLE
# HERE. python-can is not packaged (no CAN transport on this machine) and
# numpy is not either, which costs extras/stepper_resonance_tester.py and
# nothing else -- both are import-guarded by the modules that want them.
# markupsafe and setuptools are not named because anvil-python-jinja2 declares
# the first and nothing here needs the second at import.
#
# WHAT THIS FIXES, and it is not bookkeeping. cffi and greenlet were installed
# by a hardcoded list in bin/patch.sh, so every build had them and no `opkg
# install anvil-klipper` did. pyserial and jinja2 were worse: they arrived
# only as anvil-moonraker's Depends, so a BUILD_MOONRAKER=0 build shipped a
# klippy that imports serialhdl and dies the first time it opens an MCU.
#
# anvil-klipper-config is not optional either: printer.base.cfg is the file
# printer.cfg includes and every other config hangs off it, so a klippy tree
# without it is a Klipper that cannot start. Naming any of these in
# bin/patch.sh's MOD_ROOTS instead would answer the dependency in the one
# place a printer's own `opkg install anvil-klipper` never reads.
PKG_DEPENDS="anvil-klipper-config, anvil-python, anvil-python-cffi,
anvil-python-greenlet, anvil-python-pyserial,
anvil-python-jinja2"
PKG_BUILD_DEPENDS=""
# NO PKG_WHEN. The fork is the only Klipper, and nothing here is optional --
+44 -3
View File
@@ -58,7 +58,11 @@ FE = "/usr/prog/PROGRAM/software/firmwareExe"
APP = "/usr/prog/app_startup.sh"
SOURCE = MODDIR + "/etc/s6-rc/source"
DB = MODDIR + "/etc/s6-rc/compiled/current"
CHELPER = "/usr/prog/klipper/klippy/chelper/c_helper.so"
# The klipper s6-rc service execs $FF_PYTHON against $MODDIR/klipper/klippy,
# so this is the .so that actually gets dlopened. /usr/prog/klipper/klippy
# still holds FlashForge's stock tree; nothing reads it.
KLIPPY_DIR = MODDIR + "/klipper/klippy"
CHELPER = KLIPPY_DIR + "/chelper/c_helper.so"
INSTALL_LOG = "/usr/data/anvil-install.log"
@@ -194,8 +198,45 @@ def test_klipper_depends_on_the_mcu_bringup(box):
# --------------------------------------------------------------- klippy itself
def test_klippy_is_present(box):
assert box.file("/usr/prog/klipper/klippy/klippy.py").exists, (
"klippy.py missing -- there is nothing to start")
"""In the payload, which is the only copy: anvil-klipper installs it and
the s6 service execs it there. There is no component copy to fall back
on any more, so this failing means the printer has no Klipper at all."""
assert box.file(KLIPPY_DIR + "/klippy.py").exists, (
"%s/klippy.py missing -- anvil-klipper did not install and there is "
"nothing to start" % KLIPPY_DIR)
def test_the_klipper_service_runs_the_payload_tree_on_our_python(box):
"""The regression that would silently restore the old arrangement: a run
script pointing back at /usr/prog gets its klippy from a stock flash, not
from the package, and no other test here would notice."""
# The compiled servicedir is what s6-supervise actually execs; the source
# tree is the fallback so this still says something useful on a machine
# where the database did not compile.
body = box.file(DB + "/servicedirs/klipper/run").text \
or box.file(SOURCE + "/klipper/run").text
assert body, "no klipper run script in either the database or the source tree"
assert "/usr/prog/klipper/klippy" not in body, (
"the klipper service still execs klippy out of /usr/prog -- that tree "
"is FlashForge's stock 0.12 and a stock flash owns it")
# The run script spells it $MODDIR, unexpanded, so match the tail rather
# than the absolute path this file computes.
assert "klipper/klippy/klippy.py" in body, (
"the klipper service does not exec %s/klippy.py" % KLIPPY_DIR)
assert "$FF_PYTHON" in body, (
"the klipper service does not run klippy under $FF_PYTHON")
def test_klippy_imports_resolve_on_our_python(box):
"""cffi, greenlet, pyserial and jinja2 are anvil-klipper's Depends. They
used to arrive as anvil-moonraker's, or from a hardcoded list in
bin/patch.sh -- so a BUILD_MOONRAKER=0 build shipped a klippy that dies
the first time it opens an MCU."""
for mod in ("cffi", "greenlet", "serial", "jinja2"):
r = box.sh(". %s/anvil-env.sh; $FF_PYTHON -c 'import %s' 2>&1"
% (MODDIR, mod))
assert r.ok, (
"klippy's %s does not import on FF_PYTHON: %s" % (mod, r.text))
def _bytes_at(box, path, skip, count):