. "$BST_LIB" # BST-TIMEOUT: 900 # # python3-installer 0.7.0 (poky meta/recipes-devtools/python) on wasm32-hwjs -- # the low-level library and `python -m installer` tool that turns a built wheel # into files on disk. No network, no resolver, no build backend: it reads a # PEP 427 archive and writes what the archive's RECORD promises. # # THE PORT NEEDED NO BBAPPEND, NO PATCH AND NO AUTOCONF ANSWER. The recipe is # `inherit pypi python_flit_core` over a pure-Python source tree, and every # DEPENDS it carries is `-native` and resolves on the host. What makes it # portable HERE is that it ships no compiled extension: this image's python3 is # one static binary with an empty lib-dynload and no dynamic loader # (BRINTOS-1476), so a module carrying an accelerator could not be imported on # target at all. Section 4 asserts that negative over the INSTALLED tree, so a # version bump that grows a compiled speedup fails here rather than at `import` # three tickets later. # # ONE INTERPRETER START FOR SECTIONS 2-4, AND THE ARITHMETIC IS THE REASON. # An exec on this arm is a fresh wasm module instantiation at ~18 s and a boot # is worth roughly 85-100 s of guest work before brintos_local_verify ends it # (BRINTOS-1682) -- and brintos-selftest replays a suite's output only when the # suite ENDS (BRINTOS-1742), so a suite removed mid-run loses every case it had # already passed. The whole behavioural battery therefore runs in ONE python3 # process, data/python3-installer-selftest.py, which prints one tagged # `R_ ` line per measurement; this file names and compares them, # so a single wrong answer is one red case rather than one red suite. Section 1 # starts no interpreter at all and so still reports on a boot where python3 # cannot run. # # `python -m installer` IS CALLED AS _main() WITH AN ARGV, in that same process, # rather than through a second exec. It is the identical code path -- the # module's `if __name__ == "__main__"` block is one line calling `_main(argv, # "python -m installer")` -- and a second exec would spend another ~18 s of the # boot to reach a function that is one call away. # # EVERY EXPECTED VALUE BELOW WAS MEASURED, NOT READ OFF THE SOURCE. The control # is an x86-64 run of the IDENTICAL payload against the identical installer # 0.7.0 source with poky's interpreter.patch applied, on CPython 3.12.3. # Repeated three times, byte-identical each time: 47 tagged lines and # ALL-INSTALLER-OK. Recorded 2026-08-31. Re-run under umask 0022, 0002 and 0077 # as well, because two of the cases are about file modes -- see the note on # script-mode below. # # EXACTLY ONE VALUE DIFFERS BETWEEN THE ARMS, and it is a path rather than a # behaviour: installer/dir, which is the source tree on the control and # site-packages here. Every other measurement is identical on both, which is the # claim this port is making -- a pure-Python package that behaves the same # because there is nothing architecture-dependent left in it. # # THE FIXTURE WHEEL IS BUILT ON TARGET, not shipped. A wheel checked into the # image would be bytes this port never produced; a wheel zipfile writes in the # payload's own process is read back through the same zlib the installer uses. # What the install then exercises is worth naming, because none of it is # installer's own code: zipfile+zlib inflate, hashlib sha256 and urlsafe base64 # for every RECORD entry, csv for the RECORD, the email parser for METADATA and # WHEEL, os.chmod for the script bit, and compileall for the bytecode levels -- # stdlib splits of the interpreter recipe, which is what this package RDEPENDS # on and therefore what a red here would most likely be about. # # NOT COVERED HERE, on purpose: upstream's own pytest suite. It is not in the # sdist's install set, it needs pytest and it exercises Windows launcher # selection this arm cannot reach. This port closes on the battery below. # # NON-INTERACTIVE SUBJECT: no manual browser pass is claimed, and no follow-on # interactive-test ticket is filed. Deferred -- no manual pass (headless run). require python3 python3-installer/present SITE=/usr/lib/python3.12/site-packages PKG=$SITE/installer # ----------------------------------- 1. what the port SHIPS: no interpreter --- # The module set is the sdist's `src/installer` in full. Asserting the SET # rather than one entry point is what catches a packaging change that silently # drops half of it -- and every one of these is reachable from `import # installer` through _core, so a missing file is an ImportError on first use # and nothing sooner. check python3-installer/site-dir test -d $SITE check python3-installer/package-dir test -d $PKG check python3-installer/mod-init test -f $PKG/__init__.py check python3-installer/mod-main test -f $PKG/__main__.py check python3-installer/mod-core test -f $PKG/_core.py check python3-installer/mod-dests test -f $PKG/destinations.py check python3-installer/mod-exc test -f $PKG/exceptions.py check python3-installer/mod-records test -f $PKG/records.py check python3-installer/mod-scripts test -f $PKG/scripts.py check python3-installer/mod-sources test -f $PKG/sources.py check python3-installer/mod-utils test -f $PKG/utils.py check python3-installer/py-typed test -f $PKG/py.typed # THE WINDOWS LAUNCHERS ARE PE BINARIES AND THEY SHIP. installer/_scripts # carries eight prebuilt .exe launchers upstream uses on Windows; none of them # is loadable here and none is ever opened, because get_launcher_kind() answers # "posix" on this image (asserted in section 2). They are counted rather than # pruned so that a recipe which started pruning them shows up as a change here # instead of as a surprise in a size budget. check python3-installer/scripts-dir test -d $PKG/_scripts check python3-installer/scripts-pkg test -f $PKG/_scripts/__init__.py check_eq python3-installer/launcher-count "8" \ "$(ls $PKG/_scripts/*.exe 2>/dev/null | wc -l | tr -d ' ')" # THE BUILD-TIME BYTECODE SHIPS, one .pyc per module. This is the recipe's own # do_install compiling the package with python3-compile -- the same stdlib split # the payload's own install exercises in section 2 -- so a build that stopped # shipping it would leave every import here paying a compile on first use, on an # image where the interpreter start is already the expensive part. The count is # the module count asserted above and moves with it. check python3-installer/pycache-dir test -d $PKG/__pycache__ check_eq python3-installer/pycache-count "9" \ "$(ls $PKG/__pycache__/*.pyc 2>/dev/null | wc -l | tr -d ' ')" # THE NEGATIVE, ASSERTED ON THE FILESYSTEM AS WELL AS IN SECTION 4. This costs # nothing, starts no interpreter, and is the one form of the assertion that # still reports on a boot where python3 will not start -- which is exactly the # boot on which a stray extension module would be the suspect. check_eq python3-installer/no-shared-object "" \ "$(find $PKG -name '*.so' 2>/dev/null)" bst_lib_shape python3-installer/lib-shape "$PKG/*.so" "$PKG/*/*.so" # THE DIST-INFO THE INSTALL LEFT BEHIND -- this package installed by its own # native build of itself, which is how python_pep517 installs every wheel Yocto # produces. RECORD and METADATA are globbed rather than pinned to a directory # name so the case is about the metadata being there, not about poky's # directory-naming; the VERSION is pinned separately, in section 2, out of the # module's own __version__. check python3-installer/dist-info \ sh -c 'ls -d /usr/lib/python3.12/site-packages/installer-*.dist-info >/dev/null 2>&1' check python3-installer/dist-info-record \ sh -c 'ls /usr/lib/python3.12/site-packages/installer-*.dist-info/RECORD >/dev/null 2>&1' check python3-installer/dist-info-metadata \ sh -c 'ls /usr/lib/python3.12/site-packages/installer-*.dist-info/METADATA >/dev/null 2>&1' check python3-installer/dist-info-wheel \ sh -c 'ls /usr/lib/python3.12/site-packages/installer-*.dist-info/WHEEL >/dev/null 2>&1' check python3-installer/dist-info-license \ sh -c 'ls /usr/lib/python3.12/site-packages/installer-*.dist-info/LICENSE >/dev/null 2>&1' # REGISTERED WITH THE PACKAGE MANAGER, under the name the image installs. This # is the .deb this port ships arriving through dpkg rather than a file tree # somebody staged: `dpkg -s` answers only for a package whose install actually # completed. check python3-installer/dpkg-registered \ sh -c 'dpkg -s python3-installer >/dev/null 2>&1' check_out python3-installer/dpkg-status "Status: install ok installed" \ sh -c 'dpkg -s python3-installer 2>&1' check_out python3-installer/dpkg-version "Version: 0.7.0" \ sh -c 'dpkg -s python3-installer 2>&1' # ------------------- 2-4. the battery: ONE python3 spawn, 47 measurements ---- # The payload takes its scratch root as argv[1] and sets no environment # variable: setenv(3) returns EINVAL for every name on this arm # (brintos/glibc#11), so `os.environ[x] = y` raises OSError [Errno 22] and a # payload that staged its own environment would die on its first assignment and # report every measurement empty. # # BRINTOS-WORKAROUND: brintos/glibc#11 revisit -- when setenv(3) stops returning # EINVAL the payload may build its own scratch paths, and this argv hand-off can # go. Nothing else about the fixture changes. PIROOT=$(mktemp -d /tmp/bst-py3installer.XXXXXX 2>/dev/null) [ -n "$PIROOT" ] || { PIROOT=/tmp/bst-py3installer.$$; mkdir -p "$PIROOT"; } out=$(python3 "$BST_DATA/python3-installer-selftest.py" "$PIROOT" 2>&1); rc=$? case "$out" in *ALL-INSTALLER-OK*) all=1 ;; *) all=0 ;; esac if [ "$rc" -eq 0 ] && [ "$all" -eq 1 ]; then bst_ok python3-installer/all-ok elif bst_execfail "$out"; then bst_unrun python3-installer/all-ok "$out" else bst_notok python3-installer/all-ok "rc=$rc $(_bst_1line "$out")"; fi # Read the tagged lines into shell variables with no subprocess at all. The # payload's tags are already valid sh identifiers for exactly this reason: one # `awk` per value would cost an exec per lookup, and an exec here is a wasm # module instantiation. # # 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 "PI_${line%% *}=\${line#* }" ;; esac done IFS=$_oifs # --- 2a. the import, and the two facts poky's patch adds. # # The import is asked from inside the payload's own process as well as being # implied by everything after it: it is the answer that tells a reader whether # an empty value below is a real measurement or the wreckage of a failed import. check_eq python3-installer/import 'ok' "$PI_R0_import" check_eq python3-installer/version '0.7.0' "$PI_R0_version" check_eq python3-installer/api-all 'install' "$PI_R0_all" check_eq python3-installer/api-callable 'yes' "$PI_R0_install_callable" # posix, not a win-* kind -- which is why the eight .exe launchers counted in # section 1 are dead weight rather than a load-bearing dependency. check_eq python3-installer/launcher-kind 'posix' "$PI_R0_launcher_kind" # POKY'S interpreter.patch, ASSERTED BY ITS TWO EFFECTS. Neither exists in # upstream 0.7.0, and Yocto's own python_pep517 install depends on both: it # passes `--interpreter` so the shebangs written into a target rootfs name the # TARGET python and not the build host's, and it needs the plain shebang form # because the /bin/sh trampoline upstream emits for a spaced interpreter path is # not what a cross install wants. A version bump that drops the patch is caught # here rather than by a rootfs full of host shebangs. # # The SPACED path is the whole of the second case: upstream returns the # trampoline for it and the plain form for an unspaced one, so an unspaced probe # would pass against both and prove nothing. check_eq python3-installer/cli-interpreter-flag '/usr/bin/python3' \ "$PI_R0_cli_interpreter_flag" check_eq python3-installer/shebang-plain '#!/opt/py 3/bin/python3' \ "$PI_R0_shebang_plain" # --- 2b. the fixture archive, before the installer is blamed for anything. # # zip-roundtrip and zip-content are this image's zlib answering for itself. If # they are red, nothing below them is about installer at all -- which is the # distinction this pair exists to make. check_eq python3-installer/wheel-built 'ok' "$PI_R1_wheel_built" check_eq python3-installer/wheel-members '8' "$PI_R1_wheel_members" check_eq python3-installer/zip-roundtrip 'ok' "$PI_R1_zip_roundtrip" check_eq python3-installer/zip-content 'ok' "$PI_R1_zip_content" # installer's OWN reader over the same file: the wheel-filename parser, the # .dist-info and .data directory discovery, and the RECORD-driven content walk. # get_contents() asserts every archive member has a RECORD row, so the listing # below is also the statement that the two agree. check_eq python3-installer/source-distribution 'bstfix' "$PI_R1_source_distribution" check_eq python3-installer/source-version '1.2.3' "$PI_R1_source_version" check_eq python3-installer/source-dist-info 'bstfix-1.2.3.dist-info' "$PI_R1_source_dist_info" check_eq python3-installer/source-data-dir 'bstfix-1.2.3.data' "$PI_R1_source_data_dir" check_eq python3-installer/source-contents \ 'bstfix-1.2.3.data/data/share/bstfix/plain.txt,bstfix-1.2.3.data/scripts/bst-fixture-script,bstfix-1.2.3.dist-info/METADATA,bstfix-1.2.3.dist-info/RECORD,bstfix-1.2.3.dist-info/WHEEL,bstfix-1.2.3.dist-info/entry_points.txt,bstfix/__init__.py,bstfix/deep/mod.py' \ "$PI_R1_source_contents" # --- 2c. THE INSTALL, and the RECORD it wrote. # # record-all-present is the assertion the ticket names: every path the GENERATED # RECORD promises is on disk at the scheme the installer chose. record-all-valid # is the half that makes it worth having -- each entry's sha256 and size # re-computed from the bytes actually written, so a short write through virtiofs # fails here while a name-only check would pass. rows is 10 and not 8: the six # archive members that are installed, plus the generated console script, plus # INSTALLER, plus RECORD's own row. check_eq python3-installer/installed 'ok' "$PI_R2_installed" check_eq python3-installer/purelib-under-prefix 'yes' "$PI_R2_purelib_under_prefix" check_eq python3-installer/record-rows '10' "$PI_R2_record_rows" check_eq python3-installer/record-all-present 'ok' "$PI_R2_record_all_present" check_eq python3-installer/record-all-valid 'ok' "$PI_R2_record_all_valid" check_eq python3-installer/module-file 'ok' "$PI_R2_module_file" check_eq python3-installer/deep-module 'ok' "$PI_R2_deep_module" check_eq python3-installer/metadata 'ok' "$PI_R2_metadata" # additional_metadata is how a caller stamps who installed a distribution; pip # writes its own name here and Yocto writes the wheel's. Asserting the CONTENT # rather than the file's existence is what says the bytes were passed through. check_eq python3-installer/installer-marker 'brintos-selftest' "$PI_R2_installer_marker" # THE .data/ SPLIT, WHICH IS TWO SCHEMES AND NOT ONE. `.data/scripts/` goes to # the scripts scheme with its shebang rewritten; `.data/data/` goes to the data # scheme at the prefix root, untouched. A destination that collapsed the two # would land both beside the modules, and script-not-in-purelib is the case that # says it did not. check_eq python3-installer/script-scheme 'ok' "$PI_R2_script_scheme" check_eq python3-installer/script-shebang 'ok' "$PI_R2_script_shebang" check_eq python3-installer/script-not-in-purelib 'ok' "$PI_R2_script_not_in_purelib" check_eq python3-installer/data-scheme 'ok' "$PI_R2_data_scheme" # THE MODE PAIR, and it is a pair on purpose: 0o755 in the archive must arrive # executable and 0o644 must not. Either case alone is also passed by an # installer that sets the bit unconditionally, or never. Only the 0o111 bits are # compared -- make_file_executable does `chmod(0o777 & ~umask | 0o111)`, so the # read/write bits report the runner's umask rather than the port. Measured # identical under umask 0022, 0002 and 0077. check_eq python3-installer/script-executable 'yes' "$PI_R2_script_executable" check_eq python3-installer/script-mode '0o111' "$PI_R2_script_mode" check_eq python3-installer/data-not-executable 'no' "$PI_R2_data_not_executable" check_eq python3-installer/data-mode '0o0' "$PI_R2_data_mode" # THE CONSOLE SCRIPT IS GENERATED, NOT COPIED. Nothing in the archive carries # it: installer parses entry_points.txt and writes the launcher itself, so its # presence proves the entry-point parser and the script writer together, and its # shebang proves they share the interpreter the destination was handed. check_eq python3-installer/console-script 'ok' "$PI_R2_console_script" check_eq python3-installer/console-shebang 'ok' "$PI_R2_console_shebang" check_eq python3-installer/console-calls-main 'yes' "$PI_R2_console_calls_main" # BYTECODE AT THE LEVELS ASKED FOR. compileall is why this recipe RDEPENDS on # python3-compile; level 1 lands beside level 0 under a different tag, so # counting BOTH is what says the level list was honoured rather than the default # taken. Their absence under --no-compile-bytecode is section 3. check_eq python3-installer/pyc-level0 'yes' "$PI_R2_pyc_level0" check_eq python3-installer/pyc-level1 'yes' "$PI_R2_pyc_level1" # THE END OF THE CHAIN: the wheel's own module imported back out of the scratch # prefix by the interpreter that installed it. import-installed-from is what # makes it an assertion rather than a coincidence -- without it, a `bstfix` # already importable from anywhere else on the image would pass. check_eq python3-installer/import-installed 'bst-installer-fixture-ok' \ "$PI_R2_import_installed" check_eq python3-installer/import-installed-from 'ok' "$PI_R2_import_installed_from" check_eq python3-installer/import-deep '42' "$PI_R2_import_deep" # --- 3. `python -m installer`, as _main() with an argv. # # A SECOND PREFIX, and cli-two-prefixes-differ is why: the same wheel installed # twice into two roots. installer refuses to overwrite an existing file # (write_to_fs raises FileExistsError), so a CLI that ignored --prefix would not # quietly agree with section 2 -- it would fail. cli-no-pyc is the # --no-compile-bytecode arm, and it is the control for the two pyc cases above: # together they say the bytecode level list is read rather than assumed. check_eq python3-installer/cli-install 'ok' "$PI_R3_cli_install" check_eq python3-installer/cli-record 'ok' "$PI_R3_cli_record" check_eq python3-installer/cli-no-pyc 'ok' "$PI_R3_cli_no_pyc" check_eq python3-installer/cli-two-prefixes-differ 'ok' "$PI_R3_cli_two_prefixes_differ" # --- 4. the negative the port's shape depends on, over the INSTALLED tree. # # Same claim as section 1's find(1), asked of the module the interpreter # actually resolved rather than of a path this file typed. installer-dir is the # ONE value that differs from the x86-64 control, and it differs because it is a # path: site-packages here, the source tree there. check_eq python3-installer/no-so-installed 'ok' "$PI_R4_no_so_installed" check_eq python3-installer/installer-dir \ '/usr/lib/python3.12/site-packages/installer' "$PI_R4_pure_python_dir" rm -rf "$PIROOT" 2>/dev/null