Skip to content

fix(scripts): guard empty arrays so the build scripts survive bash 3.2 - #602

Open
gqf2008 wants to merge 2 commits into
0xShug0:mainfrom
gqf2008:fix/build-script-empty-arrays
Open

gqf2008 wants to merge 2 commits into
0xShug0:mainfrom
gqf2008:fix/build-script-empty-arrays

Conversation

@gqf2008

@gqf2008 gqf2008 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

scripts/build_metal.sh (and scripts/build_linux.sh) run with set -euo pipefail and expand "${TARGETS[@]}" / "${RUNNER[@]}" unconditionally, but both arrays are empty in the default invocation:

  • TARGETS only receives entries from --target; the script's own usage lists scripts/build_metal.sh (no --target) as an example, and the flag is documented as optional.
  • RUNNER only receives entries from --conda-env.

Stock macOS /bin/bash is 3.2, and bash < 4.4 also treats an empty array as unbound under set -u. The script therefore always dies right after a successful configure — the build never starts:

$ scripts/build_metal.sh --build-dir /Volumes/DataExt/tmp/bt-repro --model-set custom --models breeze_tts
...
-- Configuring done (8.0s)
-- Generating done (0.1s)
-- Build files have been written to: /Volumes/DataExt/tmp/bt-repro
scripts/build_metal.sh: line 324: TARGETS[@]: unbound variable

Changes

Use the ${arr[@]+"${arr[@]}"} form, which expands to nothing when the array is empty and to the quoted elements otherwise. Four lines, no behaviour change when targets or the conda runner are set:

  • scripts/build_metal.sh: the TARGETS loop.
  • scripts/build_linux.sh: the RUNNER cmake invocation, the BUILD_CMD array, and the TARGETS loop (same mechanism, reachable on bash < 4.4).

build_xcframework.sh was checked as well and needs no change — its lipo_inputs array is only expanded after at least one element is appended, and ${#ARCH_LIST[@]} is safe on an empty array in bash 3.2.

Verification

macOS 26.5.2, Apple M4, /bin/bash 3.2.57.

Before (unfixed, same tree as this PR's base):

$ scripts/build_metal.sh --build-dir /Volumes/DataExt/tmp/bt-repro --model-set custom --models breeze_tts
EXIT=1
scripts/build_metal.sh: line 324: TARGETS[@]: unbound variable

After:

$ scripts/build_metal.sh --build-dir /Volumes/DataExt/tmp/bt-repro --model-set custom --models breeze_tts
EXIT=0
[291/292] Linking CXX executable bin/audiocpp_server

$ scripts/build_metal.sh --build-dir /Volumes/DataExt/tmp/bt-repro --model-set custom --models breeze_tts --target audiocpp_cli
EXIT=0
ninja: no work to do.

Both scripts also pass /bin/bash -n. For the build_linux.sh lines (not directly runnable on macOS) I drove the exact expansions through a stub harness on bash 3.2:

# old form, RUNNER and TARGETS empty
/bin/bash: line 4: RUNNER[@]: unbound variable        (exit 1)

# new form, RUNNER and TARGETS empty
run cmake --stub
run cmake --build /tmp/bt --parallel 4                (exit 0)

# new form, RUNNER=(conda run -n "my env"), TARGETS=(audiocpp_cli audiocpp_server)
prefix conda run -n my env cmake --stub
build_cmd: conda run -n my env cmake --build /tmp/bt --parallel 4 --target audiocpp_cli --target audiocpp_server

Elements are preserved individually (the runner element containing a space stays a single argument), so this only removes the empty-array failure.

Notes

I could not find an existing report for this in the issue tracker. If you want it enforced, a bash -n + stub-expansion check in CI would catch a regression, but I kept the diff to the four lines since the repo has no shell-test harness wired into the workflows.

Both build scripts run with `set -euo pipefail` and expand `"${TARGETS[@]}"`
(and `"${RUNNER[@]}"` in build_linux.sh) unconditionally, but both arrays are
empty in the default invocation: TARGETS only receives entries from `--target`,
which build_metal.sh documents as optional, and RUNNER only receives entries
from `--conda-env`.

Stock macOS /bin/bash is 3.2 and bash < 4.4 also treats an empty array as
unbound under `set -u`, so `scripts/build_metal.sh` without `--target` always
dies right after a successful configure:

  -- Build files have been written to: <build dir>
  scripts/build_metal.sh: line 324: TARGETS[@]: unbound variable

Use the ${arr[@]+"${arr[@]}"} form, which expands to nothing when the array is
empty and to the quoted elements otherwise, so the default invocations work
again without changing behaviour when targets or the conda runner are set.
Add scripts/check_build_scripts.sh and run it from mac-build before the
configure step. It runs `bash -n` over scripts/*.sh, then drives
build_metal.sh and build_linux.sh with stub cmake/xcrun executables and no
--target under /bin/bash (3.2 on macOS), and requires both to reach the
configure and `cmake --build` invocations instead of dying on `set -u`.

Positive control — guards reverted in a scratch copy of the scripts, same
harness:
  build_metal.sh: line 327: TARGETS[@]: unbound variable   -> FAIL (exit 1)
  build_linux.sh: line 455: RUNNER[@]: unbound variable    -> FAIL (exit 1)
With the guards in place the check reports ok for both scripts, so the
regression cannot come back silently on macOS.
@gqf2008

gqf2008 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Added the CI check you asked about (as a follow-up commit on this branch):

  • scripts/check_build_scripts.sh — runs bash -n over scripts/*.sh, then drives build_metal.sh and build_linux.sh with stub cmake/xcrun executables and no --target, under /bin/bash (3.2 on macOS), and requires both to reach the configure and cmake --build invocations. It never configures or builds anything real, so it costs about a second.
  • .github/workflows/mac-build.yml — runs it right after the loader/spec sync check.

The point of pinning /bin/bash is that this bug only exists on bash 3.2 / < 4.4, so a check running under a newer bash would be a no-op.

Positive control (same harness, guards reverted in a scratch copy of the scripts):

$ scratch/scripts/check_build_scripts.sh
ok: /bin/bash -n passed for 4 scripts
--- build_metal output (tail) ---
/…/scripts/build_metal.sh: line 327: TARGETS[@]: unbound variable
FAIL: build_metal: … exited non-zero under /bin/bash (3.2.57(1)-release)     # exit 1

# with only build_linux.sh reverted:
ok: build_metal completed under bash 3.2.57(1)-release
--- build_linux output (tail) ---
/…/scripts/build_linux.sh: line 455: RUNNER[@]: unbound variable
FAIL: build_linux: … exited non-zero under /bin/bash (3.2.57(1)-release)     # exit 1

With the fix in place the check prints ok for both scripts, so a regression shows up as a red step before the real build starts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant