# pkgconf -- the pkg-config implementation: reads the .pc metadata a library # installs and prints the -I/-L/-l fragments a build needs to compile and link # against it. # # The suite is in three parts and the split is deliberate. The PAYLOAD half # asserts what the package actually put on the target. The DEFAULT-PATH half runs # pkgconf with no environment set at all, against whatever .pc files the image's # -dev packages really ship -- the only way to prove the recipe's compiled-in # --with-pkg-config-dir reached the binary rather than a search path the test # handed it. Everything after that runs against a HERMETIC fixture tree selected # with PKG_CONFIG_LIBDIR, so the semantic assertions (variable interpolation, # Requires resolution, static vs shared, system-directory filtering) keep their # meaning when a future ticket adds or drops a -dev package. # # Pass criteria were derived from a native pkgconf 2.1.1 built from the same # upstream tarball and configured with the same --with-pkg-config-dir, # --with-system-libdir and --with-system-includedir the recipe uses, then dry-run # under dash. Where upstream's answer was surprising it is pinned rather than # smoothed over -- see the system-directory and sysroot sections. . "$BST_LIB" require pkgconf pkgconf/present # Assert against text already captured, so one pkgconf run can carry several # assertions. The suite makes ~40 execs to produce ~75 cases, which keeps it # inside the runner's per-suite wall-clock bound with margin. has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want:'$2' got:'$(_bst_1line "$3")'" ;; esac } hasnt() { case "$3" in *"$2"*) bst_notok "$1" "unwanted:'$2' in '$(_bst_1line "$3")'" ;; *) bst_ok "$1" ;; esac } # --------------------------------------------------------------------------- # 1. The shipped payload. # --------------------------------------------------------------------------- check pkgconf/bin-present test -x /usr/bin/pkgconf check pkgconf/bin-nonempty test -s /usr/bin/pkgconf # wasm module magic is \0 a s m; command substitution drops the NUL, tr makes # that explicit rather than incidental. check_eq pkgconf/bin-wasm "asm" "$(head -c 4 /usr/bin/pkgconf | tr -d '\000')" check_out pkgconf/version "2.1.1" pkgconf --version # The recipe installs its own /usr/bin/pkg-config on top of pkgconf: a two-line # /bin/sh wrapper that unsets PKG_CONFIG_SYSROOT_DIR for --variable queries (real # pkg-config does not sysroot-prefix user variables) and then execs pkgconf. Both # halves are asserted -- the script's presence here, its behaviour in section 4. check pkgconf/wrapper-present test -x /usr/bin/pkg-config check pkgconf/wrapper-is-script grep -q '^#!/bin/sh' /usr/bin/pkg-config check pkgconf/wrapper-execs-pkgconf grep -q 'exec pkgconf' /usr/bin/pkg-config # bomtool is upstream's second binary (bin_PROGRAMS = pkgconf bomtool) and ships # in the same package; it writes an SPDX bill of materials for a dependency tree. check pkgconf/bomtool-present test -x /usr/bin/bomtool # pkg.m4 is the autoconf macro PKG_CHECK_MODULES expands from. The recipe moves # ${datadir}/aclocal OUT of -dev and into the main package on purpose, so it is # on the target even though no -dev package is installed here. check pkgconf/pkgm4-present test -f /usr/share/aclocal/pkg.m4 check pkgconf/pkgm4-check-modules grep -q 'AC_DEFUN(\[PKG_CHECK_MODULES\]' /usr/share/aclocal/pkg.m4 check pkgconf/pkgm4-prog-check grep -q 'PKG_PROG_PKG_CONFIG' /usr/share/aclocal/pkg.m4 # Static-only is distro policy and libpkgconf is a libtool library, so a stray # .so or .la here would mean the distro's --disable-shared override was lost. check pkgconf/no-shared sh -c 'set -- /usr/lib/libpkgconf.so*; [ ! -e "$1" ]' check pkgconf/no-libtool test ! -e /usr/lib/libpkgconf.la # ...and pkgconf-dev / pkgconf-staticdev are deliberately NOT installed: there is # no compiler on the target to consume libpkgconf's headers or its archive. check pkgconf/no-dev-header test ! -e /usr/include/pkgconf/libpkgconf/libpkgconf.h check pkgconf/no-staticdev test ! -e /usr/lib/libpkgconf.a # --------------------------------------------------------------------------- # 2. The recipe's configure-time paths reached the binary. # # --dump-personality prints the three compiled-in path sets. This is the one # assertion that pins EXTRA_OECONF: get --with-pkg-config-dir wrong and pkgconf # still builds, still runs, and silently finds nothing. # --------------------------------------------------------------------------- pers=$(pkgconf --dump-personality 2>&1) has pkgconf/personality-triplet "Triplet: default" "$pers" has pkgconf/searchpath-libdir "/usr/lib/pkgconfig" "$pers" has pkgconf/searchpath-datadir "/usr/share/pkgconfig" "$pers" has pkgconf/system-includedir "SystemIncludePaths: /usr/include" "$pers" has pkgconf/system-libdir "SystemLibraryPaths: /usr/lib" "$pers" # --------------------------------------------------------------------------- # 3. The default search path, against the image's own .pc corpus. # # No PKG_CONFIG_PATH, no PKG_CONFIG_LIBDIR -- the packages below are found only # because both directories are compiled in. Which packages ship a .pc file is a # property of the image's -dev set and moves between tickets, so each is picked # out of the directory rather than named, and its expected version is read from # the file itself. # --------------------------------------------------------------------------- libpc=$(ls /usr/lib/pkgconfig/*.pc 2>/dev/null | head -1) datapc=$(ls /usr/share/pkgconfig/*.pc 2>/dev/null | head -1) if [ -n "$libpc" ]; then n=$(basename "$libpc" .pc) v=$(sed -n 's/^Version:[[:space:]]*//p' "$libpc" | head -1) check pkgconf/default-libdir-exists pkgconf --exists "$n" check_eq pkgconf/default-libdir-modversion "$v" "$(pkgconf --modversion "$n" 2>&1)" check_eq pkgconf/default-libdir-path "$libpc" "$(pkgconf --path "$n" 2>&1)" has pkgconf/default-libdir-listed "$n" "$(pkgconf --list-package-names 2>&1)" else bst_skip pkgconf/default-libdir-exists "no .pc files under /usr/lib/pkgconfig on this image" fi if [ -n "$datapc" ]; then n=$(basename "$datapc" .pc) v=$(sed -n 's/^Version:[[:space:]]*//p' "$datapc" | head -1) check pkgconf/default-datadir-exists pkgconf --exists "$n" check_eq pkgconf/default-datadir-modversion "$v" "$(pkgconf --modversion "$n" 2>&1)" check_eq pkgconf/default-datadir-path "$datapc" "$(pkgconf --path "$n" 2>&1)" else bst_skip pkgconf/default-datadir-exists "no .pc files under /usr/share/pkgconfig on this image" fi # --------------------------------------------------------------------------- # 4. Semantics, against a hermetic fixture tree. # # PKG_CONFIG_LIBDIR REPLACES the default search path (PKG_CONFIG_PATH would only # prepend to it), so from here on pkgconf sees exactly these six .pc files and # nothing the image happens to install. # --------------------------------------------------------------------------- t=${TMPDIR:-/tmp}/bst-pkgconf.$$ mkdir -p "$t" || { bst_notok pkgconf/fixture-setup "cannot create $t"; exit 0; } # Variable chain prefix -> exec_prefix -> libdir, a non-path variable, and a # Libs.private that must appear only under --static. cat > "$t/bstbase.pc" <<'EOF' prefix=/opt/bst exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include answer=42 Name: bstbase Description: BST base fixture URL: https://example.invalid/bstbase Version: 1.4.2 Cflags: -I${includedir}/bstbase -DBST_BASE=1 Libs: -L${libdir} -lbstbase Libs.private: -lbstextra -lm EOF # A public Requires with a version comparator: its cflags and libs must be # pulled into the dependent's output in both linking modes. cat > "$t/bstdep.pc" <<'EOF' prefix=/opt/bst libdir=${prefix}/lib includedir=${prefix}/include Name: bstdep Description: BST dependent fixture Version: 2.0 Requires: bstbase >= 1.0 Cflags: -I${includedir}/bstdep Libs: -L${libdir} -lbstdep EOF # A Requires.private: invisible to a shared link, pulled in for a static one. cat > "$t/bstpriv.pc" <<'EOF' prefix=/opt/bst libdir=${prefix}/lib Name: bstpriv Description: BST private-requires fixture Version: 0.9 Requires.private: bstbase Libs: -L${libdir} -lbstpriv EOF # Flags naming the system directories, to exercise the filter. cat > "$t/bstsys.pc" <<'EOF' Name: bstsys Description: BST system-dir fixture Version: 3.1 Cflags: -I/usr/include -DBST_SYS=1 Libs: -L/usr/lib -lbstsys EOF # An unresolvable Requires, for --validate's negative case. cat > "$t/bstbroken.pc" <<'EOF' Name: bstbroken Description: BST unresolvable-requires fixture Version: 1.0 Requires: bstnosuch EOF # Name and Description but no Version: not a loadable package. cat > "$t/bstnover.pc" <<'EOF' Name: bstnover Description: BST fixture missing the mandatory Version field EOF PKG_CONFIG_LIBDIR="$t"; export PKG_CONFIG_LIBDIR # --- the metadata a caller reads ------------------------------------------ check_eq pkgconf/modversion "1.4.2" "$(pkgconf --modversion bstbase 2>&1)" check_eq pkgconf/modversion-multi "1.4.2 2.0" "$(pkgconf --modversion bstbase bstdep 2>&1)" check_eq pkgconf/print-provides "bstbase = 1.4.2" "$(pkgconf --print-provides bstbase 2>&1)" check_eq pkgconf/license "bstbase: NOASSERTION" "$(pkgconf --license bstbase 2>&1)" check_eq pkgconf/path "$t/bstbase.pc" "$(pkgconf --path bstbase 2>&1)" # --- compiler and linker fragments ---------------------------------------- # The whole string is pinned, not a substring: fragment ORDER is part of a link # line's meaning, and a renderer that emitted the right pieces in the wrong order # would pass a contains-check while producing an unlinkable command. check_eq pkgconf/cflags "-I/opt/bst/include/bstbase -DBST_BASE=1" "$(pkgconf --cflags bstbase 2>&1)" check_eq pkgconf/cflags-only-I "-I/opt/bst/include/bstbase" "$(pkgconf --cflags-only-I bstbase 2>&1)" check_eq pkgconf/cflags-only-other "-DBST_BASE=1" "$(pkgconf --cflags-only-other bstbase 2>&1)" check_eq pkgconf/libs "-L/opt/bst/lib -lbstbase" "$(pkgconf --libs bstbase 2>&1)" check_eq pkgconf/libs-only-L "-L/opt/bst/lib" "$(pkgconf --libs-only-L bstbase 2>&1)" check_eq pkgconf/libs-only-l "-lbstbase" "$(pkgconf --libs-only-l bstbase 2>&1)" check_eq pkgconf/msvc-syntax "/libpath:/opt/bst/lib bstbase.lib " "$(pkgconf --msvc-syntax --libs bstbase 2>&1)" check_eq pkgconf/fragment-filter "-L/opt/bst/lib" "$(pkgconf --fragment-filter=L --libs bstbase 2>&1)" # --- variable interpolation ------------------------------------------------ # libdir is written as ${exec_prefix}/lib and exec_prefix as ${prefix}, so this # is a two-level expansion, not a lookup. check_eq pkgconf/variable-chain "/opt/bst/lib" "$(pkgconf --variable=libdir bstbase 2>&1)" check_eq pkgconf/variable-plain "42" "$(pkgconf --variable=answer bstbase 2>&1)" check_eq pkgconf/define-variable "/tmp/xx/lib" "$(pkgconf --define-variable=prefix=/tmp/xx --variable=libdir bstbase 2>&1)" vars=$(pkgconf --print-variables bstbase 2>&1) has pkgconf/print-variables-declared "answer" "$vars" has pkgconf/print-variables-chain "exec_prefix" "$vars" # pcfiledir is synthesised by pkgconf itself, not declared in the file -- it is # how a relocatable .pc refers to its own directory. has pkgconf/print-variables-pcfiledir "pcfiledir" "$vars" # --- Requires resolution --------------------------------------------------- depc=$(pkgconf --cflags bstdep 2>&1) has pkgconf/requires-cflags-own "-I/opt/bst/include/bstdep" "$depc" has pkgconf/requires-cflags-dep "-I/opt/bst/include/bstbase" "$depc" has pkgconf/requires-cflags-dep-define "-DBST_BASE=1" "$depc" check_eq pkgconf/requires-libs "-L/opt/bst/lib -lbstdep -lbstbase" "$(pkgconf --libs bstdep 2>&1)" # The comparator survives into --print-requires; a caller reading dependency # metadata needs the constraint, not just the name. check_eq pkgconf/print-requires "bstbase >= 1.0" "$(pkgconf --print-requires bstdep 2>&1)" # -L is emitted once even though both packages declare the same libdir. check_eq pkgconf/dedup-ldpath "-L/opt/bst/lib" "$(pkgconf --libs-only-L bstdep 2>&1)" # --maximum-traverse-depth=1 stops at the direct dependency. check_eq pkgconf/traverse-depth "-L/opt/bst/lib -lbstdep" "$(pkgconf --maximum-traverse-depth=1 --libs bstdep 2>&1)" # --- static vs shared ------------------------------------------------------ # This is the distinction that matters most on this image: everything here is # statically linked, so a caller that forgot --static would silently drop the # transitive libraries a static link needs. shl=$(pkgconf --libs bstbase 2>&1) hasnt pkgconf/shared-omits-private "-lm" "$shl" stl=$(pkgconf --libs --static bstbase 2>&1) has pkgconf/static-adds-private-lib "-lbstextra" "$stl" has pkgconf/static-adds-private-libm "-lm" "$stl" prl=$(pkgconf --libs bstpriv 2>&1) hasnt pkgconf/shared-omits-private-req "-lbstbase" "$prl" prs=$(pkgconf --libs --static bstpriv 2>&1) has pkgconf/static-adds-private-req "-lbstbase" "$prs" check_eq pkgconf/print-requires-private "bstbase" "$(pkgconf --print-requires-private bstpriv 2>&1)" # --- system-directory filtering ------------------------------------------- # -I/usr/include and -L/usr/lib are dropped as system directories, so EMPTY # --cflags for a header-only library installed under /usr is the correct answer # and must not be read as a lookup failure. The --keep-system-* pair restores # them, which is the only way to tell filtering from a missing fragment. check_eq pkgconf/sysdir-cflags-filtered "-DBST_SYS=1" "$(pkgconf --cflags bstsys 2>&1)" check_eq pkgconf/sysdir-libs-filtered "-lbstsys" "$(pkgconf --libs bstsys 2>&1)" check_eq pkgconf/keep-system-cflags "-I/usr/include -DBST_SYS=1" "$(pkgconf --keep-system-cflags --cflags bstsys 2>&1)" check_eq pkgconf/keep-system-libs "-L/usr/lib -lbstsys" "$(pkgconf --keep-system-libs --libs bstsys 2>&1)" # --- existence and version predicates -------------------------------------- check pkgconf/exists pkgconf --exists bstbase check_rc pkgconf/exists-missing 1 pkgconf --exists bstnosuch # --exists reports by exit status ALONE: it prints nothing at all unless # --print-errors is given. A caller that greps its output instead of testing $? # sees the same empty string for both answers. check_eq pkgconf/exists-silent "" "$(pkgconf --exists bstnosuch 2>&1)" has pkgconf/print-errors "was not found in the pkg-config search path" "$(pkgconf --print-errors --exists bstnosuch 2>&1)" check pkgconf/atleast-version pkgconf --atleast-version=1.0 bstbase check_rc pkgconf/atleast-version-no 1 pkgconf --atleast-version=9.0 bstbase check pkgconf/exact-version pkgconf --exact-version=1.4.2 bstbase check_rc pkgconf/max-version-no 1 pkgconf --max-version=1.0 bstbase check pkgconf/atleast-pkgconfig-version pkgconf --atleast-pkgconfig-version=0.29 check_rc pkgconf/atleast-pkgconfig-version-no 1 pkgconf --atleast-pkgconfig-version=99.0 # Name and Description are not enough: a .pc with no Version is not a package. check_rc pkgconf/no-version-rejected 1 pkgconf --exists bstnover # --- enumeration and validation -------------------------------------------- names=$(pkgconf --list-package-names 2>&1) has pkgconf/list-names "bstbase" "$names" has pkgconf/list-names-all "bstpriv" "$names" has pkgconf/list-all-desc "BST base fixture" "$(pkgconf --list-all 2>&1)" check pkgconf/validate pkgconf --validate bstdep check_rc pkgconf/validate-broken 1 pkgconf --validate bstbroken # --- graph reporting ------------------------------------------------------- sim=$(pkgconf --simulate bstdep 2>&1) has pkgconf/simulate-node "node 'bstbase'" "$sim" has pkgconf/simulate-comparator "comparator = '>='" "$sim" has pkgconf/digraph "\"bstdep\" -> \"bstbase\"" "$(pkgconf --digraph bstdep 2>&1)" sol=$(pkgconf --solution bstdep 2>&1) has pkgconf/solution-root "virtual:world" "$sol" has pkgconf/solution-dep "bstbase" "$sol" # --- error reporting ------------------------------------------------------- # The three error-routing switches, each read from stdout ALONE -- capturing # 2>&1 would make every one of them look identical. check_eq pkgconf/errors-on-stderr "" "$(pkgconf --cflags bstnosuch 2>/dev/null)" has pkgconf/errors-to-stdout "was not found" "$(pkgconf --errors-to-stdout --cflags bstnosuch 2>/dev/null)" check_eq pkgconf/silence-errors "" "$(pkgconf --silence-errors --cflags bstnosuch 2>&1)" short=$(pkgconf --short-errors --cflags bstnosuch 2>&1) has pkgconf/short-errors-keeps-summary "Package 'bstnosuch', required by" "$short" hasnt pkgconf/short-errors-drops-hint "PKG_CONFIG_PATH environment" "$short" check_rc pkgconf/missing-rc 1 pkgconf --cflags bstnosuch check_rc pkgconf/unknown-option 1 pkgconf --no-such-option bstbase has pkgconf/unknown-option-msg "unknown option" "$(pkgconf --no-such-option bstbase 2>&1)" check_rc pkgconf/no-args 1 pkgconf has pkgconf/no-args-msg "Please specify at least one package name" "$(pkgconf 2>&1)" # --env-only looks in PKG_CONFIG_PATH only, so the fixtures reached through # PKG_CONFIG_LIBDIR become invisible -- proof the two variables are distinct # search-path mechanisms and not aliases. check_rc pkgconf/env-only 1 pkgconf --env-only --exists bstbase # --------------------------------------------------------------------------- # 5. Sysroot prefixing, and the wrapper that exists to suppress it. # # PKG_CONFIG_SYSROOT_DIR prefixes every path fragment, INCLUDING a --variable # query. Real pkg-config does not do the latter, so the recipe ships a # /usr/bin/pkg-config wrapper that unsets the variable for --variable and execs # pkgconf. The pair below is the wrapper's entire reason to exist: same query, # same environment, two different answers. # --------------------------------------------------------------------------- PKG_CONFIG_SYSROOT_DIR=/sysroot; export PKG_CONFIG_SYSROOT_DIR has pkgconf/sysroot-cflags "-I/sysroot/opt/bst/include/bstbase" "$(pkgconf --cflags bstdep 2>&1)" check_eq pkgconf/sysroot-variable "/sysroot/opt/bst/lib" "$(pkgconf --variable=libdir bstbase 2>&1)" check_eq pkgconf/wrapper-variable-no-sysroot "/opt/bst/lib" "$(pkg-config --variable=libdir bstbase 2>&1)" # ...and the wrapper does NOT touch the sysroot for a normal flags query. has pkgconf/wrapper-cflags-keeps-sysroot "-I/sysroot/opt/bst/include/bstbase" "$(pkg-config --cflags bstdep 2>&1)" unset PKG_CONFIG_SYSROOT_DIR # The wrapper is on PATH as pkg-config and reaches pkgconf through PATH too, so # these also prove /usr/bin is resolvable for an exec'd helper on this target. check_eq pkgconf/wrapper-modversion "1.4.2" "$(pkg-config --modversion bstbase 2>&1)" check_eq pkgconf/wrapper-cflags "-I/opt/bst/include/bstdep -I/opt/bst/include/bstbase -DBST_BASE=1" "$(pkg-config --cflags bstdep 2>&1)" check_rc pkgconf/wrapper-missing 1 pkg-config --exists bstnosuch # --------------------------------------------------------------------------- # 6. bomtool -- the second binary in the package. # --------------------------------------------------------------------------- check_out pkgconf/bomtool-version "bomtool 2.1.1" bomtool --version bom=$(bomtool bstdep 2>&1) has pkgconf/bomtool-spdx-version "SPDXVersion: SPDX-2.2" "$bom" has pkgconf/bomtool-package "PackageName: bstdep@2.0" "$bom" has pkgconf/bomtool-transitive "PackageName: bstbase@1.4.2" "$bom" has pkgconf/bomtool-relationship "DEPENDS_ON" "$bom" rm -rf "$t"