. "$BST_LIB" # hidapi is a thin portable wrapper over the OS's HID transport: hid_init/ # hid_enumerate/hid_open discover and open a Human Interface Device, and # hid_read/hid_write/hid_{get,send}_feature_report move reports on it. # # ON LINUX IT HAS TWO INDEPENDENT BACK-ENDS AND THIS IMAGE SHIPS EXACTLY ONE. # hidraw (linux/hid.c) walks /sys/class/hidraw through libudev and opens # /dev/hidraw*; libusb (libusb/hid.c) talks to usbfs through libusb-1.0. They # are separate archives with separate pkg-config files, and a consumer picks # one. Nothing PROVIDES udev on this arm -- VIRTUAL-RUNTIME_dev_manager is # busybox-mdev to cut the eudev -> kmod -> openssl chain, and eudev's do_compile # is refused outright by the HWJS-3 coro pass -- so the bbappend builds the # LIBUSB back-end and patches out the hidraw one, which upstream's autotools # build otherwise makes a hard requirement of the whole package. # # SO THE BACK-END IS NOT ASSUMED HERE, IT IS ASSERTED. The archive is # libhidapi-libusb.a and the pkg-config file is hidapi-libusb.pc; the hidraw # names must be absent, and hid_libusb_wrap_sys_device -- an entry point that # exists only in the libusb back-end's own header -- must be IN the archive. # Those three together are what distinguish "we built the libusb back-end" from # "we built something and it happened to install". # # THERE IS NO PROGRAM TO DRIVE. hidapi installs no binaries at all: hidtest is # noinst_PROGRAMS upstream, so it is built and never installed, on this image # and on every other. With no consumer on the image and no on-target C compiler, # every artifact case asserts the shipped artifact, in the shape 81-json-c # established. The runtime half is a probe at the end of the file. # # --------------------------------------------------------------------------- # WHY THIS SUITE USES BUILTIN MATCHING INSTEAD OF check_out ... cat # # Files are read ONCE into shell variables and every text assertion is a `case` # glob against them -- no fork per case -- and the archive symbol lookup is a # single batched grep whose result is matched the same way. 81-cjson was killed # by the runner's per-suite bound at 156 cases written as check_out+cat, because # on the wasm32-hwjs runtime the forking IS the cost. # --------------------------------------------------------------------------- incd=/usr/include/hidapi pc=/usr/lib/pkgconfig/hidapi-libusb.pc lib=/usr/lib/libhidapi-libusb.a nl=' ' # hasfile -- ok iff is a non-empty regular file. # nofile -- ok iff does not exist. # has -- ok iff contains the literal . # hasnot -- ok iff does NOT contain . # hasline -- ok iff carries as a WHOLE line. # Substring matching would let hid_get_input_report # satisfy a lookup for hid_get_input, and # hid_read_timeout satisfy hid_read. hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } nofile() { if [ ! -e "$2" ]; then bst_ok "$1"; else bst_notok "$1" "present: $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" "unexpectedly present: $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 two installed headers ------------------------------------------------ # # hidapi.h is the portable API, installed by whichever back-end is built. # hidapi_libusb.h is installed by the libusb back-end ONLY (libusb/Makefile.am # lists it in hdr_HEADERS; linux/Makefile.am lists hidapi.h alone), so its # presence is the first of the three back-end assertions. hasfile hidapi/header-hidapi "$incd/hidapi.h" hasfile hidapi/header-hidapi-libusb "$incd/hidapi_libusb.h" # --- version, pinned in the header the archive is compiled from --------------- verh=$(cat "$incd/hidapi.h" 2>/dev/null) has hidapi/version-major '#define HID_API_VERSION_MAJOR 0' "$verh" has hidapi/version-minor '#define HID_API_VERSION_MINOR 14' "$verh" has hidapi/version-patch '#define HID_API_VERSION_PATCH 0' "$verh" # The umbrella header must still declare the calls this port exists to ship. # Asserting the DECLARATION rather than only the symbol catches a header/library # skew, where the archive carries a function the shipped header no longer offers. has hidapi/hdr-decl-init 'int HID_API_EXPORT HID_API_CALL hid_init(void);' "$verh" has hidapi/hdr-decl-exit 'int HID_API_EXPORT HID_API_CALL hid_exit(void);' "$verh" has hidapi/hdr-decl-enumerate 'HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);' "$verh" # hid_libusb_wrap_sys_device is declared in hidapi_libusb.h and nowhere else -- # the second back-end assertion, and the one a reviewer can check by eye. lusbh=$(cat "$incd/hidapi_libusb.h" 2>/dev/null) has hidapi/hdr-libusb-wrap 'hid_libusb_wrap_sys_device' "$lusbh" # --- pkg-config --------------------------------------------------------------- hasfile hidapi/pc-present "$pc" pct=$(cat "$pc" 2>/dev/null) has hidapi/pc-name 'Name: hidapi-libusb' "$pct" has hidapi/pc-version 'Version: 0.14.0' "$pct" has hidapi/pc-libs '-lhidapi-libusb' "$pct" has hidapi/pc-cflags '-I${includedir}/hidapi' "$pct" has hidapi/pc-libdir 'libdir=/usr/lib' "$pct" # UPSTREAM SHIPS NO Libs.private / Requires.private, ON EITHER BUILD SYSTEM -- # pc/hidapi-libusb.pc.in is a static template used by the autotools build and by # cmake alike, and it names none of the transitive dependencies. On a static-only # distro every consumer is a static consumer, so `pkg-config --static --libs # hidapi-libusb` under-reports: it omits -lusb-1.0 and -lrt, which the archive # genuinely needs. It is NOT repaired here, deliberately -- adding # Requires.private would still not produce a working link on this image, because # libusb-1.0.pc's own -L is dropped by pkg-config as a system path (see the # bbappend), and a remedy that fixes half of a two-part failure reads as a # complete one. The gap is pinned instead: if either line ever appears, upstream # or a version bump has changed this and the decision is worth re-taking. hasnot hidapi/pc-no-libs-private 'Libs.private' "$pct" hasnot hidapi/pc-no-requires-private 'Requires.private' "$pct" # --- the hidraw back-end must be ABSENT --------------------------------------- # # The third back-end assertion, and the one that fails if the patch is ever # dropped or upstream's autotools build regains libudev by another route. Both # names are what a hidraw build would install beside the libusb ones. nofile hidapi/no-hidraw-pc /usr/lib/pkgconfig/hidapi-hidraw.pc nofile hidapi/no-hidraw-archive /usr/lib/libhidapi-hidraw.a # The single-backend pkg-config name is what non-Linux platforms install; seeing # it here would mean configure took a branch this arm must not take. nofile hidapi/no-generic-pc /usr/lib/pkgconfig/hidapi.pc # --- the archive -------------------------------------------------------------- hasfile hidapi/archive-present "$lib" check_eq hidapi/archive-format '!' "$(head -c 7 "$lib" 2>/dev/null)" # --- completeness: every declared entry point is in the archive --------------- # # Flatten the archive's printable runs to one token per line first: a raw # `grep -o` over binary data reports "Binary file matches" instead of the # matches, which would silently under-count and read as a partial compile. tmp=${TMPDIR:-/tmp} flat=$tmp/hidapi-flat.$$ namef=$tmp/hidapi-names.$$ ( tr -c '[:print:]' '\n' < "$lib" > "$flat" 2>/dev/null ) # hidapi marks its public entry points with two spellings -- HID_API_CALL after # an HID_API_EXPORT return type, and the combined HID_API_EXPORT_CALL. Taking # only the first would miss the five string accessors, which are exactly the # calls the dropped glibc-gconv-utf-16 package would have served, so a suite # that reads one spelling would go quiet on the surface this port most affects. grep -hoE '(HID_API_CALL|HID_API_EXPORT_CALL)[[:space:]]+\**[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' \ "$incd"/*.h 2>/dev/null \ | sed 's/(.*//' | awk '{print $NF}' | sed 's/^\**//' \ | grep -E '^hid_' | sort -u > "$namef" # END-OF-RUN ANCHORED, one batched pass -- and the anchor is the whole point. # The archive is an ar of LLVM IR bitcode whose string table concatenates symbol # names with NO separator, so a plain `grep -oFf` reports any needle that is # merely a PREFIX of a token that is present. MEASURED on this archive: six # invented names (hid_ini, hid_exi, hid_rea, hid_writ, hid_clos, hid_enumerat) # were all "found" by -oFf, which means the completeness count below can be # inflated by the matcher itself and still read as a full green. Anchoring each # needle at end-of-run reports 24/24 real and none of the six. (`-owFf` also # reports 0 of the six here, but it is what UNDER-reported on 99-libusbgx's # archive, where the index's first name is glued to a printable byte of the # offset table; the anchored form is the one that has now been measured correct # on both.) patf=$tmp/hidapi-pats.$$ sed 's/$/$/' "$namef" > "$patf" found=$(grep -oEf "$patf" "$flat" 2>/dev/null | sort -u) ndecl=$(grep -c . "$namef"); nfound=$(printf '%s' "$found" | grep -c .) # The floor guards the check itself: headers that lost their declarations, or an # extraction that silently matched nothing, would otherwise pass vacuously with # zero names to look up. 0.14.0 declares 24 across the two headers. if [ "$ndecl" -ge 20 ] && [ "$nfound" -eq "$ndecl" ]; then bst_ok hidapi/sym-complete else bst_notok hidapi/sym-complete "declared=$ndecl found=$nfound"; fi # THE OVER-REPORT CONTROL, run against the same archive and the same matcher. # Six names that do not exist, each a prefix of one that does. Plain -oFf reports # all six; the anchored form must report none. Without this case the count above # could be inflated by the matcher and nothing here would say so. fakef=$tmp/hidapi-fake.$$ printf '%s\n' 'hid_ini$' 'hid_exi$' 'hid_rea$' 'hid_writ$' 'hid_clos$' 'hid_enumerat$' > "$fakef" fake=$(grep -oEf "$fakef" "$flat" 2>/dev/null | sort -u) nfake=$(printf '%s' "$fake" | grep -c .) if [ "$nfake" -eq 0 ]; then bst_ok hidapi/sym-no-prefix-inflation else bst_notok hidapi/sym-no-prefix-inflation "matcher claimed $nfake non-existent names: $(_bst_1line "$fake")"; fi # A named sample across the layers as well, so a failure names the layer that # broke rather than only reporting a count. for s in hid_init hid_exit hid_enumerate hid_free_enumeration \ hid_open hid_open_path hid_close \ hid_write hid_read hid_read_timeout hid_set_nonblocking \ hid_send_feature_report hid_get_feature_report hid_get_input_report \ hid_get_manufacturer_string hid_get_product_string \ hid_get_serial_number_string hid_get_indexed_string \ hid_get_report_descriptor hid_get_device_info \ hid_error hid_version hid_version_str; do hasline "hidapi/sym-$s" "$s" "$found" done # The back-end assertion again, this time in the COMPILED code rather than in a # file name: hid_libusb_wrap_sys_device is implemented in libusb/hid.c only. hasline hidapi/sym-libusb-wrap hid_libusb_wrap_sys_device "$found" # And the hidraw back-end's own internals must NOT be in this archive. udev_new # is libudev's entry point; linux/hid.c calls it directly, so its presence would # mean a hidraw object had been linked in after all. a=$(cat "$flat" 2>/dev/null) hasnot hidapi/no-udev-symbol 'udev_new' "$a" # The version string the header defines is compiled in, so finding it pins the # archive to the header rather than only to the .pc. has hidapi/lib-version-string '0.14.0' "$a" rm -f "$flat" "$namef" "$patf" "$fakef" # --- static-only guards ------------------------------------------------------- # # The distro appends --disable-shared at class-target scope, so libtool builds # only the archive. A stray .so or .la here would mean that selection was lost. noglob_ok() { # ok iff nothing matched the glob already expanded into "$@" _id=$1; shift if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi } set -- /usr/lib/libhidapi-libusb.so*; noglob_ok hidapi/no-shared "$@" set -- /usr/lib/libhidapi-libusb.la; noglob_ok hidapi/no-libtool "$@" # hidtest is noinst_PROGRAMS, so it must never reach the image under either name. nofile hidapi/no-hidtest-libusb /usr/bin/hidtest-libusb nofile hidapi/no-hidtest-hidraw /usr/bin/hidtest-hidraw nofile hidapi/no-hidtest /usr/bin/hidtest # --- the runtime probe ---------------------------------------------------------- # # THE ONE THING THE SHIPPED ARTIFACT CANNOT ANSWER is whether hid_init succeeds # and hid_enumerate returns a device. That needs a HID node behind a real bus AND # a consumer, and this image has neither. The probe below establishes WHICH is # missing and says so in the skip reason, rather than asserting an environment # fact that would go red the day the runtime grows a host controller -- a machine # with USB is a HEALTHIER machine, and a suite that reds on it is wrong. # # The bus that matters here is USB, not hidraw: this is the LIBUSB back-end, so # hid_enumerate walks libusb_get_device_list rather than /sys/class/hidraw. The # hidraw paths are probed anyway and reported, because their absence is what the # ticket's premise turns on and a future image could grow one without the other. devtmpfs=absent; [ -d /dev/bus/usb ] && devtmpfs=present procusb=absent; [ -d /proc/bus/usb ] && procusb=present usbcore=absent; [ -d /sys/bus/usb ] && usbcore=present hidrawcls=absent; [ -d /sys/class/hidraw ] && hidrawcls=present ndev=0 if [ -d /sys/bus/usb/devices ]; then for d in /sys/bus/usb/devices/*; do [ -e "$d" ] && ndev=$((ndev + 1)); done fi nhidraw=0 for d in /dev/hidraw*; do [ -e "$d" ] && nhidraw=$((nhidraw + 1)); done probe="usbfs:/dev/bus/usb=$devtmpfs /proc/bus/usb=$procusb sysfs:/sys/bus/usb=$usbcore usb-devices=$ndev /sys/class/hidraw=$hidrawcls /dev/hidraw*=$nhidraw" if [ "$devtmpfs" = absent ] && [ "$procusb" = absent ]; then # hid_init() is `if (libusb_init(&usb_context)) return -1;` (libusb/hid.c), and # libusb1 on this image is built --disable-udev, which compiles out # find_usbfs_path()'s assume-/dev/bus/usb fallback. In this state op_init() # logs "could not find usbfs" and libusb_init returns LIBUSB_ERROR_OTHER (-99), # so hid_init would return -1 -- it would FAIL here, not merely enumerate # empty. Measured for libusb1 under BRINTOS-1576 on the identical source built # natively with /dev/bus and /sys/bus/usb hidden; 99-libusb1.test records the # same state from the other side. bst_skip hidapi/init-against-bus \ "no USB device tree, so libusb_init refuses -99 and hid_init would return -1 -- $probe" bst_skip hidapi/enumerate-against-device \ "no USB device tree to enumerate, and no HID node behind it -- $probe" else bst_skip hidapi/init-against-bus \ "a usbfs tree exists but this image installs no hidapi consumer and has no C compiler, so hid_init cannot be called here -- $probe" bst_skip hidapi/enumerate-against-device \ "a usbfs tree exists but nothing on this image links hidapi -- $probe" fi