unos-repository

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

test_deps.sh (9176B)


      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 # Hand-maintaining the `needs` edges drifts, and the failure mode is a package
     69 # building against a stale copy of what it links against -- quietly, and still
     70 # succeeding.
     71 [ -f "${ROOT}/.conductor.yml" ] || { echo "FAIL: no .conductor.yml" >&2; exit 1; }
     72 out=$("${ROOT}/mk/gen-pipeline.sh" --check 2>&1) || {
     73   echo "FAIL: .conductor.yml is out of date; run ./mk/gen-pipeline.sh --write" >&2
     74   echo "$out" | head -20 >&2
     75   exit 1
     76 }
     77 echo "PASS: .conductor.yml matches the dependency graph"
     78 
     79 # Every package of every published channel must have a task, or a push would
     80 # silently not build it.
     81 expected=0
     82 for c in $("${ROOT}/mk/deps.sh" channels); do
     83   n=$("${ROOT}/mk/deps.sh" --channel "$c" list | wc -l | tr -d ' ')
     84   expected=$((expected + n))
     85   ntask=$(grep -c "^  pkg-${c}-" "${ROOT}/.conductor.yml")
     86   assert_eq "$ntask" "$n" "every ${c} package has a pipeline task"
     87   grep -q "^  index-${c}:" "${ROOT}/.conductor.yml" \
     88     && echo "PASS: ${c} has an index task" \
     89     || { echo "FAIL: ${c} has no index task" >&2; exit 1; }
     90 done
     91 ntask=$(grep -c '^  pkg-' "${ROOT}/.conductor.yml")
     92 assert_eq "$ntask" "$expected" "no pipeline task outside a published channel"
     93 
     94 # --- builder image -----------------------------------------------------------
     95 # A stale tag is invisible: tasks keep running on the previously pushed image
     96 # and only fail once something actually needs the missing tool.
     97 read -r bver < "${ROOT}/builder/VERSION"
     98 assert_file_contains "${ROOT}/.conductor.yml" "image: finwo/unos-builder:${bver}" \
     99   "pipeline default image matches builder/VERSION"
    100 
    101 grep -q '^  builder:' "${ROOT}/.conductor.yml" \
    102   || { echo "FAIL: no builder task" >&2; exit 1; }
    103 echo "PASS: pipeline has a builder task"
    104 
    105 grep -q '^      - builder$' "${ROOT}/.conductor.yml" \
    106   || { echo "FAIL: check does not depend on builder" >&2; exit 1; }
    107 echo "PASS: check runs after builder"
    108 
    109 # The image bakes the toolchain; installing it again per task is the thing the
    110 # builder exists to avoid.
    111 if grep -q -- '- \./mk/ci-setup\.sh$' "${ROOT}/.conductor.yml"; then
    112   echo "FAIL: a task installs the toolchain instead of using the builder image" >&2
    113   grep -n -- '- \./mk/ci-setup\.sh$' "${ROOT}/.conductor.yml" | head -5 >&2
    114   exit 1
    115 fi
    116 echo "PASS: no task reinstalls the toolchain"
    117 
    118 assert_file_contains "${ROOT}/builder/Dockerfile" "ci-setup.sh --install-only" \
    119   "builder image is built from mk/ci-setup.sh, not a copy of its apt list"
    120 
    121 # The credentials the runner provides, and nothing invented alongside them.
    122 # The rest of the BUCKET_* set is read inside publish.sh and bootstrap-repo.sh,
    123 # which the pipeline calls but does not parameterise.
    124 assert_file_contains "${ROOT}/.conductor.yml" "BUCKET_PUBLIC_URL" "pipeline uses BUCKET_PUBLIC_URL"
    125 if grep -q 'REPO_URL' "${ROOT}/.conductor.yml"; then
    126   echo "FAIL: pipeline references REPO_URL, which the runner does not provide" >&2
    127   exit 1
    128 fi
    129 echo "PASS: pipeline references only provided environment"
    130 
    131 # $ARCH is only injected into tasks that declare arch; using it without is an
    132 # empty string and a silently wrong repo URL.
    133 if grep -q '\$ARCH' "${ROOT}/.conductor.yml"; then
    134   nuse=$(grep -c '\$ARCH' "${ROOT}/.conductor.yml")
    135   narch=$(grep -c '^    arch: \[' "${ROOT}/.conductor.yml")
    136   [ "$narch" -gt 0 ] || { echo "FAIL: pipeline uses \$ARCH but no task declares arch" >&2; exit 1; }
    137   echo "PASS: \$ARCH used in ${nuse} places, ${narch} tasks declare arch"
    138 fi
    139 
    140 # Feature names are a global namespace on the conductor, which hosts more than
    141 # UNOS, so ours are project-scoped. A task asking for a feature no worker
    142 # advertises is not scheduled at all -- it does not fail loudly, it simply
    143 # never runs, which is far harder to diagnose than a build error.
    144 if grep -q 'requires:.*[][ ]sign-key[],]' "${ROOT}/.conductor.yml"; then
    145   echo "FAIL: pipeline requires bare 'sign-key'; the feature is 'unos-sign-key'" >&2
    146   grep -n 'sign-key' "${ROOT}/.conductor.yml" | head -5 >&2
    147   exit 1
    148 fi
    149 nsign=$(grep -c 'requires: \[unos-sign-key\]' "${ROOT}/.conductor.yml")
    150 [ "${nsign}" -gt 0 ] || { echo "FAIL: no task requires unos-sign-key" >&2; exit 1; }
    151 echo "PASS: ${nsign} tasks require the project-scoped unos-sign-key feature"
    152 
    153 # Every task that signs must ask for the key. Building without it fails late,
    154 # after the whole package has compiled.
    155 nbuild=$(grep -c 'mk/build.sh' "${ROOT}/.conductor.yml")
    156 [ "${nsign}" -ge "${nbuild}" ] || {
    157   echo "FAIL: ${nbuild} tasks build but only ${nsign} require the signing key" >&2
    158   exit 1
    159 }
    160 echo "PASS: every building task requires the signing key"
    161 
    162 # --- build.sh actually reads the declarations ------------------------------
    163 assert_file_contains "${ROOT}/mk/build.sh" "hostmakedepends" "build.sh reads hostmakedepends"
    164 assert_file_contains "${ROOT}/mk/build.sh" "makedepends" "build.sh reads makedepends"
    165 
    166 # PATH ordering trap: hostmakedepends is checked before the build phases, so
    167 # build/host/bin must already be on PATH or a package declaring `muon` fails
    168 # its own check despite muon being present.
    169 # Anchor on the executable check (`command -v "${_h}"`), not on the word
    170 # hostmakedepends, which also appears in comments above it.
    171 pathline=$(grep -n 'export PATH=.*build/host/bin' "${ROOT}/mk/build.sh" | head -1 | cut -d: -f1)
    172 checkline=$(grep -n 'command -v "\${_h}"' "${ROOT}/mk/build.sh" | head -1 | cut -d: -f1)
    173 if [ -z "$pathline" ] || [ -z "$checkline" ] || [ "$pathline" -ge "$checkline" ]; then
    174   echo "FAIL: build/host/bin must be on PATH before hostmakedepends is checked (PATH line $pathline, check line $checkline)" >&2
    175   exit 1
    176 fi
    177 echo "PASS: host tools are on PATH before hostmakedepends is checked"
    178 
    179 echo "==> test_deps done"