diff --git a/build/mk-bootimg.sh b/build/mk-bootimg.sh index 77d1071..facb8b5 100755 --- a/build/mk-bootimg.sh +++ b/build/mk-bootimg.sh @@ -159,14 +159,36 @@ ITS # already 512-aligns, so omitting the flag there is correct rather than a # fallback. project/build.sh prepends the SDK tool dir to PATH, so that # binary IS what a build inside the SDK environment resolves. -if mkimage 2>&1 | grep -q -- '-B '; then - _align_flag="-B 0x200" -else - _align_flag="" - echo "== mkimage has no -B (vendor 2017.09); it 512-aligns natively" +# A -B-capable mkimage is REQUIRED, not preferred. The SDK vendors 2017.09, +# which has no -B, and project/build.sh:64 puts it first on PATH -- so the +# wrong one is what a build inside the SDK environment picks up. Measured: +# that binary produces metadata of 1064 bytes here, not a multiple of 512, +# so FIT_ALIGN rounds it up and every payload is read late. It does NOT +# 512-align this .its natively. Rather than silently emit an image that +# fails on a panel, pick the first mkimage on PATH that supports -B and say +# plainly when there is none. +# >>> mkimage-probe (extracted verbatim by tests/mk-bootimg/run-probe-tests.sh) +MKIMAGE="" +_IFS_SAVE="$IFS"; IFS=: +for _d in $PATH; do + _c="$_d/mkimage" + [ -x "$_c" ] || continue + # mkimage with no args prints its usage and exits non-zero; under the + # pipefail set at the top of this file that failure would sink the whole + # pipeline, so the probe would reject every mkimage including good ones. + if { "$_c" 2>&1 || true; } | grep -q -- '-B '; then MKIMAGE="$_c"; break; fi +done +IFS="$_IFS_SAVE" +# <<< mkimage-probe +if [ -z "$MKIMAGE" ]; then + echo "FATAL: no mkimage on PATH supports -B (sub-image alignment)." >&2 + echo " The SDK vendors mkimage 2017.09, which does not, and its output" >&2 + echo " is not 512-aligned here. Install u-boot-tools (2025.01 works)" >&2 + echo " and make sure it precedes the SDK tools on PATH." >&2 + exit 1 fi -echo "== FIT (external data, -E -p 0x800 ${_align_flag:-no -B})" -( cd "$WORKDIR" && mkimage -f boot.its -E -p 0x800 ${_align_flag} boot.img >/dev/null ) +echo "== FIT (external data, -E -p 0x800 -B 0x200) using $MKIMAGE" +( cd "$WORKDIR" && "$MKIMAGE" -f boot.its -E -p 0x800 -B 0x200 boot.img >/dev/null ) # Assert what U-Boot actually requires, on every build: the failure is silent -- # a misread offset does not fail the build, it fails on a panel, and sometimes diff --git a/tests/mk-bootimg/run-probe-tests.sh b/tests/mk-bootimg/run-probe-tests.sh new file mode 100755 index 0000000..2b33e51 --- /dev/null +++ b/tests/mk-bootimg/run-probe-tests.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Regression tests for the mkimage capability probe in build/mk-bootimg.sh. +# +# Guards issue #17: the probe ran the candidate binary and piped it into grep. +# mkimage with no arguments prints usage and exits non-zero, and mk-bootimg.sh +# runs under `set -euo pipefail`, so the pipeline reported failure even when +# grep matched. Every mkimage was rejected and the script failed closed with +# "no mkimage on PATH supports -B" while a capable mkimage sat first on PATH. +# +# The probe is extracted verbatim from mk-bootimg.sh between its sentinel +# comments, so these tests exercise the shipping code rather than a copy. +set -uo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$HERE/../../build/mk-bootimg.sh" +PROBE="$(mktemp)"; TMP="$(mktemp -d)" +trap 'rm -rf "$PROBE" "$TMP"' EXIT + +sed -n '/^# >>> mkimage-probe/,/^# <<< mkimage-probe/p' "$SCRIPT" > "$PROBE" +grep -q 'MKIMAGE=' "$PROBE" || { echo "FAIL: probe not extracted (sentinels missing)"; exit 1; } + +# The probe needs grep on PATH, but the real /usr/bin must stay off it or the +# host's own mkimage would answer these tests. Give it a directory holding +# nothing but grep. +mkdir -p "$TMP/util" +ln -s "$(command -v grep)" "$TMP/util/grep" + +# A faithful stand-in for real mkimage: usage on stderr, non-zero exit. +mk_fake() { # mk_fake + mkdir -p "$1" + { echo '#!/bin/sh' + echo 'echo "Usage: mkimage [-T type] -l image" >&2' + [ "$2" = 1 ] && echo 'echo " -B => align size in hex for FIT structure and header" >&2' + echo 'exit 1' + } > "$1/mkimage" + chmod +x "$1/mkimage" +} + +fails=0 +check() { # check + local name="$1" want="$2"; shift 2 + local path=""; for d in "$@"; do path="$path${path:+:}$d"; done + path="$path${path:+:}$TMP/util" + local got + # /bin/bash by absolute path: a PATH holding only the fixture dirs cannot + # locate the interpreter itself. + got="$(TEST_PATH="$path" PROBE="$PROBE" /bin/bash -c \ + 'set -euo pipefail; PATH="$TEST_PATH"; . "$PROBE"; echo "${MKIMAGE:-}"' 2>/dev/null)" + got="${got##*/}" + if [ "$got" = "$want" ]; then + echo "PASS: $name" + else + echo "FAIL: $name (wanted '${want:-}', got '${got:-}')" + fails=$((fails + 1)) + fi +} + +mk_fake "$TMP/good" 1 +mk_fake "$TMP/old" 0 + +# The regression itself: a capable mkimage that exits non-zero must be accepted. +check "capable mkimage accepted despite non-zero exit" mkimage "$TMP/good" +# The guarantee the FATAL message depends on: no false positives. +check "mkimage without -B rejected" "" "$TMP/old" +# PATH order decides, and an incapable earlier entry must not stop the scan. +check "scan continues past an incapable mkimage" mkimage "$TMP/old" "$TMP/good" +check "no mkimage anywhere selects nothing" "" "$TMP/none" + +[ "$fails" -eq 0 ] && echo "All mk-bootimg probe tests passed." || echo "$fails test(s) failed." +exit "$fails"