. "$BST_LIB" # libfm-extra 1.3.2 (BRINTOS-1856) -- the GUI-free half of libfm, and the # library menu-cache-gen links against. # # WHAT IT ACTUALLY IS, which is not what the package name suggests. Configured # `--with-extra-only --with-gtk=no`, the recipe builds exactly ONE object -- # src/extra/fm-xml-file.c -- and ships 25 public functions, every one of them # `fm_xml_file_*`. There are no fm_* string helpers in it and no .desktop # parser; those are in libfm proper, which this image does not carry. So the # whole of this port's runtime surface is FmXmlFile: a GObject wrapping a # hand-written XML reader and a serializer that writes the parsed tree back out. # # The recipe is poky's own (meta/recipes-support/libfm/libfm-extra_1.3.2.bb, # autotools + pkgconfig + gtk-doc + gettext) and takes NO bbappend, NO patch and # NO site answer. The distro links statically, so it produces libfm-extra-dev, # libfm-extra-staticdev and libfm-extra-dbg and no bare libfm-extra package -- # the same shape as libmnl and jansson, and the reason the image entry names the # two halves rather than a runtime name that does not exist. # # --------------------------------------------------------------------------- # WHY A PROBE AND NOT A FILE-SHAPE SUITE # # This is a -dev/-staticdev pair: the deliverable is an archive and three # headers, so "the files are present" is the whole of what a shell test can say # about it. BRINTOS-217 is why that is not enough -- oniguruma compiled and # packaged perfectly on this arm and then returned silently wrong offsets to # every caller. So the library is RUN, through data/libfm-extra-probe (source in # src/libfm_extra_probe.c), cross-linked with wasmcc-glibc against the very # /usr/lib/libfm-extra.a that ships, plus the image's own glib-2.0 archives. # The coro pass reported `refused=0 invalid_functions=0 module_broken=0` on it. # # EVERY EXPECTED VALUE BELOW IS A MEASURED ONE. A native x86_64 build of the # identical source scores 95/95, and it corrected four assertions of mine before # it did -- each a plausible reading of the API that the library does not hold # to, and each silent rather than diagnosed: # # - fm_xml_file_item_append_text takes the ELEMENT and CREATES the text item # itself. Handing it a FM_XML_FILE_TEXT item you made yourself puts the text # on a grandchild and leaves the item you made with text == comment == NULL # -- and the serializer picks CDATA by a pointer EQUALITY on those two, so # NULL == NULL wins and the document comes out carrying a literal # . No error, no warning. # - whitespace between elements is NOT a child: holding two # elements reports 2 children, not 5. # - indentation is a property of what the children ARE, not of nesting depth. # A tag whose children all take the short in_line form returns before the # serializer sets has_nl, so the closing tag gets an empty prefix and the # whole element is emitted on one line -- while ONE attribute on one child # is enough to bring the prefix back. # - fm_xml_file_item_destroy asserts that the item is IN a container. On an # unattached item it is a g_assert abort, not the FALSE return the # `Returns:` line suggests. # # WHY BUILTIN MATCHING INSTEAD OF check_out. The probe runs ONCE and its output # is read ONCE into a shell variable; every assertion is a `case` glob against # it, so there is no fork per case. On this runtime the forking is the cost # rather than the work -- the cjson suite next door was killed by the runner's # per-suite bound at 156 check_out cases. # --------------------------------------------------------------------------- incd=/usr/include/libfm-1.0 lib=/usr/lib/libfm-extra.a pc=/usr/lib/pkgconfig/libfm-extra.pc nl=' ' # hasfile -- ok iff is a non-empty regular file. # has -- ok iff contains the literal . # hasnot -- ok iff it does not. # hasline -- ok iff carries as a WHOLE line, # so fm_xml_file_item_get_data cannot satisfy a # lookup for fm_xml_file_item_get_dat. hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasnot() { case "$3" in *"$2"*) bst_notok "$1" "present but must not be: $2" ;; *) bst_ok "$1" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not in archive: $2" ;; esac; } # --- the shipped payload ----------------------------------------------------- hasfile libfm-extra/header-extra "$incd/fm-extra.h" hasfile libfm-extra/header-version "$incd/fm-version.h" hasfile libfm-extra/header-xml "$incd/fm-xml-file.h" hasfile libfm-extra/archive "$lib" hasfile libfm-extra/pkgconfig "$pc" # There is deliberately no absent-shared-object case here; bst_lib_shape is the # statement that survives BRINTOS-1740's per-recipe conversion. bst_lib_shape libfm-extra/lib-shape "/usr/lib/libfm-extra.so*" # THE COMPAT SYMLINK, which is what makes the empty Cflags below correct rather # than a packaging defect. Upstream installs the headers under # $includedir/libfm-1.0 and then symlinks $includedir/libfm to it # (src/Makefile.am install-data-local), so the consumer contract is # `#include ` resolved off the DEFAULT include path. If this # link is missing, every consumer breaks at compile and the .pc file says # nothing about why. if [ -L /usr/include/libfm ]; then bst_ok libfm-extra/compat-symlink else bst_notok libfm-extra/compat-symlink "/usr/include/libfm is not a symlink" fi hasfile libfm-extra/compat-reaches "/usr/include/libfm/fm-extra.h" pctext=$(cat "$pc" 2>/dev/null) has libfm-extra/pc-name "Name: libfm-extra" "$pctext" has libfm-extra/pc-version "Version: 1.3.2" "$pctext" has libfm-extra/pc-libs "-lfm-extra" "$pctext" has libfm-extra/pc-requires-glib "Requires: glib-2.0" "$pctext" # THE TICKET'S OWN QUESTION: no GTK anywhere in what ships. `--with-gtk=no` is a # configure switch, and a switch that did not take leaves its evidence in the # pkg-config file and in the archive rather than in the build log. hasnot libfm-extra/pc-no-gtk "gtk" "$pctext" hasnot libfm-extra/pc-no-gdk "gdk" "$pctext" hasnot libfm-extra/pc-no-x11 "x11" "$pctext" hasnot libfm-extra/pc-no-pango "pango" "$pctext" # Cflags carries no -I, and that is upstream's own arithmetic rather than a # defect: configure.ac sets FMINCLUDEADD to '' when includedir is exactly # /usr/include, which it is here, and leans on the compat symlink asserted # above. The absence of the flag is what is asserted rather than the exact # spelling of the line, which ships as "Cflags: " with a trailing space. has libfm-extra/pc-cflags "Cflags:" "$pctext" hasnot libfm-extra/pc-cflags-no-i "-I" "$pctext" # The header's own constants. A header/archive skew shows up here before # anything runs. vertext=$(cat "$incd/fm-version.h" 2>/dev/null) has libfm-extra/hdr-major "#define FM_VERSION_MAJOR 1" "$vertext" has libfm-extra/hdr-minor "#define FM_VERSION_MINOR 3" "$vertext" has libfm-extra/hdr-micro "#define FM_VERSION_MICRO 2" "$vertext" xmltext=$(cat "$incd/fm-xml-file.h" 2>/dev/null) has libfm-extra/hdr-not-handled "#define FM_XML_FILE_TAG_NOT_HANDLED 0" "$xmltext" has libfm-extra/hdr-text-tag "#define FM_XML_FILE_TEXT (FmXmlFileTag)-1" "$xmltext" has libfm-extra/hdr-handler-typedef "typedef gboolean (*FmXmlFileHandler)" "$xmltext" # fm-extra.h is a two-line umbrella; asserting that it INCLUDES the other two is # what says a consumer needs only the one header the package is named for. extratext=$(cat "$incd/fm-extra.h" 2>/dev/null) has libfm-extra/hdr-umbrella-version "#include \"fm-version.h\"" "$extratext" has libfm-extra/hdr-umbrella-xml "#include \"fm-xml-file.h\"" "$extratext" # --- the archive's symbols, DERIVED from the shipped header ------------------- # # The list is not written down: it is every function the INSTALLED header # declares, so it survives a version bump instead of going stale against one. # Every one of them must be in the INSTALLED archive -- a header declaring what # the archive does not define is the half-shipped pair that fails at the # consumer's link, and it fails there rather than here. # # The archive is read with `grep -ao` rather than with nm: nm on this image is # GNU binutils built for wasm32 and its ability to enumerate a wasm object is # not something this suite should be asserting as a side effect. Two pipelines # are the only forks. arcsyms=$(grep -ao 'fm_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) hdrsyms=$(grep -oE '\bfm_xml_file_[a-z0-9_]*\(' "$incd/fm-xml-file.h" 2>/dev/null \ | sed 's/(//' | sort -u) nsym=0 missing="" for f in $hdrsyms; do nsym=$((nsym + 1)) case "$nl$arcsyms$nl" in *"$nl$f$nl"*) ;; *) missing="$missing $f" ;; esac done # 25 is the whole public surface of this package. Asserting the COUNT is what # keeps the loop above from passing vacuously: a derivation that produced an # empty header list would otherwise report every symbol present. check_eq libfm-extra/sym-count 25 "$nsym" if [ -z "$missing" ]; then bst_ok libfm-extra/sym-all-defined else bst_notok libfm-extra/sym-all-defined "declared but not in the archive:$missing" fi # Four spot checks by name, as a second guard on the same vacuity. hasline libfm-extra/sym-new fm_xml_file_new "$arcsyms" hasline libfm-extra/sym-set-handler fm_xml_file_set_handler "$arcsyms" hasline libfm-extra/sym-parse-data fm_xml_file_parse_data "$arcsyms" hasline libfm-extra/sym-to-data fm_xml_file_to_data "$arcsyms" # And the ABSENCE that `--with-extra-only` is supposed to have produced: not one # symbol from libfm proper is in this archive. This is the switch's evidence in # the artifact, which is where it has to be -- a switch that silently did not # take leaves the build log clean. case "$nl$arcsyms$nl" in *"${nl}fm_file_info"*|*"${nl}fm_path_"*|*"${nl}fm_folder_"*|*"${nl}fm_app_info"*) bst_notok libfm-extra/sym-extra-only "libfm-proper symbols are in the extra-only archive" ;; *) bst_ok libfm-extra/sym-extra-only ;; esac # --- the library RUNNING, via the cross-linked probe -------------------------- # # data/ is installed 0644 and the rootfs is read-only at browser runtime, so the # probe cannot be executed where it lies. /tmp is a mode=1777 tmpfs with no # noexec; stage an executable copy there. Asserting `test -x` on the SHIPPED # payload would pass against a hand-placed copy and fail against every image # that ever built the package, so it is `test -f` here and the chmod carries the # executable question. hasfile libfm-extra/probe-payload "$BST_DATA/libfm-extra-probe" tmp=${TMPDIR:-/tmp}/bst-libfm-extra.$$ _p=$tmp.probe cp "$BST_DATA/libfm-extra-probe" "$_p" 2>/dev/null && chmod 0755 "$_p" 2>/dev/null if [ ! -x "$_p" ]; then bst_notok libfm-extra/probe-exec "could not stage an executable copy of $BST_DATA/libfm-extra-probe" rm -f "$_p" exit 0 fi bst_ok libfm-extra/probe-exec # Bounded by construction: the probe opens no descriptor, arms no timer and # sleeps on nothing. It parses string literals held in .rodata and prints. "$_p" > "$tmp.out" 2>&1 prc=$? out=$(cat "$tmp.out" 2>/dev/null) lastline=$(tail -n 2 "$tmp.out" 2>/dev/null) rm -f "$_p" "$tmp.out" check_eq libfm-extra/probe-rc 0 "$prc" # The terminator first: a probe killed part way through would otherwise have # every case it never reached reported as an ordinary failure, with nothing # saying the run was cut short. The tail is captured BEFORE the file is removed # -- reading it afterwards would report an empty detail exactly when the detail # is the evidence. case "$out" in *"FMX-EXIT 0"*) bst_ok libfm-extra/probe-complete ;; *) bst_notok libfm-extra/probe-complete "no terminator; last saw: $(_bst_1line "$lastline")" ;; esac has libfm-extra/probe-summary "FMX-SUMMARY 95/95" "$out" # p -- the probe's own verdict for one check. The probe compares # internally against the native control's values and prints OK or FAIL(detail); # the console carries the detail either way. p() { case "$out" in *"FMX-$1 OK"*) bst_ok "libfm-extra/$1" ;; *) bst_notok "libfm-extra/$1" "probe says: $(_bst_1line "$(printf '%s\n' "$out" | grep "^FMX-$1 ")")" ;; esac; } # type-* : G_DEFINE_TYPE registration. Nothing below means anything if the GType # is 0, and registration fails by RETURNING 0 rather than by trapping -- so if # this group is red, read the rest as unmeasured rather than as broken. p type-registered p type-new p type-is-instance p type-stable p type-unref # tag-* : the handler table. Ids are dense from 1, and a duplicate is refused # with a GError while still returning the id already in use. p tag-first-id p tag-first-noerr p tag-second-id p tag-third-id p tag-dup-id p tag-dup-error p tag-dup-domain p tag-dup-code p tag-name-1 p tag-name-2 p tag-name-3 p tag-sibling p tag-sibling-name-1 p tag-sibling-name-3 # cb-* : the parse. FmXmlFileHandler is a FUNCTION POINTER the parser calls at # every CLOSING tag, which is the indirect-call path the hwjs-cc coroutinize # pass has to mark -- a pass that marks it wrongly links and then calls the # wrong frame, and cb-order is where that shows. p cb-parse-rc p cb-parse-noerr p cb-finish p cb-finish-noerr p cb-order p cb-count p cb-user-data p cb-attr-none p cb-attr-one p cb-attr-name p cb-attr-value p cb-children p cb-line # tree-* : the item accessors, which are the whole of what a consumer that # installs no handler ever sees. tree-text-size is the gsize out parameter -- # 32 bits here against the control's 64. p tree-root-tag p tree-root-name p tree-root-parent p tree-find p tree-find-tag p tree-child-parent p tree-text-child p tree-text-tag p tree-text-data p tree-text-size p tree-text-nosize p tree-find-miss p tree-find-nested p tree-find-deep p tree-deep-text p tree-deep-data p tree-no-comment p tree-dtd # ser-* : the serializer, byte for byte. Its indentation is a GString prefix # pushed and truncated by 4 around every level of recursion, and its escaping is # g_markup_escape_text -- an off-by-N there produces valid-looking XML with the # wrong shape rather than an error. p ser-dtd-set p ser-dtd-get p ser-item-new p ser-item-tag p ser-append-child-text p ser-append-child p ser-child-parent p ser-insert-first p ser-to-data p ser-to-data-noerr p ser-bytes p ser-size p ser-item-unescaped # edit-* : insert_before and destroy, the two operations menu-cache-gen uses to # rewrite a merged menu. Both relink a GList in place. p edit-insert-before p edit-insert-parent p edit-order p edit-destroy p edit-after-destroy p edit-set-attr p edit-attr-bytes p edit-comment p edit-comment-bytes # err-* : the refusal paths. A guard that answers "fine" to malformed input is a # silent misparse in menu-cache-gen, not a visible failure. p err-unclosed p err-mismatch p err-junk-after p err-bad-entity p err-unterminated-attr p err-empty # unh-* : a tag with no handler is kept in the tree with # FM_XML_FILE_TAG_NOT_HANDLED and its own name, and serializes back out. That # pass-through is what lets a consumer rewrite a menu without knowing every # element in it, so an implementation that dropped it would lose data silently. p unh-parse p unh-kept p unh-tag p unh-name p unh-find-first p unh-handled-name # cur-* : get_current_line, the pointer-arithmetic position counter, read across # a two-chunk feed. Pointer-as-offset confusion is the wasm32 hazard here and it # corrupts the position report rather than trapping. p cur-initial p cur-after-part1 p cur-pos-after-part1 p cur-after-part2 p cur-finish # cdata-* : CDATA and the explicit-length append. CDATA is selected by a pointer # EQUALITY test (item->text == item->comment) rather than by a tag, which is the # kind of identity comparison a pointer representation change breaks quietly. p cdata-text-child p cdata-bytes p cdata-len-child p cdata-len-honoured p cdata-len-size p cdata-destroy