unos-repository

APK repository for unos
git clone git://git.finwo.net/misc/unos-repository
Log | Files | Refs | README

test_deps.sh (7534B)


      1 #!/bin/sh
      2 # test_deps.sh - the build-dependency graph and host bootstrap stay honest.
      3 #
      4 # Both of these were gaps that only bite in CI or on a fresh checkout, which is
      5 # exactly when nobody is around to debug them:
      6 #   - build/host/bin/{apk,muon} were built by hand from prose in mk/README.md
      7 #   - hostmakedepends/makedepends were documented but read by nothing
      8 set -eu
      9 
     10 HERE=$(cd "$(dirname "$0")" && pwd)
     11 ROOT=$(cd "${HERE}/../.." && pwd)
     12 . "${ROOT}/tests/helpers.sh"
     13 
     14 echo "==> test_deps: build graph + host bootstrap"
     15 
     16 # --- the graph itself ------------------------------------------------------
     17 out=$("${ROOT}/mk/deps.sh" check 2>&1) || { echo "FAIL: deps.sh check: $out" >&2; exit 1; }
     18 assert_contains "$out" "graph OK" "dependency graph validates (no cycles, no dangling makedepends)"
     19 
     20 # Ordering must be a real topological sort, not just a list. Every package must
     21 # appear exactly once, and every makedepends strictly before its dependent.
     22 order=$("${ROOT}/mk/deps.sh" order)
     23 n_all=$("${ROOT}/mk/deps.sh" list | wc -l | tr -d ' ')
     24 n_ord=$(printf '%s\n' "$order" | wc -l | tr -d ' ')
     25 n_uniq=$(printf '%s\n' "$order" | sort -u | wc -l | tr -d ' ')
     26 assert_eq "$n_ord" "$n_all" "every package appears in the build order"
     27 assert_eq "$n_uniq" "$n_all" "no package appears twice in the build order"
     28 
     29 # This is the property that actually matters, and the one a naive recursive
     30 # shell function gets wrong (no function-local variables in POSIX sh).
     31 fail=0
     32 "${ROOT}/mk/deps.sh" graph | while IFS="$(printf '\t')" read -r pkg dep; do
     33   [ -n "$pkg" ] || continue
     34   pos_pkg=$(printf '%s\n' "$order" | grep -nxF "$pkg" | cut -d: -f1)
     35   pos_dep=$(printf '%s\n' "$order" | grep -nxF "$dep" | cut -d: -f1)
     36   if [ -z "$pos_dep" ] || [ -z "$pos_pkg" ] || [ "$pos_dep" -ge "$pos_pkg" ]; then
     37     echo "FAIL: $dep must be built before $pkg (positions $pos_dep vs $pos_pkg)" >&2
     38     exit 1
     39   fi
     40 done || fail=1
     41 [ "$fail" = "0" ] || exit 1
     42 echo "PASS: every makedepends is ordered before its dependent"
     43 
     44 # The graph must not be vacuously empty -- that was the original bug report.
     45 edges=$("${ROOT}/mk/deps.sh" graph | wc -l | tr -d ' ')
     46 if [ "$edges" -lt 5 ]; then
     47   echo "FAIL: dependency graph has only ${edges} edges; templates are not declaring makedepends" >&2
     48   exit 1
     49 fi
     50 echo "PASS: graph is populated (${edges} edges)"
     51 
     52 # --- host bootstrap --------------------------------------------------------
     53 [ -x "${ROOT}/mk/bootstrap-host.sh" ] || { echo "FAIL: mk/bootstrap-host.sh missing" >&2; exit 1; }
     54 out=$("${ROOT}/mk/bootstrap-host.sh" --check 2>&1) || { echo "FAIL: host tools not bootstrapped: $out" >&2; exit 1; }
     55 assert_contains "$out" "host tools present and pinned" "bootstrap-host.sh --check passes"
     56 
     57 # --check must verify VERSION, not just existence: a stale binary from an older
     58 # pin is worse than a missing one because everything downstream still "works".
     59 assert_file_contains "${ROOT}/mk/bootstrap-host.sh" "MUON_SHA256" "muon is pinned by sha256"
     60 assert_file_contains "${ROOT}/mk/bootstrap-host.sh" "APK_SHA256" "apk-tools is pinned by sha256"
     61 
     62 # The bootstrap must not reach the network beyond its pinned tarballs. muon's
     63 # setup git-clones meson-docs/meson-tests unless they are disabled.
     64 assert_file_contains "${ROOT}/mk/bootstrap-host.sh" "-Dmeson-docs=disabled" "muon docs subproject disabled (no build-time git fetch)"
     65 assert_file_contains "${ROOT}/mk/bootstrap-host.sh" "-Dmeson-tests=disabled" "muon tests subproject disabled (no build-time git fetch)"
     66 
     67 # --- the CI pipeline is generated from that same graph ---------------------
     68 # .conductor.yml carries ~30 tasks and their `needs` edges. Hand-maintaining
     69 # those against packages/*/template drifts, and the failure mode is a package
     70 # building before the library it links against -- which usually still succeeds,
     71 # quietly, against a stale copy.
     72 [ -f "${ROOT}/.conductor.yml" ] || { echo "FAIL: no .conductor.yml" >&2; exit 1; }
     73 out=$("${ROOT}/mk/gen-pipeline.sh" --check 2>&1) || {
     74   echo "FAIL: .conductor.yml is out of date; run ./mk/gen-pipeline.sh --write" >&2
     75   echo "$out" | head -20 >&2
     76   exit 1
     77 }
     78 echo "PASS: .conductor.yml matches the dependency graph"
     79 
     80 # Every package must have a task, or a push would silently not build it.
     81 npkg=$("${ROOT}/mk/deps.sh" list | wc -l | tr -d ' ')
     82 ntask=$(grep -c '^  pkg-' "${ROOT}/.conductor.yml")
     83 assert_eq "$ntask" "$npkg" "every package has a pipeline task"
     84 
     85 # The credentials the runner provides, and nothing invented alongside them.
     86 assert_file_contains "${ROOT}/.conductor.yml" "BUCKET_ENDPOINT" "pipeline uses BUCKET_ENDPOINT"
     87 assert_file_contains "${ROOT}/.conductor.yml" "BUCKET_NAME" "pipeline uses BUCKET_NAME"
     88 if grep -q 'REPO_URL' "${ROOT}/.conductor.yml"; then
     89   echo "FAIL: pipeline references REPO_URL, which the runner does not provide" >&2
     90   exit 1
     91 fi
     92 echo "PASS: pipeline references only provided environment"
     93 
     94 # $ARCH is only injected into tasks that declare arch; using it without is an
     95 # empty string and a silently wrong repo URL.
     96 if grep -q '\$ARCH' "${ROOT}/.conductor.yml"; then
     97   nuse=$(grep -c '\$ARCH' "${ROOT}/.conductor.yml")
     98   narch=$(grep -c '^    arch: \[' "${ROOT}/.conductor.yml")
     99   [ "$narch" -gt 0 ] || { echo "FAIL: pipeline uses \$ARCH but no task declares arch" >&2; exit 1; }
    100   echo "PASS: \$ARCH used in ${nuse} places, ${narch} tasks declare arch"
    101 fi
    102 
    103 # Feature names are a global namespace on the conductor, which hosts more than
    104 # UNOS, so ours are project-scoped. A task asking for a feature no worker
    105 # advertises is not scheduled at all -- it does not fail loudly, it simply
    106 # never runs, which is far harder to diagnose than a build error.
    107 if grep -q 'requires:.*[][ ]sign-key[],]' "${ROOT}/.conductor.yml"; then
    108   echo "FAIL: pipeline requires bare 'sign-key'; the feature is 'unos-sign-key'" >&2
    109   grep -n 'sign-key' "${ROOT}/.conductor.yml" | head -5 >&2
    110   exit 1
    111 fi
    112 nsign=$(grep -c 'requires: \[unos-sign-key\]' "${ROOT}/.conductor.yml")
    113 [ "${nsign}" -gt 0 ] || { echo "FAIL: no task requires unos-sign-key" >&2; exit 1; }
    114 echo "PASS: ${nsign} tasks require the project-scoped unos-sign-key feature"
    115 
    116 # Every task that signs must ask for the key. Building without it fails late,
    117 # after the whole package has compiled.
    118 nbuild=$(grep -c 'mk/build.sh' "${ROOT}/.conductor.yml")
    119 [ "${nsign}" -ge "${nbuild}" ] || {
    120   echo "FAIL: ${nbuild} tasks build but only ${nsign} require the signing key" >&2
    121   exit 1
    122 }
    123 echo "PASS: every building task requires the signing key"
    124 
    125 # --- build.sh actually reads the declarations ------------------------------
    126 assert_file_contains "${ROOT}/mk/build.sh" "hostmakedepends" "build.sh reads hostmakedepends"
    127 assert_file_contains "${ROOT}/mk/build.sh" "makedepends" "build.sh reads makedepends"
    128 
    129 # PATH ordering trap: hostmakedepends is checked before the build phases, so
    130 # build/host/bin must already be on PATH or a package declaring `muon` fails
    131 # its own check despite muon being present.
    132 # Anchor on the executable check (`command -v "${_h}"`), not on the word
    133 # hostmakedepends, which also appears in comments above it.
    134 pathline=$(grep -n 'export PATH=.*build/host/bin' "${ROOT}/mk/build.sh" | head -1 | cut -d: -f1)
    135 checkline=$(grep -n 'command -v "\${_h}"' "${ROOT}/mk/build.sh" | head -1 | cut -d: -f1)
    136 if [ -z "$pathline" ] || [ -z "$checkline" ] || [ "$pathline" -ge "$checkline" ]; then
    137   echo "FAIL: build/host/bin must be on PATH before hostmakedepends is checked (PATH line $pathline, check line $checkline)" >&2
    138   exit 1
    139 fi
    140 echo "PASS: host tools are on PATH before hostmakedepends is checked"
    141 
    142 echo "==> test_deps done"