# The part of a cross-build that is the same for every package. Sourced by # every recipe's build.sh after bin/common.sh, and by bin/payload.sh for pkg_out # alone. Recipes run as their own process, so no cross CC leaks onward. # # ONE RECIPE BUILDS ONE SOURCE: exactly one source verb per recipe, counted by # qa/static/test_packages.py. One build may still produce two packages, # and -dev (PKG_DEV_FILES). # # . ./bin/common.sh # . pkgs/lib.sh # pkg_begin libsodium || exit 0 # pkg_toolchain # pkg_deps # pkg_unpack "$SODIUM_TGZ" # pkg_build "libsodium-$SODIUM_VERSION" --disable-static --enable-shared # pkg_ship "lib/libsodium.so*" # pkg_end # # Version, dependencies and metadata live in the recipe's pkg.conf. Everything # cross-compiles with the one Ingenic glibc 2.29 / gcc 7.2 toolchain. pkg_say() { printf '>> %s\n' "$*"; } pkg_skip() { printf ' (skip) %s\n' "$*"; } pkg_die() { printf ' !! %s\n' "$*" >&2; exit 1; } # pkg_conf -- read pkg//pkg.conf into the PKG_* metadata. # Sets no build state, so pkg_stamp can read another recipe's in a subshell. # PKG_BUILD_DEPENDS recipe ids; build order and pkg_deps only. PKG_DEPENDS # is what the printer's apk reads. Keeping them apart # stops a runtime # dependency on a merely-linked library. # PKG_STAMP_EXTRA content hash for inputs no version number describes. # PKG_DEV_FILES files that move into a separate -dev package. # PKG_WHEN whether the recipe exists at all; empty means always. # A false condition is absence, not failure. # PKG_STAMP_EXTRA="$(pkg_payload_hash)" -- sixteen hex digits over payload/ # and control/, for the half of a package that comes out of this checkout # rather than a tarball; without it the stamp reports "already current" and # hands back the previous build. control/ counts, because an edited postinst # changes what the package does; seed/ ships in nothing. $PKG_DIR is already # set. # # TWO find CALLS AND AN `|| true`: `find a b` with b missing exits non-zero, # which under the recipes' `set -euo pipefail` kills the build silently. pkg_payload_hash() { { find "$PKG_DIR/payload" -type f -print0 2>/dev/null || true find "$PKG_DIR/control" -type f -print0 2>/dev/null || true } | LC_ALL=C sort -z | xargs -0 sha256sum 2>/dev/null \ | sha256sum | cut -c1-16 } # pkg_release_stamp -> the packaging revision for a recipe whose VERSION is an # upstream pin but whose contents are partly this repo's. # # apk decides whether to upgrade from the version string alone, and for those # recipes the string does not move when our files change: anvil-moonraker is # `0.11.0` whatever we ship beside upstream's tree, anvil-klipper is the # pinned commit whatever our klippy extras say. The revision is what moves, so # it is the release date rather than a number somebody has to remember to # raise -- a printer on any earlier release sees a higher `-r` and upgrades. # # THE TRAILING LETTER IS A SAME-DAY RE-RELEASE (20260827b, and there were four # that day). apk compares `-r` numerically, so the letter becomes a digit # after the date rather than beside it: 20260827 -> 202608270, `b` -> # 202608272, and the next day's 202608280 is still larger. Appending the # letter's index without the extra place would order 20260827b above 20260828. pkg_release_stamp() { _d=${MOD_VER%%[a-z]} _l=${MOD_VER#"$_d"} printf '%s%s' "$_d" "$(awk -v l="$_l" \ 'BEGIN { print (l == "") ? 0 : index("abcdefghijklmnopqrstuvwxyz", l) }')" } # pkg_signkey_hash -> a cache key for the feed's public key, or `unsigned`. # # anvil-core ships that key, and it lives OUTSIDE the repo, so # pkg_payload_hash cannot see it: without this, changing the signing key # leaves a warm cache and ships a package that still trusts the old one. # # THE MISSING-KEY CASE IS THE WHOLE REASON THIS IS A FUNCTION. Spelled inline # as `sha256sum "${APK_SIGN_KEY:-/dev/null}.pub" | cut`, an unsigned build # fails the pipeline under `set -o pipefail` -- while pkg.conf is being # SOURCED, so build-packages.sh died before emitting anything and the # 2>/dev/null ate the reason. That is every CI run, which builds with no key. pkg_signkey_hash() { [ -n "${APK_SIGN_KEY:-}" ] && [ -f "$APK_SIGN_KEY.pub" ] || { echo unsigned; return 0 } sha256sum "$APK_SIGN_KEY.pub" | cut -c1-16 } # pkg_dir -> the directory holding its pkg.conf. Recipes live at # two depths and this is the only function that knows it: # pkgs// carries files of this repo -- a payload/, prog/ # or seed/. Four of them. # pkgs/3rdparty// builds a pinned tarball, stages nothing from the # checkout. Thirty-four. # Names are unique ACROSS both levels, since pkg_out, pkg_stamp and the # package filename are keyed by the bare name; test_recipe_layout.py holds # that and the split, which is mechanical rather than editorial. pkg_dir() { for _c in "$ROOT/pkgs/$1" "$ROOT/pkgs/3rdparty/$1"; do if [ -f "$_c/pkg.conf" ]; then printf '%s' "$_c"; return 0; fi done return 1 } pkg_conf() { PKG_NAME=''; PKG_VERSION=''; PKG_RELEASE=1 PKG_ROOT=''; PKG_EXCLUDE=''; PKG_DEPENDS=''; PKG_BUILD_DEPENDS='' PKG_PROVIDES=''; PKG_CONFLICTS='' PKG_DESCRIPTION=''; PKG_ARCH="$IPK_ARCH" PKG_MAINTAINER='anvil ' PKG_STAMP_EXTRA=''; PKG_WHEN='' PKG_DEV_FILES=''; PKG_DEV_DESCRIPTION='' # PKG_DIR is set before the file is sourced, so pkg.conf and build.sh can # name their own directory without spelling out where recipes live. PKG_DIR=$(pkg_dir "$1") \ || pkg_die "no recipe named '$1' under pkgs/ or pkgs/3rdparty/" # shellcheck disable=SC1090 . "$PKG_DIR/pkg.conf" PKG_ROOT="${PKG_ROOT:-$(pkg_out "$1")}" } # Where a recipe's build output lives, derived rather than named, so adding a # package is one directory under pkg/ and no edit to bin/common.sh. pkg_out() { printf '%s/work/pkg/%s' "$ROOT" "$1"; } # A package's full version, upstream and packaging revision together, spelled # the way apk spells it: `1.2.3-r1`, not `1.2.3-1`. # # THE `r` IS NOT DECORATION. apk's grammar is # number{.number}{letter}{_suffix}{~hash}{-r#} and apk_version_validate # rejects anything else, so a bare `-1` is not a version it will parse. opkg # reads the same string as upstream `1.2.3` revision `r1` and is unbothered, # which is why this can land before the format changes. pkg_fullversion() { printf '%s-r%s' "$1" "$2"; } # The architecture as it goes INTO a package, which is not always what the # recipe says. `all` is opkg's spelling of arch-independent; apk interns the # literal `noarch` (src/database.c) and compares against it, so `all` would # read as an unknown architecture and every such package would be refused. # # ONE MAPPING, ONE PLACE. It first lived in the emitter alone, and the prune # step -- which builds its expected set from pkg_pkgfile -- then went looking # for a name nothing wrote, and deleted seventeen packages it had just # built. Anything that spells a package's filename has to agree with whatever # wrote it. pkg_arch() { case "$1" in all) printf 'noarch' ;; *) printf '%s' "$1" ;; esac } # Is this a version apk can parse? Called by the recipe gate rather than at # build time, so a bad pin is caught by `make qa-static` on a bare checkout # instead of half an hour into a cross-compile. # # The `+` traps: `1.2.3+git` is the natural spelling and is NOT in the # grammar -- the hash slot is `~`. Two recipes here used `+git` and both were # valid opkg versions, which is exactly why nothing caught them. # The suffix set is CLOSED, and copied from src/version.c's DECLARE_SUFFIXES # rather than guessed: apk knows these nine words and treats anything else as # an invalid version, so a `_creator5` would be as rejected as a `+`. PKG_VERSION_SUFFIXES='alpha|beta|pre|rc|cvs|svn|git|hg|p' pkg_version_ok() { case "$1" in # Rejected before the shape test so the message can be specific. *+*) return 1 ;; *' '*|'') return 1 ;; esac printf '%s' "$1" | grep -Eq \ "^[0-9]+(\.[0-9]+)*[a-z]?(_($PKG_VERSION_SUFFIXES)[0-9]*)*(~[0-9a-f]+)?(-r[0-9]+)?\$" } # The package file bin/build-packages.sh will write for a recipe. # # ONE FUNCTION, because the name is the interface between the producer and # everything that reads the feed -- bin/payload.sh, bin/build-payload.py and # the prune step all used to spell it themselves, and a format change has to # be able to move it in one place. # # THE NAME PUTS `-` BETWEEN NAME AND VERSION and carries no architecture, # which is why callers must match it EXACTLY and never by prefix glob -- # `anvil-python-*` matches `anvil-python-pillow-11.0.0-r1.apk`. pkg_pkgfile() { ( pkg_conf "$1" printf '%s/%s%s-%s.apk' "$PKG_FEED" "$PKG_NAME" "${2:+-$2}" \ "$(pkg_fullversion "$PKG_VERSION" "$PKG_RELEASE")" ) } # pkg_name_map -- ` ` for every recipe, one per line. # # MOD_ROOTS names PACKAGES and pkg_pkgfile takes a RECIPE, and the two differ # whenever a recipe lives under 3rdparty/ (recipe `apk-tools`, package # `anvil-apk-tools`). bin/payload.sh used to bridge that with a prefix glob, # which was safe only while the name put `_` between the name and the # version. The .apk name uses `-`, so `anvil-python-*` would match # `anvil-python-pillow-11.0.0-r1.apk` and a missing package would look # present. One pass over the recipes, so the answer is exact. pkg_name_map() { for _r in $(pkg_recipes); do ( pkg_conf "$_r" printf '%s %s\n' "$PKG_NAME" "$(pkg_pkgfile "$_r")" ) done } # The cache key for a recipe: its version, the toolchain that determines its # ABI, and recursively the stamp of all it builds against, so a zlib bump # rebuilds everything built against it. From pkg.conf alone, so fetch-assets.sh # can ask whether a build needs a compiler before one exists. pkg_stamp() { ( _PKG_DEPTH=$(( ${_PKG_DEPTH:-0} + 1 )) export _PKG_DEPTH [ "$_PKG_DEPTH" -le 16 ] \ || pkg_die "pkg_stamp: dependency cycle reached '$1'" pkg_conf "$1" _s="$1 $PKG_VERSION-$PKG_RELEASE $MIPS_TOOLCHAIN_FILE" # Before the dependency stamps, so the string still reads # outermost-first when two are compared by eye. [ -z "$PKG_STAMP_EXTRA" ] || _s="$_s $PKG_STAMP_EXTRA" for _d in $PKG_BUILD_DEPENDS; do _s="$_s [$(pkg_stamp "$_d")]" done printf '%s' "$_s" ) } # True when a recipe's output tree is missing or was built from other inputs. pkg_stale() { [ "$(cat "$(pkg_out "$1")/.version" 2>/dev/null || true)" != "$(pkg_stamp "$1")" ] } # Every recipe under pkg/, one per line. pkg.conf is what makes a directory a # recipe -- build.sh alone is not enough, because a package with no metadata # cannot be built into anything. pkg_recipes() { for _d in "$ROOT"/pkgs/*/ "$ROOT"/pkgs/3rdparty/*/; do [ -f "$_d/pkg.conf" ] || continue _r=$(basename "$_d") # PKG_WHEN in a subshell: arbitrary shell out of a recipe's metadata, # and this is called by the fetcher, the packager and the tests. ( pkg_conf "$_r"; [ -z "$PKG_WHEN" ] || eval "$PKG_WHEN" ) || continue printf '%s\n' "$_r" done } # True when any recipe needs compiling -- what bin/fetch-assets.sh asks before # pulling the ~203MB toolchain. pkg_needs() { for _r in $(pkg_recipes); do if pkg_stale "$_r"; then return 0; fi done return 1 } # pkg_order ... -- the given recipes and all they build against, # dependency first. Depth-first, so one recipe gets its whole closure: # `PKG=apk-tools make packages` builds zlib and openssl first, where # alphabetical order would invert those two. pkg_order() { _order=''; _seen=' '; _path=' ' for _r in "$@"; do _pkg_visit "$_r"; done printf '%s' "${_order# }" } _pkg_visit() { case "$_seen" in *" $1 "*) return 0 ;; esac # A cycle is a recipe reachable from itself, reported by name because the # name is what somebody has to fix. case "$_path" in *" $1 "*) pkg_die "pkg_order: dependency cycle through '$1'" ;; esac _path="$_path$1 " for _d in $( ( pkg_conf "$1"; printf '%s' "$PKG_BUILD_DEPENDS" ) ); do _pkg_visit "$_d" done _path="${_path% "$1" }" _seen="$_seen$1 " _order="$_order $1" } # pkg_begin -- read the recipe's metadata, check the cache, lay # out the scratch tree. Non-zero when the output is already current, so every # recipe starts `pkg_begin || exit 0`. pkg_begin() { PKG_ID=$1 pkg_conf "$PKG_ID" PKG_OUT=$PKG_ROOT PKG_STAMP=$(pkg_stamp "$PKG_ID") PKG_WORK="work/.pkg-$PKG_ID" if [ "$(cat "$PKG_OUT/.version" 2>/dev/null || true)" = "$PKG_STAMP" ]; then pkg_skip "$PKG_ID: $PKG_OUT already holds this build" return 1 fi pkg_say "$PKG_ID: building $PKG_NAME $PKG_VERSION-$PKG_RELEASE" rm -rf "$PKG_WORK" "$PKG_OUT" # src/ unpacked sources stage/ DESTDIR of the install # xw/ compiler wrappers sysroot/ build dependencies, unpacked # dep/ where apk extract drops them before they merge into sysroot/ mkdir -p "$PKG_WORK/src" "$PKG_WORK/stage" "$PKG_WORK/xw/bin" \ "$PKG_WORK/sysroot" "$PKG_WORK/dep" PKG_SYSROOT="$PWD/$PKG_WORK/sysroot" PKG_LOG="$PWD/$PKG_WORK" # pkg_build's knobs, reset so a recipe only spells what is unusual about # 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_PY_PIP_ARGS=''; PKG_CC_SHARED='' return 0 } # Seal the cache. Called last; anything that exits before it leaves no stamp, # so a build interrupted halfway is rebuilt rather than believed. pkg_end() { rm -rf "$PKG_WORK/src" "$PKG_WORK/stage" "$PKG_WORK/xw" \ "$PKG_WORK/sysroot" "$PKG_WORK/dep" echo "$PKG_STAMP" > "$PKG_OUT/.version" pkg_say "$PKG_ID: $PKG_OUT sealed" } # Unpack the toolchain, write the compiler wrappers, put them on PATH, and # prove they produce the ABI this printer's kernel will exec. -mnan=2008 and # -EL are baked into the compiler DRIVER, not CFLAGS: autotools link lines do # not all forward CFLAGS, and one object linked without them poisons the whole # binary's ABI flags. pkg_toolchain() { PKG_HOST=$PY_HOST PKG_TC=$PY_TOOLCHAIN_DIR PKG_CC_FLAGS='-EL -mnan=2008' if [ ! -x "$PKG_TC/bin/$PKG_HOST-gcc" ]; then [ -f "${MIPS_TOOLCHAIN_TGZ:-}" ] || pkg_die \ "$PKG_ID needs the toolchain and '${MIPS_TOOLCHAIN_TGZ:-}' is missing. Run ./bin/fetch-assets.sh." pkg_say "$PKG_ID: unpacking the toolchain" mkdir -p work/.mips-toolchain tar -xf "$MIPS_TOOLCHAIN_TGZ" -C work/.mips-toolchain [ -x "$PKG_TC/bin/$PKG_HOST-gcc" ] \ || pkg_die "the toolchain archive did not unpack $PKG_TC/bin/$PKG_HOST-gcc" fi PKG_XW="$PWD/$PKG_WORK/xw" _tc="$PWD/$PKG_TC" for _t in gcc g++ cpp; do printf '#!/bin/sh\nexec %s/bin/%s-%s %s "$@"\n' \ "$_tc" "$PKG_HOST" "$_t" "$PKG_CC_FLAGS" > "$PKG_XW/bin/$PKG_HOST-$_t" chmod +x "$PKG_XW/bin/$PKG_HOST-$_t" done for _t in ar as ld nm objcopy objdump ranlib readelf strip strings size; do ln -sf "$_tc/bin/$PKG_HOST-$_t" "$PKG_XW/bin/$PKG_HOST-$_t" done export PATH="$PKG_XW/bin:$PATH" # Exported as well as on PATH: an autoconf configure finds the cross # compiler as $host-gcc on PATH, while OpenSSL takes a target name and # reads $CC from the environment. export CC="$PKG_HOST-gcc" CXX="$PKG_HOST-g++" export AR="$PKG_HOST-ar" RANLIB="$PKG_HOST-ranlib" export STRIP="$PKG_HOST-strip" NM="$PKG_HOST-nm" export OBJCOPY="$PKG_HOST-objcopy" OBJDUMP="$PKG_HOST-objdump" export LD="$PKG_HOST-ld" PKG_STRIP="$_tc/bin/$PKG_HOST-strip" # Gated before anything is built on it: a wrapper that lost -mnan=2008 # builds a tree that passes every test here and is refused at exec(). echo 'int main(void){return 0;}' > "$PKG_WORK/src/.abi.c" "$PKG_HOST-gcc" "$PKG_WORK/src/.abi.c" -o "$PKG_WORK/src/.abi.out" \ || pkg_die "the $PKG_ID compiler wrapper cannot build a hello-world" _abi=$("$PKG_HOST-readelf" -h "$PKG_WORK/src/.abi.out" | awk '/Flags:/{print $2}' | tr -d ,) case "$_abi" in 0x70001405|0x70001407) ;; *) pkg_die "the $PKG_ID compiler wrapper produces e_flags=$_abi, want 0x70001405 or 0x70001407" ;; esac pkg_say "$PKG_ID: toolchain ready ($PKG_HOST, e_flags=$_abi)" } # An x86-64 CPython of exactly $PY_VERSION, exported as $HOSTPY. Nothing it # produces ships; it is a compiler for the build machine. # # THE VERSIONS MUST MATCH EXACTLY. Cross-compiling CPython needs a # build-python of the same version and configure hard-errors on a mismatch, so # the build image's python3 cannot stand in. The pkg/python-* recipes need it # for a second reason: it runs pip and setuptools under # _PYTHON_SYSCONFIGDATA_NAME (see pkg_pytarget), and an extension compiled # against 3.13 headers by a 3.14 setuptools crashes at import on the printer. # # One cache for every python recipe, at work/.py-host so pkg_end does not # delete it, stamped on $PY_VERSION plus the PEP 517 backend sdists. # --with-ensurepip=install, where the cross build has --without-ensurepip: # here pip is what builds every wheel. pkg_buildpython() { PKG_HOSTPY_ROOT="$PWD/work/.py-host" 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 mkndx 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)" done if [ "$(cat "$PKG_HOSTPY_ROOT/.version" 2>/dev/null || true)" = "$_hpstamp" ]; then pkg_skip "$PKG_ID: build-python $PY_VERSION is already at work/.py-host" return 0 fi [ -f "${PY_TGZ:-}" ] || pkg_die \ "$PKG_ID needs the CPython source and '${PY_TGZ:-}' is missing. Run ./bin/fetch-assets.sh." command -v gcc >/dev/null || pkg_die \ "$PKG_ID needs a host gcc to build the build-python. Run through 'make packages'." pkg_say "$PKG_ID: building the x86-64 build-python $PY_VERSION (once, then cached)" rm -rf "$PKG_HOSTPY_ROOT" work/.py-hostsrc mkdir -p work/.py-hostsrc tar -xf "$PY_TGZ" -C work/.py-hostsrc \ || pkg_die "$PKG_ID: could not untar $PY_TGZ" _hplog="$PKG_LOG/buildpython.log" ( set -e # The cross environment is removed rather than avoided by calling # order: a configure inheriting the printer's CC and sysroot builds an # x86-64 interpreter with mipsel flags. unset CC CXX AR RANLIB STRIP NM OBJCOPY OBJDUMP LD unset CFLAGS CXXFLAGS CPPFLAGS LDFLAGS LIBS CONFIG_SITE unset PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG_SYSROOT_DIR unset _PYTHON_SYSCONFIGDATA_NAME PYTHONPATH cd "work/.py-hostsrc/Python-$PY_VERSION" ./configure --prefix="$PKG_HOSTPY_ROOT" --with-ensurepip=install make -j"$(nproc 2>/dev/null || echo 4)" make install ) > "$_hplog" 2>&1 || pkg_die "$PKG_ID: the build-python failed to build -- see $_hplog" # zlib is asserted, not assumed: CPython records a missing library as an # absent module and carries on, so ensurepip fails inside a make install # that reports success and surfaces later as "No module named pip". "$HOSTPY" -c 'import zlib' 2>/dev/null || pkg_die \ "the build-python has no zlib module, so it cannot unpack a single wheel. Install zlib1g-dev in docker/Dockerfile.build and delete work/.py-host." "$HOSTPY" -m pip --version >> "$_hplog" 2>&1 || pkg_die \ "the build-python has no pip -- ensurepip failed inside 'make install'; its traceback is in $_hplog" # The backends go into the build-python, not a per-wheel isolated # environment: --no-build-isolation is what keeps # _PYTHON_SYSCONFIGDATA_NAME reaching setup.py. for _p in setuptools $PYPKG_HOST_LIST; do _sd=$(pypkg_tgz "$_p") [ -f "$_sd" ] || pkg_die \ "no sdist for the build backend $_p at '$_sd' -- run ./bin/fetch-assets.sh" ( set -e unset CC CXX AR RANLIB STRIP NM OBJCOPY OBJDUMP LD unset CFLAGS CXXFLAGS CPPFLAGS LDFLAGS LIBS CONFIG_SITE unset _PYTHON_SYSCONFIGDATA_NAME PYTHONPATH "$HOSTPY" -m pip install --quiet --no-index --no-cache-dir \ --no-build-isolation --no-deps "$_sd" ) >> "$_hplog" 2>&1 || pkg_die \ "$PKG_ID: could not install the build backend $_p -- see $_hplog" done rm -rf work/.py-hostsrc # Written last, so an interrupted build leaves a stale cache, not a lying # one. echo "$_hpstamp" > "$PKG_HOSTPY_ROOT/.version" pkg_say "$PKG_ID: build-python ready ($("$HOSTPY" -V 2>&1))" } # Fill the recipe's sysroot from the feed: everything in PKG_BUILD_DEPENDS is # unpacked out of its own package by `apk extract` and merged in, then the # cross-build variables point at the result. Building against the package # rather than the build tree is the point -- one that forgot to ship a header # fails the next recipe's configure. The sysroot mirrors the printer, deps # under $MODDIR inside it, so PKG_CONFIG_SYSROOT_DIR is set to the sysroot # rather than emptied: the .pc files say prefix=/usr/data/anvil. pkg_deps() { [ -n "$PKG_BUILD_DEPENDS" ] || return 0 for _d in $PKG_BUILD_DEPENDS; do # Both halves, when the dependency has two: which of and # -dev exists is the dependency's business, so only neither is # an error. _found=0 for _v in '' dev; do _pf=$(pkg_pkgfile "$_d" "$_v") [ -f "$_pf" ] || continue _found=1 _un="$PKG_WORK/dep/$_d${_v:+-$_v}" rm -rf "$_un"; mkdir -p "$_un" _log="$PKG_LOG/$_d${_v:+-$_v}-unpack.log" # apk's own inverse of mkpkg. A v3 package is ADB, not a tarball -- # `tar` cannot open one, which is why this is the host apk and not # two lines of shell. It writes the tree at its own absolute paths, # so what lands is already $MODDIR. "$APK_BIN" extract --allow-untrusted --destination "$_un" "$_pf" \ > "$_log" 2>&1 \ || pkg_die "$PKG_ID: could not unpack $_pf -- see $_log" _payload="$_un$MODDIR" [ -d "$_payload" ] || pkg_die \ "$PKG_ID: $_d unpacked nothing under $MODDIR -- is it built for this prefix?" mkdir -p "$PKG_SYSROOT$MODDIR" cp -a "$_payload/." "$PKG_SYSROOT$MODDIR/" pkg_say "$PKG_ID: sysroot += $_d${_v:+-$_v}" done [ "$_found" = 1 ] || pkg_die \ "$PKG_ID builds against '$_d' and neither $(pkg_pkgfile "$_d") nor $(pkg_pkgfile "$_d" dev) exists -- build it first (./bin/build-packages.sh $_d)" done _inc="$PKG_SYSROOT$MODDIR/include" _lib="$PKG_SYSROOT$MODDIR/lib" export CPPFLAGS="-I$_inc ${CPPFLAGS:-}" export LDFLAGS="-L$_lib ${LDFLAGS:-}" export PKG_CONFIG_PATH="$_lib/pkgconfig" export PKG_CONFIG_LIBDIR="$_lib/pkgconfig" export PKG_CONFIG_SYSROOT_DIR="$PKG_SYSROOT" } # SOURCE VERBS. A recipe names where its inputs come from exactly once, with # one of the verbs below; qa/static/test_packages.py counts these calls. # # pkg_unpack -- extract this recipe's one pinned source archive into # $PKG_WORK/src. Zip as well as tar, dispatched on the name, because Mainsail # publishes a .zip. pkg_unpack() { [ -f "${1:-}" ] || pkg_die "no source at '${1:-}' -- run ./bin/fetch-assets.sh" case "$1" in *.zip) command -v unzip >/dev/null 2>&1 \ || pkg_die "$PKG_ID needs unzip to unpack $(basename "$1")" unzip -q -o "$1" -d "$PKG_WORK/src" \ || pkg_die "$PKG_ID: could not unzip $1" ;; *) tar -xf "$1" -C "$PKG_WORK/src" \ || pkg_die "$PKG_ID: could not untar $1" ;; esac } # pkg_checkout -- this recipe's source is a VENDORED GIT CHECKOUT # pinned by commit, copied to $PKG_WORK/src/ so the build tree is # scratch and vendor/ stays exactly at the sha bin/fetch-assets.sh verified. # # For upstreams that publish no release tarball, where a sha256 pin is not # available and a tag archive is not stable enough to be one. The integrity # guarantee is the commit sha, checked at fetch time; copying rather than # building in place is what keeps that true across a build. pkg_checkout() { [ -d "${1:-}/.git" ] || pkg_die \ "no checkout at '${1:-}' -- run ./bin/fetch-assets.sh" [ -n "${2:-}" ] || pkg_die "$PKG_ID: pkg_checkout needs a destination name" rm -rf "${PKG_WORK:?}/src/$2" mkdir -p "$PKG_WORK/src" # No .git: it is 3MB of history the build never reads, and leaving it out # means nothing in the build tree can quietly become a second source. cp -a "$1" "$PKG_WORK/src/$2" \ || pkg_die "$PKG_ID: could not copy $1 into the build tree" rm -rf "$PKG_WORK/src/$2/.git" pkg_say "$PKG_ID: source is the pinned checkout at $1" } # pkg_intree -- this recipe's sources are the checked-out repository, not a # download; anvil-core alone. Unpacks nothing, and exists so the source-verb # count needs no exemption. Freshness of those inputs is PKG_STAMP_EXTRA's job. pkg_intree() { PKG_SRC="$ROOT" pkg_say "$PKG_ID: source is this checkout, at $ROOT" } # pkg_prebuilt -- this recipe's source is a finished binary from # outside the build, a path in config.env. pkgs/busybox alone, and it should # stay that way: a recipe reading a typed path cannot promise a rebuild from a # sha256. A source verb rather than a helper so the same count covers it. pkg_prebuilt() { [ -f "${1:-}" ] || pkg_die \ "$PKG_ID: no binary at '${1:-}' -- check the path in config.env" PKG_SRC=$1 pkg_say "$PKG_ID: source is a prebuilt binary, at $1" } # pkg_stage -- put a file or tree where # pkg_ship expects it, $PKG_WORK/stage$MODDIR/. `make install` for things # that have no make (Mainsail's static files, Moonraker's python tree, # anvil-core's scripts), landing them where an autotools install would so # pkg_ship needs no special case. cp -a, because these trees contain symlinks # and modes that are part of what ships. pkg_stage() { [ -e "${1:-}" ] || pkg_die "$PKG_ID: nothing to stage at '${1:-}'" [ -n "${2:-}" ] || pkg_die "$PKG_ID: pkg_stage needs a destination" _dst="$PKG_WORK/stage$MODDIR/$2" mkdir -p "$(dirname "$_dst")" cp -a "$1" "$_dst" || pkg_die "$PKG_ID: could not stage $1 -> $2" } # pkg_build [configure args...] -- configure, make, install # into the staging tree; the only way a recipe compiles anything. Projects # differ in the settings of those three steps, not in the steps: # PKG_CONFIGURE the configure program. default ./configure # 'none' skips the step entirely (bzip2). # PKG_CONFIGURE_AUTO 1 = prepend --host and --prefix, which an autoconf # configure wants and zlib and OpenSSL refuse. # PKG_MAKE_TARGET what to build. default: everything # PKG_INSTALL_TARGET how to install it. default install # 'none' = the recipe places the files itself. # PKG_MAKE_ARGS extra variables for make (LDLIBS=-lpthread for s6). # PKG_CC_SHARED the whole link line for a project with no build system # at all, appended to `$CC -shared -fPIC`; when set, # configure and make are skipped. # # PKG_CC_SHARED exists for Klipper: klippy/chelper has no Makefile and # normally compiles c_helper.so at first run with a cc this printer lacks. A # knob rather than a gcc line in the recipe because $CC is the gated wrapper # where -EL -mnan=2008 live. # # RETURNS NON-ZERO, NEVER DIES, for the recoverable case: OpenSSL's mips # target hardcodes -mips2 against this toolchain's -mfp64, which gcc refuses, # and `if ! pkg_build` can express the retry that pkg_die could not. pkg_build() { _dir=$1; shift _tag=$(basename "$_dir") _dest="$PWD/$PKG_WORK/stage" ( set -e cd "$PKG_WORK/src/$_dir" # No build system: link the sources named by the recipe and stop. # -shared -fPIC and $CC are pkg_build's; the rest is Klipper's own # COMPILE_ARGS, spelled in the recipe against chelper/__init__.py. if [ -n "${PKG_CC_SHARED:-}" ]; then # shellcheck disable=SC2086 $CC -shared -fPIC $PKG_CC_SHARED > "$PKG_LOG/$_tag-cc.log" 2>&1 exit 0 fi # Trailing arguments go to whichever step consumes them: configure # when there is one, make when there is not (bzip2's CC/AR/RANLIB). if [ "${PKG_CONFIGURE:-./configure}" != none ]; then if [ "${PKG_CONFIGURE_AUTO:-1}" = 1 ]; then set -- --host="$PKG_HOST" --prefix="$MODDIR" "$@" fi "${PKG_CONFIGURE:-./configure}" "$@" \ > "$PKG_LOG/$_tag-configure.log" 2>&1 set -- fi # shellcheck disable=SC2086 make -j"$(nproc 2>/dev/null || echo 4)" ${PKG_MAKE_TARGET:-} ${PKG_MAKE_ARGS:-} "$@" \ > "$PKG_LOG/$_tag-make.log" 2>&1 if [ "${PKG_INSTALL_TARGET:-install}" != none ]; then # shellcheck disable=SC2086 make "${PKG_INSTALL_TARGET:-install}" DESTDIR="$_dest" ${PKG_MAKE_ARGS:-} \ >> "$PKG_LOG/$_tag-make.log" 2>&1 fi ) || { printf ' !! %s: building %s failed -- see %s\n' \ "$PKG_ID" "$_tag" "$PKG_WORK/$_tag-{configure,make,cc}.log" >&2 return 1 } pkg_say "$PKG_ID: built $_tag" } # pkg_pytarget -- point the build-python's sysconfig at the TARGET interpreter # in the sysroot, so pip and setuptools answer for mipsel while running on # x86-64. After pkg_toolchain, pkg_deps and pkg_buildpython. # # anvil-python ships _sysconfigdata__linux_mipsel-linux-gnu.py, recording the # cross CC, LDSHARED, EXT_SUFFIX and INCLUDEPY; _PYTHON_SYSCONFIGDATA_NAME # pointed at it is what makes build_ext build for mipsel. It is rewritten # first because every path in it is /usr/data/anvil/..., which exists on the # printer and nowhere here -- blanket over every string value, because that # list is what nobody gets right by hand: INCLUDEPY fails loudly, LIBDIR and # LIBPL quietly. Read only by the build, never shipped. Pure-python recipes # call it too: sysconfig is what a wheel build asks for its tags either way. pkg_pytarget() { [ -n "${HOSTPY:-}" ] || pkg_die \ "$PKG_ID: pkg_pytarget needs the build-python -- call pkg_buildpython first" [ -n "${PKG_HOST:-}" ] || pkg_die \ "$PKG_ID: pkg_pytarget needs the cross toolchain -- call pkg_toolchain first" PKG_PYROOT="$PKG_SYSROOT$MODDIR" PKG_PYINC="$PKG_PYROOT/include/python$PY_MM" _sc="_sysconfigdata__linux_mipsel-linux-gnu" _scsrc="$PKG_PYROOT/lib/python$PY_MM/$_sc.py" [ -f "$_scsrc" ] || pkg_die \ "$PKG_ID: no $_sc in the sysroot -- is 'python' in PKG_BUILD_DEPENDS?" [ -d "$PKG_PYINC" ] || pkg_die \ "$PKG_ID: no target headers at $PKG_PYINC -- anvil-python-dev is what carries them" _xsys="$PWD/$PKG_WORK/xsysconfig" rm -rf "$_xsys"; mkdir -p "$_xsys" "$HOSTPY" - "$_scsrc" "$_xsys/$_sc.py" "$MODDIR" "$PKG_PYROOT" <<'PYEOF' \ || pkg_die "$PKG_ID: could not rewrite $_sc for the sysroot" import sys src, dst, prefix, sysroot = sys.argv[1:5] ns = {} exec(compile(open(src).read(), src, "exec"), ns) out = {k: (v.replace(prefix, sysroot) if isinstance(v, str) else v) for k, v in ns["build_time_vars"].items()} with open(dst, "w") as fh: fh.write("# Generated by pkgs/lib.sh: %s with the build-time prefix rewritten\n" "# to this recipe's sysroot. Build-time only; never ships.\n" "build_time_vars = %r\n" % (src, out)) PYEOF export _PYTHON_SYSCONFIGDATA_NAME="$_sc" # Only the rewritten module, deliberately not the target's stdlib: that # puts the host interpreter importing the target's os.py one path-ordering # mistake away. export PYTHONPATH="$_xsys" export PYTHONDONTWRITEBYTECODE=1 export PIP_DISABLE_PIP_VERSION_CHECK=1 # setuptools does not forward CFLAGS to its link lines, which is why the # ABI flags live in the gcc wrapper. What is passed here only helps a # compile find headers. export LDSHARED="$PKG_HOST-gcc -shared -L$PKG_PYROOT/lib" export CFLAGS="-O2 -fPIC -D_FILE_OFFSET_BITS=64 -I$PKG_PYROOT/include -I$PKG_PYINC" export CXXFLAGS="$CFLAGS" # Where every wheel is unpacked: the recipe's own staging tree, at the # path site-packages has on the printer. pkg_ship reads from here. PKG_PYSP="$PWD/$PKG_WORK/stage$MODDIR/lib/python$PY_MM/site-packages" mkdir -p "$PKG_PYSP" # Gated before anything is built on it: if _PYTHON_SYSCONFIGDATA_NAME has # not taken, the result is a tree of host objects that builds perfectly # and imports nowhere. "$HOSTPY" - "$PKG_PYINC" ".cpython-$(printf '%s' "$PY_MM" | tr -d .)-mipsel-linux-gnu.so" <<'PYEOF' \ || pkg_die "$PKG_ID: the sysconfig cross trick did not take" import os, sys, sysconfig inc_want, suffix_want = sys.argv[1:3] suffix = sysconfig.get_config_var("EXT_SUFFIX") inc = sysconfig.get_config_var("INCLUDEPY") if suffix != suffix_want: raise SystemExit(" !! sysconfig is answering for the HOST (EXT_SUFFIX=%s," " wanted %s) -- _PYTHON_SYSCONFIGDATA_NAME did not take" % (suffix, suffix_want)) if inc != inc_want: raise SystemExit(" !! INCLUDEPY is %s, expected %s" % (inc, inc_want)) if not os.path.isdir(inc): raise SystemExit(" !! INCLUDEPY %s does not exist" % inc) 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" <&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 -- where this package's sdist unpacked to: the file # name with .tar.gz removed, under $PKG_WORK/src. pkg_pysrc() { _f=$(pypkg_var "$1" FILE) printf '%s/src/%s' "$PKG_WORK" "${_f%.tar.gz}" } # pkg_pywheel [VAR=VAL ...] -- build one wheel for the target # from the source pkg_unpack extracted and unpack it into the staging # site-packages. Trailing VAR=VAL pairs are environment for that build alone. # 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 # x86-64 wheels the day someone adds an index URL. Unzipped rather than # pip-installed, because `pip install` installs FOR the interpreter running # it, which is x86-64. .dist-info goes, since nothing on the printer resolves # a dependency or runs an entry point; the .data directories go because their # console-script shebangs name the build-python under work/.py-host. pkg_pywheel() { _pw=$1; shift _wdir=$(pkg_pysrc "$_pw") [ -d "$_wdir" ] || pkg_die \ "$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" if [ -n "${PKG_PY_SETUP_ARGS:-}" ]; then ( set -e cd "$_wdir" # Deliberately unquoted: PKG_PY_SETUP_ARGS is a list of options. # shellcheck disable=SC2086 env "$@" "$HOSTPY" setup.py build_ext $PKG_PY_SETUP_ARGS bdist_wheel ) > "$_wlog" 2>&1 || { printf ' !! %s: %s failed to build -- see %s\n' "$PKG_ID" "$_pw" "$_wlog" >&2 tail -25 "$_wlog" | sed 's/^/ /' >&2 return 1; } 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 \ ${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 return 1; } fi _nw=0 for _w in "$_wheels"/*.whl; do [ -f "$_w" ] || continue "$HOSTPY" -m zipfile -e "$_w" "$PKG_PYSP" \ || pkg_die "$PKG_ID: could not unpack $(basename "$_w") into site-packages" pkg_say "$PKG_ID: $(basename "$_w")" _nw=$((_nw + 1)) done [ "$_nw" -gt 0 ] || pkg_die "$PKG_ID: no wheel came out of $_pw -- see $_wlog" find "$PKG_PYSP" -name '__pycache__' -prune -exec rm -rf {} + 2>/dev/null || true find "$PKG_PYSP" -maxdepth 1 -name '*.dist-info' -prune -exec rm -rf {} + 2>/dev/null || true rm -rf "${PKG_PYSP:?}"/bin "${PKG_PYSP:?}"/*.data } # pkg_pynative [count] -- assert this package shipped at least `count` # (default 1) compiled extension modules. After pkg_ship, so it reads $PKG_OUT. # # WHAT IT CATCHES has no other symptom: a package with a native extension AND # a pure-python fallback, where the extension fails to cross-compile and # setup.py quietly ships the fallback. Nothing errors, and the printer gets # the slow path -- or, for cffi and lmdb, nothing that works at all. A count # and not a list of module names, because the names are upstream's and change # on a version bump while "this recipe compiles something" must not. pkg_pynative() { _want=${1:-1} _got=$(find "$PKG_OUT/lib/python$PY_MM/site-packages" -name '*.so' 2>/dev/null | wc -l) [ "$_got" -ge "$_want" ] || pkg_die \ "$PKG_ID: $_got extension module(s) in site-packages, expected at least $_want. The native build fell back to a pure-python wheel -- see $PKG_LOG/wheel-*.log for the compile that did not happen" pkg_say "$PKG_ID: $_got extension module(s)" } # pkg_ship [...] -- copy what the package contains out of the # staged install into $PKG_OUT, the tree bin/build-packages.sh turns into the # package. Globs are relative to the prefix inside the DESTDIR, and what a # package contains depends on what it is for, so each recipe says which files # rather than taking a default. # # cp -a and never plain cp: a shared library is three names, two of them # symlinks, and the first is the one libnacl's dlopen fallback constructs. # # PKG_STRIP_ARGS defaults to --strip-unneeded, keeping the dynamic symbols # anything will dlsym; set it empty for a plain strip-all. Static archives are # never stripped -- strip on a .a removes symbols the linker still needs. pkg_ship() { for _g in "$@"; do _dstdir="$PKG_OUT/$(dirname "$_g")" mkdir -p "$_dstdir" _n=0 # Deliberately unquoted: $_g is a glob and this is where it expands. # Not `set --`, which would destroy this function's own positional # parameters as a side effect. for _m in $PKG_WORK/stage$MODDIR/$_g; do [ -e "$_m" ] || continue cp -a "$_m" "$_dstdir/" _n=$((_n + 1)) done [ "$_n" -gt 0 ] || pkg_die "$PKG_ID: nothing matched '$_g' in the staged install" done # .la files name absolute build-machine paths and are useless to anything # that links against a package rather than against a build tree. find "$PKG_OUT" -name '*.la' -delete # NOR PYTHON BYTECODE. anvil-core stages a directory of .py helpers # wholesale, and any test that imports one leaves a __pycache__ beside it # -- gitignored, so invisible in a diff, and copied by cp -a. That made # package contents depend on whether pytest had run on the build machine. # The .pyc are useless anyway: built by the image's python 3.11, where the # printer runs 3.13. Swept here because the trap is set for any recipe # that stages a directory of .py files. find "$PKG_OUT" -name '__pycache__' -type d -prune -exec rm -rf {} + \ 2>/dev/null || true find "$PKG_OUT" -name '*.pyc' -delete # STATIC ARCHIVES ARE NOT REPRODUCIBLE UNTIL MADE SO. An ar archive stores # a per-member mtime and the uid/gid of whoever compiled, and the symbol # index carries a timestamp of its own -- and SOURCE_DATE_EPOCH reaches # the archive mkpkg writes, not the inside of a .a it merely contains. # objcopy -D zeroes uid, gid and mtime in the MEMBER headers. # ranlib -D rewrites the symbol INDEX with a zeroed header. # The cross binutils is 2.27 and its ranlib writes the current timestamp # despite -D, so the index is rewritten with the build machine's ranlib; # ar is a container format and BFD reads mipsel members fine. Skipped when # a recipe never called pkg_toolchain, which qa/replica/test_abi.py # backstops. if [ -n "${PKG_HOST:-}" ]; then find "$PKG_OUT" -name '*.a' -print | while IFS= read -r _a; do "$PKG_HOST-objcopy" --enable-deterministic-archives "$_a" \ || pkg_die "$PKG_ID: could not normalise the member headers of $_a" ranlib -D "$_a" \ || pkg_die "$PKG_ID: could not write a deterministic index for $_a" done find "$PKG_OUT" -type f -print | while IFS= read -r _f; do case "$_f" in *.a|*.h|*.pc|*.la) continue ;; esac # readelf is the ELF test: it exits non-zero on anything else. "$PKG_HOST-readelf" -h "$_f" >/dev/null 2>&1 || continue # The cross strip; the host's would refuse a MIPS object. # shellcheck disable=SC2086 "$PKG_STRIP" ${PKG_STRIP_ARGS---strip-unneeded} "$_f" 2>/dev/null || true done fi }