. "$BST_LIB" # BST-TIMEOUT: 600 # # MEASURED: `==BST-SUITE python3-hatchling elapsed=38s bound=600s cases=39==` # (batch-1826, 2026-08-31), which lands inside the 20-40 s the estimate below # predicted from an ~18 s interpreter start (99-pyxdg.test's header, batch-1649) # plus one python3 start for the whole suite and shell builtins over a dpkg file # list for the rest. 600 stays: it is a backstop against a boot where the # interpreter start is slow, not a prediction of the runtime, and the same order # of headroom 99-pyxdg.test's 900 gives its two starts. # # python3-hatchling 1.21.1 (poky meta/recipes-devtools/python) -- the extensible # PEP 517 build backend used by Hatch, on the wasm32-hwjs glibc image. # # ============================================================================ # THE WHOLE OF THE PORT IS THAT THE PACKAGE IS PURE PYTHON, AND THIS SUITE # ASSERTS THAT NEGATIVE DIRECTLY. # ============================================================================ # # This image's python3 is ONE STATIC BINARY: on the wasm32 arm configure builds # every extension module into the interpreter, lib-dynload ships empty, and # there is no dynamic loader to import a .so with (BRINTOS-1476, and # 99-python3.test's lib-dynload-empty case). A third-party module carrying a # compiled extension has nowhere to load from. hatchling carries none -- it is # `.py` only -- which is what makes the port the packaging path plus the import # and nothing else. no-shared-object asserts that rather than assuming it, so a # version bump that grows a compiled speedup fails HERE, loudly, instead of at # `import` on a later image. # # Nothing in the recipe is architecture-dependent either: python_hatchling # builds the wheel with NATIVE tooling (python_pep517's do_compile is # `nativepython3 -m build`) and do_install unpacks it with the native installer, # so no target compiler runs and the recipe needed no patch, no autoconf answer # and no override. # # ============================================================================ # THE BACKEND'S FIFTH RUNTIME DEPENDENCY WAS MISSING, AND THE RECIPE NOW # DECLARES IT. MEASURED ON TARGET, batch-1826, 2026-08-31. # ============================================================================ # # hatchling's pyproject.toml names five runtime dependencies -- editables, # packaging, pathspec, pluggy and trove-classifiers. poky's recipe declares NONE # of them as RDEPENDS: it names only the `-native` halves it builds WITH, so the # target package shipped able to import nothing it needs. What was importable # was whatever the image installed by other means, and that was four of five: # pathspec (BRINTOS-1834), trove-classifiers (BRINTOS-1835) and editables # (BRINTOS-1833) from this batch, and packaging, already on the image at 23.2. # pluggy was pulled in by nothing. # # The first on-target run measured exactly that and no more: 31 of 39 green, # and all eight reds naming pluggy alone -- runtime-deps-present reporting # `pluggy`, both build paths dying at `ModuleNotFoundError: No module named # 'pluggy'` (hatchling/plugin/__init__.py imports it at module scope and every # builder reaches that module), and the five wheel-content cases falling out # behind them because no wheel was produced to read. # # The fix is this layer's python3-hatchling_%.bbappend: RDEPENDS on # python3-pluggy, which is the declaration poky omits. pluggy and its own chain # -- importlib-metadata, more-itertools, zipp, pathlib2 -- were built at recipe # scope before the RDEPENDS was added, all five clean, so the shared image takes # no risk from packages nobody had built. The rebuilt package carries # `Depends: python3-core, python3-pluggy`. # # A NOTE ON WHAT THIS BLOCK USED TO SAY, because it matters to anyone reading # the cases below. Until this run it carried a PREDICTION, written in the recipe # phase of a batched Epic pass, which forbids building the image and so could # not run this suite. That prediction got the count right -- eight reds, exactly # these eight -- and the cause half wrong: it named packaging (BRINTOS-1746) and # pluggy as both missing, and expected the version lookup in metadata/core.py's # _get_version to fail as well. packaging was on the image the whole time. The # eight cases below assert green values and are expected to BE green now; they # are not skips, because hatchling being present and unable to build a wheel is # `not ok` by this harness's own rule. # # ============================================================================ # TWO BUILD PATHS, AND THE SECOND ONE IS NOT REDUNDANT. # ============================================================================ # # build-wheel-pep517 drives hatchling.build.build_wheel, the entry point a PEP # 517 frontend calls. It takes its project root from os.getcwd(), so it needs a # chdir -- and chdir on this arm is not reliable: the kernel's restart park # sentinel reaches userspace through glibc's errno return path, and a chdir has # ended a suite where it stood (brintos/linux#73; 99-pyxdg-ptest.test measured # it failing from a tmpfs copy as well as from the rootfs). build-wheel-api # drives WheelBuilder(root) with an explicit path and NO chdir, which is exactly # what build_wheel does after its getcwd(). If the chdir is what fails, the # backend is still measured and the two cases say which of the two it was -- # where a single cwd-based case would report a platform defect as a broken port. # # EVERY FILE AND dpkg ASSERTION BELOW WAS READ OFF THE BUILT PACKAGE, not # predicted from the recipe. python3-hatchling_1.21.1-r0_wasm32.deb, built at # recipe scope on 2026-08-31 (1858 of 1858 tasks, zero ERROR: lines): the module # tree lands under /usr/lib/python3.12/site-packages/hatchling, the console # script at /usr/bin/hatchling, the dist-info at hatchling-1.21.1.dist-info, # nothing outside those two, no `.so` anywhere in the package, and the control # file's `Depends:` is `python3-core` and nothing else -- which is the recipe's # under-declared RDEPENDS arriving in the artifact. What is NOT measured is # anything above requiring the interpreter; those cases are still the control's # answers rather than this image's. # # ONE INTERPRETER START, for the whole file. 99-pyxdg had to be split across # four files because eleven starts do not fit in a boot; this suite is built to # need one, and every filesystem and dpkg assertion below is deliberately placed # BEFORE it so that they still hold on a boot where python3 cannot run at all. require python3 python3-hatchling/present # THE CANARY, which asserts nothing about hatchling. # # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and not a defect in the host tool # (BRINTOS-1149, filed as linux-in-the-browser#177). The harness computes its # pass count as total-minus-failures, so the dropped line is counted as a pass; # a nothing-case in that position costs the eaten line and nothing else. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok python3-hatchling/suite-canary S=python3-hatchling SITE=/usr/lib/python3.12/site-packages PKG=$SITE/hatchling DINFO=$SITE/hatchling-1.21.1.dist-info LIST=/var/lib/dpkg/info/python3-hatchling.list tmp=${TMPDIR:-/tmp} W=$tmp/bst-python3-hatchling.$$ # ------------------------------------------- 1. what the port SHIPS: files --- # Nothing here starts the interpreter, so all of it holds on a boot where # python3 cannot run. The module set is asserted rather than one entry point: a # packaging change that dropped half the tree would leave `import hatchling` # green and every build red with no line saying why. check $S/site-dir test -d $SITE check $S/package-dir test -d $PKG check $S/mod-init test -f $PKG/__init__.py check $S/mod-about test -f $PKG/__about__.py check $S/mod-build test -f $PKG/build.py check $S/mod-ouroboros test -f $PKG/ouroboros.py check $S/dir-builders test -d $PKG/builders check $S/mod-wheelbuild test -f $PKG/builders/wheel.py check $S/mod-sdistbuild test -f $PKG/builders/sdist.py check $S/dir-metadata test -d $PKG/metadata check $S/mod-metacore test -f $PKG/metadata/core.py check $S/dir-plugin test -d $PKG/plugin check $S/mod-pluginmgr test -f $PKG/plugin/manager.py check $S/dir-version test -d $PKG/version check $S/dir-licenses test -d $PKG/licenses check $S/dir-cli test -d $PKG/cli # The console script the recipe's [project.scripts] declares. It is the one # thing in this package that is not a module, and the installer writes its # interpreter line, so an absent one means the PEP 517 install half did not run. check $S/console-script test -f /usr/bin/hatchling # ---------------------------------------- 2. the packaging, from dpkg's own --- # The image installs this package with dpkg (package_deb), so the package # database is where "it was installed" is a fact rather than an inference from # the files being present -- a hand-dropped tree would satisfy section 1 alone. # # THE STANZA IS PULLED WITH sed AND READ FROM TMPFS. A `while read ... < # /var/lib/dpkg/status` in the top-level shell is a stdin redirect from the # virtiofs root: dup2(2) returns 513, ash prints `Unknown error 513` and EXITS, # and the runner reports every case already emitted as a clean pass # (brintos/linux#73; the repo's own check-suite-redirects.py refuses it at # do_install). # # BRINTOS-WORKAROUND: brintos/linux#73 remove -- stage the dpkg stanza and file # list on tmpfs rather than redirecting the top-level shell's stdin from the # rootfs. sed -n '/^Package: python3-hatchling$/,/^$/p' /var/lib/dpkg/status > "$W.st" 2>/dev/null _known=0; _installed=0; _version="" while IFS= read -r l; do case "$l" in "Package: python3-hatchling") _known=1 ;; "Status: install ok installed") _installed=1 ;; "Version: "*) _version=${l#Version: } ;; esac done < "$W.st" if [ "$_known" -eq 1 ]; then bst_ok $S/pkg-known else bst_notok $S/pkg-known "dpkg does not know the package python3-hatchling"; fi if [ "$_installed" -eq 1 ]; then bst_ok $S/pkg-installed else bst_notok $S/pkg-installed "python3-hatchling is not 'install ok installed' in the dpkg status"; fi # The recipe's PV reaches the package metadata. dpkg's Version carries the # Yocto -rN revision suffix, which is not this port's to pin, so the assertion # is on the upstream part alone. check_eq $S/pkg-version "1.21.1" "${_version%%-*}" # THE FILE LIST IS THE ONLY PLACE THE PACKAGE'S OWN CONTENTS ARE VISIBLE ON # TARGET, and it is what the two assertions below need: everything this package # ships lands under site-packages (or is the console script), and NONE of it is # a shared object. cat "$LIST" > "$W.ls" 2>/dev/null _n=0; _stray=""; _so=""; _dinfo=0 while IFS= read -r l; do case "$l" in ""|/.|/usr|/usr/*/) continue ;; esac [ -d "$l" ] && continue _n=$((_n + 1)) case "$l" in "$SITE"/*) ;; /usr/bin/hatchling) ;; *) _stray="$_stray $l" ;; esac case "$l" in *.so|*.so.*) _so="$_so $l" ;; esac case "$l" in "$SITE"/hatchling-*.dist-info/METADATA) _dinfo=1 ;; esac done < "$W.ls" if [ "$_n" -gt 0 ]; then bst_ok $S/pkg-file-list else bst_notok $S/pkg-file-list "python3-hatchling's dpkg file list is empty or absent -- $LIST"; fi # ============================================================================ # THE CASE THIS PORT EXISTS TO ASSERT. # ============================================================================ # A .so in this package is not a packaging nit on this image, it is a module # that cannot be imported: there is no dynamic loader here to load it with. It # would go unnoticed until some later import failed, which is why the negative # is asserted at the file layer rather than left to the build. if [ -z "$_so" ]; then bst_ok $S/no-shared-object else bst_notok $S/no-shared-object "python3-hatchling ships shared object(s), which this image's static interpreter cannot load:$(_bst_1line "$_so")"; fi if [ -z "$_stray" ]; then bst_ok $S/files-under-site-packages else bst_notok $S/files-under-site-packages "file(s) outside $SITE and /usr/bin/hatchling:$(_bst_1line "$_stray")"; fi if [ "$_dinfo" -eq 1 ]; then bst_ok $S/dist-info-shipped else bst_notok $S/dist-info-shipped "no hatchling-*.dist-info/METADATA in the package -- the PEP 517 install half did not run"; fi # -------------------------- 3. THE ONE INTERPRETER START: the backend itself --- # The payload measures everything in one process and prints one tagged line per # value; this file names and compares them, so a single wrong answer is one red # case rather than one red suite. Its scratch root is staged HERE and passed as # argv[1]: setenv(3) returns EINVAL on this arm for every name # (brintos/glibc#11), so a payload that set its own TMPDIR would die on the # assignment and report every measurement empty. HLROOT=$(mktemp -d /tmp/bst-hatchling.XXXXXX 2>/dev/null) [ -n "$HLROOT" ] || { HLROOT=/tmp/bst-hatchling.$$; mkdir -p "$HLROOT"; } out=$(python3 "$BST_DATA/python3-hatchling-selftest.py" "$HLROOT" 2>&1); rc=$? # The payload reaches ALL-HATCHLING-OK even on an image where hatchling cannot # import at all -- every measurement in it is wrapped -- so this case is about # the payload RUNNING, not about hatchling working. A red here means every # below is wreckage rather than a measurement. case "$out" in *ALL-HATCHLING-OK*) all=1 ;; *) all=0 ;; esac if [ "$rc" -eq 0 ] && [ "$all" -eq 1 ]; then bst_ok $S/payload-ran elif bst_execfail "$out"; then bst_unrun $S/payload-ran "$out" else bst_notok $S/payload-ran "rc=$rc $(_bst_1line "$out")"; fi # Read the tagged lines into shell variables with no subprocess at all. One awk # per value would cost an exec each, and an exec on this arm is a fresh wasm # module instantiation at 2-3 s. The eval's right-hand side is `${line#* }`, # expanded at eval time from $line, so no value's content can reach the shell as # syntax however it is quoted. _oifs=$IFS IFS=' ' for line in $out; do case "$line" in R[0-9]*_*\ *) eval "HL_${line%% *}=\${line#* }" ;; R_fixture\ *) HL_R_fixture=${line#* } ;; esac done IFS=$_oifs # The import, from inside the payload's own process. Separate from section 1's # file cases because a failure here and a failure there mean different things: # that is the port's contents, this is whether this image's interpreter can load # them. check_eq $S/import "ok" "$HL_R0_import" check_eq $S/version "1.21.1" "$HL_R1_version" check_eq $S/site-dir-resolved "$SITE" "$HL_R2_sitedir" # WHICH OF THE FIVE DECLARED RUNTIME DEPENDENCIES ARE MISSING. This case is the # cause the build reds below cite, measured rather than assumed, and it is the # one that flips first when either port lands. Sorted as the payload emits them. # # Measured `pluggy` here on the first on-target run; green once the layer's # bbappend declares the RDEPENDS poky omits -- see this file's header. check_eq $S/runtime-deps-present "none" "$HL_R3_deps_missing" check_eq $S/fixture-staged "ok" "$HL_R_fixture" # chdir is a platform question, not a hatchling one, and it is asked separately # so that a chdir defect cannot be read as a broken backend (brintos/linux#73). check_eq $S/chdir "ok" "$HL_R4_chdir" # ---- THE BACKEND DRIVEN END TO END, by both paths. check_eq $S/build-wheel-pep517 "brintos_fixture-0.4.2-py3-none-any.whl" "$HL_R5_pep517_wheel" check_eq $S/build-wheel-api "brintos_fixture-0.4.2-py3-none-any.whl" "$HL_R6_api_wheel" # Which build the content assertions below were read from. Named so a green # wheel-* block cannot hide the fact that only one of the two paths ran. check_eq $S/wheel-read-from "api" "$HL_R7_read_from" # The wheel is a VALID ZIP -- zipfile.testzip() over every member, not merely a # file that exists with the right name. check_eq $S/wheel-zip "ok" "$HL_R8_zip" # ...and it contains exactly the fixture's module plus the three dist-info # members hatchling writes. The SET is asserted, so a backend that produced a # well-formed but empty wheel is a red rather than a pass. check_eq $S/wheel-names \ "brintos_fixture-0.4.2.dist-info/METADATA,brintos_fixture-0.4.2.dist-info/RECORD,brintos_fixture-0.4.2.dist-info/WHEEL,fixmod/__init__.py" \ "$HL_R9_names" # The fixture's NAME reaches METADATA -- normalised from the pyproject table. check_eq $S/wheel-metadata-name "brintos-fixture" "$HL_R10_meta_name" # THE FIELD THE BACKEND IS RESPONSIBLE FOR. The fixture declares `dynamic = # ["version"]` and no version at all; 0.4.2 exists only as __version__ in the # fixture module, and hatchling's own version source is what has to read it and # put it in the metadata. A static version here would assert the fixture's own # text instead of the backend's work -- and would still need `packaging`, which # is measured, not assumed (metadata/core.py's _get_version imports it on both # paths). check_eq $S/wheel-metadata-version "0.4.2" "$HL_R11_meta_version" rm -rf "$HLROOT" 2>/dev/null rm -f "$W.st" "$W.ls" 2>/dev/null