cross-remote.sh: the default cross-build runs again (a trailing comment had swallowed the defaults line); the class gets a CI check
The shipper's report: with no arguments, cross-remote.sh died under the Mac's bash 3.2 with 'CARGO_ARGS: unbound variable' and
its chain kept the previous exes. Cause: a comment appended to the defaults line in the box-work merge turned OUT, COMPARE,
TARGET_DIR and CARGO_ARGS=() into comment text, so the array was never declared (the same shape build-remote.sh had at 19:5x).
Fix: the line split, and the default tests use [ -n "${CARGO_ARGS[*]:-}" ] in both tools, safe whether or not the array
exists. Proof: the default cross-build from a fork worktree under /bin/bash 3.2.57 built both exes (igneumd.exe 8f7e2ae3...,
igneum-miner.exe 6f8b49d8...). tools/ci/defaults-line-check.sh fails CI on a line where a comment start is followed by an
assignment list (quoted strings, ${...}, $# and [^#] dropped first); self-test fires on the swallowed shape and passes
${a#b}, sed s#x#y#, $# loops and prose mentions; the tree is clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0445a51d77
commit
36e4ee7c5a
4 changed files with 70 additions and 8 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -95,6 +95,8 @@ jobs:
|
|||
run: bash tools/ci/commit-string-check.sh --self-test
|
||||
- name: build server remote checkout self-test (the stale-overlay class of 6 October 2026)
|
||||
run: bash infra/build-server/remote-run.sh --self-test
|
||||
- name: no shell assignment hides behind a trailing comment (the swallowed-defaults class of 6 October 2026)
|
||||
run: bash tools/ci/defaults-line-check.sh --self-test && bash tools/ci/defaults-line-check.sh
|
||||
- name: no secret file names and no 64-hex secrets in the tree (self-test first, then the tree)
|
||||
run: bash tools/ci/no-secrets-check.sh --self-test && bash tools/ci/no-secrets-check.sh
|
||||
- name: faucet unit tests (validation, the daily limits, the signed transaction; keccak, RLP and secp256k1 vectors)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ while [ $# -gt 0 ]; do
|
|||
esac
|
||||
done
|
||||
[ "${CARGO_ARGS[0]:-}" = cargo ] && CARGO_ARGS=("${CARGO_ARGS[@]:1}")
|
||||
CARGO_ARGS_GIVEN=""; [ "${#CARGO_ARGS[@]}" -gt 0 ] && CARGO_ARGS_GIVEN=1
|
||||
CARGO_ARGS_GIVEN=""; [ -n "${CARGO_ARGS[*]:-}" ] && CARGO_ARGS_GIVEN=1
|
||||
|
||||
bs_host
|
||||
bs_context
|
||||
|
|
@ -106,16 +106,16 @@ fi
|
|||
# defaults per crate
|
||||
case "$BS_KIND:$BS_CRATE_REL" in
|
||||
node:*)
|
||||
[ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features kaspad/igneum-pow)
|
||||
[ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features kaspad/igneum-pow)
|
||||
[ -n "$ARTEFACTS" ] || ARTEFACTS="$TARGET_DIR/release/igneumd $TARGET_DIR/release/igneum-miner" ;;
|
||||
repo:app/igneum-app)
|
||||
[ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release)
|
||||
[ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release)
|
||||
[ -n "$ARTEFACTS" ] || ARTEFACTS="$TARGET_DIR/release/igneum-app $TARGET_DIR/release/igneum-ota-sign $TARGET_DIR/release/igneum-prove-verify" ;;
|
||||
repo:proving/igneum-prove)
|
||||
[ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release)
|
||||
[ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release)
|
||||
[ -n "$ARTEFACTS" ] || ARTEFACTS="$TARGET_DIR/release/igneum-prove-host $TARGET_DIR/release/igneum-prove-export" ;;
|
||||
*)
|
||||
[ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release) ;;
|
||||
[ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release) ;;
|
||||
esac
|
||||
case "${CARGO_ARGS[0]}" in build) ;; *) [ -n "${ARTEFACTS_SET:-}" ] || { FETCH=0; ARTEFACTS=""; } ;; esac # test, check, clippy: nothing to fetch
|
||||
[ -n "$OUT" ] || OUT="$BS_CRATE/target-remote"
|
||||
|
|
|
|||
57
tools/ci/defaults-line-check.sh
Executable file
57
tools/ci/defaults-line-check.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env bash
|
||||
# The swallowed-defaults class (6 October 2026, twice in one evening): a comment appended to a line of shell assignments
|
||||
# (`JOBS=...; # ... OUT=""; CARGO_ARGS=()`) turns every assignment after the `#` into comment text; the script still parses,
|
||||
# bash -n and shellcheck say nothing, and the first use of the undeclared array dies under the Mac's bash 3.2 with
|
||||
# "unbound variable" (build-remote.sh at 19:5x UTC, cross-remote.sh at 21:xx UTC: the shipper's default cross-build never ran
|
||||
# and its chain kept the previous exes). Rule: no `#` comment on a line that carries shell assignments after it.
|
||||
# The scan (python3, which every CI host has) first drops quoted strings, ${...} expansions, $# and [^#] so a `#` inside them
|
||||
# is not a comment, then flags a line where a comment start (start of line or whitespace, then #) is followed by `NAME=`.
|
||||
#
|
||||
# tools/ci/defaults-line-check.sh # exit 1 with file:line on a hit
|
||||
# tools/ci/defaults-line-check.sh --self-test # fires on the swallowed shape, passes clean ones (including ${a#b} and sed s#x#y#)
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
scan() { # file names as arguments (python3 - takes its script from stdin, so stdin cannot carry the list), file:line per hit, exit 1 if any
|
||||
python3 - "$@" <<'PY'
|
||||
import re, sys
|
||||
strip = [
|
||||
(re.compile(r"\$\{[^}]*\}"), ""), # ${var#pat}, ${var%pat}, ${!i}
|
||||
(re.compile(r"\$#"), ""), # the argument count
|
||||
(re.compile(r"\[\^#\]"), ""), # a bracket expression excluding #
|
||||
(re.compile(r"'[^']*'"), "''"), # single-quoted strings
|
||||
(re.compile(r'"(?:[^"\\]|\\.)*"'), '""'), # double-quoted strings
|
||||
(re.compile(r"\\#"), ""), # an escaped #
|
||||
]
|
||||
# the swallowed shape is an assignment LIST after the comment start: `NAME=...;` or `NAME=()`; a prose mention such as
|
||||
# "(access public|private, private only in NET_MODE=private)" or "OTA_SKIP=1 (the default now)" has neither
|
||||
hit = re.compile(r"(^|\s)#.*\s[A-Za-z_][A-Za-z0-9_]*=(\(\)|[^;()]*;)")
|
||||
bad = 0
|
||||
for path in sys.argv[1:]:
|
||||
try: lines = open(path, encoding="utf-8", errors="replace").read().split("\n")
|
||||
except OSError: continue
|
||||
for n, line in enumerate(lines, 1):
|
||||
s = line
|
||||
for rx, rep in strip: s = rx.sub(rep, s)
|
||||
if s.lstrip().startswith("#"): continue # a whole-line comment may say anything
|
||||
if hit.search(s):
|
||||
print(f"defaults-line: {path}:{n}: a comment swallows assignments after it: {line.strip()[:140]}"); bad = 1
|
||||
sys.exit(bad)
|
||||
PY
|
||||
}
|
||||
if [ "${1:-}" = --self-test ]; then
|
||||
t=$(mktemp -d); trap 'rm -rf "$t"' EXIT
|
||||
printf '%s\n' 'JOBS="${JOBS:-}"; # empty = the box decides OUT=""; CARGO_ARGS=()' > "$t/bad.sh"
|
||||
printf '%s\n' 'JOBS="${JOBS:-}"; OUT=""; CARGO_ARGS=() # one line, nothing assigned after the comment' \
|
||||
'ver=${tarball#node-}; ver=${ver%-linux-x64.tar.xz}' \
|
||||
"epoch=\$(sed -n 's/^#define X \"\\(.*\\)\"/\\1/p' \"\$ph\"); day=\$(sed -n 's/^#define Y/x/p')" \
|
||||
'rel="${a#"$TARGET_DIR"/}"; dest="$OUT/$rel"' \
|
||||
'for ((i=1;i<=$#;i++)); do fee="${!i}"; done' \
|
||||
"A=\"\$(find . | sed 's#^\\./##' | sort)\"; B=1" \
|
||||
'hetzner_create_one() { # name type location [role] [access] (access public|private, private only in NET_MODE=private)' \
|
||||
'if [ "${OTA_SKIP:-}" = 0 ]; then SIGN=1; fi # the old spelling; OTA_SKIP=1 (the default now) leaves the manifest alone' \
|
||||
'# a whole-line comment with OUT=""; CARGO_ARGS=() in it' > "$t/good.sh"
|
||||
if scan "$t/bad.sh" >/dev/null; then echo "defaults-line self-test: the swallowed shape did NOT fire"; exit 1; fi
|
||||
if scan "$t/good.sh"; then echo "defaults-line self-test: fires on the swallowed shape, passes the clean shapes"; exit 0; else echo "defaults-line self-test: a clean shape fired"; exit 1; fi
|
||||
fi
|
||||
# shellcheck disable=SC2046 # paths in this tree carry no spaces
|
||||
if scan $(git ls-files 'tools/*.sh' 'tools/**/*.sh' 'infra/**/*.sh' 'proto-cuda/**/*.sh' 'packaging/**/*.sh'); then echo "defaults-line: no assignment hides behind a comment"; else exit 1; fi
|
||||
|
|
@ -37,7 +37,10 @@ BS_TOOL=cross-remote
|
|||
. "$HERE/../infra/build-server/lib.sh"
|
||||
|
||||
TARGET=x86_64-pc-windows-gnu
|
||||
JOBS="${JOBS:-}"; # empty = the box decides: 90 alone, 45 beside another slot holder (remote-run.sh, main's ruling 6 Oct 2026) OUT=""; COMPARE=""; TARGET_DIR="target"; CARGO_ARGS=()
|
||||
# JOBS empty = the box decides: 90 alone, 45 beside another slot holder (remote-run.sh, main's ruling 6 Oct 2026). One line, no
|
||||
# trailing comment: a `#` on this line once swallowed every assignment after it, CARGO_ARGS was never declared and the default
|
||||
# cross-build died with "unbound variable" under the Mac's bash 3.2 (the shipper, 6 Oct 2026, 21:xx UTC)
|
||||
JOBS="${JOBS:-}"; OUT=""; COMPARE=""; TARGET_DIR="target"; CARGO_ARGS=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--jobs) JOBS="$2"; shift 2 ;;
|
||||
|
|
@ -55,10 +58,10 @@ bs_host
|
|||
bs_context
|
||||
case "$BS_KIND:$BS_CRATE_REL" in
|
||||
node:*)
|
||||
[ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features igneum-pow --target "$TARGET")
|
||||
[ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features igneum-pow --target "$TARGET")
|
||||
EXES="igneumd.exe igneum-miner.exe" ;;
|
||||
repo:app/igneum-app)
|
||||
[ "${#CARGO_ARGS[@]}" -gt 0 ] || CARGO_ARGS=(build --release --target "$TARGET")
|
||||
[ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release --target "$TARGET")
|
||||
EXES="igneum-app.exe igneum-ota-sign.exe igneum-prove-verify.exe" ;;
|
||||
*) bs_die "cross-remote builds the fork (run from a vendor/igneum-node* worktree) or app/igneum-app, not $BS_CRATE_REL" ;;
|
||||
esac
|
||||
|
|
|
|||
Loading…
Reference in a new issue