Files
firmware/pkgs/3rdparty/python/pkg.conf
T
Oleksandr ShyshatskyiandClaude Opus 5 f67e7a269f ls pkg/ was thirty-eight entries and four of them mattered
pkg/ -> pkgs/, and the thirty-four recipes that build a pinned tarball and
carry no files of this repo move down a level into pkgs/3rdparty/. What is
left at the top is the four components a person actually edits: anvil-core,
klipper, moonraker, helixscreen.

THE LINE IS MECHANICAL, NOT EDITORIAL, and that is why it can be a directory
rather than a field in pkg.conf. "Do we actively modify it" drifts -- the day
somebody patches zlib nobody would agree which side it belongs on, and the
answer would be a rename touching CI and docs decided by taste. "Does it carry
files of ours" is a fact about the tree: it is exactly whether the recipe has
a payload/, prog/ or seed/, checkable in one assertion, and
test_recipe_layout.py now demands the move in both directions -- a 3rdparty
recipe that grows one of the three must come up, and a top-level recipe with
none of them must go down, or the top level slowly refills and the split stops
meaning anything.

ONE FUNCTION KNOWS THE TWO DEPTHS. pkgs/lib.sh's pkg_dir resolves a recipe id
to its directory and nothing else spells either path -- not a recipe, not
bin/build-packages.sh, not a test. Names stay unique across both levels
because pkg_out, pkg_stamp and the .ipk filename are all keyed by the bare
name; a duplicate would resolve to whichever level is searched first and
build the wrong thing silently, so that is a test too.

Recipes now source `. ./bin/common.sh` instead of counting `..` from $0.
Depth-counting would have needed two spellings for two levels, and it was
never buying anything: `. pkg/lib.sh` on the next line already required the
working directory to be the repo root.

A SECOND SILENT-SHRINK BUG, and this one was live. qa/static's _files()
expanded a list of globs and returned whatever matched, so "pkgs/*/build.sh"
quietly stopped covering thirty-four recipes the moment they moved -- the lane
stayed green, and the only visible sign was a test count dropping by 34 in a
run nobody was counting. _files() now refuses a pattern that matches nothing.
That is the same failure the ash-compatibility check had a guard for and the
syntax check did not, which is why the guard reads the way it does.

AND ONE PACKAGE WAS SHIPPING PYTHON BYTECODE. anvil-core stages a directory of
.py helpers wholesale, so any test that imported one left a __pycache__ beside
it -- gitignored, invisible in a diff, copied by cp -a like anything else. The
package's contents depended on whether pytest had been run on the machine that
built it: two builds of one commit, different sha256, cause nowhere in the
tree. What it shipped was worthless as well as unstable, compiled by the build
image's python 3.11 for a printer running the 3.13 this repo cross-builds. The
sweep is in pkg_ship now, once, and the two hand-written copies in
pkgs/klipper and pkgs/moonraker are deleted -- the trap is set for any recipe
that stages a tree of .py files, and the two recipes that had hit it had each
solved it locally while the third, which had not, was the one shipping.

Measured:
  * the rename changes no package bytes. Same cached build trees, feed built
    with the diff stashed and unstashed: all 41 .ipk match.
  * with the bytecode sweep, anvil-core returns to exactly the bytes the
    previous commit produced.
  * no package in the feed contains a .pyc or a __pycache__.
  * make qa-static: 247 passed (236 before this work + 11 layout tests).
  * make test: 8 passed, 0 failed, including unpack/patch/pack/verify on the
    synthetic stock package. make test-py: 181 passed.

Not explained: anvil-python-dev produced two different sha256 from an
UNCHANGED work/pkg/python at two points today, under code that was identical
both times -- so it is not reproducible from a cached tree, and it is not this
commit's doing (the stashed and unstashed builds above agree on it). Worth a
look; the dev split in bin/build-packages.sh is where to start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 20:04:20 +03:00

61 lines
3.6 KiB
Plaintext

# anvil-python -- CPython, cross-compiled for the printer.
#
# WHY THERE IS A SECOND PYTHON ON THIS MACHINE AT ALL. FlashForge built its
# 3.8.2 without _sqlite3, and that single omission is what pins
# MOONRAKER_VERSION to a 2023 commit: every Moonraker from v0.9.0 on keeps its
# database in sqlite. This interpreter has a working sqlite3, which is what
# eventually unpins it.
#
# THE PREFIX IS COMPILED IN, exactly as it is for s6. sys.prefix and the stdlib
# search that follows from it are baked in at configure time, so moving the
# directory on the printer breaks the interpreter -- it has to be rebuilt, not
# renamed. That is also the argument for putting it in $MODDIR with everything
# else rather than somewhere private: there is one prefix to keep stable, not
# two.
#
# ONLY bin/python3.13 SHIPS, and the name matters. payload/anvil-env.sh
# PREPENDS $MODDIR/bin to PATH (s6-svscan execs s6-supervise by name off PATH,
# so it has to), so a bin/python3 of ours would sit AHEAD of FlashForge's for
# every process that sources it, and anything saying `python3` rather than
# "$FF_PYTHON" would silently change interpreter. `make install` creates that
# symlink, plus idle3, pydoc3 and the *-config scripts; build.sh names the one
# binary it ships and the rest are simply never copied out of the staging tree.
PKG_NAME=anvil-python
PKG_VERSION="$PY_VERSION"
PKG_RELEASE=1
PKG_SECTION=lang
PKG_EXCLUDE=".version"
# NO Depends, AND THAT IS A MEASURED CLAIM RATHER THAN AN OVERSIGHT. Every C
# library this interpreter uses is linked in STATICALLY -- see build.sh -- so
# the only shared objects it needs are the printer's own libc, libm, libdl,
# libpthread and libatomic.so.1, all of which are on the stock rootfs. Nothing
# in this feed has to be installed first.
PKG_DEPENDS=""
# SEVEN LIBRARIES, AND EACH ONE IS EXACTLY ONE stdlib MODULE. openssl is _ssl
# and _hashlib, sqlite is _sqlite3, zlib is zlib, libffi is _ctypes, xz is
# _lzma, bzip2 is _bz2, expat is pyexpat. build.sh gates all seven by name
# after the build, because CPython's answer to a library it cannot link is to
# record the module as absent and CARRY ON -- a silent success that produces an
# interpreter missing the one module this package exists for.
PKG_BUILD_DEPENDS="openssl sqlite zlib libffi xz bzip2 expat"
# THE HEADERS ARE THE POINT OF THE DEV SPLIT HERE, more than anywhere else in
# this feed. The eighteen pkgs/3rdparty/python-* recipes cross-compile C extensions
# against them, and before this package existed they reached them by building
# inside bin/patch.sh at the one moment an untrimmed staging tree happened to
# be on disk. A -dev package is that moment made durable: the headers are in
# the feed, a wheel recipe names `python` as a build dependency and gets them,
# and a printer -- which builds no extension modules -- installs none of it.
#
# config-3.13-* comes along because sysconfig's LIBPL points into it, and a
# sysconfig answering with a directory that does not exist is the kind of thing
# a future setuptools notices. libpython3.13.a is deleted in build.sh rather
# than shipped inside it: 35MB of static archive that nothing links.
PKG_DEV_FILES="include lib/pkgconfig lib/python$PY_MM/config-$PY_MM-*"
PKG_DEV_DESCRIPTION="CPython headers and build configuration for mipsel. This is what the pkgs/3rdparty/python-* recipes compile their C extensions against; nothing installs it on a printer."
PKG_DESCRIPTION="CPython $PY_VERSION for the printer, with sqlite3, ssl, lzma, bz2, ctypes and expat linked in statically. Installs as bin/python3.13 -- never as python3, which would shadow the stock interpreter on PATH."