Some checks failed
check / check (push) Has been cancelled
check_unlisted_bundles read dist/ line by line, so two unlisted paths carrying a debug marker went unchecked while the script still exited 0: a name with a trailing space (read stripped it and the remnant matched a manifest line) and a name containing a newline (find printed it as a listed path plus an empty one). The walk is now find -print0 into a temporary listing, checked for find's exit status as before, and xargs -0 hands the paths back to this script as arguments, where the same is_listed and has_marker run on them. is_listed also answers "not listed" for any path containing a newline without asking grep, which would otherwise read such a path as two patterns and match on the first half — the escape survives NUL delimiting on its own. dist/ being a symlink is now its own check with its own message. It failed before only because GNU grep exits 2 on a directory, so a grep that exits 1 instead would have turned the whole cross-check into a pass. The comment claiming every file under dist/ is searched now says what is actually guaranteed: every regular file and every symlink, with the four properties that coverage rests on stated as enforced rather than assumed.
296 lines
12 KiB
Bash
Executable File
296 lines
12 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/verify-build: assert the compiled DEBUG state of the emitted
|
|
# bundles. Our own extension to scripts-to-rule-them-all, run at the end of
|
|
# make build / make build-debug.
|
|
#
|
|
# Why this exists: DEBUG makes the publicly committed test recovery phrase the
|
|
# output of wallet creation, so a release artifact built with it live hands
|
|
# every new wallet to anyone who reads the repo. The test suite cannot see
|
|
# this, because it loads src/shared/constants.js outside a bundle and takes
|
|
# the fallback branch; the property only exists in the emitted output, so it
|
|
# has to be asserted against the emitted output.
|
|
#
|
|
# What it reads: dist/constants-bundles.txt, written by build.js from
|
|
# esbuild's metafile, naming every emitted bundle that contains
|
|
# src/shared/constants.js. Each of those must carry exactly one of the two
|
|
# BUILD_DEBUG_MARKER literals that constants.js folds down to.
|
|
#
|
|
# It fails rather than passes whenever it cannot determine a bundle's state.
|
|
# Minified output is not a stable contract, so "matched neither form" is not
|
|
# evidence of anything and must never read as green.
|
|
set -eu
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
|
|
# Absolute path to this script, resolved before anything cd's anywhere.
|
|
# check_unlisted_bundles re-invokes it through xargs, and $0 on its own may be
|
|
# relative to a directory we are about to leave.
|
|
SELF="$(cd "$(dirname "$0")" && pwd -P)/$(basename "$0")"
|
|
|
|
# Internal re-entry flag; see scan_dist_paths.
|
|
SCAN_FLAG="--scan-dist-paths"
|
|
|
|
# A literal newline, for the is_listed guard.
|
|
NEWLINE='
|
|
'
|
|
|
|
MANIFEST="dist/constants-bundles.txt"
|
|
MARKER_ON="autistmask-build-debug=on"
|
|
MARKER_OFF="autistmask-build-debug=off"
|
|
|
|
# Set by read_marker.
|
|
MARKER=""
|
|
|
|
# Temporary file holding the NUL-delimited dist/ listing, removed by the EXIT
|
|
# trap because fail() exits from wherever it is called.
|
|
LISTING=""
|
|
|
|
fail() {
|
|
echo "verify-build: FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
cleanup() {
|
|
[ -z "$LISTING" ] || rm -f "$LISTING"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Is the literal $1 present in the file $2? Match (grep exit 0) and no-match
|
|
# (exit 1) are answers about the emitted output. Anything else (exit 2: the
|
|
# file could not be read) is not an answer at all, and must not be reported as
|
|
# "no marker" — that would blame the bundle for a permissions or I/O fault.
|
|
has_marker() {
|
|
_hm_status=0
|
|
grep -q -F -e "$1" -- "$2" || _hm_status=$?
|
|
case "$_hm_status" in
|
|
0) return 0 ;;
|
|
1) return 1 ;;
|
|
*)
|
|
fail "grep exited $_hm_status reading $2, so the file could not be
|
|
searched and its DEBUG state was not checked at all. That is a permissions
|
|
or I/O fault on the artifact, not a change in the emitted output. Refusing
|
|
to report success."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Does the manifest list the path $1, as a whole line? Same discipline as
|
|
# has_marker: exit 0 and 1 are answers about the manifest, exit 2 means the
|
|
# manifest could not be read and is not an answer at all. Without this, an
|
|
# unreadable manifest reads as "this file is not listed" and every emitted
|
|
# bundle gets reported as an unlisted one.
|
|
#
|
|
# A path containing a newline is answered without asking grep, because grep
|
|
# would read the pattern as two patterns and report a match on either. That is
|
|
# how such a path escaped this check even once the walk stopped splitting it:
|
|
# the half before the newline matched a listed line and the file was skipped.
|
|
# The manifest is line-delimited, so it cannot name such a path at all, and
|
|
# "not listed" is the only true answer.
|
|
is_listed() {
|
|
case "$1" in
|
|
*"$NEWLINE"*) return 1 ;;
|
|
esac
|
|
_il_status=0
|
|
grep -q -x -F -e "$1" -- "$MANIFEST" || _il_status=$?
|
|
case "$_il_status" in
|
|
0) return 0 ;;
|
|
1) return 1 ;;
|
|
*)
|
|
fail "grep exited $_il_status reading $MANIFEST, so it could not be
|
|
searched and nothing was established about which bundles it lists. That is
|
|
a permissions or I/O fault on the manifest, not a stale manifest. Refusing
|
|
to report success."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Read one bundle's DEBUG state into MARKER. Exactly one marker must be
|
|
# present. Both means the ternary in constants.js was never folded, which is
|
|
# what happens when the __BUILD_DEBUG__ define goes missing from build.js:
|
|
# DEBUG stops being known at build time. Neither means we are reading output
|
|
# we do not understand. Both are hard failures; neither is ever treated as
|
|
# absence of a problem.
|
|
read_marker() {
|
|
_file="$1"
|
|
_on=no
|
|
_off=no
|
|
if has_marker "$MARKER_ON" "$_file"; then _on=yes; fi
|
|
if has_marker "$MARKER_OFF" "$_file"; then _off=yes; fi
|
|
|
|
if [ "$_on" = yes ] && [ "$_off" = yes ]; then
|
|
fail "$_file carries both debug markers, so DEBUG was not resolved at
|
|
build time: the ternary in src/shared/constants.js survived into the
|
|
emitted output. This does not mean the debug branch is live in this
|
|
artifact: an unresolved __BUILD_DEBUG__ is undeclared in extension
|
|
context, so DEBUG evaluates to false at runtime. It does mean the
|
|
release/debug distinction is no longer enforced at build time, and which
|
|
way that fallback happens to evaluate is then an accident a refactor can
|
|
flip. Check that build.js still defines __BUILD_DEBUG__."
|
|
fi
|
|
if [ "$_on" = no ] && [ "$_off" = no ]; then
|
|
fail "$_file carries no debug marker, so its DEBUG state cannot be
|
|
determined. Either BUILD_DEBUG_MARKER is gone from src/shared/constants.js
|
|
or the emitted output changed shape. Refusing to report success."
|
|
fi
|
|
|
|
if [ "$_on" = yes ]; then
|
|
MARKER="$MARKER_ON"
|
|
else
|
|
MARKER="$MARKER_OFF"
|
|
fi
|
|
}
|
|
|
|
# The manifest says which bundles must carry a marker. This says no other
|
|
# emitted file may carry one, which catches a manifest that has gone stale
|
|
# or short rather than trusting whatever it happens to list.
|
|
#
|
|
# Deliberately unfiltered by extension. build.js selects manifest entries with
|
|
# an endsWith(".js") test; repeating that literal here would mean a bundle
|
|
# emitted under some other extension escaped the manifest AND this check at
|
|
# once, which is the correlated blind spot the two-source design exists to
|
|
# avoid. Every regular file and every symlink under dist/ is searched — that
|
|
# is the whole of what a build emits — so build.js's filter is the only place
|
|
# the assumption lives and this check is what catches it being wrong.
|
|
#
|
|
# That claim only holds if the walk is exhaustive and every name survives it
|
|
# intact, so four things are enforced here rather than assumed:
|
|
#
|
|
# - the walk is NUL-delimited and the paths reach the check as arguments, so
|
|
# no name can be reshaped on the way in. Read line by line, a name with a
|
|
# trailing space lost it to read's field splitting and the remnant then
|
|
# matched a manifest line, and a name containing a newline arrived as a
|
|
# listed path plus an empty one. Both left a marker-carrying, unlisted file
|
|
# unchecked while the script still reported success. Delivering such a name
|
|
# intact is only half of it; is_listed also has to keep it out of grep's
|
|
# pattern, for the same reason.
|
|
# - find's exit status is checked. A subtree it cannot descend is reported on
|
|
# stderr and then simply missing from the listing, so an unchecked status
|
|
# turns "could not look" into "nothing was there" — the same conflation
|
|
# has_marker exists to prevent. The status cannot be read off a pipeline,
|
|
# so the listing lands in a file that xargs then reads back.
|
|
# - symlinks are walked too (-type l), not skipped. A marker-carrying bundle
|
|
# reachable under an unlisted path in dist/ is a stale manifest whether the
|
|
# path is a link or a file, and grep reads through the link. A link that
|
|
# cannot be read through — dangling, or pointing at a directory — fails
|
|
# hard via has_marker's exit-2 path, which is the fail-closed answer: the
|
|
# build emits neither, so their DEBUG state is unproven, not fine.
|
|
# - dist/ itself must be a directory and not a symlink, which main asserts
|
|
# before anything reads through it. find does not follow a symlink named on
|
|
# its own command line, so a linked dist/ collapses this walk to one entry
|
|
# and cross-checks nothing.
|
|
#
|
|
# Types other than regular files and symlinks are left out on purpose: a build
|
|
# emits none of them, and grep on a fifo would hang rather than fail.
|
|
check_unlisted_bundles() {
|
|
LISTING="$(mktemp "${TMPDIR:-/tmp}/verify-build-dist.XXXXXX")" ||
|
|
fail "could not create a temporary file for the dist/ listing, so the
|
|
tree was never walked. Refusing to report success."
|
|
|
|
_find_status=0
|
|
find dist \( -type f -o -type l \) -print0 >"$LISTING" || _find_status=$?
|
|
[ "$_find_status" -eq 0 ] ||
|
|
fail "find exited $_find_status enumerating dist/, so part of the tree
|
|
was never walked and nothing was established about the files in it. Any
|
|
unlisted bundle there went unchecked. That is a permissions or I/O fault on
|
|
the artifact, not a stale manifest. Refusing to report success."
|
|
|
|
_scan_status=0
|
|
xargs -0 "$SELF" "$SCAN_FLAG" <"$LISTING" || _scan_status=$?
|
|
[ "$_scan_status" -eq 0 ] ||
|
|
fail "the unlisted-bundle scan exited $_scan_status: either a path
|
|
under dist/ failed the check reported above, or the scan could not be run
|
|
at all. Refusing to report success."
|
|
}
|
|
|
|
# The per-path half of check_unlisted_bundles. It runs in a re-invocation of
|
|
# this script, so it uses the same is_listed and has_marker as the rest of the
|
|
# file rather than a second copy of them that could drift. Paths arrive as
|
|
# arguments and are never split, joined or trimmed.
|
|
scan_dist_paths() {
|
|
for _file in "$@"; do
|
|
if is_listed "$_file"; then
|
|
continue
|
|
fi
|
|
if has_marker "$MARKER_ON" "$_file" ||
|
|
has_marker "$MARKER_OFF" "$_file"; then
|
|
fail "$_file carries a debug marker but is absent from $MANIFEST,
|
|
so the manifest no longer describes the emitted bundles."
|
|
fi
|
|
done
|
|
}
|
|
|
|
# The requested mode, read from our own environment using build.js's exact
|
|
# rule: only the literal 1 opts in. Deliberately not taken from anything
|
|
# build.js records about itself, so build.js cannot vouch for build.js.
|
|
expected_marker() {
|
|
if [ "${AUTISTMASK_DEBUG-}" = "1" ]; then
|
|
echo "$MARKER_ON"
|
|
else
|
|
echo "$MARKER_OFF"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
cd "$ROOT"
|
|
|
|
# Internal re-entry from check_unlisted_bundles' xargs. Not part of the
|
|
# command-line interface: nothing else invokes it, and it is a distinct
|
|
# entry point rather than a mode flag threaded through the checks below.
|
|
if [ "${1-}" = "$SCAN_FLAG" ]; then
|
|
shift
|
|
scan_dist_paths "$@"
|
|
return 0
|
|
fi
|
|
|
|
expected="$(expected_marker)"
|
|
echo "Verifying emitted bundles (expecting $expected)..."
|
|
|
|
# Asserted here rather than left to grep. A symlinked dist/ used to fail
|
|
# only because GNU grep exits 2 on a directory, so check_unlisted_bundles'
|
|
# single entry hit has_marker's I/O path by luck; under a grep that exits 1
|
|
# instead, the whole cross-check would have collapsed into a pass.
|
|
if [ -h dist ]; then
|
|
fail "dist is a symlink, not a directory. find does not follow a
|
|
symlink named on its own command line, so the unlisted-bundle cross-check
|
|
would see one entry instead of the emitted tree and establish nothing about
|
|
it. Refusing to report success."
|
|
fi
|
|
[ -d dist ] ||
|
|
fail "dist is not a directory, so there is no emitted tree to verify.
|
|
build.js writes it; run make build first."
|
|
|
|
[ -f "$MANIFEST" ] ||
|
|
fail "$MANIFEST is missing. build.js writes it at the end of a
|
|
successful build; run make build first."
|
|
[ -s "$MANIFEST" ] ||
|
|
fail "$MANIFEST is empty, so no emitted bundle was found to contain
|
|
src/shared/constants.js. That is never correct, so it is a failure and not
|
|
a pass."
|
|
[ -r "$MANIFEST" ] ||
|
|
fail "$MANIFEST is not readable, so nothing was inspected. That is a
|
|
permissions or I/O fault, not a pass."
|
|
|
|
count=0
|
|
while read -r file; do
|
|
[ -n "$file" ] || continue
|
|
[ -f "$file" ] ||
|
|
fail "$MANIFEST lists $file, which does not exist."
|
|
[ -s "$file" ] ||
|
|
fail "$MANIFEST lists $file, which is empty. An empty bundle
|
|
carries no marker and proves nothing, so this is a failure and not a pass."
|
|
read_marker "$file"
|
|
[ "$MARKER" = "$expected" ] ||
|
|
fail "$file is $MARKER but this build expects $expected."
|
|
echo " ok: $file ($MARKER)"
|
|
count=$((count + 1))
|
|
done <"$MANIFEST"
|
|
|
|
[ "$count" -gt 0 ] || fail "no bundles were inspected."
|
|
|
|
check_unlisted_bundles
|
|
|
|
echo "verify-build: $count bundle(s) verified $expected"
|
|
}
|
|
|
|
main "$@"
|