# BST-TIMEOUT: 1200 # 163 cases, 91s measured locally; the nightly's browser run still exceeds 300s # libpcre2 10.43 — Perl-compatible regular expressions, 2nd API (BRINTOS-279). # # The port ships two runnable binaries, pcre2test and pcre2grep, and both are # statically linked against the archives this recipe builds — so every case below # that matches a pattern is running THE LIBRARY, not inspecting a file. That # distinction is the whole lesson of 76-onig.test, whose payload half was 159/159 # green on a library that returned silently wrong match offsets; a suite that only # asserted headers and archive members would have passed on it. # # Four things here would be got wrong by copying a neighbouring suite: # # 1. THE THREE CODE-UNIT WIDTHS ARE THREE SEPARATE ARCHIVES, and pcre2test is # the only thing on this image that can drive the 16- and 32-bit ones. # `pcre2test -16` and `-32` select libpcre2-16.a and libpcre2-32.a at # runtime, so the -16/-32 cases are the sole runtime evidence those two # archives are anything more than bytes in /usr/lib. A suite covering only # the 8-bit path would ship two-thirds of the port unexercised. # 2. pcre2test EXITS 0 ON A PATTERN THAT FAILS TO COMPILE. Its job is to report # what the library did, and "the library rejected this pattern" is a # successful report. So the compile-error and limit-exceeded cases assert the # library's DIAGNOSTIC TEXT through check_out; asserting a non-zero rc would # fail against a correctly-behaving tool. # 3. THE LIMIT CASES ARE THE HEAP-FRAME MACHINERY, not decoration. Since 10.30 # pcre2_match() is not recursive: it allocates a vector of match frames on # the HEAP and grows it (pcre2_match.c:7050), which is why this library has # no deep-backtracking stack hazard on an arm that copies coroutine frames. # match_limit / heap_limit / depth_limit each fault a different counter in # that machinery, and find_limits reads all three back, so the four cases # together are what says the heap-frame path works rather than merely not # crashing. # 4. THE JIT IS ABSENT BY DESIGN AND MUST STAY ABSENT. pcre2's JIT is sljit, # which emits real machine code for a fixed set of architectures and has no # wasm32 backend; upstream defaults --enable-jit to off and the recipe does # not turn it on, so `pcre2test -C` must report "No just-in-time compiler # support". If that line ever flips, something enabled a code generator that # cannot work here, and the /jit modifier case below asserts the documented # consequence: requesting JIT is a silent no-op that falls back to the # interpreter, not an error. # # Every expected value was taken from a native x86_64 build of the identical # 10.43 source configured with this recipe's own EXTRA_OECONF, driven green there # before it ran on target — so a red here is the platform's, not the test's. . "$BST_LIB" require pcre2test libpcre2/present bst_ok libpcre2/present root=${BST_ROOT:-} t=${TMPDIR:-/tmp}/bst-libpcre2.$$ mkdir -p "$t" 2>/dev/null in=$t/in # pcre2test reads a script of pattern lines and indented subject lines. Writing # it with printf '%s\n' (not an interpreted format string) is what keeps the # backslashes in the patterns literal. mkin() { printf '%s\n' "$@" > "$in"; } # ------------------------------------------------- the library describes itself # -C is the built library's own account of how it was configured. Each line below # is a build decision this port had to get right, so they are asserted # individually rather than as one blob. check_out libpcre2/ver-8bit 'PCRE2 version 10.43 2024-02-16' pcre2test -C check_out libpcre2/cfg-8 '8-bit support' pcre2test -C check_out libpcre2/cfg-16 '16-bit support' pcre2test -C check_out libpcre2/cfg-32 '32-bit support' pcre2test -C check_out libpcre2/cfg-utf 'UTF and UCP support (Unicode version 15.0.0)' pcre2test -C check_out libpcre2/cfg-nojit 'No just-in-time compiler support' pcre2test -C check_out libpcre2/cfg-nl 'Default newline sequence is LF' pcre2test -C check_out libpcre2/cfg-bsr '\R matches all Unicode newlines' pcre2test -C check_out libpcre2/cfg-link2 'Internal link size = 2' pcre2test -C check_out libpcre2/cfg-nest 'Parentheses nest limit = 250' pcre2test -C check_out libpcre2/cfg-heap 'Default heap limit = 20000000 kibibytes' pcre2test -C check_out libpcre2/cfg-match 'Default match limit = 10000000' pcre2test -C check_out libpcre2/cfg-noedit 'pcre2test has neither libreadline nor libedit support' pcre2test -C # ------------------------------------------------------- the 8-bit match engine mkin '/ab(c+)d/' ' xxabcccdyy' check_out libpcre2/match-whole ' 0: abcccd' pcre2test "$in" check_out libpcre2/match-group ' 1: ccc' pcre2test "$in" mkin '/abc/' ' xyz' check_out libpcre2/no-match 'No match' pcre2test "$in" # Named groups: the name table is compiled state read back out of the pattern, # and the group values come out of the ovector — two different things, so both # are asserted. mkin '/(?\d{4})-(?\d{2})/info' ' 2026-07' check_out libpcre2/names-count 'Capture group count = 2' pcre2test "$in" # The table is emitted in NAME order, not group order, so mo (group 2) precedes # yr (group 1) — asserting the pairing is what catches a table read back skewed. check_out libpcre2/names-table-mo ' mo 2' pcre2test "$in" check_out libpcre2/names-table-yr ' yr 1' pcre2test "$in" check_out libpcre2/names-g1 ' 1: 2026' pcre2test "$in" check_out libpcre2/names-g2 ' 2: 07' pcre2test "$in" # A backreference is the canonical thing a DFA cannot do and a backtracker can; # it is why this library exists rather than POSIX regex. mkin '/(\w+) \1/' ' hello hello world' check_out libpcre2/backref ' 0: hello hello' pcre2test "$in" mkin '/(?<=foo)bar/' ' foobar' check_out libpcre2/lookbehind ' 0: bar' pcre2test "$in" mkin '/(?!foo)\w{3}/' ' foobar' check_out libpcre2/neg-lookahead ' 0: oob' pcre2test "$in" # \R is a build-time decision (--enable-bsr-anycrlf would change it), asserted # against the CRLF pair the -C line above promises it matches. mkin '/a\Rb/' ' a\r\nb' check_out libpcre2/bsr-crlf ' 0: a\x0d\x0ab' pcre2test "$in" mkin '/\d/g' ' a1b2c3' check_out libpcre2/global-3 ' 0: 3' pcre2test "$in" # Substitution is a separate entry point (pcre2_substitute), not a match plus # string surgery in the caller, so it is covered apart from the match cases. mkin '/(\d+)/' ' abc123def\=replace=<$1>' check_out libpcre2/substitute ' 1: abc<123>def' pcre2test "$in" mkin '/\d/g' ' a1b2c3\=replace=X' check_out libpcre2/substitute-global ' 3: aXbXcX' pcre2test "$in" # An internal callout: the library calls back into pcre2test mid-match. This is # the in-process callout, distinct from pcre2grep's forking one further down. mkin '/a(?C1)b/' ' ab' check_out libpcre2/callout-internal ' 1 ^^ b' pcre2test "$in" # See note 2: rc is 0 here, so the diagnostic text is the assertion. mkin '/a(b/' check_out libpcre2/compile-error 'Failed: error 114 at offset 3: missing closing parenthesis' pcre2test "$in" # ------------------------------------------------ the heap-frame machinery (3) # See note 3. Each limit faults a different counter in the non-recursive matcher. mkin '/(a+)*b/' ' aaaaaaaaaab\=match_limit=1' check_out libpcre2/limit-match 'Failed: error -47: match limit exceeded' pcre2test "$in" mkin '/(a+)*b/' ' aaaaaaaaaab\=heap_limit=0' check_out libpcre2/limit-heap 'Failed: error -63: heap limit exceeded' pcre2test "$in" mkin '/(a+)*b/' ' aaaaaaaaaab\=depth_limit=3' check_out libpcre2/limit-depth 'Failed: error -53: matching depth limit exceeded' pcre2test "$in" # ...and read all three back for a match that succeeds. The match and depth # figures are counts -- of backtracks and of nested frames -- so they are # properties of the pattern and subject and carry over from the x86_64 control # unchanged. The heap figure is not a count: it is the frame vector's size in # kibibytes, and a match frame is mostly pointers, so the same match needs 2 KiB # where the control ran and 1 KiB on this 32-bit target. Pinned to what wasm32 # prints, which is the number this suite exists to hold still. mkin '/(a+)*b/' ' aaaaaaaaaab\=find_limits' check_out libpcre2/find-heap 'Minimum heap limit = 1' pcre2test "$in" check_out libpcre2/find-match 'Minimum match limit = 7' pcre2test "$in" check_out libpcre2/find-depth 'Minimum depth limit = 7' pcre2test "$in" check_out libpcre2/find-match-ok ' 0: aaaaaaaaaab' pcre2test "$in" # See note 4: /jit is a documented no-op on a build with no JIT, not an error. mkin '/abc/jit' ' abc' check_out libpcre2/jit-noop ' 0: abc' pcre2test "$in" # --------------------------------------------------------- Unicode (UTF + UCP) mkin '/\p{Greek}+/utf' ' abc\x{3b1}\x{3b2}def' check_out libpcre2/utf-property ' 0: \x{3b1}\x{3b2}' pcre2test "$in" mkin '/^\X/utf' ' \x{e9}abc' check_out libpcre2/utf-grapheme ' 0: \x{e9}' pcre2test "$in" mkin '/\w+/utf,ucp' ' caf\x{e9}!' check_out libpcre2/ucp-word ' 0: caf\x{e9}' pcre2test "$in" # ------------------------------------------------------ the 16- and 32-bit libs # See note 1. The banner names the width the binary selected, so it is asserted # alongside the match: a -16 run that silently fell back to the 8-bit library # would still match, and only the banner tells the two apart. mkin '/ab(c+)d/' ' xxabcccdyy' check_out libpcre2/w16-banner 'PCRE2 version 10.43 2024-02-16 (16-bit)' pcre2test -16 "$in" check_out libpcre2/w16-match ' 0: abcccd' pcre2test -16 "$in" check_out libpcre2/w16-group ' 1: ccc' pcre2test -16 "$in" check_out libpcre2/w32-banner 'PCRE2 version 10.43 2024-02-16 (32-bit)' pcre2test -32 "$in" check_out libpcre2/w32-match ' 0: abcccd' pcre2test -32 "$in" check_out libpcre2/w32-group ' 1: ccc' pcre2test -32 "$in" # ------------------------------------------------------------- the DFA matcher # pcre2_dfa_match() is a second, independent engine with its own 30 KB # recursion workspace (pcre2_dfa_match.c:3391), so it exercises a code path the # cases above never reach. Its contract also differs: it returns EVERY match # length at the start point, longest first — a caller reading ' 1:' as capture # group 1 would be wrong, which is exactly why the multi-length case is here. mkin '/a(b|bc)/' ' abcx\=dfa' check_out libpcre2/dfa-longest ' 0: abc' pcre2test "$in" check_out libpcre2/dfa-shorter ' 1: ab' pcre2test "$in" mkin '/ab(c|cc)d/' ' abccd\=dfa' check_out libpcre2/dfa-single ' 0: abccd' pcre2test "$in" # ------------------------------------------------------------------- pcre2grep if command -v pcre2grep >/dev/null 2>&1; then bst_ok libpcre2/grep-present check_out libpcre2/grep-version 'pcre2grep version 10.43 2024-02-16' pcre2grep -V d=$t/g; mkdir -p "$d/sub" printf 'alpha one\nBeta two\ngamma three\nalpha four\n' > "$d/a.txt" printf 'delta five\nalpha six\n' > "$d/sub/b.txt" check_out libpcre2/grep-match 'alpha four' pcre2grep 'alpha' "$d/a.txt" check_out libpcre2/grep-count '2' pcre2grep -c 'alpha' "$d/a.txt" check_out libpcre2/grep-lineno '3:gamma three' pcre2grep -n 'gamma' "$d/a.txt" check_out libpcre2/grep-icase 'Beta two' pcre2grep -i 'BETA' "$d/a.txt" check_out libpcre2/grep-invert '2' pcre2grep -c -v 'alpha' "$d/a.txt" check_out libpcre2/grep-only 'gamma' pcre2grep -o '\w+a' "$d/a.txt" check_out libpcre2/grep-word '1' pcre2grep -c -w 'one' "$d/a.txt" check_out libpcre2/grep-multi '3' pcre2grep -c -e alpha -e gamma "$d/a.txt" check_out libpcre2/grep-after 'alpha four' pcre2grep -A1 'gamma' "$d/a.txt" check_out libpcre2/grep-recurse 'sub/b.txt:1' pcre2grep -r -c 'alpha' "$d" check_out libpcre2/grep-files-l 'sub/b.txt' pcre2grep -r -l 'delta' "$d" # A pattern file, the -f form autoconf-era scripts use. printf 'alpha\ngamma\n' > "$d/pats" check_out libpcre2/grep-patfile '3' pcre2grep -c -f "$d/pats" "$d/a.txt" # -M crosses a line boundary, which means the buffer, not just the matcher. printf 'one\ntwo\nthree\n' > "$d/m.txt" check_out libpcre2/grep-multiline 'two' pcre2grep -M 'one\ntwo' "$d/m.txt" # stdin rather than a named file: a different read path in fill_buffer(). check_out libpcre2/grep-stdin 'alpha one' sh -c 'pcre2grep alpha < "$1"' _ "$d/a.txt" # UTF-8 mode: the same Unicode tables the pcre2test cases drove, reached # through the grep front end. printf 'caf\303\251 x\nplain\n' > "$d/u.txt" check_out libpcre2/grep-utf 'caf' pcre2grep -u '\x{e9}' "$d/u.txt" # The exit-status contract — 0 match, 1 no match, 2 error — is what a caller # branches on, so all three are asserted. Reading nonzero as "broken" is the # misreading this guards against. check_rc libpcre2/grep-rc-match 0 pcre2grep 'alpha' "$d/a.txt" check_rc libpcre2/grep-rc-nomatch 1 pcre2grep 'zzzz' "$d/a.txt" check_rc libpcre2/grep-rc-error 2 pcre2grep 'a(' "$d/a.txt" check_out libpcre2/grep-err-text 'missing closing parenthesis' \ sh -c 'pcre2grep "a(" "$1" 2>&1; exit 0' _ "$d/a.txt" # THE FORKING CALLOUT. (?C"prog|arg") makes pcre2grep fork(), execv() the named # program and waitpid() for it, once per match — the only fork+exec path this # port carries, and the reason it is worth having on an arm where that surface # is thin. flex and bison already fork m4 on this image, so this is expected to # work; if it ever stops, this case is the tripwire and the failure is the # platform's, not pcre2's. ec= for c in /usr/bin/echo /bin/echo; do if [ -x "$c" ]; then ec=$c; break; fi done if [ -n "$ec" ]; then check_out libpcre2/grep-callout-fork 'CALLOUT-RAN' \ pcre2grep "alpha(?C\"$ec|CALLOUT-RAN\")" "$d/a.txt" else bst_skip libpcre2/grep-callout-fork "no echo binary on PATH to exec" fi else bst_notok libpcre2/grep-present "pcre2grep not installed (pcre2test is)" fi # ------------------------------------------------------------ the shipped files # Everything above is true of a correctly-built library. This half is what says # the PACKAGING is right — that a future consumer finds the archives, the headers # and the pkg-config metadata where it expects them, and that the distro's # static-only policy actually reached this recipe. inc=$root/usr/include/pcre2.h posixinc=$root/usr/include/pcre2posix.h check libpcre2/hdr-present test -f "$inc" check_out libpcre2/hdr-guard '#define PCRE2_H_IDEMPOTENT_GUARD' cat "$inc" check_out libpcre2/hdr-major '#define PCRE2_MAJOR 10' cat "$inc" check_out libpcre2/hdr-minor '#define PCRE2_MINOR 43' cat "$inc" check libpcre2/hdr-posix test -f "$posixinc" for s in pcre2_regcomp pcre2_regexec pcre2_regerror pcre2_regfree; do check_out "libpcre2/hdr-posix-$s" "$s" cat "$posixinc" done # pcre2.h carries ONE set of declarations and multiplexes them across the three # widths by token-pasting PCRE2_CODE_UNIT_WIDTH onto every name, so the literal # `pcre2_compile_8` is nowhere in the header — it exists only after the # preprocessor has run, and only in the archives. Both halves are asserted, apart: # the header's suffix machinery here, the resulting per-width symbols below. A # header whose macro says pcre2_match_16 against an archive that has no such # symbol is a link error in the consumer, not a compile error, which is precisely # why asserting either half alone would miss it. check_out libpcre2/hdr-suffix-macro '#define PCRE2_SUFFIX(a) PCRE2_GLUE(a,PCRE2_LOCAL_WIDTH)' cat "$inc" for s in pcre2_compile_ pcre2_match_ pcre2_dfa_match_ pcre2_substitute_ \ pcre2_jit_compile_ pcre2_match_data_create_; do check_out "libpcre2/hdr-suffix-$s" "PCRE2_SUFFIX($s)" cat "$inc" done for a in libpcre2-8 libpcre2-16 libpcre2-32 libpcre2-posix; do check "libpcre2/lib-$a" test -s "$root/usr/lib/$a.a" check_out "libpcre2/lib-$a-archive" '!' head -c 8 "$root/usr/lib/$a.a" done # The per-width symbols the header's macros resolve to. Read out of the archive # with tr+grep rather than nm: there is no binutils on the target, and these are # not ELF objects for it to read anyway. flat=$t/flat for w in 8 16 32; do tr -c '\40-\176\n' ' ' < "$root/usr/lib/libpcre2-$w.a" > "$flat" 2>/dev/null for s in compile match dfa_match substitute match_data_create jit_compile \ substring_get_byname pattern_info config; do check "libpcre2/sym-$w-$s" grep -q "pcre2_${s}_$w" "$flat" done # One translation unit per source file, libtool-named. The two that matter most # are the two matchers, since a missing engine object still packages cleanly. for m in pcre2_compile.o pcre2_match.o pcre2_dfa_match.o pcre2_tables.o \ pcre2_chartables.o; do check "libpcre2/member-$w-$m" grep -q "$m" "$flat" done done # The POSIX wrapper is one object exporting four functions, and it is a separate # archive precisely so a consumer of the POSIX API does not drag the native one. tr -c '\40-\176\n' ' ' < "$root/usr/lib/libpcre2-posix.a" > "$flat" 2>/dev/null for s in pcre2_regcomp pcre2_regexec pcre2_regerror pcre2_regfree; do check "libpcre2/sym-posix-$s" grep -q "$s" "$flat" done # The wasm32 arm witness: these are not ELF archives. check libpcre2/arm-not-elf sh -c '! head -c 512 "$1" | grep -q "ELF"' _ "$root/usr/lib/libpcre2-8.a" # Static-only packaging policy. pcre2's configure builds libpcre2-8.so.0 by # default; the distro's blanket --disable-shared is what turns it off, so a stray # .so would mean that flag stopped reaching this recipe. Yocto strips .la files. check libpcre2/no-shared sh -c 'set -- "$1"/usr/lib/libpcre2-8.so*; [ ! -e "$1" ]' _ "$root" check libpcre2/no-la sh -c '[ ! -e "$1/usr/lib/libpcre2-8.la" ]' _ "$root" for p in libpcre2-8 libpcre2-16 libpcre2-32 libpcre2-posix; do pc=$root/usr/lib/pkgconfig/$p.pc check "libpcre2/pc-$p" test -f "$pc" check_out "libpcre2/pc-$p-name" "Name: $p" cat "$pc" check_out "libpcre2/pc-$p-ver" 'Version: 10.43' cat "$pc" done # -DPCRE2_STATIC in Cflags is pcre2's own witness that it was configured static: # it is the define a consumer must compile with so pcre2.h does not declare the # API dllimport/visibility-shared. It appears only in a static build, so this one # assertion ties the .pc back to the distro policy above. check_out libpcre2/pc-static-define '-DPCRE2_STATIC' \ cat "$root/usr/lib/pkgconfig/libpcre2-8.pc" # The posix wrapper's Requires.private is what makes `pkgconf --static --libs # libpcre2-posix` pull -lpcre2-8 in behind -lpcre2-posix; without it a consumer # of the POSIX API links half a library. check_out libpcre2/pc-posix-requires 'Requires.private: libpcre2-8' \ cat "$root/usr/lib/pkgconfig/libpcre2-posix.pc" # pcre2-config is a binconfig-disabled STUB and its contract is to REFUSE — the # recipe inherits that class so nothing on the image resolves flags through a # script instead of pkg-config. Asserting that it answers anything would be # asserting the policy had broken. Its output is captured first because # check_out also requires exit 0 and this script's whole job is to exit 1. cfg=$root/usr/bin/pcre2-config if [ -f "$cfg" ]; then bst_ok libpcre2/cfg-stub-present check_rc libpcre2/cfg-stub-exits-1 1 "$cfg" --version cfgout=$("$cfg" --cflags 2>&1 || true) check_out libpcre2/cfg-stub-says-pkgconfig 'use an alternative such as pkg-config' \ printf '%s\n' "$cfgout" else bst_notok libpcre2/cfg-stub-present "no $cfg (libpcre2-dev not installed?)" fi rm -rf "$t"